1 /* $NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $ */
4 * Copyright (c) 1986, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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
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,
52 * 6: Calculated probabilities for all files, so that -f will print them.
55 /* Changes Copyright (c) 1997 Dennis L. Clark. All rights reserved.
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.
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
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
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
77 * Added to debian by Alastair McKinstry, <mckinstry@computer.org>, 2002-07-31
80 #if 0 /* comment out the stuff here, and get rid of silly warnings */
82 static char copyright[] =
83 "@(#) Copyright (c) 1986, 1993\n\
84 The Regents of the University of California. All rights reserved.\n";
90 static char sccsid[] = "@(#)fortune.c 8.1 (Berkeley) 5/31/93";
93 static char rcsid[] = "$NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $";
97 #endif /* killing warnings */
99 #define PROGRAM_NAME "fortune-mod"
101 #include "fortune-mod-common.h"
109 #include <langinfo.h>
132 #define MINW 6 /* minimum wait if desired */
133 #define CPERS 20 /* # of chars for each sec */
135 #define POS_UNKNOWN ((int32_t)-1) /* pos for file unknown */
136 #define NO_PROB (-1) /* no prob specified for file */
139 #define DPRINTF(l, x) \
143 #define DPRINTF(l, x)
154 char *datfile, *posfile;
160 struct fd *child, *parent;
161 struct fd *next, *prev;
164 static char *env_lang;
166 static bool Found_one; /* did we find a match? */
167 static bool Find_files = FALSE; /* just find a list of proper fortune files */
168 static bool Wait = FALSE; /* wait desired after fortune */
169 static bool Short_only = FALSE; /* short fortune desired */
170 static bool Long_only = FALSE; /* long fortune desired */
171 static bool Offend = FALSE; /* offensive fortunes only */
172 static bool All_forts = FALSE; /* any fortune allowed */
173 static bool Equal_probs = FALSE; /* scatter un-allocated prob equally */
174 static bool Show_filename = FALSE;
175 static bool No_recode = FALSE; /* Do we want to stop recoding from occuring */
177 static bool ErrorMessage =
178 FALSE; /* Set to true if an error message has been displayed */
181 static bool Match = FALSE; /* dump fortunes matching a pattern */
185 static bool Debug = FALSE; /* print debug messages */
189 static unsigned char *Fortbuf = NULL; /* fortune buffer for -m */
191 static int Fort_len = 0, Spec_prob = 0, /* total prob specified on cmd line */
192 Num_files, Num_kids, /* totals of files and children. */
193 SLEN = 160; /* max. characters in a "short" fortune */
195 static int32_t Seekpts[2]; /* seek pointers to fortunes */
197 static FILEDESC *File_list = NULL, /* Head of file list */
198 *File_tail = NULL; /* Tail of file list */
199 static FILEDESC *Fortfile; /* Fortune file to use */
201 static STRFILE Noprob_tbl; /* sum of data for all no prob files */
204 #define RE_COMP(p) regcomp(&Re_pat, (p), REG_NOSUB)
205 #define BAD_COMP(f) ((f) != 0)
206 #define RE_EXEC(p) (regexec(&Re_pat, (p), 0, NULL, 0) == 0)
208 static regex_t Re_pat;
211 #endif /* POSIX_REGEX */
214 static RECODE_REQUEST request;
215 static RECODE_OUTER outer;
218 int add_dir(FILEDESC *);
220 static unsigned long my_random(unsigned long base)
222 unsigned long long l = 0;
223 char *hard_coded_val = getenv("FORTUNE_MOD_RAND_HARD_CODED_VALS");
226 return ((unsigned long)atol(hard_coded_val) % base);
228 if (getenv("FORTUNE_MOD_USE_SRAND"))
232 FILE *const fp = fopen("/dev/urandom", "rb");
237 if (fread(&l, sizeof(l), 1, fp) != 1)
245 return random() % base;
248 static char *program_version(void)
250 static char buf[BUFSIZ];
251 (void)sprintf(buf, "%s version %s", PROGRAM_NAME, VERSION);
255 static void __attribute__((noreturn)) usage(void)
257 (void)fprintf(stderr, "%s\n", program_version());
258 (void)fprintf(stderr, "fortune [-a");
260 (void)fprintf(stderr, "D");
262 (void)fprintf(stderr, "f");
264 (void)fprintf(stderr, "i");
265 #endif /* NO_REGEX */
266 (void)fprintf(stderr, "l");
268 (void)fprintf(stderr, "o");
270 (void)fprintf(stderr, "sw]");
272 (void)fprintf(stderr, " [-m pattern]");
273 #endif /* NO_REGEX */
274 (void)fprintf(stderr, " [-n number] [ [#%%] file/directory/all]\n");
278 #define STR(str) ((str) == NULL ? "NULL" : (str))
282 * Set the global values for number of files/children, to be used
283 * in printing probabilities when listing files
285 static void calc_equal_probs(void)
287 Num_files = Num_kids = 0;
288 FILEDESC *fiddlylist = File_list;
289 while (fiddlylist != NULL)
292 Num_kids += fiddlylist->num_children;
293 fiddlylist = fiddlylist->next;
299 * Print out the actual list, recursively.
301 static void print_list(FILEDESC *list, int lev)
305 fprintf(stderr, "%*s", lev * 4, "");
306 if (list->percent == NO_PROB)
308 /* This, with some changes elsewhere, gives proper percentages
309 * for every case fprintf(stderr, "___%%"); */
310 fprintf(stderr, "%5.2f%%",
311 (100.0 - Spec_prob) * list->tbl.str_numstr /
312 Noprob_tbl.str_numstr);
314 fprintf(stderr, "%5.2f%%", 100.0 / Num_files);
316 fprintf(stderr, "%5.2f%%", 100.0 / Num_kids);
318 fprintf(stderr, "%5.2f%%", 1.0 * list->percent);
319 fprintf(stderr, " %s", STR(list->name));
320 DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
321 STR(list->datfile), STR(list->posfile)));
323 if (list->child != NULL)
324 print_list(list->child, lev + 1);
332 * Convert the pattern to an ignore-case equivalent.
334 static char *conv_pat(char *orig)
340 cnt = 1; /* allow for '\0' */
341 for (sp = orig; *sp != '\0'; sp++)
346 if ((new = malloc(cnt)) == NULL)
348 fprintf(stderr, "pattern too long for ignoring case\n");
352 for (sp = new; *orig != '\0'; orig++)
358 *sp++ = (char)toupper(*orig);
361 else if (isupper(*orig))
365 *sp++ = (char)tolower(*orig);
374 #endif /* NO_REGEX */
378 * Do a malloc, checking for NULL return.
380 static void *do_malloc(size_t size)
384 if ((new = malloc(size)) == NULL)
386 (void)fprintf(stderr, "fortune: out of memory.\n");
394 * Free malloc'ed space, if any.
396 static void do_free(void *ptr)
404 * Return a malloc()'ed copy of the string
406 static char *copy(char *str, unsigned int len)
410 new = do_malloc(len + 1);
421 * Return a pointer to an initialized new FILEDESC.
423 static FILEDESC *new_fp(void)
427 fp = (FILEDESC *)do_malloc(sizeof *fp);
429 fp->pos = POS_UNKNOWN;
432 fp->percent = NO_PROB;
433 fp->read_tbl = FALSE;
434 fp->tbl.str_version = 0;
435 fp->tbl.str_numstr = 0;
436 fp->tbl.str_longlen = 0;
437 fp->tbl.str_shortlen = 0;
438 fp->tbl.str_flags = 0;
439 fp->tbl.stuff[0] = 0;
440 fp->tbl.stuff[1] = 0;
441 fp->tbl.stuff[2] = 0;
442 fp->tbl.stuff[3] = 0;
453 static inline void debugprint(const char *msg, ...)
458 vfprintf(stderr, msg, ap);
463 #define debugprint(format, ...) \
469 * Return TRUE if the file is a directory, FALSE otherwise.
471 static int is_dir(const char *const file)
475 if (stat(file, &sbuf) < 0)
477 debugprint("is_dir failed for file=<%s>\n", file);
480 const bool ret = ((sbuf.st_mode & S_IFDIR) ? true : false);
481 debugprint("is_dir for file=<%s> gave ret=<%d>\n", file, ret);
487 * Return TRUE if the file exists, FALSE otherwise.
489 static int is_existant(char *file)
493 if (stat(file, &staat) == 0)
501 perror("fortune: bad juju in is_existant");
508 * Return TRUE if the file is a fortune database file. We try and
509 * exclude files without reading them if possible to avoid
510 * overhead. Files which start with ".", or which have "illegal"
511 * suffixes, as contained in suflist[], are ruled out.
513 static int is_fortfile(char *file, char **datp)
518 static const char *suflist[] = {/* list of "illegal" suffixes" */
519 "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas",
520 "ins.ftn", "sml", NULL};
522 DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
524 if ((sp = strrchr(file, '/')) == NULL)
530 DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
533 if ((sp = strrchr(sp, '.')) != NULL)
536 for (i = 0; suflist[i] != NULL; i++)
537 if (strcmp(sp, suflist[i]) == 0)
539 DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
544 datfile = copy(file, (unsigned int)(strlen(file) + 4)); /* +4 for ".dat" */
545 strcat(datfile, ".dat");
546 if (access(datfile, R_OK) < 0)
549 DPRINTF(2, (stderr, "FALSE (no \".dat\" file)\n"));
556 DPRINTF(2, (stderr, "TRUE\n"));
560 static bool path_is_absolute(const char *const path)
567 if (isalpha(path[0]) && path[1] == ':' && path[2] == '/')
576 * Add a file to the file list.
578 static int add_file(int percent, const char *file, const char *dir,
579 FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
583 char *path, *testpath;
594 path = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2));
595 sprintf(path, "%s/%s", dir, file);
598 !is_existant(path)) /* If doesn't exist, don't do anything. */
603 const int isdir = is_dir(path);
604 if ((isdir > 0 && parent != NULL) || (isdir < 0))
607 return FALSE; /* don't recurse */
610 DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
615 ((fd = open(path, O_RDONLY | O_BINARY)) < 0)) ||
616 !path_is_absolute(path))
618 debugprint("sarahhhhh fd=%d path=<%s> dir=<%s> file=<%s> percent=%d\n",
619 fd, path, dir, file, percent);
621 if (dir == NULL && (strchr(file, '/') == NULL))
623 if (((sp = strrchr(file, '-')) != NULL) && (strcmp(sp, "-o") == 0))
625 #define CALL__add_file(dir) add_file(percent, file, dir, head, tail, parent)
626 #define COND_CALL__add_file(loc_dir, dir) \
627 ((!strcmp((loc_dir), (dir))) ? 0 : CALL__add_file(dir))
628 /* BSD-style '-o' offensive file suffix */
630 found = CALL__add_file(LOCOFFDIR) ||
631 COND_CALL__add_file(LOCOFFDIR, OFFDIR);
632 /* put the suffix back in for better identification later */
637 (CALL__add_file(LOCFORTDIR) || CALL__add_file(LOCOFFDIR) ||
638 COND_CALL__add_file(LOCFORTDIR, FORTDIR) ||
639 COND_CALL__add_file(LOCOFFDIR, OFFDIR));
641 found = (CALL__add_file(LOCOFFDIR) ||
642 COND_CALL__add_file(LOCOFFDIR, OFFDIR));
644 found = (CALL__add_file(LOCFORTDIR) ||
645 COND_CALL__add_file(LOCFORTDIR, FORTDIR));
646 #undef COND_CALL__add_file
647 #undef CALL__add_file
649 if (!found && parent == NULL && dir == NULL)
650 { /* don't display an error when trying language specific files */
657 strncpy(llang, env_lang, sizeof(llang));
658 llang[sizeof(llang) - 1] = '\0';
661 /* the language string can be like "es:fr_BE:ga" */
662 while (!ret && lang && (*lang))
664 char *p = strchr(lang, ':');
667 snprintf(langdir, sizeof(langdir), "%s/%s", FORTDIR, lang);
669 if (strncmp(path, lang, 2) == 0)
671 else if (strncmp(path, langdir, strlen(FORTDIR) + 3) == 0)
677 debugprint("moshe\n");
693 DPRINTF(2, (stderr, "path = \"%s\"\n", path));
697 fp->percent = percent;
699 fp->name = strdup(file);
700 fp->path = strdup(path);
703 fp->utf8_charset = FALSE;
704 testpath = do_malloc(strlen(path) + 4UL);
705 sprintf(testpath, "%s.u8", path);
706 // fprintf(stderr, "State mal: %s\n", testpath);
707 if (stat(testpath, &statbuf) == 0)
708 fp->utf8_charset = TRUE;
712 // fprintf(stderr, "Is utf8?: %i\n", fp->utf8_charset );
716 if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile)))
720 stderr, "fortune:%s not a fortune file or directory\n", path);
723 do_free(fp->datfile);
724 do_free(fp->posfile);
733 /* This is a hack to come around another hack - add_dir returns success
734 * if the directory is allowed to be empty, but we can not handle an
735 * empty directory... */
736 if (isdir && fp->num_children == 0)
740 do_free(fp->datfile);
741 do_free(fp->posfile);
753 else if (fp->percent == NO_PROB)
772 static int names_compare(const void *a, const void *b)
774 return strcmp(*(const char **)a, *(const char **)b);
778 * Add the contents of an entire directory.
780 int add_dir(FILEDESC *fp)
783 struct dirent *dirent;
785 size_t i, count_names, max_count_names;
789 if ((dir = opendir(fp->path)) == NULL)
791 debugprint("yonah\n");
795 FILEDESC *tailp = NULL;
796 DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
797 fp->num_children = 0;
798 max_count_names = 200;
800 names = malloc(sizeof(names[0]) * max_count_names);
803 debugprint("zach\n");
804 perror("Out of RAM!");
807 while ((dirent = readdir(dir)) != NULL)
809 if (dirent->d_name[0] == 0)
811 char *name = strdup(dirent->d_name);
812 if (count_names == max_count_names)
814 max_count_names += 200;
815 names = realloc(names, sizeof(names[0]) * max_count_names);
818 debugprint("rebecca\n");
819 perror("Out of RAM!");
823 names[count_names++] = name;
826 qsort(names, count_names, sizeof(names[0]), names_compare);
828 for (i = 0; i < count_names; ++i)
830 if (add_file(NO_PROB, names[i], fp->path, &fp->child, &tailp, fp))
838 if (fp->num_children == 0)
841 * Only the local fortune dir and the local offensive dir are
842 * allowed to be empty.
843 * - Brian Bassett (brianb@debian.org) 1999/07/31
845 if (strcmp(LOCFORTDIR, fp->path) == 0 ||
846 strcmp(LOCOFFDIR, fp->path) == 0)
851 stderr, "fortune: %s: No fortune files in directory.\n", fp->path);
859 * Form the file list from the file specifications.
862 static int top_level__add_file(const char *dirpath)
864 return add_file(NO_PROB, dirpath, NULL, &File_list, &File_tail, NULL);
867 static int cond_top_level__add_file(
868 const char *dirpath, const char *possible_dup)
870 if (!strcmp(dirpath, possible_dup))
874 return top_level__add_file(dirpath);
877 static int cond_top_level__LOCFORTDIR(void)
879 return cond_top_level__add_file(FORTDIR, LOCFORTDIR);
882 static int cond_top_level__OFFDIR(void)
884 return cond_top_level__add_file(OFFDIR, LOCOFFDIR);
887 static int top_level_LOCFORTDIR(void)
889 return (top_level__add_file(LOCFORTDIR) | cond_top_level__LOCFORTDIR());
892 static int form_file_list(char **files, int file_cnt)
897 char fullpathname[512], locpathname[512];
903 return (top_level__add_file(LOCFORTDIR) |
904 top_level__add_file(LOCOFFDIR) |
905 cond_top_level__LOCFORTDIR() | cond_top_level__OFFDIR());
909 return (top_level__add_file(LOCOFFDIR) | cond_top_level__OFFDIR());
920 strncpy(llang, env_lang, sizeof(llang));
921 llang[sizeof(llang) - 1] = '\0';
924 /* the language string can be like "es:fr_BE:ga" */
925 while (lang && (*lang))
927 p = strchr(lang, ':');
931 /* first try full locale */
933 NO_PROB, lang, NULL, &File_list, &File_tail, NULL);
935 /* if not try language name only (two first chars) */
940 strncpy(ll, lang, 2);
943 NO_PROB, ll, NULL, &File_list, &File_tail, NULL);
946 /* if we have found one we have finished */
952 return top_level_LOCFORTDIR();
956 /* no locales available, use default */
957 return top_level_LOCFORTDIR();
962 for (i = 0; i < file_cnt; i++)
965 if (!isdigit(files[i][0]))
970 for (sp = files[i]; isdigit(*sp); sp++)
971 percent = percent * 10 + *sp - '0';
974 fprintf(stderr, "percentages must be <= 100\n");
980 fprintf(stderr, "percentages must be integers\n");
985 * If the number isn't followed by a '%', then
986 * it was not a percentage, just the first part
987 * of a file name which starts with digits.
994 else if (*++sp == '\0')
998 fprintf(stderr, "percentages must precede files\n");
1005 if (strcmp(sp, "all") == 0)
1007 snprintf(fullpathname, sizeof(fullpathname), "%s", FORTDIR);
1008 snprintf(locpathname, sizeof(locpathname), "%s", LOCFORTDIR);
1010 /* if it isn't an absolute path or relative to . or ..
1011 make it an absolute path relative to FORTDIR */
1014 if (strncmp(sp, "/", 1) != 0 && strncmp(sp, "./", 2) != 0 &&
1015 strncmp(sp, "../", 3) != 0)
1018 fullpathname, sizeof(fullpathname), "%s/%s", FORTDIR, sp);
1020 locpathname, sizeof(locpathname), "%s/%s", LOCFORTDIR, sp);
1024 snprintf(fullpathname, sizeof(fullpathname), "%s", sp);
1025 snprintf(locpathname, sizeof(locpathname), "%s", sp);
1034 strncpy(llang, env_lang, sizeof(llang));
1035 llang[sizeof(llang) - 1] = '\0';
1038 /* the language string can be like "es:fr_BE:ga" */
1039 while (!ret && lang && (*lang))
1041 char *p = strchr(lang, ':');
1045 /* first try full locale */
1047 langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, lang, sp);
1049 percent, langdir, NULL, &File_list, &File_tail, NULL);
1051 /* if not try language name only (two first chars) */
1056 strncpy(ll, lang, 2);
1059 langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, ll, sp);
1061 percent, langdir, NULL, &File_list, &File_tail, NULL);
1069 percent, fullpathname, NULL, &File_list, &File_tail, NULL);
1071 strncmp(fullpathname, locpathname, sizeof(fullpathname)))
1073 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1077 snprintf(locpathname, sizeof(locpathname), "%s/%s",
1081 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1087 if (strncmp(fullpathname, locpathname, sizeof(fullpathname)) &&
1088 strcmp(sp, "all") == 0)
1091 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1095 percent, fullpathname, NULL, &File_list, &File_tail, NULL))
1102 * This routine evaluates the arguments on the command line
1104 static void getargs(int argc, char **argv)
1106 int ignore_case = FALSE;
1111 #endif /* NO_REGEX */
1115 #define DEBUG_GETOPT "D"
1117 #define DEBUG_GETOPT
1121 #define OFFENSIVE_GETOPT
1123 #define OFFENSIVE_GETOPT "o"
1126 while ((ch = getopt(argc, argv,
1127 "ac" DEBUG_GETOPT "efilm:n:" OFFENSIVE_GETOPT "suvw")) != EOF)
1130 case 'a': /* any fortune */
1139 Equal_probs = TRUE; /* scatter un-allocted prob equally */
1141 case 'f': /* find fortune files */
1144 case 'l': /* long ones only */
1149 SLEN = atoi(optarg);
1151 #ifndef NO_OFFENSIVE
1152 case 'o': /* offensive ones only */
1156 case 's': /* short ones only */
1160 case 'w': /* give time to read */
1164 case 'i': /* case-insensitive match */
1165 case 'm': /* dump out the fortunes */
1166 (void)fprintf(stderr,
1167 "fortune: can't match fortunes on this system (Sorry)\n");
1169 #else /* NO_REGEX */
1170 case 'm': /* dump out the fortunes */
1174 case 'i': /* case-insensitive match */
1177 #endif /* NO_REGEX */
1178 case 'u': /* Don't recode the fortune */
1182 (void)printf("%s\n", program_version());
1185 Show_filename = TRUE;
1194 if (!form_file_list(argv, argc))
1197 fprintf(stderr, "No fortunes found\n");
1198 exit(1); /* errors printed through form_file_list() */
1202 * print_list(File_list, 0); */
1204 /* If (Find_files) print_list() moved to main */
1209 pat = conv_pat(pat);
1210 if (BAD_COMP(RE_COMP(pat)))
1212 fprintf(stderr, "bad pattern: %s\n", pat);
1220 #endif /* NO_REGEX */
1225 * Initialize the fortune probabilities.
1227 static void init_prob(void)
1230 int percent = 0, num_noprob = 0, frac;
1233 * Distribute the residual probability (if any) across all
1234 * files with unspecified probability (i.e., probability of 0)
1237 FILEDESC *last = NULL;
1238 for (fp = File_tail; fp != NULL; fp = fp->prev)
1239 if (fp->percent == NO_PROB)
1246 percent += fp->percent;
1247 DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
1248 percent, num_noprob));
1251 fprintf(stderr, "fortune: probabilities sum to %d%%!\n", percent);
1254 else if (percent < 100 && num_noprob == 0)
1257 "fortune: no place to put residual probability (%d%%)\n", percent);
1260 else if (percent == 100 && num_noprob != 0)
1263 stderr, "fortune: no probability left to put in residual files\n");
1266 Spec_prob = percent; /* this is for -f when % is specified on cmd line */
1267 percent = 100 - percent;
1270 if (num_noprob != 0)
1274 frac = percent / num_noprob;
1275 DPRINTF(1, (stderr, ", frac = %d%%", frac));
1276 for (fp = File_tail; fp != last; fp = fp->prev)
1277 if (fp->percent == NO_PROB)
1283 last->percent = percent;
1284 DPRINTF(1, (stderr, ", residual = %d%%", percent));
1288 DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n",
1292 DPRINTF(1, (stderr, "\n"));
1296 * print_list(File_list, 0); *//* Causes crash with new %% code */
1302 * Zero out the fields we care about in a tbl structure.
1304 static void zero_tbl(STRFILE *tp)
1307 tp->str_longlen = 0;
1308 tp->str_shortlen = (uint32_t)(-1);
1313 * Merge the tbl data of t2 into t1.
1315 static void sum_tbl(STRFILE *t1, STRFILE *t2)
1317 t1->str_numstr += t2->str_numstr;
1318 if (t1->str_longlen < t2->str_longlen)
1319 t1->str_longlen = t2->str_longlen;
1320 if (t1->str_shortlen > t2->str_shortlen)
1321 t1->str_shortlen = t2->str_shortlen;
1326 * Get the tbl data file the datfile.
1328 static void get_tbl(FILEDESC *fp)
1335 if (fp->child == NULL)
1338 /* This should not be needed anymore since add_file takes care of
1339 * empty directories now (Torsten Landschoff <torsten@debian.org>)
1343 * Only the local fortune dir and the local offensive dir are
1344 * allowed to be empty. Don't try and fetch their tables if
1345 * they have no children (i.e. are empty).
1346 * - Brian Bassett (brianb@debian.org) 1999/07/31
1348 if (strcmp(LOCFORTDIR, fp->path) == 0 || strcmp(LOCOFFDIR, fp->path) == 0)
1350 fp->read_tbl = TRUE; /* Make it look like we've read it. */
1355 if ((fd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1357 perror(fp->datfile);
1360 if (read(fd, &fp->tbl.str_version, sizeof fp->tbl.str_version) !=
1361 sizeof fp->tbl.str_version)
1363 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1366 if (read(fd, &fp->tbl.str_numstr, sizeof fp->tbl.str_numstr) !=
1367 sizeof fp->tbl.str_numstr)
1369 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1372 if (read(fd, &fp->tbl.str_longlen, sizeof fp->tbl.str_longlen) !=
1373 sizeof fp->tbl.str_longlen)
1375 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1378 if (read(fd, &fp->tbl.str_shortlen, sizeof fp->tbl.str_shortlen) !=
1379 sizeof fp->tbl.str_shortlen)
1381 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1384 if (read(fd, &fp->tbl.str_flags, sizeof fp->tbl.str_flags) !=
1385 sizeof fp->tbl.str_flags)
1387 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1390 if (read(fd, &fp->tbl.stuff, sizeof fp->tbl.stuff) !=
1391 sizeof fp->tbl.stuff)
1393 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1396 fp->tbl.str_version = ntohl(fp->tbl.str_version);
1397 fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1398 fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1399 fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1400 fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1406 for (child = fp->child; child != NULL; child = child->next)
1409 sum_tbl(&fp->tbl, &child->tbl);
1412 fp->read_tbl = TRUE;
1417 * Sum up all the noprob probabilities, starting with fp.
1419 static void sum_noprobs(FILEDESC *fp)
1421 static bool did_noprobs = FALSE;
1425 zero_tbl(&Noprob_tbl);
1429 /* This conditional should help us return correct values for -f
1430 * when a percentage is specified */
1431 if (fp->percent == NO_PROB)
1432 sum_tbl(&Noprob_tbl, &fp->tbl);
1440 * Pick a child from a chosen parent.
1442 static FILEDESC *pick_child(FILEDESC *parent)
1449 choice = my_random(parent->num_children);
1450 DPRINTF(1, (stderr, " choice = %d (of %d)\n", choice,
1451 parent->num_children));
1452 for (fp = parent->child; choice--; fp = fp->next)
1454 DPRINTF(1, (stderr, " using %s\n", fp->name));
1460 choice = (int)(my_random(parent->tbl.str_numstr));
1461 DPRINTF(1, (stderr, " choice = %d (of %ld)\n", choice,
1462 parent->tbl.str_numstr));
1463 for (fp = parent->child; choice >= (int)fp->tbl.str_numstr;
1466 choice -= fp->tbl.str_numstr;
1467 DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n", fp->name,
1468 fp->tbl.str_numstr, choice));
1471 1, (stderr, " using %s, %ld\n", fp->name, fp->tbl.str_numstr));
1478 * Open up the dat file if we need to.
1480 static void open_dat(FILEDESC *fp)
1482 if (fp->datfd < 0 &&
1483 (fp->datfd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1491 * Get the position from the pos file, if there is one. If not,
1492 * return a random number.
1494 static void get_pos(FILEDESC *fp)
1496 assert(fp->read_tbl);
1497 if (fp->pos == POS_UNKNOWN)
1499 fp->pos = (int32_t)(my_random(fp->tbl.str_numstr));
1501 if (++(fp->pos) >= (int32_t)fp->tbl.str_numstr)
1502 fp->pos -= fp->tbl.str_numstr;
1503 DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
1508 * Get the fortune data file's seek pointer for the next fortune.
1510 static void get_fort(void)
1515 if (File_list->next == NULL || File_list->percent == NO_PROB)
1519 choice = my_random(100);
1520 DPRINTF(1, (stderr, "choice = %d\n", choice));
1521 for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
1522 if (choice < fp->percent)
1526 choice -= fp->percent;
1527 DPRINTF(1, (stderr, " skip \"%s\", %d%% (choice = %d)\n",
1528 fp->name, fp->percent, choice));
1530 DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name,
1531 fp->percent, choice));
1533 if (fp->percent != NO_PROB)
1537 if (fp->next != NULL)
1540 choice = (int)(my_random(Noprob_tbl.str_numstr));
1541 DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
1542 Noprob_tbl.str_numstr));
1543 while (choice >= (int)fp->tbl.str_numstr)
1545 choice -= (int)fp->tbl.str_numstr;
1547 DPRINTF(1, (stderr, " skip \"%s\", %ld (choice = %d)\n",
1548 fp->name, fp->tbl.str_numstr, choice));
1551 (stderr, "using \"%s\", %ld\n", fp->name, fp->tbl.str_numstr));
1555 if (fp->tbl.str_numstr == 0)
1557 fprintf(stderr, "fortune: no fortune found\n");
1560 if (fp->child != NULL)
1562 DPRINTF(1, (stderr, "picking child\n"));
1563 fp = pick_child(fp);
1569 (off_t)(sizeof fp->tbl + (size_t)fp->pos * sizeof Seekpts[0]), 0);
1570 if ((read(fp->datfd, &Seekpts[0], sizeof Seekpts[0]) < 0) ||
1571 (read(fp->datfd, &Seekpts[1], sizeof Seekpts[1]) < 0))
1575 Seekpts[0] = (int32_t)ntohl((uint32_t)Seekpts[0]);
1576 Seekpts[1] = (int32_t)ntohl((uint32_t)Seekpts[1]);
1581 * Assocatiate a FILE * with the given FILEDESC.
1583 static void open_fp(FILEDESC *fp)
1585 if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL)
1595 * Return the maximum fortune len in the file list.
1597 static int maxlen_in_list(FILEDESC *list)
1600 int len, maxlen = 0;
1602 for (fp = list; fp != NULL; fp = fp->next)
1604 if (fp->child != NULL)
1606 if ((len = maxlen_in_list(fp->child)) > maxlen)
1612 if ((int)fp->tbl.str_longlen > maxlen)
1614 maxlen = (int)fp->tbl.str_longlen;
1623 * Print out the matches from the files in the list.
1625 static void matches_in_list(FILEDESC *list)
1628 unsigned char *p; /* -allover */
1629 unsigned char ch; /* -allover */
1634 for (fp = list; fp != NULL; fp = fp->next)
1636 if (fp->child != NULL)
1638 matches_in_list(fp->child);
1641 DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1645 while (fgets((char *)sp, Fort_len, fp->inf) != NULL)
1647 if (!STR_ENDSTRING(sp, fp->tbl))
1649 sp += strlen((const char *)sp);
1654 nchar = (int)(sp - Fortbuf);
1656 if (fp->utf8_charset && (!No_recode))
1659 output = recode_string(request, (const char *)Fortbuf);
1661 output = strdup(Fortbuf);
1666 output = (char *)Fortbuf;
1668 /* Should maybe rot13 Fortbuf -allover */
1670 if (fp->tbl.str_flags & STR_ROTATED)
1672 for (p = (unsigned char *)output; (ch = *p); ++p)
1674 if (isupper(ch) && isascii(ch))
1675 *p = 'A' + (ch - 'A' + 13) % 26;
1676 else if (islower(ch) && isascii(ch))
1677 *p = 'a' + (ch - 'a' + 13) % 26;
1681 DPRINTF(1, (stdout, "nchar = %d\n", nchar));
1682 if ((nchar < SLEN || !Short_only) &&
1683 (nchar > SLEN || !Long_only) && RE_EXEC(output))
1688 stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
1692 fputs(output, stdout);
1693 printf("%c\n", fp->tbl.str_delim);
1696 if (fp->utf8_charset && (!No_recode))
1707 * Find all the fortunes which match the pattern we've been given.
1709 static int find_matches(void)
1711 Fort_len = maxlen_in_list(File_list);
1712 DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1713 /* extra length, "%\n" is appended */
1714 Fortbuf = do_malloc((unsigned int)Fort_len + 10);
1717 matches_in_list(File_list);
1721 #endif /* NO_REGEX */
1723 static void display(FILEDESC *fp)
1726 unsigned char line[BUFSIZ];
1729 fseek(fp->inf, (long)Seekpts[0], 0);
1731 printf("(%s)\n%%\n", fp->name);
1732 for (Fort_len = 0; fgets((char *)line, sizeof line, fp->inf) != NULL &&
1733 !STR_ENDSTRING(line, fp->tbl);
1736 if (fp->tbl.str_flags & STR_ROTATED)
1738 for (p = (char *)line; (ch = *p); ++p)
1740 if (isupper(ch) && isascii(ch))
1741 *p = 'A' + (ch - 'A' + 13) % 26;
1742 else if (islower(ch) && isascii(ch))
1743 *p = 'a' + (ch - 'a' + 13) % 26;
1746 if (fp->utf8_charset && (!No_recode))
1750 output = recode_string(request, (const char *)line);
1752 output = strdup(line);
1754 fputs(output, stdout);
1758 fputs((char *)line, stdout);
1765 * Return the length of the fortune.
1767 static int fortlen(void)
1772 if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
1773 nchar = (Seekpts[1] - Seekpts[0]) - 2; /* for %^J delimiter */
1777 fseek(Fortfile->inf, (long)Seekpts[0], 0);
1779 while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
1780 !STR_ENDSTRING(line, Fortfile->tbl))
1781 nchar += strlen(line);
1787 static int mymax(int i, int j) { return (i >= j ? i : j); }
1789 static void free_desc(FILEDESC *ptr)
1793 free_desc(ptr->child);
1794 do_free(ptr->datfile);
1795 do_free(ptr->posfile);
1803 FILEDESC *next = ptr->next;
1809 int main(int ac, char *av[])
1814 env_lang = getenv("LC_ALL");
1816 env_lang = getenv("LC_MESSAGES");
1818 env_lang = getenv("LANGUAGE");
1820 env_lang = getenv("LANG");
1828 #ifndef DONT_CALL_GETARGS
1833 outer = recode_new_outer(true);
1834 request = recode_new_request(outer);
1837 setlocale(LC_ALL, "");
1841 ctype = nl_langinfo(CODESET);
1842 if (!ctype || !*ctype)
1846 else if (strcmp(ctype, "ANSI_X3.4-1968") == 0)
1848 ctype = "ISO-8859-1";
1853 crequest = malloc(strlen(ctype) + 7 + 1);
1854 sprintf(crequest, "UTF-8..%s", ctype);
1855 recode_scan_request(request, crequest);
1862 exit_code = (find_matches() != 0);
1870 sum_noprobs(File_list);
1873 print_list(File_list, 0);
1877 srandom((unsigned int)(time((time_t *)NULL) + getpid()));
1881 } while ((Short_only && fortlen() > SLEN) ||
1882 (Long_only && fortlen() <= SLEN));
1889 sleep((unsigned int)mymax(Fort_len / CPERS, MINW));
1894 recode_delete_request(request);
1895 recode_delete_outer(outer);
1898 /* Free the File_list */
1899 free_desc(File_list);