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