]> granicus.if.org Git - fortune-mod/blob - fortune-mod/fortune/fortune.c
Refactoring / code cleanup.
[fortune-mod] / fortune-mod / fortune / fortune.c
1 /*      $NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $  */
2
3 /*-
4  * Copyright (c) 1986, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Ken Arnold.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *      This product includes software developed by the University of
21  *      California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38
39 /* Modified September, 1995, Amy A. Lewis
40  * 1: removed all file-locking dreck.  Unnecessary
41  * 2: Fixed bug that made fortune -f report a different list than
42  *    fortune with any other parameters, or none, and which forced
43  *    the program to read only one file (named 'fortunes')
44  * 3: removed the unnecessary print_file_list()
45  * 4: Added "OFFDIR" to pathnames.h as the directory in which offensive
46  *    fortunes are kept.  This considerably simplifies our life by
47  *    permitting us to dispense with a lot of silly tests for the string
48  *    "-o" at the end of a filename.
49  * 5: I think the problems with trying to find filenames were fixed by
50  *    the change in the way that offensive files are defined.  Two birds,
51  *    one stone!
52  * 6: Calculated probabilities for all files, so that -f will print them.
53  */
54
55 /* Changes Copyright (c) 1997 Dennis L. Clark.  All rights reserved.
56  *
57  *    The changes in this file may be freely redistributed, modified or
58  *    included in other software, as long as both the above copyright
59  *    notice and these conditions appear intact.
60  */
61
62 /* Modified May 1997, Dennis L. Clark (dbugger@progsoc.uts.edu.au)
63  *  + Various portability fixes
64  *  + Percent selection of files with -a now works on datafiles which
65  *    appear in both unoffensive and offensive directories (see man page
66  *    for details)
67  *  + The -s and -l options are now more consistent in their
68  *    interpretation of fortune length
69  *  + The -s and -l options can now be combined wit the -m option
70  */
71
72 /* Modified Jul 1999, Pablo Saratxaga <srtxg@chanae.alphanet.ch>
73  * - added use of the LANG variables; now if called without argument
74  * it will choose (if they exist) fortunes in the users' language.
75  * (that is, under a directory $LANG/ under the main fortunes directory
76  *
77  * Added to debian by Alastair McKinstry, <mckinstry@computer.org>, 2002-07-31
78  */
79
80 #define PROGRAM_NAME "fortune-mod"
81
82 #include "fortune-mod-common.h"
83
84 #include <dirent.h>
85 #include <fcntl.h>
86 #include <assert.h>
87 #include <errno.h>
88 #include <locale.h>
89 #ifndef _WIN32
90 #include <langinfo.h>
91 #define O_BINARY 0
92 #define WITH_RECODE
93 #endif
94 #ifdef WITH_RECODE
95 #include <recode.h>
96 #endif
97
98 #ifdef HAVE_REGEX_H
99 #include <regex.h>
100 #endif
101 #ifdef HAVE_REGEXP_H
102 #include <regexp.h>
103 #endif
104 #ifdef HAVE_RX_H
105 #include <rx.h>
106 #endif
107
108 #include "config.h"
109
110 #define TRUE 1
111 #define FALSE 0
112
113 #define MINW 6   /* minimum wait if desired */
114 #define CPERS 20 /* # of chars for each sec */
115
116 #define POS_UNKNOWN ((int32_t)-1) /* pos for file unknown */
117 #define NO_PROB (-1)              /* no prob specified for file */
118
119 #ifdef DEBUG
120 #define DPRINTF(l, x)                                                          \
121     if (Debug >= l)                                                            \
122         fprintf x;
123 #else
124 #define DPRINTF(l, x)
125 #endif
126
127 typedef struct fd
128 {
129     int percent;
130     int fd, datfd;
131     int32_t pos;
132     FILE *inf;
133     char *name;
134     char *path;
135     char *datfile, *posfile;
136     bool read_tbl;
137     bool was_pos_file;
138     bool utf8_charset;
139     STRFILE tbl;
140     int num_children;
141     struct fd *child, *parent;
142     struct fd *next, *prev;
143 } FILEDESC;
144
145 static char *env_lang;
146
147 static bool Found_one;           /* did we find a match? */
148 static bool Find_files = FALSE;  /* just find a list of proper fortune files */
149 static bool Wait = FALSE;        /* wait desired after fortune */
150 static bool Short_only = FALSE;  /* short fortune desired */
151 static bool Long_only = FALSE;   /* long fortune desired */
152 static bool Offend = FALSE;      /* offensive fortunes only */
153 static bool All_forts = FALSE;   /* any fortune allowed */
154 static bool Equal_probs = FALSE; /* scatter un-allocated prob equally */
155 static bool Show_filename = FALSE;
156 static bool No_recode = FALSE; /* Do we want to stop recoding from occuring */
157
158 static bool ErrorMessage =
159     FALSE; /* Set to true if an error message has been displayed */
160
161 #ifndef NO_REGEX
162 static bool Match = FALSE; /* dump fortunes matching a pattern */
163
164 #endif
165 #ifdef DEBUG
166 static bool Debug = FALSE; /* print debug messages */
167
168 #endif
169
170 static unsigned char *Fortbuf = NULL; /* fortune buffer for -m */
171
172 static int Fort_len = 0, Spec_prob = 0, /* total prob specified on cmd line */
173     Num_files, Num_kids,                /* totals of files and children. */
174     SLEN = 160; /* max. characters in a "short" fortune */
175
176 static int32_t Seekpts[2]; /* seek pointers to fortunes */
177
178 static FILEDESC *File_list = NULL, /* Head of file list */
179     *File_tail = NULL;             /* Tail of file list */
180 static FILEDESC *Fortfile;         /* Fortune file to use */
181
182 static STRFILE Noprob_tbl; /* sum of data for all no prob files */
183
184 #ifdef POSIX_REGEX
185 #define RE_COMP(p) regcomp(&Re_pat, (p), REG_NOSUB)
186 #define BAD_COMP(f) ((f) != 0)
187 #define RE_EXEC(p) (regexec(&Re_pat, (p), 0, NULL, 0) == 0)
188
189 static regex_t Re_pat;
190 #else
191 #define NO_REGEX
192 #endif /* POSIX_REGEX */
193
194 #ifdef WITH_RECODE
195 static RECODE_REQUEST request;
196 static RECODE_OUTER outer;
197 #endif
198
199 int add_dir(FILEDESC *);
200
201 static unsigned long my_random(unsigned long base)
202 {
203     unsigned long long l = 0;
204     char *hard_coded_val = getenv("FORTUNE_MOD_RAND_HARD_CODED_VALS");
205     if (hard_coded_val)
206     {
207         return ((unsigned long)atol(hard_coded_val) % base);
208     }
209     if (getenv("FORTUNE_MOD_USE_SRAND"))
210     {
211         goto fallback;
212     }
213     FILE *const fp = fopen("/dev/urandom", "rb");
214     if (!fp)
215     {
216         goto fallback;
217     }
218     if (fread(&l, sizeof(l), 1, fp) != 1)
219     {
220         fclose(fp);
221         goto fallback;
222     }
223     fclose(fp);
224     return l % base;
225 fallback:
226     return random() % base;
227 }
228
229 static char *program_version(void)
230 {
231     static char buf[BUFSIZ];
232     (void)sprintf(buf, "%s version %s", PROGRAM_NAME, VERSION);
233     return buf;
234 }
235
236 static void __attribute__((noreturn)) usage(void)
237 {
238     (void)fprintf(stderr, "%s\n", program_version());
239     (void)fprintf(stderr, "fortune [-a");
240 #ifdef DEBUG
241     (void)fprintf(stderr, "D");
242 #endif /* DEBUG */
243     (void)fprintf(stderr, "f");
244 #ifndef NO_REGEX
245     (void)fprintf(stderr, "i");
246 #endif /* NO_REGEX */
247     (void)fprintf(stderr, "l");
248 #ifndef NO_OFFENSIVE
249     (void)fprintf(stderr, "o");
250 #endif
251     (void)fprintf(stderr, "sw]");
252 #ifndef NO_REGEX
253     (void)fprintf(stderr, " [-m pattern]");
254 #endif /* NO_REGEX */
255     (void)fprintf(stderr, " [-n number] [ [#%%] file/directory/all]\n");
256     exit(1);
257 }
258
259 #define STR(str) ((str) == NULL ? "NULL" : (str))
260
261 /*
262  * calc_equal_probs:
263  *      Set the global values for number of files/children, to be used
264  * in printing probabilities when listing files
265  */
266 static void calc_equal_probs(void)
267 {
268     Num_files = Num_kids = 0;
269     FILEDESC *fiddlylist = File_list;
270     while (fiddlylist != NULL)
271     {
272         Num_files++;
273         Num_kids += fiddlylist->num_children;
274         fiddlylist = fiddlylist->next;
275     }
276 }
277
278 /*
279  * print_list:
280  *      Print out the actual list, recursively.
281  */
282 static void print_list(FILEDESC *list, int lev)
283 {
284     while (list != NULL)
285     {
286         fprintf(stderr, "%*s", lev * 4, "");
287         if (list->percent == NO_PROB)
288             if (!Equal_probs)
289                 /* This, with some changes elsewhere, gives proper percentages
290                  * for every case fprintf(stderr, "___%%"); */
291                 fprintf(stderr, "%5.2f%%",
292                     (100.0 - Spec_prob) * list->tbl.str_numstr /
293                         Noprob_tbl.str_numstr);
294             else if (lev == 0)
295                 fprintf(stderr, "%5.2f%%", 100.0 / Num_files);
296             else
297                 fprintf(stderr, "%5.2f%%", 100.0 / Num_kids);
298         else
299             fprintf(stderr, "%5.2f%%", 1.0 * list->percent);
300         fprintf(stderr, " %s", STR(list->name));
301         DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
302                        STR(list->datfile), STR(list->posfile)));
303         putc('\n', stderr);
304         if (list->child != NULL)
305             print_list(list->child, lev + 1);
306         list = list->next;
307     }
308 }
309
310 #ifndef NO_REGEX
311 /*
312  * conv_pat:
313  *      Convert the pattern to an ignore-case equivalent.
314  */
315 static char *conv_pat(char *orig)
316 {
317     char *sp;
318     unsigned int cnt;
319     char *new;
320
321     cnt = 1; /* allow for '\0' */
322     for (sp = orig; *sp != '\0'; sp++)
323         if (isalpha(*sp))
324             cnt += 4;
325         else
326             cnt++;
327     if ((new = malloc(cnt)) == NULL)
328     {
329         fprintf(stderr, "pattern too long for ignoring case\n");
330         exit(1);
331     }
332
333     for (sp = new; *orig != '\0'; orig++)
334     {
335         if (islower(*orig))
336         {
337             *sp++ = '[';
338             *sp++ = *orig;
339             *sp++ = (char)toupper(*orig);
340             *sp++ = ']';
341         }
342         else if (isupper(*orig))
343         {
344             *sp++ = '[';
345             *sp++ = *orig;
346             *sp++ = (char)tolower(*orig);
347             *sp++ = ']';
348         }
349         else
350             *sp++ = *orig;
351     }
352     *sp = '\0';
353     return new;
354 }
355 #endif /* NO_REGEX */
356
357 /*
358  * do_malloc:
359  *      Do a malloc, checking for NULL return.
360  */
361 static void *do_malloc(size_t size)
362 {
363     void *new_buf = malloc(size);
364
365     if (!new_buf)
366     {
367         (void)fprintf(stderr, "fortune: out of memory.\n");
368         exit(1);
369     }
370     return new_buf;
371 }
372
373 /*
374  * new_fp:
375  *      Return a pointer to an initialized new FILEDESC.
376  */
377 static FILEDESC *new_fp(void)
378 {
379     FILEDESC *fp;
380
381     fp = (FILEDESC *)do_malloc(sizeof *fp);
382     fp->datfd = -1;
383     fp->pos = POS_UNKNOWN;
384     fp->inf = NULL;
385     fp->fd = -1;
386     fp->percent = NO_PROB;
387     fp->read_tbl = FALSE;
388     fp->tbl.str_version = 0;
389     fp->tbl.str_numstr = 0;
390     fp->tbl.str_longlen = 0;
391     fp->tbl.str_shortlen = 0;
392     fp->tbl.str_flags = 0;
393     fp->tbl.stuff[0] = 0;
394     fp->tbl.stuff[1] = 0;
395     fp->tbl.stuff[2] = 0;
396     fp->tbl.stuff[3] = 0;
397     fp->next = NULL;
398     fp->prev = NULL;
399     fp->child = NULL;
400     fp->parent = NULL;
401     fp->datfile = NULL;
402     fp->posfile = NULL;
403     return fp;
404 }
405
406 #ifdef MYDEBUG
407 static inline void debugprint(const char *msg, ...)
408 {
409     va_list ap;
410
411     va_start(ap, msg);
412     vfprintf(stderr, msg, ap);
413     fflush(stderr);
414     va_end(ap);
415 }
416 #else
417 #define debugprint(format, ...)                                                \
418     {                                                                          \
419     }
420 #endif
421 /*
422  * is_dir:
423  *      Return TRUE if the file is a directory, FALSE otherwise.
424  */
425 static int is_dir(const char *const file)
426 {
427     struct stat sbuf;
428
429     if (stat(file, &sbuf) < 0)
430     {
431         debugprint("is_dir failed for file=<%s>\n", file);
432         return -1;
433     }
434     const bool ret = ((sbuf.st_mode & S_IFDIR) ? true : false);
435     debugprint("is_dir for file=<%s> gave ret=<%d>\n", file, ret);
436     return ret;
437 }
438
439 /*
440  * is_existant:
441  *      Return TRUE if the file exists, FALSE otherwise.
442  */
443 static int is_existant(char *file)
444 {
445     struct stat staat;
446
447     if (stat(file, &staat) == 0)
448         return TRUE;
449     switch (errno)
450     {
451     case ENOENT:
452     case ENOTDIR:
453         return FALSE;
454     default:
455         perror("fortune: bad juju in is_existant");
456         exit(1);
457     }
458 }
459
460 /*
461  * is_fortfile:
462  *      Return TRUE if the file is a fortune database file.  We try and
463  *      exclude files without reading them if possible to avoid
464  *      overhead.  Files which start with ".", or which have "illegal"
465  *      suffixes, as contained in suflist[], are ruled out.
466  */
467 static int is_fortfile(const char *const file, char **datp)
468 {
469     const char *sp = strrchr(file, '/');
470     static const char *suflist[] = {/* list of "illegal" suffixes" */
471         "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas",
472         "ins.ftn", "sml", NULL};
473
474     DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
475
476     if (!sp)
477         sp = file;
478     else
479         sp++;
480     if (*sp == '.')
481     {
482         DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
483         return FALSE;
484     }
485     if ((sp = strrchr(sp, '.')) != NULL)
486     {
487         sp++;
488         for (int i = 0; suflist[i] != NULL; i++)
489             if (strcmp(sp, suflist[i]) == 0)
490             {
491                 DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
492                 return FALSE;
493             }
494     }
495
496     char *const datfile = do_malloc((unsigned int)(strlen(file) + 6));
497     sprintf(datfile, "%s.dat", file);
498     if (access(datfile, R_OK) < 0)
499     {
500         free(datfile);
501         DPRINTF(2, (stderr, "FALSE (no \".dat\" file)\n"));
502         return FALSE;
503     }
504     if (datp != NULL)
505         *datp = datfile;
506     else
507         free(datfile);
508     DPRINTF(2, (stderr, "TRUE\n"));
509     return TRUE;
510 }
511
512 static bool path_is_absolute(const char *const path)
513 {
514     if (path[0] == '/')
515     {
516         return true;
517     }
518 #ifdef _WIN32
519     if (isalpha(path[0]) && path[1] == ':' && path[2] == '/')
520     {
521         return true;
522     }
523 #endif
524     return false;
525 }
526 /*
527  * add_file:
528  *      Add a file to the file list.
529  */
530 static int add_file(int percent, const char *file, const char *dir,
531     FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
532 {
533     FILEDESC *fp;
534     int fd = -1;
535     char *path, *testpath;
536     char *sp;
537     bool found;
538     struct stat statbuf;
539
540     if (dir == NULL)
541     {
542         path = strdup(file);
543     }
544     else
545     {
546         path = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2));
547         sprintf(path, "%s/%s", dir, file);
548     }
549     if (*path == '/' &&
550         !is_existant(path)) /* If doesn't exist, don't do anything. */
551     {
552         free(path);
553         return FALSE;
554     }
555     const int isdir = is_dir(path);
556     if ((isdir > 0 && parent != NULL) || (isdir < 0))
557     {
558         free(path);
559         return FALSE; /* don't recurse */
560     }
561
562     DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
563     if ((
564 #ifdef _WIN32
565             (!isdir) &&
566 #endif
567             ((fd = open(path, O_RDONLY | O_BINARY)) < 0)) ||
568         !path_is_absolute(path))
569     {
570         debugprint("sarahhhhh fd=%d path=<%s> dir=<%s> file=<%s> percent=%d\n",
571             fd, path, dir, file, percent);
572         found = FALSE;
573         if (dir == NULL && (strchr(file, '/') == NULL))
574         {
575             if (((sp = strrchr(file, '-')) != NULL) && (strcmp(sp, "-o") == 0))
576             {
577 #define CALL__add_file(dir) add_file(percent, file, dir, head, tail, parent)
578 #define COND_CALL__add_file(loc_dir, dir)                                      \
579     ((!strcmp((loc_dir), (dir))) ? 0 : CALL__add_file(dir))
580                 /* BSD-style '-o' offensive file suffix */
581                 *sp = '\0';
582                 found = CALL__add_file(LOCOFFDIR) ||
583                         COND_CALL__add_file(LOCOFFDIR, OFFDIR);
584                 /* put the suffix back in for better identification later */
585                 *sp = '-';
586             }
587             else if (All_forts)
588                 found =
589                     (CALL__add_file(LOCFORTDIR) || CALL__add_file(LOCOFFDIR) ||
590                         COND_CALL__add_file(LOCFORTDIR, FORTDIR) ||
591                         COND_CALL__add_file(LOCOFFDIR, OFFDIR));
592             else if (Offend)
593                 found = (CALL__add_file(LOCOFFDIR) ||
594                          COND_CALL__add_file(LOCOFFDIR, OFFDIR));
595             else
596                 found = (CALL__add_file(LOCFORTDIR) ||
597                          COND_CALL__add_file(LOCFORTDIR, FORTDIR));
598 #undef COND_CALL__add_file
599 #undef CALL__add_file
600         }
601         if (!found && parent == NULL && dir == NULL)
602         { /* don't display an error when trying language specific files */
603             if (env_lang)
604             {
605                 char llang[512];
606                 char langdir[1024];
607                 int ret = 0;
608
609                 strncpy(llang, env_lang, sizeof(llang));
610                 llang[sizeof(llang) - 1] = '\0';
611                 char *lang = llang;
612
613                 /* the language string can be like "es:fr_BE:ga" */
614                 while (!ret && lang && (*lang))
615                 {
616                     char *p = strchr(lang, ':');
617                     if (p)
618                         *p++ = '\0';
619                     snprintf(langdir, sizeof(langdir), "%s/%s", FORTDIR, lang);
620
621                     if (strncmp(path, lang, 2) == 0)
622                         ret = 1;
623                     else if (strncmp(path, langdir, strlen(FORTDIR) + 3) == 0)
624                         ret = 1;
625                     lang = p;
626                 }
627                 if (!ret)
628                 {
629                     debugprint("moshe\n");
630                     perror(path);
631                 }
632             }
633             else
634             {
635                 debugprint("abe\n");
636                 perror(path);
637             }
638         }
639
640         free(path);
641         path = NULL;
642         return found;
643     }
644
645     DPRINTF(2, (stderr, "path = \"%s\"\n", path));
646
647     fp = new_fp();
648     fp->fd = fd;
649     fp->percent = percent;
650
651     fp->name = strdup(file);
652     fp->path = strdup(path);
653
654     // FIXME
655     fp->utf8_charset = FALSE;
656     testpath = do_malloc(strlen(path) + 4UL);
657     sprintf(testpath, "%s.u8", path);
658     //    fprintf(stderr, "State mal: %s\n", testpath);
659     if (stat(testpath, &statbuf) == 0)
660         fp->utf8_charset = TRUE;
661
662     free(testpath);
663     testpath = NULL;
664     //    fprintf(stderr, "Is utf8?: %i\n", fp->utf8_charset );
665
666     fp->parent = parent;
667
668     if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile)))
669     {
670         if (parent == NULL)
671             fprintf(
672                 stderr, "fortune:%s not a fortune file or directory\n", path);
673         free(path);
674         path = NULL;
675         free(fp->datfile);
676         free(fp->posfile);
677         free(fp->name);
678         free(fp->path);
679         if (fp->fd >= 0)
680             close(fp->fd);
681         free(fp);
682         return FALSE;
683     }
684
685     /* This is a hack to come around another hack - add_dir returns success
686      * if the directory is allowed to be empty, but we can not handle an
687      * empty directory... */
688     if (isdir && fp->num_children == 0)
689     {
690         free(path);
691         path = NULL;
692         free(fp->datfile);
693         free(fp->posfile);
694         free(fp->name);
695         free(fp->path);
696         if (fp->fd >= 0)
697             close(fp->fd);
698         free(fp);
699         return TRUE;
700     }
701     /* End hack. */
702
703     if (*head == NULL)
704         *head = *tail = fp;
705     else if (fp->percent == NO_PROB)
706     {
707         (*tail)->next = fp;
708         fp->prev = *tail;
709         *tail = fp;
710     }
711     else
712     {
713         (*head)->prev = fp;
714         fp->next = *head;
715         *head = fp;
716     }
717
718     free(path);
719     path = NULL;
720
721     return TRUE;
722 }
723
724 static int names_compare(const void *a, const void *b)
725 {
726     return strcmp(*(const char *const *)a, *(const char *const *)b);
727 }
728 /*
729  * add_dir:
730  *      Add the contents of an entire directory.
731  */
732 int add_dir(FILEDESC *fp)
733 {
734     DIR *dir;
735     struct dirent *dirent;
736     char **names;
737     size_t i, count_names, max_count_names;
738
739     close(fp->fd);
740     fp->fd = -1;
741     if ((dir = opendir(fp->path)) == NULL)
742     {
743         debugprint("yonah\n");
744         perror(fp->path);
745         return FALSE;
746     }
747     FILEDESC *tailp = NULL;
748     DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
749     fp->num_children = 0;
750     max_count_names = 200;
751     count_names = 0;
752     names = malloc(sizeof(names[0]) * max_count_names);
753     if (!names)
754     {
755         debugprint("zach\n");
756         perror("Out of RAM!");
757         exit(-1);
758     }
759     while ((dirent = readdir(dir)) != NULL)
760     {
761         if (dirent->d_name[0] == 0)
762             continue;
763         char *name = strdup(dirent->d_name);
764         if (count_names == max_count_names)
765         {
766             max_count_names += 200;
767             names = realloc(names, sizeof(names[0]) * max_count_names);
768             if (!names)
769             {
770                 debugprint("rebecca\n");
771                 perror("Out of RAM!");
772                 exit(-1);
773             }
774         }
775         names[count_names++] = name;
776     }
777     closedir(dir);
778     qsort(names, count_names, sizeof(names[0]), names_compare);
779
780     for (i = 0; i < count_names; ++i)
781     {
782         if (add_file(NO_PROB, names[i], fp->path, &fp->child, &tailp, fp))
783         {
784             fp->num_children++;
785         }
786         free(names[i]);
787     }
788     free(names);
789     dir = NULL;
790     if (fp->num_children == 0)
791     {
792         /*
793          * Only the local fortune dir and the local offensive dir are
794          * allowed to be empty.
795          *  - Brian Bassett (brianb@debian.org) 1999/07/31
796          */
797         if (strcmp(LOCFORTDIR, fp->path) == 0 ||
798             strcmp(LOCOFFDIR, fp->path) == 0)
799         {
800             return TRUE;
801         }
802         fprintf(
803             stderr, "fortune: %s: No fortune files in directory.\n", fp->path);
804         return FALSE;
805     }
806     return TRUE;
807 }
808
809 /*
810  * form_file_list:
811  *      Form the file list from the file specifications.
812  */
813
814 static int top_level__add_file(const char *dirpath)
815 {
816     return add_file(NO_PROB, dirpath, NULL, &File_list, &File_tail, NULL);
817 }
818
819 static int cond_top_level__add_file(
820     const char *dirpath, const char *possible_dup)
821 {
822     if (!strcmp(dirpath, possible_dup))
823     {
824         return 0;
825     }
826     return top_level__add_file(dirpath);
827 }
828
829 static int cond_top_level__LOCFORTDIR(void)
830 {
831     return cond_top_level__add_file(FORTDIR, LOCFORTDIR);
832 }
833
834 static int cond_top_level__OFFDIR(void)
835 {
836     return cond_top_level__add_file(OFFDIR, LOCOFFDIR);
837 }
838
839 static int top_level_LOCFORTDIR(void)
840 {
841     return (top_level__add_file(LOCFORTDIR) | cond_top_level__LOCFORTDIR());
842 }
843
844 static int form_file_list(char **files, int file_cnt)
845 {
846     int i, percent;
847     char *sp;
848     char langdir[1024];
849     char fullpathname[512], locpathname[512];
850
851     if (file_cnt == 0)
852     {
853         if (All_forts)
854         {
855             return (top_level__add_file(LOCFORTDIR) |
856                     top_level__add_file(LOCOFFDIR) |
857                     cond_top_level__LOCFORTDIR() | cond_top_level__OFFDIR());
858         }
859         else if (Offend)
860         {
861             return (top_level__add_file(LOCOFFDIR) | cond_top_level__OFFDIR());
862         }
863         else
864         {
865             if (env_lang)
866             {
867                 char *lang;
868                 char llang[512];
869                 int ret = 0;
870                 char *p;
871
872                 strncpy(llang, env_lang, sizeof(llang));
873                 llang[sizeof(llang) - 1] = '\0';
874                 lang = llang;
875
876                 /* the language string can be like "es:fr_BE:ga" */
877                 while (lang && (*lang))
878                 {
879                     p = strchr(lang, ':');
880                     if (p)
881                         *p++ = '\0';
882
883                     /* first try full locale */
884                     ret = add_file(
885                         NO_PROB, lang, NULL, &File_list, &File_tail, NULL);
886
887                     /* if not try language name only (two first chars) */
888                     if (!ret)
889                     {
890                         char ll[3];
891
892                         strncpy(ll, lang, 2);
893                         ll[2] = '\0';
894                         ret = add_file(
895                             NO_PROB, ll, NULL, &File_list, &File_tail, NULL);
896                     }
897
898                     /* if we have found one we have finished */
899                     if (ret)
900                         return ret;
901                     lang = p;
902                 }
903                 /* default */
904                 return top_level_LOCFORTDIR();
905             }
906             else
907             {
908                 /* no locales available, use default */
909                 return top_level_LOCFORTDIR();
910             }
911         }
912     }
913
914     for (i = 0; i < file_cnt; i++)
915     {
916         percent = NO_PROB;
917         if (!isdigit(files[i][0]))
918             sp = files[i];
919         else
920         {
921             percent = 0;
922             for (sp = files[i]; isdigit(*sp); sp++)
923                 percent = percent * 10 + *sp - '0';
924             if (percent > 100)
925             {
926                 fprintf(stderr, "percentages must be <= 100\n");
927                 ErrorMessage = TRUE;
928                 return FALSE;
929             }
930             if (*sp == '.')
931             {
932                 fprintf(stderr, "percentages must be integers\n");
933                 ErrorMessage = TRUE;
934                 return FALSE;
935             }
936             /*
937              * If the number isn't followed by a '%', then
938              * it was not a percentage, just the first part
939              * of a file name which starts with digits.
940              */
941             if (*sp != '%')
942             {
943                 percent = NO_PROB;
944                 sp = files[i];
945             }
946             else if (*++sp == '\0')
947             {
948                 if (++i >= file_cnt)
949                 {
950                     fprintf(stderr, "percentages must precede files\n");
951                     ErrorMessage = TRUE;
952                     return FALSE;
953                 }
954                 sp = files[i];
955             }
956         }
957         if (strcmp(sp, "all") == 0)
958         {
959             snprintf(fullpathname, sizeof(fullpathname), "%s", FORTDIR);
960             snprintf(locpathname, sizeof(locpathname), "%s", LOCFORTDIR);
961         }
962         /* if it isn't an absolute path or relative to . or ..
963            make it an absolute path relative to FORTDIR */
964         else
965         {
966             if (strncmp(sp, "/", 1) != 0 && strncmp(sp, "./", 2) != 0 &&
967                 strncmp(sp, "../", 3) != 0)
968             {
969                 snprintf(
970                     fullpathname, sizeof(fullpathname), "%s/%s", FORTDIR, sp);
971                 snprintf(
972                     locpathname, sizeof(locpathname), "%s/%s", LOCFORTDIR, sp);
973             }
974             else
975             {
976                 snprintf(fullpathname, sizeof(fullpathname), "%s", sp);
977                 snprintf(locpathname, sizeof(locpathname), "%s", sp);
978             }
979         }
980
981         if (env_lang)
982         {
983             char llang[512];
984             int ret = 0;
985
986             strncpy(llang, env_lang, sizeof(llang));
987             llang[sizeof(llang) - 1] = '\0';
988             char *lang = llang;
989
990             /* the language string can be like "es:fr_BE:ga" */
991             while (!ret && lang && (*lang))
992             {
993                 char *p = strchr(lang, ':');
994                 if (p)
995                     *p++ = '\0';
996
997                 /* first try full locale */
998                 snprintf(
999                     langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, lang, sp);
1000                 ret = add_file(
1001                     percent, langdir, NULL, &File_list, &File_tail, NULL);
1002
1003                 /* if not try language name only (two first chars) */
1004                 if (!ret)
1005                 {
1006                     char ll[3];
1007
1008                     strncpy(ll, lang, 2);
1009                     ll[2] = '\0';
1010                     snprintf(
1011                         langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, ll, sp);
1012                     ret = add_file(
1013                         percent, langdir, NULL, &File_list, &File_tail, NULL);
1014                 }
1015
1016                 lang = p;
1017             }
1018             /* default */
1019             if (!ret)
1020                 ret = add_file(
1021                     percent, fullpathname, NULL, &File_list, &File_tail, NULL);
1022             if (!ret &&
1023                 strncmp(fullpathname, locpathname, sizeof(fullpathname)))
1024                 ret = add_file(
1025                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1026
1027             if (!ret)
1028             {
1029                 snprintf(locpathname, sizeof(locpathname), "%s/%s",
1030                     getenv("PWD"), sp);
1031
1032                 ret = add_file(
1033                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1034             }
1035             if (!ret)
1036             {
1037                 return FALSE;
1038             }
1039             if (strncmp(fullpathname, locpathname, sizeof(fullpathname)) &&
1040                 strcmp(sp, "all") == 0)
1041             {
1042                 add_file(
1043                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1044             }
1045         }
1046         else if (!add_file(
1047                      percent, fullpathname, NULL, &File_list, &File_tail, NULL))
1048             return FALSE;
1049     }
1050     return TRUE;
1051 }
1052
1053 /*
1054  *    This routine evaluates the arguments on the command line
1055  */
1056 static void getargs(int argc, char **argv)
1057 {
1058     int ignore_case = FALSE;
1059
1060 #ifndef NO_REGEX
1061     char *pat = NULL;
1062
1063 #endif /* NO_REGEX */
1064     int ch;
1065
1066 #ifdef DEBUG
1067 #define DEBUG_GETOPT "D"
1068 #else
1069 #define DEBUG_GETOPT
1070 #endif
1071
1072 #ifdef NO_OFFENSIVE
1073 #define OFFENSIVE_GETOPT
1074 #else
1075 #define OFFENSIVE_GETOPT "o"
1076 #endif
1077
1078     while ((ch = getopt(argc, argv,
1079                 "ac" DEBUG_GETOPT "efilm:n:" OFFENSIVE_GETOPT "suvw")) != EOF)
1080         switch (ch)
1081         {
1082         case 'a': /* any fortune */
1083             All_forts = TRUE;
1084             break;
1085 #ifdef DEBUG
1086         case 'D':
1087             Debug++;
1088             break;
1089 #endif /* DEBUG */
1090         case 'e':
1091             Equal_probs = TRUE; /* scatter un-allocted prob equally */
1092             break;
1093         case 'f': /* find fortune files */
1094             Find_files = TRUE;
1095             break;
1096         case 'l': /* long ones only */
1097             Long_only = TRUE;
1098             Short_only = FALSE;
1099             break;
1100         case 'n':
1101             SLEN = atoi(optarg);
1102             break;
1103 #ifndef NO_OFFENSIVE
1104         case 'o': /* offensive ones only */
1105             Offend = TRUE;
1106             break;
1107 #endif
1108         case 's': /* short ones only */
1109             Short_only = TRUE;
1110             Long_only = FALSE;
1111             break;
1112         case 'w': /* give time to read */
1113             Wait = TRUE;
1114             break;
1115 #ifdef NO_REGEX
1116         case 'i': /* case-insensitive match */
1117         case 'm': /* dump out the fortunes */
1118             (void)fprintf(stderr,
1119                 "fortune: can't match fortunes on this system (Sorry)\n");
1120             exit(0);
1121 #else             /* NO_REGEX */
1122         case 'm': /* dump out the fortunes */
1123             Match = TRUE;
1124             pat = optarg;
1125             break;
1126         case 'i': /* case-insensitive match */
1127             ignore_case++;
1128             break;
1129 #endif            /* NO_REGEX */
1130         case 'u': /* Don't recode the fortune */
1131             No_recode = TRUE;
1132             break;
1133         case 'v':
1134             (void)printf("%s\n", program_version());
1135             exit(0);
1136         case 'c':
1137             Show_filename = TRUE;
1138             break;
1139         case '?':
1140         default:
1141             usage();
1142         }
1143     argc -= optind;
1144     argv += optind;
1145
1146     if (!form_file_list(argv, argc))
1147     {
1148         if (!ErrorMessage)
1149             fprintf(stderr, "No fortunes found\n");
1150         exit(1); /* errors printed through form_file_list() */
1151     }
1152 #ifdef DEBUG
1153 /*      if (Debug >= 1)
1154  * print_list(File_list, 0); */
1155 #endif /* DEBUG */
1156 /* If (Find_files) print_list() moved to main */
1157 #ifndef NO_REGEX
1158     if (pat != NULL)
1159     {
1160         if (ignore_case)
1161             pat = conv_pat(pat);
1162         if (BAD_COMP(RE_COMP(pat)))
1163         {
1164             fprintf(stderr, "bad pattern: %s\n", pat);
1165             exit(1);
1166         }
1167         if (ignore_case)
1168         {
1169             free(pat);
1170         }
1171     }
1172 #endif /* NO_REGEX */
1173 }
1174
1175 /*
1176  * init_prob:
1177  *      Initialize the fortune probabilities.
1178  */
1179 static void init_prob(void)
1180 {
1181     FILEDESC *fp;
1182     int percent = 0, num_noprob = 0, frac;
1183
1184     /*
1185      * Distribute the residual probability (if any) across all
1186      * files with unspecified probability (i.e., probability of 0)
1187      * (if any).
1188      */
1189     FILEDESC *last = NULL;
1190     for (fp = File_tail; fp != NULL; fp = fp->prev)
1191         if (fp->percent == NO_PROB)
1192         {
1193             num_noprob++;
1194             if (Equal_probs)
1195                 last = fp;
1196         }
1197         else
1198             percent += fp->percent;
1199     DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
1200                    percent, num_noprob));
1201     if (percent > 100)
1202     {
1203         fprintf(stderr, "fortune: probabilities sum to %d%%!\n", percent);
1204         exit(1);
1205     }
1206     else if (percent < 100 && num_noprob == 0)
1207     {
1208         fprintf(stderr,
1209             "fortune: no place to put residual probability (%d%%)\n", percent);
1210         exit(1);
1211     }
1212     else if (percent == 100 && num_noprob != 0)
1213     {
1214         fprintf(
1215             stderr, "fortune: no probability left to put in residual files\n");
1216         exit(1);
1217     }
1218     Spec_prob = percent; /* this is for -f when % is specified on cmd line */
1219     percent = 100 - percent;
1220     if (Equal_probs)
1221     {
1222         if (num_noprob != 0)
1223         {
1224             if (num_noprob > 1)
1225             {
1226                 frac = percent / num_noprob;
1227                 DPRINTF(1, (stderr, ", frac = %d%%", frac));
1228                 for (fp = File_tail; fp != last; fp = fp->prev)
1229                     if (fp->percent == NO_PROB)
1230                     {
1231                         fp->percent = frac;
1232                         percent -= frac;
1233                     }
1234             }
1235             last->percent = percent;
1236             DPRINTF(1, (stderr, ", residual = %d%%", percent));
1237         }
1238         else
1239         {
1240             DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n",
1241                            percent));
1242         }
1243     }
1244     DPRINTF(1, (stderr, "\n"));
1245
1246 #ifdef DEBUG
1247 /*      if (Debug >= 1)
1248  * print_list(File_list, 0); *//* Causes crash with new %% code */
1249 #endif
1250 }
1251
1252 /*
1253  * zero_tbl:
1254  *      Zero out the fields we care about in a tbl structure.
1255  */
1256 static void zero_tbl(STRFILE *tp)
1257 {
1258     tp->str_numstr = 0;
1259     tp->str_longlen = 0;
1260     tp->str_shortlen = (uint32_t)(-1);
1261 }
1262
1263 /*
1264  * sum_tbl:
1265  *      Merge the tbl data of t2 into t1.
1266  */
1267 static void sum_tbl(STRFILE *t1, STRFILE *t2)
1268 {
1269     t1->str_numstr += t2->str_numstr;
1270     if (t1->str_longlen < t2->str_longlen)
1271         t1->str_longlen = t2->str_longlen;
1272     if (t1->str_shortlen > t2->str_shortlen)
1273         t1->str_shortlen = t2->str_shortlen;
1274 }
1275
1276 /*
1277  * get_tbl:
1278  *      Get the tbl data file the datfile.
1279  */
1280 static void get_tbl(FILEDESC *fp)
1281 {
1282     int fd;
1283     FILEDESC *child;
1284
1285     if (fp->read_tbl)
1286         return;
1287     if (fp->child == NULL)
1288     {
1289         if ((fd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1290         {
1291             perror(fp->datfile);
1292             exit(1);
1293         }
1294         if (read(fd, &fp->tbl.str_version, sizeof fp->tbl.str_version) !=
1295             sizeof fp->tbl.str_version)
1296         {
1297             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1298             exit(1);
1299         }
1300         if (read(fd, &fp->tbl.str_numstr, sizeof fp->tbl.str_numstr) !=
1301             sizeof fp->tbl.str_numstr)
1302         {
1303             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1304             exit(1);
1305         }
1306         if (read(fd, &fp->tbl.str_longlen, sizeof fp->tbl.str_longlen) !=
1307             sizeof fp->tbl.str_longlen)
1308         {
1309             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1310             exit(1);
1311         }
1312         if (read(fd, &fp->tbl.str_shortlen, sizeof fp->tbl.str_shortlen) !=
1313             sizeof fp->tbl.str_shortlen)
1314         {
1315             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1316             exit(1);
1317         }
1318         if (read(fd, &fp->tbl.str_flags, sizeof fp->tbl.str_flags) !=
1319             sizeof fp->tbl.str_flags)
1320         {
1321             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1322             exit(1);
1323         }
1324         if (read(fd, &fp->tbl.stuff, sizeof fp->tbl.stuff) !=
1325             sizeof fp->tbl.stuff)
1326         {
1327             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1328             exit(1);
1329         }
1330         fp->tbl.str_version = ntohl(fp->tbl.str_version);
1331         fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1332         fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1333         fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1334         fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1335         close(fd);
1336     }
1337     else
1338     {
1339         zero_tbl(&fp->tbl);
1340         for (child = fp->child; child != NULL; child = child->next)
1341         {
1342             get_tbl(child);
1343             sum_tbl(&fp->tbl, &child->tbl);
1344         }
1345     }
1346     fp->read_tbl = TRUE;
1347 }
1348
1349 /*
1350  * sum_noprobs:
1351  *      Sum up all the noprob probabilities, starting with fp.
1352  */
1353 static void sum_noprobs(FILEDESC *fp)
1354 {
1355     static bool did_noprobs = FALSE;
1356
1357     if (did_noprobs)
1358         return;
1359     zero_tbl(&Noprob_tbl);
1360     while (fp != NULL)
1361     {
1362         get_tbl(fp);
1363         /* This conditional should help us return correct values for -f
1364          * when a percentage is specified */
1365         if (fp->percent == NO_PROB)
1366             sum_tbl(&Noprob_tbl, &fp->tbl);
1367         fp = fp->next;
1368     }
1369     did_noprobs = TRUE;
1370 }
1371
1372 /*
1373  * pick_child
1374  *      Pick a child from a chosen parent.
1375  */
1376 static FILEDESC *pick_child(FILEDESC *parent)
1377 {
1378     FILEDESC *fp;
1379     int choice;
1380
1381     if (Equal_probs)
1382     {
1383         choice = my_random(parent->num_children);
1384         DPRINTF(1, (stderr, "    choice = %d (of %d)\n", choice,
1385                        parent->num_children));
1386         for (fp = parent->child; choice--; fp = fp->next)
1387             continue;
1388         DPRINTF(1, (stderr, "    using %s\n", fp->name));
1389         return fp;
1390     }
1391     else
1392     {
1393         get_tbl(parent);
1394         choice = (int)(my_random(parent->tbl.str_numstr));
1395         DPRINTF(1, (stderr, "    choice = %d (of %ld)\n", choice,
1396                        parent->tbl.str_numstr));
1397         for (fp = parent->child; choice >= (int)fp->tbl.str_numstr;
1398              fp = fp->next)
1399         {
1400             choice -= fp->tbl.str_numstr;
1401             DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n", fp->name,
1402                            fp->tbl.str_numstr, choice));
1403         }
1404         DPRINTF(
1405             1, (stderr, "    using %s, %ld\n", fp->name, fp->tbl.str_numstr));
1406         return fp;
1407     }
1408 }
1409
1410 /*
1411  * open_dat:
1412  *      Open up the dat file if we need to.
1413  */
1414 static void open_dat(FILEDESC *fp)
1415 {
1416     if (fp->datfd < 0 &&
1417         (fp->datfd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1418     {
1419         exit(1);
1420     }
1421 }
1422
1423 /*
1424  * get_pos:
1425  *      Get the position from the pos file, if there is one.  If not,
1426  *      return a random number.
1427  */
1428 static void get_pos(FILEDESC *fp)
1429 {
1430     assert(fp->read_tbl);
1431     if (fp->pos == POS_UNKNOWN)
1432     {
1433         fp->pos = (int32_t)(my_random(fp->tbl.str_numstr));
1434     }
1435     if (++(fp->pos) >= (int32_t)fp->tbl.str_numstr)
1436         fp->pos -= fp->tbl.str_numstr;
1437     DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
1438 }
1439
1440 /*
1441  * get_fort:
1442  *      Get the fortune data file's seek pointer for the next fortune.
1443  */
1444 static void get_fort(void)
1445 {
1446     FILEDESC *fp;
1447     int choice;
1448
1449     if (File_list->next == NULL || File_list->percent == NO_PROB)
1450         fp = File_list;
1451     else
1452     {
1453         choice = my_random(100);
1454         DPRINTF(1, (stderr, "choice = %d\n", choice));
1455         for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
1456             if (choice < fp->percent)
1457                 break;
1458             else
1459             {
1460                 choice -= fp->percent;
1461                 DPRINTF(1, (stderr, "    skip \"%s\", %d%% (choice = %d)\n",
1462                                fp->name, fp->percent, choice));
1463             }
1464         DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name,
1465                        fp->percent, choice));
1466     }
1467     if (fp->percent != NO_PROB)
1468         get_tbl(fp);
1469     else
1470     {
1471         if (fp->next != NULL)
1472         {
1473             sum_noprobs(fp);
1474             choice = (int)(my_random(Noprob_tbl.str_numstr));
1475             DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
1476                            Noprob_tbl.str_numstr));
1477             while (choice >= (int)fp->tbl.str_numstr)
1478             {
1479                 choice -= (int)fp->tbl.str_numstr;
1480                 fp = fp->next;
1481                 DPRINTF(1, (stderr, "    skip \"%s\", %ld (choice = %d)\n",
1482                                fp->name, fp->tbl.str_numstr, choice));
1483             }
1484             DPRINTF(1,
1485                 (stderr, "using \"%s\", %ld\n", fp->name, fp->tbl.str_numstr));
1486         }
1487         get_tbl(fp);
1488     }
1489     if (fp->tbl.str_numstr == 0)
1490     {
1491         fprintf(stderr, "fortune: no fortune found\n");
1492         exit(1);
1493     }
1494     if (fp->child != NULL)
1495     {
1496         DPRINTF(1, (stderr, "picking child\n"));
1497         fp = pick_child(fp);
1498     }
1499     Fortfile = fp;
1500     get_pos(fp);
1501     open_dat(fp);
1502     lseek(fp->datfd,
1503         (off_t)(sizeof fp->tbl + (size_t)fp->pos * sizeof Seekpts[0]), 0);
1504     if ((read(fp->datfd, &Seekpts[0], sizeof Seekpts[0]) < 0) ||
1505         (read(fp->datfd, &Seekpts[1], sizeof Seekpts[1]) < 0))
1506     {
1507         exit(1);
1508     }
1509     Seekpts[0] = (int32_t)ntohl((uint32_t)Seekpts[0]);
1510     Seekpts[1] = (int32_t)ntohl((uint32_t)Seekpts[1]);
1511 }
1512
1513 /*
1514  * open_fp:
1515  *      Assocatiate a FILE * with the given FILEDESC.
1516  */
1517 static void open_fp(FILEDESC *fp)
1518 {
1519     if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL)
1520     {
1521         perror(fp->path);
1522         exit(1);
1523     }
1524 }
1525
1526 #ifndef NO_REGEX
1527 /*
1528  * maxlen_in_list
1529  *      Return the maximum fortune len in the file list.
1530  */
1531 static int maxlen_in_list(FILEDESC *list)
1532 {
1533     FILEDESC *fp;
1534     int len, maxlen = 0;
1535
1536     for (fp = list; fp != NULL; fp = fp->next)
1537     {
1538         if (fp->child != NULL)
1539         {
1540             if ((len = maxlen_in_list(fp->child)) > maxlen)
1541                 maxlen = len;
1542         }
1543         else
1544         {
1545             get_tbl(fp);
1546             if ((int)fp->tbl.str_longlen > maxlen)
1547             {
1548                 maxlen = (int)fp->tbl.str_longlen;
1549             }
1550         }
1551     }
1552     return maxlen;
1553 }
1554
1555 /*
1556  * matches_in_list
1557  *      Print out the matches from the files in the list.
1558  */
1559 static void matches_in_list(FILEDESC *list)
1560 {
1561     unsigned char *sp;
1562     unsigned char *p; /* -allover */
1563     unsigned char ch; /* -allover */
1564     FILEDESC *fp;
1565     int in_file, nchar;
1566     char *output;
1567
1568     for (fp = list; fp != NULL; fp = fp->next)
1569     {
1570         if (fp->child != NULL)
1571         {
1572             matches_in_list(fp->child);
1573             continue;
1574         }
1575         DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1576         open_fp(fp);
1577         sp = Fortbuf;
1578         in_file = FALSE;
1579         while (fgets((char *)sp, Fort_len, fp->inf) != NULL)
1580         {
1581             if (!STR_ENDSTRING(sp, fp->tbl))
1582             {
1583                 sp += strlen((const char *)sp);
1584             }
1585             else
1586             {
1587                 *sp = '\0';
1588                 nchar = (int)(sp - Fortbuf);
1589
1590                 if (fp->utf8_charset && (!No_recode))
1591                 {
1592 #ifdef WITH_RECODE
1593                     output = recode_string(request, (const char *)Fortbuf);
1594 #else
1595                     output = strdup(Fortbuf);
1596 #endif
1597                 }
1598                 else
1599                 {
1600                     output = (char *)Fortbuf;
1601                 }
1602                 /* Should maybe rot13 Fortbuf -allover */
1603
1604                 if (fp->tbl.str_flags & STR_ROTATED)
1605                 {
1606                     for (p = (unsigned char *)output; (ch = *p); ++p)
1607                     {
1608                         if (isupper(ch) && isascii(ch))
1609                             *p = 'A' + (ch - 'A' + 13) % 26;
1610                         else if (islower(ch) && isascii(ch))
1611                             *p = 'a' + (ch - 'a' + 13) % 26;
1612                     }
1613                 }
1614
1615                 DPRINTF(1, (stdout, "nchar = %d\n", nchar));
1616                 if ((nchar < SLEN || !Short_only) &&
1617                     (nchar > SLEN || !Long_only) && RE_EXEC(output))
1618                 {
1619                     if (!in_file)
1620                     {
1621                         fprintf(
1622                             stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
1623                         Found_one = TRUE;
1624                         in_file = TRUE;
1625                     }
1626                     fputs(output, stdout);
1627                     printf("%c\n", fp->tbl.str_delim);
1628                 }
1629
1630                 if (fp->utf8_charset && (!No_recode))
1631                     free(output);
1632
1633                 sp = Fortbuf;
1634             }
1635         }
1636     }
1637 }
1638
1639 /*
1640  * find_matches:
1641  *      Find all the fortunes which match the pattern we've been given.
1642  */
1643 static int find_matches(void)
1644 {
1645     Fort_len = maxlen_in_list(File_list);
1646     DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1647     /* extra length, "%\n" is appended */
1648     Fortbuf = do_malloc((unsigned int)Fort_len + 10);
1649
1650     Found_one = FALSE;
1651     matches_in_list(File_list);
1652     return Found_one;
1653     /* NOTREACHED */
1654 }
1655 #endif /* NO_REGEX */
1656
1657 static void display(FILEDESC *fp)
1658 {
1659     char *p, ch;
1660     unsigned char line[BUFSIZ];
1661
1662     open_fp(fp);
1663     fseek(fp->inf, (long)Seekpts[0], 0);
1664     if (Show_filename)
1665         printf("(%s)\n%%\n", fp->name);
1666     for (Fort_len = 0; fgets((char *)line, sizeof line, fp->inf) != NULL &&
1667                        !STR_ENDSTRING(line, fp->tbl);
1668          Fort_len++)
1669     {
1670         if (fp->tbl.str_flags & STR_ROTATED)
1671         {
1672             for (p = (char *)line; (ch = *p); ++p)
1673             {
1674                 if (isupper(ch) && isascii(ch))
1675                     *p = 'A' + (ch - 'A' + 13) % 26;
1676                 else if (islower(ch) && isascii(ch))
1677                     *p = 'a' + (ch - 'a' + 13) % 26;
1678             }
1679         }
1680         if (fp->utf8_charset && (!No_recode))
1681         {
1682             char *output;
1683 #ifdef WITH_RECODE
1684             output = recode_string(request, (const char *)line);
1685 #else
1686             output = strdup(line);
1687 #endif
1688             fputs(output, stdout);
1689             free(output);
1690         }
1691         else
1692             fputs((char *)line, stdout);
1693     }
1694     fflush(stdout);
1695 }
1696
1697 /*
1698  * fortlen:
1699  *      Return the length of the fortune.
1700  */
1701 static int fortlen(void)
1702 {
1703     int nchar;
1704     char line[BUFSIZ];
1705
1706     if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
1707         nchar = (Seekpts[1] - Seekpts[0]) - 2; /* for %^J delimiter */
1708     else
1709     {
1710         open_fp(Fortfile);
1711         fseek(Fortfile->inf, (long)Seekpts[0], 0);
1712         nchar = 0;
1713         while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
1714                !STR_ENDSTRING(line, Fortfile->tbl))
1715             nchar += strlen(line);
1716     }
1717     Fort_len = nchar;
1718     return nchar;
1719 }
1720
1721 static int mymax(int i, int j) { return (i >= j ? i : j); }
1722
1723 static void free_desc(FILEDESC *ptr)
1724 {
1725     while (ptr)
1726     {
1727         free_desc(ptr->child);
1728         free(ptr->datfile);
1729         free(ptr->posfile);
1730         free(ptr->name);
1731         free(ptr->path);
1732         if (ptr->inf)
1733         {
1734             fclose(ptr->inf);
1735             ptr->inf = NULL;
1736         }
1737         FILEDESC *next = ptr->next;
1738         free(ptr);
1739         ptr = next;
1740     }
1741 }
1742
1743 int main(int ac, char *av[])
1744 {
1745     const char *ctype;
1746     char *crequest;
1747     int exit_code = 0;
1748     env_lang = getenv("LC_ALL");
1749     if (!env_lang)
1750         env_lang = getenv("LC_MESSAGES");
1751     if (!env_lang)
1752         env_lang = getenv("LANGUAGE");
1753     if (!env_lang)
1754         env_lang = getenv("LANG");
1755 #ifdef _WIN32
1756     if (!env_lang)
1757     {
1758         env_lang = "en";
1759     }
1760 #endif
1761
1762 #ifndef DONT_CALL_GETARGS
1763     getargs(ac, av);
1764 #endif
1765
1766 #ifdef WITH_RECODE
1767     outer = recode_new_outer(true);
1768     request = recode_new_request(outer);
1769 #endif
1770
1771     setlocale(LC_ALL, "");
1772 #ifdef _WIN32
1773     ctype = "C";
1774 #else
1775     ctype = nl_langinfo(CODESET);
1776     if (!ctype || !*ctype)
1777     {
1778         ctype = "C";
1779     }
1780     else if (strcmp(ctype, "ANSI_X3.4-1968") == 0)
1781     {
1782         ctype = "ISO-8859-1";
1783     }
1784 #endif
1785
1786 #ifdef WITH_RECODE
1787     crequest = malloc(strlen(ctype) + 7 + 1);
1788     sprintf(crequest, "UTF-8..%s", ctype);
1789     recode_scan_request(request, crequest);
1790     free(crequest);
1791 #endif
1792
1793 #ifndef NO_REGEX
1794     if (Match)
1795     {
1796         exit_code = (find_matches() != 0);
1797         regfree(&Re_pat);
1798         goto cleanup;
1799     }
1800 #endif
1801     init_prob();
1802     if (Find_files)
1803     {
1804         sum_noprobs(File_list);
1805         if (Equal_probs)
1806             calc_equal_probs();
1807         print_list(File_list, 0);
1808     }
1809     else
1810     {
1811         srandom((unsigned int)(time((time_t *)NULL) + getpid()));
1812         do
1813         {
1814             get_fort();
1815         } while ((Short_only && fortlen() > SLEN) ||
1816                  (Long_only && fortlen() <= SLEN));
1817
1818         display(Fortfile);
1819
1820         if (Wait)
1821         {
1822             fortlen();
1823             sleep((unsigned int)mymax(Fort_len / CPERS, MINW));
1824         }
1825     }
1826 cleanup:
1827 #ifdef WITH_RECODE
1828     recode_delete_request(request);
1829     recode_delete_outer(outer);
1830 #endif
1831
1832     /* Free the File_list */
1833     free_desc(File_list);
1834     free(Fortbuf);
1835     exit(exit_code);
1836 }