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