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