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