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