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