]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - games/fortune/fortune/fortune.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / games / fortune / fortune / fortune.c
1 /*-
2  * Copyright (c) 1986, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Ken Arnold.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed by the University of
19  *      California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36
37 #if 0
38 #ifndef lint
39 static const char copyright[] =
40 "@(#) Copyright (c) 1986, 1993\n\
41         The Regents of the University of California.  All rights reserved.\n";
42 #endif /* not lint */
43
44 #ifndef lint
45 static const char sccsid[] = "@(#)fortune.c   8.1 (Berkeley) 5/31/93";
46 #endif /* not lint */
47 #endif
48 #include <sys/cdefs.h>
49 __FBSDID("$FreeBSD$");
50
51 # include       <sys/stat.h>
52 # include       <sys/endian.h>
53
54 # include       <dirent.h>
55 # include       <fcntl.h>
56 # include       <assert.h>
57 # include       <unistd.h>
58 # include       <stdio.h>
59 # include       <ctype.h>
60 # include       <stdlib.h>
61 # include       <string.h>
62 # include       <locale.h>
63 # include       <time.h>
64 # include       <regex.h>
65 # include       "strfile.h"
66 # include       "pathnames.h"
67
68 # define        TRUE    1
69 # define        FALSE   0
70 # define        bool    short
71
72 # define        MINW    6               /* minimum wait if desired */
73 # define        CPERS   20              /* # of chars for each sec */
74 # define        SLEN    160             /* # of chars in short fortune */
75
76 # define        POS_UNKNOWN     ((uint32_t) -1) /* pos for file unknown */
77 # define        NO_PROB         (-1)            /* no prob specified for file */
78
79 # ifdef DEBUG
80 # define        DPRINTF(l,x)    { if (Debug >= l) fprintf x; }
81 # undef         NDEBUG
82 # else
83 # define        DPRINTF(l,x)
84 # define        NDEBUG  1
85 # endif
86
87 typedef struct fd {
88         int             percent;
89         int             fd, datfd;
90         uint32_t        pos;
91         FILE            *inf;
92         char            *name;
93         char            *path;
94         char            *datfile, *posfile;
95         bool            read_tbl;
96         bool            was_pos_file;
97         STRFILE         tbl;
98         int             num_children;
99         struct fd       *child, *parent;
100         struct fd       *next, *prev;
101 } FILEDESC;
102
103 bool    Found_one;                      /* did we find a match? */
104 bool    Find_files      = FALSE;        /* just find a list of proper fortune files */
105 bool    Fortunes_only   = FALSE;        /* check only "fortunes" files */
106 bool    Wait            = FALSE;        /* wait desired after fortune */
107 bool    Short_only      = FALSE;        /* short fortune desired */
108 bool    Long_only       = FALSE;        /* long fortune desired */
109 bool    Offend          = FALSE;        /* offensive fortunes only */
110 bool    All_forts       = FALSE;        /* any fortune allowed */
111 bool    Equal_probs     = FALSE;        /* scatter un-allocted prob equally */
112 bool    Match           = FALSE;        /* dump fortunes matching a pattern */
113 #ifdef DEBUG
114 bool    Debug = FALSE;                  /* print debug messages */
115 #endif
116
117 char    *Fortbuf = NULL;                        /* fortune buffer for -m */
118
119 int     Fort_len = 0;
120
121 off_t   Seekpts[2];                     /* seek pointers to fortunes */
122
123 FILEDESC        *File_list = NULL,      /* Head of file list */
124                 *File_tail = NULL;      /* Tail of file list */
125 FILEDESC        *Fortfile;              /* Fortune file to use */
126
127 STRFILE         Noprob_tbl;             /* sum of data for all no prob files */
128
129 char    *Fortune_path;
130 char    **Fortune_path_arr;
131
132 int      add_dir(FILEDESC *);
133 int      add_file __P((int,
134             char *, char *, FILEDESC **, FILEDESC **, FILEDESC *));
135 void     all_forts(FILEDESC *, char *);
136 char    *copy(char *, u_int);
137 void     display(FILEDESC *);
138 void     do_free(void *);
139 void    *do_malloc(u_int);
140 int      form_file_list(char **, int);
141 int      fortlen(void);
142 void     get_fort(void);
143 void     get_pos(FILEDESC *);
144 void     get_tbl(FILEDESC *);
145 void     getargs(int, char *[]);
146 void     getpath(void);
147 void     init_prob(void);
148 int      is_dir(char *);
149 int      is_fortfile(char *, char **, char **, int);
150 int      is_off_name(char *);
151 int      max(int, int);
152 FILEDESC *
153          new_fp(void);
154 char    *off_name(char *);
155 void     open_dat(FILEDESC *);
156 void     open_fp(FILEDESC *);
157 FILEDESC *
158          pick_child(FILEDESC *);
159 void     print_file_list(void);
160 void     print_list(FILEDESC *, int);
161 void     sum_noprobs(FILEDESC *);
162 void     sum_tbl(STRFILE *, STRFILE *);
163 void     usage(void);
164 void     zero_tbl(STRFILE *);
165
166 char    *conv_pat(char *);
167 int      find_matches(void);
168 void     matches_in_list(FILEDESC *);
169 int      maxlen_in_list(FILEDESC *);
170
171 static regex_t Re_pat;
172
173 int
174 main(ac, av)
175 int     ac;
176 char    *av[];
177 {
178 #ifdef  OK_TO_WRITE_DISK
179         int     fd;
180 #endif  /* OK_TO_WRITE_DISK */
181
182         (void) setlocale(LC_ALL, "");
183
184         getpath();
185         getargs(ac, av);
186
187         if (Match)
188                 exit(find_matches() != 0);
189
190         init_prob();
191         do {
192                 get_fort();
193         } while ((Short_only && fortlen() > SLEN) ||
194                  (Long_only && fortlen() <= SLEN));
195
196         display(Fortfile);
197
198 #ifdef  OK_TO_WRITE_DISK
199         if ((fd = creat(Fortfile->posfile, 0666)) < 0) {
200                 perror(Fortfile->posfile);
201                 exit(1);
202         }
203 #ifdef  LOCK_EX
204         /*
205          * if we can, we exclusive lock, but since it isn't very
206          * important, we just punt if we don't have easy locking
207          * available.
208          */
209         (void) flock(fd, LOCK_EX);
210 #endif  /* LOCK_EX */
211         write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
212         if (!Fortfile->was_pos_file)
213                 (void) chmod(Fortfile->path, 0666);
214 #ifdef  LOCK_EX
215         (void) flock(fd, LOCK_UN);
216 #endif  /* LOCK_EX */
217 #endif  /* OK_TO_WRITE_DISK */
218         if (Wait) {
219                 if (Fort_len == 0)
220                         (void) fortlen();
221                 sleep((unsigned int) max(Fort_len / CPERS, MINW));
222         }
223         exit(0);
224         /* NOTREACHED */
225 }
226
227 void
228 display(fp)
229 FILEDESC        *fp;
230 {
231         char   *p;
232         unsigned char ch;
233         char    line[BUFSIZ];
234
235         open_fp(fp);
236         (void) fseeko(fp->inf, Seekpts[0], 0);
237         for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
238             !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
239                 if (fp->tbl.str_flags & STR_ROTATED)
240                         for (p = line; (ch = *p) != '\0'; ++p) {
241                                 if (isascii(ch)) {
242                                         if (isupper(ch))
243                                                 *p = 'A' + (ch - 'A' + 13) % 26;
244                                         else if (islower(ch))
245                                                 *p = 'a' + (ch - 'a' + 13) % 26;
246                                 }
247                         }
248                 if (fp->tbl.str_flags & STR_COMMENTS
249                     && line[0] == fp->tbl.str_delim
250                     && line[1] == fp->tbl.str_delim)
251                         continue;
252                 fputs(line, stdout);
253         }
254         (void) fflush(stdout);
255 }
256
257 /*
258  * fortlen:
259  *      Return the length of the fortune.
260  */
261 int
262 fortlen()
263 {
264         int     nchar;
265         char    line[BUFSIZ];
266
267         if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
268                 nchar = (int)(Seekpts[1] - Seekpts[0]);
269         else {
270                 open_fp(Fortfile);
271                 (void) fseeko(Fortfile->inf, Seekpts[0], 0);
272                 nchar = 0;
273                 while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
274                        !STR_ENDSTRING(line, Fortfile->tbl))
275                         nchar += strlen(line);
276         }
277         Fort_len = nchar;
278         return nchar;
279 }
280
281 /*
282  *      This routine evaluates the arguments on the command line
283  */
284 void
285 getargs(argc, argv)
286 int     argc;
287 char    **argv;
288 {
289         int     ignore_case;
290         char    *pat;
291         extern char *optarg;
292         extern int optind;
293         int ch;
294
295         ignore_case = FALSE;
296         pat = NULL;
297
298 # ifdef DEBUG
299         while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1)
300 #else
301         while ((ch = getopt(argc, argv, "aefilm:osw")) != -1)
302 #endif /* DEBUG */
303                 switch(ch) {
304                 case 'a':               /* any fortune */
305                         All_forts++;
306                         break;
307 # ifdef DEBUG
308                 case 'D':
309                         Debug++;
310                         break;
311 # endif /* DEBUG */
312                 case 'e':
313                         Equal_probs++;  /* scatter un-allocted prob equally */
314                         break;
315                 case 'f':               /* find fortune files */
316                         Find_files++;
317                         break;
318                 case 'l':               /* long ones only */
319                         Long_only++;
320                         Short_only = FALSE;
321                         break;
322                 case 'o':               /* offensive ones only */
323                         Offend++;
324                         break;
325                 case 's':               /* short ones only */
326                         Short_only++;
327                         Long_only = FALSE;
328                         break;
329                 case 'w':               /* give time to read */
330                         Wait++;
331                         break;
332                 case 'm':                       /* dump out the fortunes */
333                         Match++;
334                         pat = optarg;
335                         break;
336                 case 'i':                       /* case-insensitive match */
337                         ignore_case++;
338                         break;
339                 case '?':
340                 default:
341                         usage();
342                 }
343         argc -= optind;
344         argv += optind;
345
346         if (!form_file_list(argv, argc))
347                 exit(1);        /* errors printed through form_file_list() */
348         if (Find_files) {
349                 print_file_list();
350                 exit(0);
351         }
352 #ifdef DEBUG
353         else if (Debug >= 1)
354                 print_file_list();
355 #endif /* DEBUG */
356
357         if (pat != NULL) {
358                 int error;
359
360                 if (ignore_case)
361                         pat = conv_pat(pat);
362                 error = regcomp(&Re_pat, pat, REG_BASIC);
363                 if (error) {
364                         fprintf(stderr, "regcomp(%s) fails\n", pat);
365                         exit (1);
366                 }
367         }
368 }
369
370 /*
371  * form_file_list:
372  *      Form the file list from the file specifications.
373  */
374 int
375 form_file_list(files, file_cnt)
376 char    **files;
377 int     file_cnt;
378 {
379         int     i, percent;
380         char    *sp;
381         char    **pstr;
382
383         if (file_cnt == 0) {
384                 if (Find_files) {
385                         Fortunes_only = TRUE;
386                         pstr = Fortune_path_arr;
387                         i = 0;
388                         while (*pstr) {
389                                 i += add_file(NO_PROB, *pstr++, NULL, 
390                                               &File_list, &File_tail, NULL);
391                         }
392                         Fortunes_only = FALSE;
393                         if (!i) {
394                                 fprintf(stderr, "No fortunes found in %s.\n",
395                                     Fortune_path);
396                         }
397                         return i != 0;
398                 } else {
399                         pstr = Fortune_path_arr;
400                         i = 0;
401                         while (*pstr) {
402                                 i += add_file(NO_PROB, "fortunes", *pstr++,
403                                               &File_list, &File_tail, NULL);
404                         }
405                         if (!i) {
406                                 fprintf(stderr, "No fortunes found in %s.\n",
407                                     Fortune_path);
408                         }
409                         return i != 0;
410                 }
411         }
412         for (i = 0; i < file_cnt; i++) {
413                 percent = NO_PROB;
414                 if (!isdigit((unsigned char)files[i][0]))
415                         sp = files[i];
416                 else {
417                         percent = 0;
418                         for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
419                                 percent = percent * 10 + *sp - '0';
420                         if (percent > 100) {
421                                 fprintf(stderr, "percentages must be <= 100\n");
422                                 return FALSE;
423                         }
424                         if (*sp == '.') {
425                                 fprintf(stderr, "percentages must be integers\n");
426                                 return FALSE;
427                         }
428                         /*
429                          * If the number isn't followed by a '%', then
430                          * it was not a percentage, just the first part
431                          * of a file name which starts with digits.
432                          */
433                         if (*sp != '%') {
434                                 percent = NO_PROB;
435                                 sp = files[i];
436                         }
437                         else if (*++sp == '\0') {
438                                 if (++i >= file_cnt) {
439                                         fprintf(stderr, "percentages must precede files\n");
440                                         return FALSE;
441                                 }
442                                 sp = files[i];
443                         }
444                 }
445                 if (strcmp(sp, "all") == 0) {
446                         pstr = Fortune_path_arr;
447                         i = 0;
448                         while (*pstr) {
449                                 i += add_file(NO_PROB, *pstr++, NULL, 
450                                               &File_list, &File_tail, NULL);
451                         }
452                         if (!i) {
453                                 fprintf(stderr, "No fortunes found in %s.\n",
454                                     Fortune_path);
455                                 return FALSE;
456                         }
457                 } else if (!add_file(percent, sp, NULL, &File_list, 
458                                      &File_tail, NULL)) {
459                         return FALSE;
460                 }
461         }
462         return TRUE;
463 }
464
465 /*
466  * add_file:
467  *      Add a file to the file list.
468  */
469 int
470 add_file(percent, file, dir, head, tail, parent)
471 int             percent;
472 char            *file;
473 char            *dir;
474 FILEDESC        **head, **tail;
475 FILEDESC        *parent;
476 {
477         FILEDESC        *fp;
478         int             fd;
479         char            *path, *offensive;
480         bool            was_malloc;
481         bool            isdir;
482
483         if (dir == NULL) {
484                 path = file;
485                 was_malloc = FALSE;
486         }
487         else {
488                 path = do_malloc((unsigned int) (strlen(dir) + strlen(file) + 2));
489                 (void) strcat(strcat(strcpy(path, dir), "/"), file);
490                 was_malloc = TRUE;
491         }
492         if ((isdir = is_dir(path)) && parent != NULL) {
493                 if (was_malloc)
494                         free(path);
495                 return FALSE;   /* don't recurse */
496         }
497         offensive = NULL;
498         if (!isdir && parent == NULL && (All_forts || Offend) &&
499             !is_off_name(path)) {
500                 offensive = off_name(path);
501                 if (Offend) {
502                         if (was_malloc)
503                                 free(path);
504                         path = offensive;
505                         offensive = NULL;
506                         was_malloc = TRUE;
507                         DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
508                         file = off_name(file);
509                 }
510         }
511
512         DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
513 over:
514         if ((fd = open(path, 0)) < 0) {
515                 /*
516                  * This is a sneak.  If the user said -a, and if the
517                  * file we're given isn't a file, we check to see if
518                  * there is a -o version.  If there is, we treat it as
519                  * if *that* were the file given.  We only do this for
520                  * individual files -- if we're scanning a directory,
521                  * we'll pick up the -o file anyway.
522                  */
523                 if (All_forts && offensive != NULL) {
524                         if (was_malloc)
525                                 free(path);
526                         path = offensive;
527                         offensive = NULL;
528                         was_malloc = TRUE;
529                         DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
530                         file = off_name(file);
531                         goto over;
532                 }
533                 if (dir == NULL && file[0] != '/') {
534                         int i = 0;
535                         char **pstr = Fortune_path_arr;
536
537                         while (*pstr) {
538                                 i += add_file(percent, file, *pstr++, 
539                                               head, tail, parent);
540                         }
541                         if (!i) {
542                                 fprintf(stderr, "No '%s' found in %s.\n",
543                                     file, Fortune_path);
544                         }
545                         return i != 0;
546                 }
547                 /*
548                 if (parent == NULL)
549                         perror(path);
550                 */
551                 if (was_malloc)
552                         free(path);
553                 return FALSE;
554         }
555
556         DPRINTF(2, (stderr, "path = \"%s\"\n", path));
557
558         fp = new_fp();
559         fp->fd = fd;
560         fp->percent = percent;
561         fp->name = file;
562         fp->path = path;
563         fp->parent = parent;
564
565         if ((isdir && !add_dir(fp)) ||
566             (!isdir &&
567              !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
568         {
569                 if (parent == NULL)
570                         fprintf(stderr,
571                                 "fortune:%s not a fortune file or directory\n",
572                                 path);
573                 if (was_malloc)
574                         free(path);
575                 do_free(fp->datfile);
576                 do_free(fp->posfile);
577                 free((char *) fp);
578                 do_free(offensive);
579                 return FALSE;
580         }
581         /*
582          * If the user said -a, we need to make this node a pointer to
583          * both files, if there are two.  We don't need to do this if
584          * we are scanning a directory, since the scan will pick up the
585          * -o file anyway.
586          */
587         if (All_forts && parent == NULL && !is_off_name(path))
588                 all_forts(fp, offensive);
589         if (*head == NULL)
590                 *head = *tail = fp;
591         else if (fp->percent == NO_PROB) {
592                 (*tail)->next = fp;
593                 fp->prev = *tail;
594                 *tail = fp;
595         }
596         else {
597                 (*head)->prev = fp;
598                 fp->next = *head;
599                 *head = fp;
600         }
601 #ifdef  OK_TO_WRITE_DISK
602         fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
603 #endif  /* OK_TO_WRITE_DISK */
604
605         return TRUE;
606 }
607
608 /*
609  * new_fp:
610  *      Return a pointer to an initialized new FILEDESC.
611  */
612 FILEDESC *
613 new_fp()
614 {
615         FILEDESC        *fp;
616
617         fp = (FILEDESC *) do_malloc(sizeof *fp);
618         fp->datfd = -1;
619         fp->pos = POS_UNKNOWN;
620         fp->inf = NULL;
621         fp->fd = -1;
622         fp->percent = NO_PROB;
623         fp->read_tbl = FALSE;
624         fp->next = NULL;
625         fp->prev = NULL;
626         fp->child = NULL;
627         fp->parent = NULL;
628         fp->datfile = NULL;
629         fp->posfile = NULL;
630         return fp;
631 }
632
633 /*
634  * off_name:
635  *      Return a pointer to the offensive version of a file of this name.
636  */
637 char *
638 off_name(file)
639 char    *file;
640 {
641         char    *new;
642
643         new = copy(file, (unsigned int) (strlen(file) + 2));
644         return strcat(new, "-o");
645 }
646
647 /*
648  * is_off_name:
649  *      Is the file an offensive-style name?
650  */
651 int
652 is_off_name(file)
653 char    *file;
654 {
655         int     len;
656
657         len = strlen(file);
658         return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
659 }
660
661 /*
662  * all_forts:
663  *      Modify a FILEDESC element to be the parent of two children if
664  *      there are two children to be a parent of.
665  */
666 void
667 all_forts(fp, offensive)
668 FILEDESC        *fp;
669 char            *offensive;
670 {
671         char            *sp;
672         FILEDESC        *scene, *obscene;
673         int             fd;
674         auto char               *datfile, *posfile;
675
676         if (fp->child != NULL)  /* this is a directory, not a file */
677                 return;
678         if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
679                 return;
680         if ((fd = open(offensive, 0)) < 0)
681                 return;
682         DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
683         scene = new_fp();
684         obscene = new_fp();
685         *scene = *fp;
686
687         fp->num_children = 2;
688         fp->child = scene;
689         scene->next = obscene;
690         obscene->next = NULL;
691         scene->child = obscene->child = NULL;
692         scene->parent = obscene->parent = fp;
693
694         fp->fd = -1;
695         scene->percent = obscene->percent = NO_PROB;
696
697         obscene->fd = fd;
698         obscene->inf = NULL;
699         obscene->path = offensive;
700         if ((sp = rindex(offensive, '/')) == NULL)
701                 obscene->name = offensive;
702         else
703                 obscene->name = ++sp;
704         obscene->datfile = datfile;
705         obscene->posfile = posfile;
706         obscene->read_tbl = FALSE;
707 #ifdef  OK_TO_WRITE_DISK
708         obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
709 #endif  /* OK_TO_WRITE_DISK */
710 }
711
712 /*
713  * add_dir:
714  *      Add the contents of an entire directory.
715  */
716 int
717 add_dir(fp)
718 FILEDESC        *fp;
719 {
720         DIR             *dir;
721         struct dirent   *dirent;
722         auto FILEDESC   *tailp;
723         auto char       *name;
724
725         (void) close(fp->fd);
726         fp->fd = -1;
727         if ((dir = opendir(fp->path)) == NULL) {
728                 perror(fp->path);
729                 return FALSE;
730         }
731         tailp = NULL;
732         DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
733         fp->num_children = 0;
734         while ((dirent = readdir(dir)) != NULL) {
735                 if (dirent->d_namlen == 0)
736                         continue;
737                 name = copy(dirent->d_name, dirent->d_namlen);
738                 if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
739                         fp->num_children++;
740                 else
741                         free(name);
742         }
743         if (fp->num_children == 0) {
744                 (void) fprintf(stderr,
745                     "fortune: %s: No fortune files in directory.\n", fp->path);
746                 return FALSE;
747         }
748         return TRUE;
749 }
750
751 /*
752  * is_dir:
753  *      Return TRUE if the file is a directory, FALSE otherwise.
754  */
755 int
756 is_dir(file)
757 char    *file;
758 {
759         auto struct stat        sbuf;
760
761         if (stat(file, &sbuf) < 0)
762                 return FALSE;
763         return (sbuf.st_mode & S_IFDIR);
764 }
765
766 /*
767  * is_fortfile:
768  *      Return TRUE if the file is a fortune database file.  We try and
769  *      exclude files without reading them if possible to avoid
770  *      overhead.  Files which start with ".", or which have "illegal"
771  *      suffixes, as contained in suflist[], are ruled out.
772  */
773 /* ARGSUSED */
774 int
775 is_fortfile(file, datp, posp, check_for_offend)
776 char    *file, **datp, **posp;
777 int     check_for_offend;
778 {
779         int     i;
780         char    *sp;
781         char    *datfile;
782         static char     *suflist[] = {  /* list of "illegal" suffixes" */
783                                 "dat", "pos", "c", "h", "p", "i", "f",
784                                 "pas", "ftn", "ins.c", "ins,pas",
785                                 "ins.ftn", "sml",
786                                 NULL
787                         };
788
789         DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
790
791         /*
792          * Preclude any -o files for offendable people, and any non -o
793          * files for completely offensive people.
794          */
795         if (check_for_offend && !All_forts) {
796                 i = strlen(file);
797                 if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o')) {
798                         DPRINTF(2, (stderr, "FALSE (offending file)\n"));
799                         return FALSE;
800                 }
801         }
802
803         if ((sp = rindex(file, '/')) == NULL)
804                 sp = file;
805         else
806                 sp++;
807         if (*sp == '.') {
808                 DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
809                 return FALSE;
810         }
811         if (Fortunes_only && strncmp(sp, "fortunes", 8) != 0) {
812                 DPRINTF(2, (stderr, "FALSE (check fortunes only)\n"));
813                 return FALSE;
814         }
815         if ((sp = rindex(sp, '.')) != NULL) {
816                 sp++;
817                 for (i = 0; suflist[i] != NULL; i++)
818                         if (strcmp(sp, suflist[i]) == 0) {
819                                 DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
820                                 return FALSE;
821                         }
822         }
823
824         datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
825         strcat(datfile, ".dat");
826         if (access(datfile, R_OK) < 0) {
827                 DPRINTF(2, (stderr, "FALSE (no readable \".dat\" file)\n"));
828 #ifdef DEBUG
829                 if (Debug < 2)
830                         DPRINTF(0, (stderr, "Warning: file \"%s\" unreadable\n", datfile));
831 #endif
832                 free(datfile);
833                 return FALSE;
834         }
835         if (datp != NULL)
836                 *datp = datfile;
837         else
838                 free(datfile);
839         if (posp != NULL) {
840 #ifdef  OK_TO_WRITE_DISK
841                 *posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
842                 (void) strcat(*posp, ".pos");
843 #else
844                 *posp = NULL;
845 #endif  /* OK_TO_WRITE_DISK */
846         }
847         DPRINTF(2, (stderr, "TRUE\n"));
848         return TRUE;
849 }
850
851 /*
852  * copy:
853  *      Return a malloc()'ed copy of the string
854  */
855 char *
856 copy(str, len)
857 char            *str;
858 unsigned int    len;
859 {
860         char    *new, *sp;
861
862         new = do_malloc(len + 1);
863         sp = new;
864         do {
865                 *sp++ = *str;
866         } while (*str++);
867         return new;
868 }
869
870 /*
871  * do_malloc:
872  *      Do a malloc, checking for NULL return.
873  */
874 void *
875 do_malloc(size)
876 unsigned int    size;
877 {
878         void    *new;
879
880         if ((new = malloc(size)) == NULL) {
881                 (void) fprintf(stderr, "fortune: out of memory.\n");
882                 exit(1);
883         }
884         return new;
885 }
886
887 /*
888  * do_free:
889  *      Free malloc'ed space, if any.
890  */
891 void
892 do_free(ptr)
893 void    *ptr;
894 {
895         if (ptr != NULL)
896                 free(ptr);
897 }
898
899 /*
900  * init_prob:
901  *      Initialize the fortune probabilities.
902  */
903 void
904 init_prob()
905 {
906         FILEDESC       *fp, *last = NULL;
907         int             percent, num_noprob, frac;
908
909         /*
910          * Distribute the residual probability (if any) across all
911          * files with unspecified probability (i.e., probability of 0)
912          * (if any).
913          */
914
915         percent = 0;
916         num_noprob = 0;
917         for (fp = File_tail; fp != NULL; fp = fp->prev)
918                 if (fp->percent == NO_PROB) {
919                         num_noprob++;
920                         if (Equal_probs)
921                                 last = fp;
922                 }
923                 else
924                         percent += fp->percent;
925         DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
926                     percent, num_noprob));
927         if (percent > 100) {
928                 (void) fprintf(stderr,
929                     "fortune: probabilities sum to %d%% > 100%%!\n", percent);
930                 exit(1);
931         }
932         else if (percent < 100 && num_noprob == 0) {
933                 (void) fprintf(stderr,
934                     "fortune: no place to put residual probability (%d%% < 100%%)\n",
935                     percent);
936                 exit(1);
937         }
938         else if (percent == 100 && num_noprob != 0) {
939                 (void) fprintf(stderr,
940                     "fortune: no probability left to put in residual files (100%%)\n");
941                 exit(1);
942         }
943         percent = 100 - percent;
944         if (Equal_probs) {
945                 if (num_noprob != 0) {
946                         if (num_noprob > 1) {
947                                 frac = percent / num_noprob;
948                                 DPRINTF(1, (stderr, ", frac = %d%%", frac));
949                                 for (fp = File_tail; fp != last; fp = fp->prev)
950                                         if (fp->percent == NO_PROB) {
951                                                 fp->percent = frac;
952                                                 percent -= frac;
953                                         }
954                         }
955                         last->percent = percent;
956                         DPRINTF(1, (stderr, ", residual = %d%%", percent));
957                 }
958         else
959                 DPRINTF(1, (stderr,
960                             ", %d%% distributed over remaining fortunes\n",
961                             percent));
962         }
963         DPRINTF(1, (stderr, "\n"));
964
965 #ifdef DEBUG
966         if (Debug >= 1)
967                 print_file_list();
968 #endif
969 }
970
971 /*
972  * get_fort:
973  *      Get the fortune data file's seek pointer for the next fortune.
974  */
975 void
976 get_fort()
977 {
978         FILEDESC        *fp;
979         int             choice;
980
981         if (File_list->next == NULL || File_list->percent == NO_PROB)
982                 fp = File_list;
983         else {
984                 choice = arc4random_uniform(100);
985                 DPRINTF(1, (stderr, "choice = %d\n", choice));
986                 for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
987                         if (choice < fp->percent)
988                                 break;
989                         else {
990                                 choice -= fp->percent;
991                                 DPRINTF(1, (stderr,
992                                             "    skip \"%s\", %d%% (choice = %d)\n",
993                                             fp->name, fp->percent, choice));
994                         }
995                         DPRINTF(1, (stderr,
996                                     "using \"%s\", %d%% (choice = %d)\n",
997                                     fp->name, fp->percent, choice));
998         }
999         if (fp->percent != NO_PROB)
1000                 get_tbl(fp);
1001         else {
1002                 if (fp->next != NULL) {
1003                         sum_noprobs(fp);
1004                         choice = arc4random_uniform(Noprob_tbl.str_numstr);
1005                         DPRINTF(1, (stderr, "choice = %d (of %u) \n", choice,
1006                                     Noprob_tbl.str_numstr));
1007                         while (choice >= fp->tbl.str_numstr) {
1008                                 choice -= fp->tbl.str_numstr;
1009                                 fp = fp->next;
1010                                 DPRINTF(1, (stderr,
1011                                             "    skip \"%s\", %u (choice = %d)\n",
1012                                             fp->name, fp->tbl.str_numstr,
1013                                             choice));
1014                         }
1015                         DPRINTF(1, (stderr, "using \"%s\", %u\n", fp->name,
1016                                     fp->tbl.str_numstr));
1017                 }
1018                 get_tbl(fp);
1019         }
1020         if (fp->child != NULL) {
1021                 DPRINTF(1, (stderr, "picking child\n"));
1022                 fp = pick_child(fp);
1023         }
1024         Fortfile = fp;
1025         get_pos(fp);
1026         open_dat(fp);
1027         (void) lseek(fp->datfd,
1028                      (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), 0);
1029         read(fp->datfd, Seekpts, sizeof Seekpts);
1030         Seekpts[0] = be64toh(Seekpts[0]);
1031         Seekpts[1] = be64toh(Seekpts[1]);
1032 }
1033
1034 /*
1035  * pick_child
1036  *      Pick a child from a chosen parent.
1037  */
1038 FILEDESC *
1039 pick_child(parent)
1040 FILEDESC        *parent;
1041 {
1042         FILEDESC        *fp;
1043         int             choice;
1044
1045         if (Equal_probs) {
1046                 choice = arc4random_uniform(parent->num_children);
1047                 DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
1048                             choice, parent->num_children));
1049                 for (fp = parent->child; choice--; fp = fp->next)
1050                         continue;
1051                 DPRINTF(1, (stderr, "    using %s\n", fp->name));
1052                 return fp;
1053         }
1054         else {
1055                 get_tbl(parent);
1056                 choice = arc4random_uniform(parent->tbl.str_numstr);
1057                 DPRINTF(1, (stderr, "    choice = %d (of %u)\n",
1058                             choice, parent->tbl.str_numstr));
1059                 for (fp = parent->child; choice >= fp->tbl.str_numstr;
1060                      fp = fp->next) {
1061                         choice -= fp->tbl.str_numstr;
1062                         DPRINTF(1, (stderr, "\tskip %s, %u (choice = %d)\n",
1063                                     fp->name, fp->tbl.str_numstr, choice));
1064                 }
1065                 DPRINTF(1, (stderr, "    using %s, %u\n", fp->name,
1066                             fp->tbl.str_numstr));
1067                 return fp;
1068         }
1069 }
1070
1071 /*
1072  * sum_noprobs:
1073  *      Sum up all the noprob probabilities, starting with fp.
1074  */
1075 void
1076 sum_noprobs(fp)
1077 FILEDESC        *fp;
1078 {
1079         static bool     did_noprobs = FALSE;
1080
1081         if (did_noprobs)
1082                 return;
1083         zero_tbl(&Noprob_tbl);
1084         while (fp != NULL) {
1085                 get_tbl(fp);
1086                 sum_tbl(&Noprob_tbl, &fp->tbl);
1087                 fp = fp->next;
1088         }
1089         did_noprobs = TRUE;
1090 }
1091
1092 int
1093 max(i, j)
1094 int     i, j;
1095 {
1096         return (i >= j ? i : j);
1097 }
1098
1099 /*
1100  * open_fp:
1101  *      Assocatiate a FILE * with the given FILEDESC.
1102  */
1103 void
1104 open_fp(fp)
1105 FILEDESC        *fp;
1106 {
1107         if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) {
1108                 perror(fp->path);
1109                 exit(1);
1110         }
1111 }
1112
1113 /*
1114  * open_dat:
1115  *      Open up the dat file if we need to.
1116  */
1117 void
1118 open_dat(fp)
1119 FILEDESC        *fp;
1120 {
1121         if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, 0)) < 0) {
1122                 perror(fp->datfile);
1123                 exit(1);
1124         }
1125 }
1126
1127 /*
1128  * get_pos:
1129  *      Get the position from the pos file, if there is one.  If not,
1130  *      return a random number.
1131  */
1132 void
1133 get_pos(fp)
1134 FILEDESC        *fp;
1135 {
1136 #ifdef  OK_TO_WRITE_DISK
1137         int     fd;
1138 #endif /* OK_TO_WRITE_DISK */
1139
1140         assert(fp->read_tbl);
1141         if (fp->pos == POS_UNKNOWN) {
1142 #ifdef  OK_TO_WRITE_DISK
1143                 if ((fd = open(fp->posfile, 0)) < 0 ||
1144                     read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
1145                         fp->pos = arc4random_uniform(fp->tbl.str_numstr);
1146                 else if (fp->pos >= fp->tbl.str_numstr)
1147                         fp->pos %= fp->tbl.str_numstr;
1148                 if (fd >= 0)
1149                         (void) close(fd);
1150 #else
1151                 fp->pos = arc4random_uniform(fp->tbl.str_numstr);
1152 #endif /* OK_TO_WRITE_DISK */
1153         }
1154         if (++(fp->pos) >= fp->tbl.str_numstr)
1155                 fp->pos -= fp->tbl.str_numstr;
1156         DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, (long)fp->pos));
1157 }
1158
1159 /*
1160  * get_tbl:
1161  *      Get the tbl data file the datfile.
1162  */
1163 void
1164 get_tbl(fp)
1165 FILEDESC        *fp;
1166 {
1167         auto int                fd;
1168         FILEDESC        *child;
1169
1170         if (fp->read_tbl)
1171                 return;
1172         if (fp->child == NULL) {
1173                 if ((fd = open(fp->datfile, 0)) < 0) {
1174                         perror(fp->datfile);
1175                         exit(1);
1176                 }
1177                 if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
1178                         (void)fprintf(stderr,
1179                             "fortune: %s corrupted\n", fp->path);
1180                         exit(1);
1181                 }
1182                 /* fp->tbl.str_version = be32toh(fp->tbl.str_version); */
1183                 fp->tbl.str_numstr = be32toh(fp->tbl.str_numstr);
1184                 fp->tbl.str_longlen = be32toh(fp->tbl.str_longlen);
1185                 fp->tbl.str_shortlen = be32toh(fp->tbl.str_shortlen);
1186                 fp->tbl.str_flags = be32toh(fp->tbl.str_flags);
1187                 (void) close(fd);
1188         }
1189         else {
1190                 zero_tbl(&fp->tbl);
1191                 for (child = fp->child; child != NULL; child = child->next) {
1192                         get_tbl(child);
1193                         sum_tbl(&fp->tbl, &child->tbl);
1194                 }
1195         }
1196         fp->read_tbl = TRUE;
1197 }
1198
1199 /*
1200  * zero_tbl:
1201  *      Zero out the fields we care about in a tbl structure.
1202  */
1203 void
1204 zero_tbl(tp)
1205 STRFILE *tp;
1206 {
1207         tp->str_numstr = 0;
1208         tp->str_longlen = 0;
1209         tp->str_shortlen = ~0;
1210 }
1211
1212 /*
1213  * sum_tbl:
1214  *      Merge the tbl data of t2 into t1.
1215  */
1216 void
1217 sum_tbl(t1, t2)
1218 STRFILE *t1, *t2;
1219 {
1220         t1->str_numstr += t2->str_numstr;
1221         if (t1->str_longlen < t2->str_longlen)
1222                 t1->str_longlen = t2->str_longlen;
1223         if (t1->str_shortlen > t2->str_shortlen)
1224                 t1->str_shortlen = t2->str_shortlen;
1225 }
1226
1227 #define STR(str)        ((str) == NULL ? "NULL" : (str))
1228
1229 /*
1230  * print_file_list:
1231  *      Print out the file list
1232  */
1233 void
1234 print_file_list()
1235 {
1236         print_list(File_list, 0);
1237 }
1238
1239 /*
1240  * print_list:
1241  *      Print out the actual list, recursively.
1242  */
1243 void
1244 print_list(list, lev)
1245 FILEDESC        *list;
1246 int                     lev;
1247 {
1248         while (list != NULL) {
1249                 fprintf(stderr, "%*s", lev * 4, "");
1250                 if (list->percent == NO_PROB)
1251                         fprintf(stderr, "___%%");
1252                 else
1253                         fprintf(stderr, "%3d%%", list->percent);
1254                 fprintf(stderr, " %s", STR(list->name));
1255                 DPRINTF(1, (stderr, " (%s, %s, %s)", STR(list->path),
1256                             STR(list->datfile), STR(list->posfile)));
1257                 fprintf(stderr, "\n");
1258                 if (list->child != NULL)
1259                         print_list(list->child, lev + 1);
1260                 list = list->next;
1261         }
1262 }
1263
1264 /*
1265  * conv_pat:
1266  *      Convert the pattern to an ignore-case equivalent.
1267  */
1268 char *
1269 conv_pat(orig)
1270 char    *orig;
1271 {
1272         char            *sp;
1273         unsigned int    cnt;
1274         char            *new;
1275
1276         cnt = 1;        /* allow for '\0' */
1277         for (sp = orig; *sp != '\0'; sp++)
1278                 if (isalpha((unsigned char)*sp))
1279                         cnt += 4;
1280                 else
1281                         cnt++;
1282         if ((new = malloc(cnt)) == NULL) {
1283                 fprintf(stderr, "pattern too long for ignoring case\n");
1284                 exit(1);
1285         }
1286
1287         for (sp = new; *orig != '\0'; orig++) {
1288                 if (islower((unsigned char)*orig)) {
1289                         *sp++ = '[';
1290                         *sp++ = *orig;
1291                         *sp++ = toupper((unsigned char)*orig);
1292                         *sp++ = ']';
1293                 }
1294                 else if (isupper((unsigned char)*orig)) {
1295                         *sp++ = '[';
1296                         *sp++ = *orig;
1297                         *sp++ = tolower((unsigned char)*orig);
1298                         *sp++ = ']';
1299                 }
1300                 else
1301                         *sp++ = *orig;
1302         }
1303         *sp = '\0';
1304         return new;
1305 }
1306
1307 /*
1308  * find_matches:
1309  *      Find all the fortunes which match the pattern we've been given.
1310  */
1311 int
1312 find_matches()
1313 {
1314         Fort_len = maxlen_in_list(File_list);
1315         DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1316         /* extra length, "%\n" is appended */
1317         Fortbuf = do_malloc((unsigned int) Fort_len + 10);
1318
1319         Found_one = FALSE;
1320         matches_in_list(File_list);
1321         return Found_one;
1322         /* NOTREACHED */
1323 }
1324
1325 /*
1326  * maxlen_in_list
1327  *      Return the maximum fortune len in the file list.
1328  */
1329 int
1330 maxlen_in_list(list)
1331 FILEDESC        *list;
1332 {
1333         FILEDESC        *fp;
1334         int             len, maxlen;
1335
1336         maxlen = 0;
1337         for (fp = list; fp != NULL; fp = fp->next) {
1338                 if (fp->child != NULL) {
1339                         if ((len = maxlen_in_list(fp->child)) > maxlen)
1340                                 maxlen = len;
1341                 }
1342                 else {
1343                         get_tbl(fp);
1344                         if (fp->tbl.str_longlen > maxlen)
1345                                 maxlen = fp->tbl.str_longlen;
1346                 }
1347         }
1348         return maxlen;
1349 }
1350
1351 /*
1352  * matches_in_list
1353  *      Print out the matches from the files in the list.
1354  */
1355 void
1356 matches_in_list(list)
1357 FILEDESC        *list;
1358 {
1359         char           *sp, *p;
1360         FILEDESC        *fp;
1361         int                     in_file;
1362         unsigned char           ch;
1363
1364         for (fp = list; fp != NULL; fp = fp->next) {
1365                 if (fp->child != NULL) {
1366                         matches_in_list(fp->child);
1367                         continue;
1368                 }
1369                 DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1370                 open_fp(fp);
1371                 sp = Fortbuf;
1372                 in_file = FALSE;
1373                 while (fgets(sp, Fort_len, fp->inf) != NULL)
1374                         if (fp->tbl.str_flags & STR_COMMENTS
1375                             && sp[0] == fp->tbl.str_delim
1376                             && sp[1] == fp->tbl.str_delim)
1377                                 continue;
1378                         else if (!STR_ENDSTRING(sp, fp->tbl))
1379                                 sp += strlen(sp);
1380                         else {
1381                                 *sp = '\0';
1382                                 if (fp->tbl.str_flags & STR_ROTATED)
1383                                         for (p = Fortbuf; (ch = *p) != '\0'; ++p) {
1384                                                 if (isascii(ch)) {
1385                                                         if (isupper(ch))
1386                                                                 *p = 'A' + (ch - 'A' + 13) % 26;
1387                                                         else if (islower(ch))
1388                                                                 *p = 'a' + (ch - 'a' + 13) % 26;
1389                                                 }
1390                                         }
1391                                 if (regexec(&Re_pat, Fortbuf, 0, NULL, 0) != REG_NOMATCH) {
1392                                         printf("%c%c", fp->tbl.str_delim,
1393                                             fp->tbl.str_delim);
1394                                         if (!in_file) {
1395                                                 printf(" (%s)", fp->name);
1396                                                 Found_one = TRUE;
1397                                                 in_file = TRUE;
1398                                         }
1399                                         putchar('\n');
1400                                         (void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
1401                                 }
1402                                 sp = Fortbuf;
1403                         }
1404         }
1405 }
1406
1407 void
1408 usage()
1409 {
1410         (void) fprintf(stderr, "fortune [-a");
1411 #ifdef  DEBUG
1412         (void) fprintf(stderr, "D");
1413 #endif  /* DEBUG */
1414         (void) fprintf(stderr, "efilosw]");
1415         (void) fprintf(stderr, " [-m pattern]");
1416         (void) fprintf(stderr, " [[N%%] file/directory/all]\n");
1417         exit(1);
1418 }
1419
1420 /*
1421  * getpath
1422  *      Set up file search patch from environment var FORTUNE_PATH;
1423  *      if not set, use the compiled in FORTDIR.
1424  */
1425
1426 void
1427 getpath(void)
1428 {
1429         int     nstr, foundenv;
1430         char    *pch, **ppch, *str, *path;
1431
1432         foundenv = 1;
1433         Fortune_path = getenv("FORTUNE_PATH");
1434         if (Fortune_path == NULL) {
1435                 Fortune_path = FORTDIR;
1436                 foundenv = 0;
1437         }
1438         path = strdup(Fortune_path);
1439
1440         for (nstr = 2, pch = path; *pch != '\0'; pch++) {
1441                 if (*pch == ':')
1442                         nstr++;
1443         }
1444
1445         ppch = Fortune_path_arr = (char **)calloc(nstr, sizeof(char *));
1446         
1447         nstr = 0;
1448         str = strtok(path, ":");
1449         while (str) {
1450                 if (is_dir(str)) {
1451                         nstr++;
1452                         *ppch++ = str;
1453                 }
1454                 str = strtok(NULL, ":");
1455         }
1456
1457         if (nstr == 0) {
1458                 if (foundenv == 1) {
1459                         fprintf(stderr,
1460                             "fortune: FORTUNE_PATH: None of the specified "
1461                             "directories found.\n");
1462                         exit(1);
1463                 }
1464                 free(path);
1465                 Fortune_path_arr[0] = FORTDIR;
1466         }
1467 }