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