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