diff --git a/usr.bin/fortune/fortune/fortune.6 b/usr.bin/fortune/fortune/fortune.6 index 2660cf8bf20c..c04d5838941a 100644 --- a/usr.bin/fortune/fortune/fortune.6 +++ b/usr.bin/fortune/fortune/fortune.6 @@ -1,204 +1,204 @@ .\" Copyright (c) 1985, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" This code is derived from software contributed to Berkeley by .\" Ken Arnold. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" @(#)fortune.6 8.3 (Berkeley) 4/19/94 .\" $FreeBSD$ .\" -.Dd January 3, 2021 +.Dd July 1, 2021 .Dt FORTUNE 6 .Os .Sh NAME .Nm fortune .Nd "print a random, hopefully interesting, adage" .Sh SYNOPSIS .Nm .Op Fl aDefilosw .Op Fl m Ar pattern .Oo .Op Ar \&N% .Ar file Ns / Ns Ar directory Ns / Ns Cm all .Oc .Sh DESCRIPTION When .Nm is run with no arguments it prints out a random epigram. Epigrams are divided into several categories, where each category is subdivided into those which are potentially offensive and those which are not. The options are as follows: .Bl -tag -width indent .It Fl a Choose from all lists of maxims, both offensive and not. (See the .Fl o option for more information on offensive fortunes.) .It Fl D Enable additional debugging output. Specify this option multiple times for more verbose output. Only available if compiled with .Li -DDEBUG . .It Fl e Consider all fortune files to be of equal size (see discussion below on multiple files). .It Fl f Print out the list of files which would be searched, but do not print a fortune. .It Fl l Long dictums only. .It Fl m Ar pattern Print out all fortunes which match the regular expression .Ar pattern . See .Xr regex 3 for a description of patterns. .It Fl o Choose only from potentially offensive aphorisms. .Bf -symbolic Please, please, please request a potentially offensive fortune if and only if you believe, deep down in your heart, that you are willing to be offended. (And that if you are not willing, you will just quit using .Fl o rather than give us grief about it, okay?) .Ef .Bd -unfilled -offset indent \&... let us keep in mind the basic governing philosophy of The Brotherhood, as handsomely summarized in these words: we believe in healthy, hearty laughter -- at the expense of the whole human race, if needs be. Needs be. --H. Allen Smith, "Rude Jokes" .Ed .It Fl s Short apothegms only. .It Fl i Ignore case for .Fl m patterns. .It Fl w Wait before termination for an amount of time calculated from the number of characters in the message. This is useful if it is executed as part of the logout procedure to guarantee that the message can be read before the screen is cleared. .El .Pp The user may specify alternate sayings. You can specify a specific file, a directory which contains one or more files, or the special word .Cm all which says to use all the standard databases. Any of these may be preceded by a percentage, which is a number .Ar N between 0 and 100 inclusive, followed by a .Ql % . If it is, there will be an .Ar N percent probability that an adage will be picked from that file or directory. If the percentages do not sum to 100, and there are specifications without percentages, the remaining percent will apply to those files and/or directories, in which case the probability of selecting from one of them will be based on their relative sizes. .Pp As an example, given two databases .Pa funny and .Pa not-funny , with .Pa funny twice as big, saying .Pp .Dl "fortune funny not-funny" .Pp will get you fortunes out of .Pa funny two-thirds of the time. The command .Pp .Dl "fortune 90% funny 10% not-funny" .Pp will pick out 90% of its fortunes from .Pa funny (the .Dq Li "10% not-funny" is unnecessary, since 10% is all that is left). The .Fl e option says to consider all files equal; thus .Pp .Dl "fortune -e funny not-funny" .Pp is equivalent to .Pp .Dl "fortune 50% funny 50% not-funny" .Sh ENVIRONMENT .Bl -tag -width ".Ev FORTUNE_PATH" .It Ev FORTUNE_PATH The search path for the data files. It is a colon-separated list of directories in which .Nm looks for data files. If not set it will default to -.Pa /usr/share/games/fortune . +.Pa /usr/share/games/fortune:/usr/local/share/games/fortune . If none of the directories specified exist, it will print a warning and exit. .It Ev FORTUNE_SAVESTATE If set, fortune will save some state about what fortune it was up to on disk. .El .Sh FILES .Bl -tag -width ".Pa /usr/share/games/fortune/*" .It Pa /usr/share/games/fortune/* the fortunes databases (those files ending .Dq Pa -o contain the .Sy offensive fortunes) .El .Sh EXIT STATUS .Ex -std In particular, if .Fl l , .Fl m , or .Fl s is specified, failure to find a matching citation in the selected files counts as an error. .Sh SEE ALSO .Xr arc4random_uniform 3 , .Xr regcomp 3 , .Xr regex 3 , .Xr strfile 8 .Sh HISTORY The .Nm utility first appeared .At v7 . diff --git a/usr.bin/fortune/fortune/fortune.c b/usr.bin/fortune/fortune/fortune.c index 724fb4a2372e..f375ec645ede 100644 --- a/usr.bin/fortune/fortune/fortune.c +++ b/usr.bin/fortune/fortune/fortune.c @@ -1,1428 +1,1428 @@ /*- * Copyright (c) 1986, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Ken Arnold. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1986, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static const char sccsid[] = "@(#)fortune.c 8.1 (Berkeley) 5/31/93"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "strfile.h" #include "pathnames.h" #define TRUE true #define FALSE false #define MINW 6 /* minimum wait if desired */ #define CPERS 20 /* # of chars for each sec */ #define SLEN 160 /* # of chars in short fortune */ #define POS_UNKNOWN ((uint32_t) -1) /* pos for file unknown */ #define NO_PROB (-1) /* no prob specified for file */ #ifdef DEBUG #define DPRINTF(l,x) { if (Debug >= l) fprintf x; } #undef NDEBUG #else #define DPRINTF(l,x) #define NDEBUG 1 #endif typedef struct fd { int percent; int fd, datfd; uint32_t pos; FILE *inf; const char *name; const char *path; char *datfile, *posfile; bool read_tbl; bool was_pos_file; STRFILE tbl; int num_children; struct fd *child, *parent; struct fd *next, *prev; } FILEDESC; static bool Found_one; /* did we find a match? */ static bool Find_files = FALSE; /* just find a list of proper fortune files */ static bool Fortunes_only = FALSE; /* check only "fortunes" files */ static bool Wait = FALSE; /* wait desired after fortune */ static bool Short_only = FALSE; /* short fortune desired */ static bool Long_only = FALSE; /* long fortune desired */ static bool Offend = FALSE; /* offensive fortunes only */ static bool All_forts = FALSE; /* any fortune allowed */ static bool Equal_probs = FALSE; /* scatter un-allocted prob equally */ static bool Match = FALSE; /* dump fortunes matching a pattern */ static bool WriteToDisk = false; /* use files on disk to save state */ #ifdef DEBUG static int Debug = 0; /* print debug messages */ #endif static char *Fortbuf = NULL; /* fortune buffer for -m */ static int Fort_len = 0; static off_t Seekpts[2]; /* seek pointers to fortunes */ static FILEDESC *File_list = NULL, /* Head of file list */ *File_tail = NULL; /* Tail of file list */ static FILEDESC *Fortfile; /* Fortune file to use */ static STRFILE Noprob_tbl; /* sum of data for all no prob files */ static const char *Fortune_path; static char **Fortune_path_arr; static int add_dir(FILEDESC *); static int add_file(int, const char *, const char *, FILEDESC **, FILEDESC **, FILEDESC *); static void all_forts(FILEDESC *, char *); static char *copy(const char *, u_int); static void display(FILEDESC *); static void do_free(void *); static void *do_malloc(u_int); static int form_file_list(char **, int); static int fortlen(void); static void get_fort(void); static void get_pos(FILEDESC *); static void get_tbl(FILEDESC *); static void getargs(int, char *[]); static void getpath(void); static void init_prob(void); static int is_dir(const char *); static int is_fortfile(const char *, char **, char **, int); static int is_off_name(const char *); static int max(int, int); static FILEDESC *new_fp(void); static char *off_name(const char *); static void open_dat(FILEDESC *); static void open_fp(FILEDESC *); static FILEDESC *pick_child(FILEDESC *); static void print_file_list(void); static void print_list(FILEDESC *, int); static void sum_noprobs(FILEDESC *); static void sum_tbl(STRFILE *, STRFILE *); static void usage(void); static void zero_tbl(STRFILE *); static char *conv_pat(char *); static int find_matches(void); static void matches_in_list(FILEDESC *); static int maxlen_in_list(FILEDESC *); static regex_t Re_pat; int main(int argc, char *argv[]) { int fd; if (getenv("FORTUNE_SAVESTATE") != NULL) WriteToDisk = true; (void) setlocale(LC_ALL, ""); getpath(); getargs(argc, argv); if (Match) exit(find_matches() != 0); init_prob(); do { get_fort(); } while ((Short_only && fortlen() > SLEN) || (Long_only && fortlen() <= SLEN)); display(Fortfile); if (WriteToDisk) { if ((fd = creat(Fortfile->posfile, 0666)) < 0) { perror(Fortfile->posfile); exit(1); } /* * if we can, we exclusive lock, but since it isn't very * important, we just punt if we don't have easy locking * available. */ flock(fd, LOCK_EX); write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos); if (!Fortfile->was_pos_file) chmod(Fortfile->path, 0666); flock(fd, LOCK_UN); } if (Wait) { if (Fort_len == 0) (void) fortlen(); sleep((unsigned int) max(Fort_len / CPERS, MINW)); } exit(0); } static void display(FILEDESC *fp) { char *p; unsigned char ch; char line[BUFSIZ]; open_fp(fp); fseeko(fp->inf, Seekpts[0], SEEK_SET); for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL && !STR_ENDSTRING(line, fp->tbl); Fort_len++) { if (fp->tbl.str_flags & STR_ROTATED) for (p = line; (ch = *p) != '\0'; ++p) { if (isascii(ch)) { if (isupper(ch)) *p = 'A' + (ch - 'A' + 13) % 26; else if (islower(ch)) *p = 'a' + (ch - 'a' + 13) % 26; } } if (fp->tbl.str_flags & STR_COMMENTS && line[0] == fp->tbl.str_delim && line[1] == fp->tbl.str_delim) continue; fputs(line, stdout); } (void) fflush(stdout); } /* * fortlen: * Return the length of the fortune. */ static int fortlen(void) { int nchar; char line[BUFSIZ]; if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED))) nchar = (int)(Seekpts[1] - Seekpts[0]); else { open_fp(Fortfile); fseeko(Fortfile->inf, Seekpts[0], SEEK_SET); nchar = 0; while (fgets(line, sizeof line, Fortfile->inf) != NULL && !STR_ENDSTRING(line, Fortfile->tbl)) nchar += strlen(line); } Fort_len = nchar; return (nchar); } /* * This routine evaluates the arguments on the command line */ static void getargs(int argc, char *argv[]) { int ignore_case; char *pat; int ch; ignore_case = FALSE; pat = NULL; #ifdef DEBUG while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1) #else while ((ch = getopt(argc, argv, "aefilm:osw")) != -1) #endif /* DEBUG */ switch(ch) { case 'a': /* any fortune */ All_forts = TRUE; break; #ifdef DEBUG case 'D': Debug++; break; #endif /* DEBUG */ case 'e': /* scatter un-allocted prob equally */ Equal_probs = TRUE; break; case 'f': /* find fortune files */ Find_files = TRUE; break; case 'l': /* long ones only */ Long_only = TRUE; Short_only = FALSE; break; case 'o': /* offensive ones only */ Offend = TRUE; break; case 's': /* short ones only */ Short_only = TRUE; Long_only = FALSE; break; case 'w': /* give time to read */ Wait = TRUE; break; case 'm': /* dump out the fortunes */ Match = TRUE; pat = optarg; break; case 'i': /* case-insensitive match */ ignore_case++; break; case '?': default: usage(); } argc -= optind; argv += optind; if (!form_file_list(argv, argc)) exit(1); /* errors printed through form_file_list() */ if (Find_files) { print_file_list(); exit(0); } #ifdef DEBUG else if (Debug >= 1) print_file_list(); #endif /* DEBUG */ if (pat != NULL) { int error; if (ignore_case) pat = conv_pat(pat); error = regcomp(&Re_pat, pat, REG_BASIC); if (error) { fprintf(stderr, "regcomp(%s) fails\n", pat); exit(1); } } } /* * form_file_list: * Form the file list from the file specifications. */ static int form_file_list(char **files, int file_cnt) { int i, percent; char *sp; char **pstr; if (file_cnt == 0) { if (Find_files) { Fortunes_only = TRUE; pstr = Fortune_path_arr; i = 0; while (*pstr) { i += add_file(NO_PROB, *pstr++, NULL, &File_list, &File_tail, NULL); } Fortunes_only = FALSE; if (!i) { fprintf(stderr, "No fortunes found in %s.\n", Fortune_path); } return (i != 0); } else { pstr = Fortune_path_arr; i = 0; while (*pstr) { i += add_file(NO_PROB, "fortunes", *pstr++, &File_list, &File_tail, NULL); } if (!i) { fprintf(stderr, "No fortunes found in %s.\n", Fortune_path); } return (i != 0); } } for (i = 0; i < file_cnt; i++) { percent = NO_PROB; if (!isdigit((unsigned char)files[i][0])) sp = files[i]; else { percent = 0; for (sp = files[i]; isdigit((unsigned char)*sp); sp++) { percent = percent * 10 + *sp - '0'; if (percent > 100) { fprintf(stderr, "percentages must be <= 100\n"); return (FALSE); } } if (*sp == '.') { fprintf(stderr, "percentages must be integers\n"); return (FALSE); } /* * If the number isn't followed by a '%', then * it was not a percentage, just the first part * of a file name which starts with digits. */ if (*sp != '%') { percent = NO_PROB; sp = files[i]; } else if (*++sp == '\0') { if (++i >= file_cnt) { fprintf(stderr, "percentages must precede files\n"); return (FALSE); } sp = files[i]; } } if (strcmp(sp, "all") == 0) { pstr = Fortune_path_arr; i = 0; while (*pstr) { i += add_file(NO_PROB, *pstr++, NULL, &File_list, &File_tail, NULL); } if (!i) { fprintf(stderr, "No fortunes found in %s.\n", Fortune_path); return (FALSE); } } else if (!add_file(percent, sp, NULL, &File_list, &File_tail, NULL)) { return (FALSE); } } return (TRUE); } /* * add_file: * Add a file to the file list. */ static int add_file(int percent, const char *file, const char *dir, FILEDESC **head, FILEDESC **tail, FILEDESC *parent) { FILEDESC *fp; int fd; const char *path; char *tpath, *offensive; bool was_malloc; bool isdir; if (dir == NULL) { path = file; tpath = NULL; was_malloc = FALSE; } else { tpath = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2)); strcat(strcat(strcpy(tpath, dir), "/"), file); path = tpath; was_malloc = TRUE; } if ((isdir = is_dir(path)) && parent != NULL) { if (was_malloc) free(tpath); return (FALSE); /* don't recurse */ } offensive = NULL; if (!isdir && parent == NULL && (All_forts || Offend) && !is_off_name(path)) { offensive = off_name(path); if (Offend) { if (was_malloc) free(tpath); path = tpath = offensive; offensive = NULL; was_malloc = TRUE; DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path)); file = off_name(file); } } DPRINTF(1, (stderr, "adding file \"%s\"\n", path)); over: if ((fd = open(path, O_RDONLY)) < 0) { /* * This is a sneak. If the user said -a, and if the * file we're given isn't a file, we check to see if * there is a -o version. If there is, we treat it as * if *that* were the file given. We only do this for * individual files -- if we're scanning a directory, * we'll pick up the -o file anyway. */ if (All_forts && offensive != NULL) { if (was_malloc) free(tpath); path = tpath = offensive; offensive = NULL; was_malloc = TRUE; DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path)); file = off_name(file); goto over; } if (dir == NULL && file[0] != '/') { int i = 0; char **pstr = Fortune_path_arr; while (*pstr) { i += add_file(percent, file, *pstr++, head, tail, parent); } if (!i) { fprintf(stderr, "No '%s' found in %s.\n", file, Fortune_path); } return (i != 0); } /* if (parent == NULL) perror(path); */ if (was_malloc) free(tpath); return (FALSE); } DPRINTF(2, (stderr, "path = \"%s\"\n", path)); fp = new_fp(); fp->fd = fd; fp->percent = percent; fp->name = file; fp->path = path; fp->parent = parent; if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL)))) { if (parent == NULL) fprintf(stderr, "fortune:%s not a fortune file or directory\n", path); if (was_malloc) free(tpath); do_free(fp->datfile); do_free(fp->posfile); free(fp); do_free(offensive); return (FALSE); } /* * If the user said -a, we need to make this node a pointer to * both files, if there are two. We don't need to do this if * we are scanning a directory, since the scan will pick up the * -o file anyway. */ if (All_forts && parent == NULL && !is_off_name(path)) all_forts(fp, offensive); if (*head == NULL) *head = *tail = fp; else if (fp->percent == NO_PROB) { (*tail)->next = fp; fp->prev = *tail; *tail = fp; } else { (*head)->prev = fp; fp->next = *head; *head = fp; } if (WriteToDisk) fp->was_pos_file = (access(fp->posfile, W_OK) >= 0); return (TRUE); } /* * new_fp: * Return a pointer to an initialized new FILEDESC. */ static FILEDESC * new_fp(void) { FILEDESC *fp; fp = do_malloc(sizeof(*fp)); fp->datfd = -1; fp->pos = POS_UNKNOWN; fp->inf = NULL; fp->fd = -1; fp->percent = NO_PROB; fp->read_tbl = FALSE; fp->next = NULL; fp->prev = NULL; fp->child = NULL; fp->parent = NULL; fp->datfile = NULL; fp->posfile = NULL; return (fp); } /* * off_name: * Return a pointer to the offensive version of a file of this name. */ static char * off_name(const char *file) { char *new; new = copy(file, (unsigned int) (strlen(file) + 2)); return (strcat(new, "-o")); } /* * is_off_name: * Is the file an offensive-style name? */ static int is_off_name(const char *file) { int len; len = strlen(file); return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o'); } /* * all_forts: * Modify a FILEDESC element to be the parent of two children if * there are two children to be a parent of. */ static void all_forts(FILEDESC *fp, char *offensive) { char *sp; FILEDESC *scene, *obscene; int fd; char *datfile, *posfile; if (fp->child != NULL) /* this is a directory, not a file */ return; if (!is_fortfile(offensive, &datfile, &posfile, FALSE)) return; if ((fd = open(offensive, O_RDONLY)) < 0) return; DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive)); scene = new_fp(); obscene = new_fp(); *scene = *fp; fp->num_children = 2; fp->child = scene; scene->next = obscene; obscene->next = NULL; scene->child = obscene->child = NULL; scene->parent = obscene->parent = fp; fp->fd = -1; scene->percent = obscene->percent = NO_PROB; obscene->fd = fd; obscene->inf = NULL; obscene->path = offensive; if ((sp = strrchr(offensive, '/')) == NULL) obscene->name = offensive; else obscene->name = ++sp; obscene->datfile = datfile; obscene->posfile = posfile; obscene->read_tbl = false; if (WriteToDisk) obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0); } /* * add_dir: * Add the contents of an entire directory. */ static int add_dir(FILEDESC *fp) { DIR *dir; struct dirent *dirent; FILEDESC *tailp; char *name; (void) close(fp->fd); fp->fd = -1; if ((dir = opendir(fp->path)) == NULL) { perror(fp->path); return (FALSE); } tailp = NULL; DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path)); fp->num_children = 0; while ((dirent = readdir(dir)) != NULL) { if (dirent->d_namlen == 0) continue; name = copy(dirent->d_name, dirent->d_namlen); if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp)) fp->num_children++; else free(name); } if (fp->num_children == 0) { (void) fprintf(stderr, "fortune: %s: No fortune files in directory.\n", fp->path); return (FALSE); } return (TRUE); } /* * is_dir: * Return TRUE if the file is a directory, FALSE otherwise. */ static int is_dir(const char *file) { struct stat sbuf; if (stat(file, &sbuf) < 0) return (FALSE); return (sbuf.st_mode & S_IFDIR); } /* * is_fortfile: * Return TRUE if the file is a fortune database file. We try and * exclude files without reading them if possible to avoid * overhead. Files which start with ".", or which have "illegal" * suffixes, as contained in suflist[], are ruled out. */ /* ARGSUSED */ static int is_fortfile(const char *file, char **datp, char **posp, int check_for_offend) { int i; const char *sp; char *datfile; static const char *suflist[] = { /* list of "illegal" suffixes" */ "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas", "ins.ftn", "sml", NULL }; DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file)); /* * Preclude any -o files for offendable people, and any non -o * files for completely offensive people. */ if (check_for_offend && !All_forts) { i = strlen(file); if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o')) { DPRINTF(2, (stderr, "FALSE (offending file)\n")); return (FALSE); } } if ((sp = strrchr(file, '/')) == NULL) sp = file; else sp++; if (*sp == '.') { DPRINTF(2, (stderr, "FALSE (file starts with '.')\n")); return (FALSE); } if (Fortunes_only && strncmp(sp, "fortunes", 8) != 0) { DPRINTF(2, (stderr, "FALSE (check fortunes only)\n")); return (FALSE); } if ((sp = strrchr(sp, '.')) != NULL) { sp++; for (i = 0; suflist[i] != NULL; i++) if (strcmp(sp, suflist[i]) == 0) { DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp)); return (FALSE); } } datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */ strcat(datfile, ".dat"); if (access(datfile, R_OK) < 0) { DPRINTF(2, (stderr, "FALSE (no readable \".dat\" file)\n")); free(datfile); return (FALSE); } if (datp != NULL) *datp = datfile; else free(datfile); if (posp != NULL) { if (WriteToDisk) { *posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */ strcat(*posp, ".pos"); } else { *posp = NULL; } } DPRINTF(2, (stderr, "TRUE\n")); return (TRUE); } /* * copy: * Return a malloc()'ed copy of the string */ static char * copy(const char *str, unsigned int len) { char *new, *sp; new = do_malloc(len + 1); sp = new; do { *sp++ = *str; } while (*str++); return (new); } /* * do_malloc: * Do a malloc, checking for NULL return. */ static void * do_malloc(unsigned int size) { void *new; if ((new = malloc(size)) == NULL) { (void) fprintf(stderr, "fortune: out of memory.\n"); exit(1); } return (new); } /* * do_free: * Free malloc'ed space, if any. */ static void do_free(void *ptr) { if (ptr != NULL) free(ptr); } /* * init_prob: * Initialize the fortune probabilities. */ static void init_prob(void) { FILEDESC *fp, *last = NULL; int percent, num_noprob, frac; /* * Distribute the residual probability (if any) across all * files with unspecified probability (i.e., probability of 0) * (if any). */ percent = 0; num_noprob = 0; for (fp = File_tail; fp != NULL; fp = fp->prev) if (fp->percent == NO_PROB) { num_noprob++; if (Equal_probs) last = fp; } else percent += fp->percent; DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's", percent, num_noprob)); if (percent > 100) { (void) fprintf(stderr, "fortune: probabilities sum to %d%% > 100%%!\n", percent); exit(1); } else if (percent < 100 && num_noprob == 0) { (void) fprintf(stderr, "fortune: no place to put residual probability (%d%% < 100%%)\n", percent); exit(1); } else if (percent == 100 && num_noprob != 0) { (void) fprintf(stderr, "fortune: no probability left to put in residual files (100%%)\n"); exit(1); } percent = 100 - percent; if (Equal_probs) { if (num_noprob != 0) { if (num_noprob > 1) { frac = percent / num_noprob; DPRINTF(1, (stderr, ", frac = %d%%", frac)); for (fp = File_tail; fp != last; fp = fp->prev) if (fp->percent == NO_PROB) { fp->percent = frac; percent -= frac; } } last->percent = percent; DPRINTF(1, (stderr, ", residual = %d%%", percent)); } else DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n", percent)); } DPRINTF(1, (stderr, "\n")); #ifdef DEBUG if (Debug >= 1) print_file_list(); #endif } /* * get_fort: * Get the fortune data file's seek pointer for the next fortune. */ static void get_fort(void) { FILEDESC *fp; int choice; if (File_list->next == NULL || File_list->percent == NO_PROB) fp = File_list; else { choice = arc4random_uniform(100); DPRINTF(1, (stderr, "choice = %d\n", choice)); for (fp = File_list; fp->percent != NO_PROB; fp = fp->next) if (choice < fp->percent) break; else { choice -= fp->percent; DPRINTF(1, (stderr, " skip \"%s\", %d%% (choice = %d)\n", fp->name, fp->percent, choice)); } DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name, fp->percent, choice)); } if (fp->percent != NO_PROB) get_tbl(fp); else { if (fp->next != NULL) { sum_noprobs(fp); choice = arc4random_uniform(Noprob_tbl.str_numstr); DPRINTF(1, (stderr, "choice = %d (of %u) \n", choice, Noprob_tbl.str_numstr)); while ((unsigned int)choice >= fp->tbl.str_numstr) { choice -= fp->tbl.str_numstr; fp = fp->next; DPRINTF(1, (stderr, " skip \"%s\", %u (choice = %d)\n", fp->name, fp->tbl.str_numstr, choice)); } DPRINTF(1, (stderr, "using \"%s\", %u\n", fp->name, fp->tbl.str_numstr)); } get_tbl(fp); } if (fp->child != NULL) { DPRINTF(1, (stderr, "picking child\n")); fp = pick_child(fp); } Fortfile = fp; get_pos(fp); open_dat(fp); lseek(fp->datfd, (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), SEEK_SET); read(fp->datfd, Seekpts, sizeof Seekpts); Seekpts[0] = be64toh(Seekpts[0]); Seekpts[1] = be64toh(Seekpts[1]); } /* * pick_child * Pick a child from a chosen parent. */ static FILEDESC * pick_child(FILEDESC *parent) { FILEDESC *fp; int choice; if (Equal_probs) { choice = arc4random_uniform(parent->num_children); DPRINTF(1, (stderr, " choice = %d (of %d)\n", choice, parent->num_children)); for (fp = parent->child; choice--; fp = fp->next) continue; DPRINTF(1, (stderr, " using %s\n", fp->name)); return (fp); } else { get_tbl(parent); choice = arc4random_uniform(parent->tbl.str_numstr); DPRINTF(1, (stderr, " choice = %d (of %u)\n", choice, parent->tbl.str_numstr)); for (fp = parent->child; (unsigned)choice >= fp->tbl.str_numstr; fp = fp->next) { choice -= fp->tbl.str_numstr; DPRINTF(1, (stderr, "\tskip %s, %u (choice = %d)\n", fp->name, fp->tbl.str_numstr, choice)); } DPRINTF(1, (stderr, " using %s, %u\n", fp->name, fp->tbl.str_numstr)); return (fp); } } /* * sum_noprobs: * Sum up all the noprob probabilities, starting with fp. */ static void sum_noprobs(FILEDESC *fp) { static bool did_noprobs = FALSE; if (did_noprobs) return; zero_tbl(&Noprob_tbl); while (fp != NULL) { get_tbl(fp); sum_tbl(&Noprob_tbl, &fp->tbl); fp = fp->next; } did_noprobs = TRUE; } static int max(int i, int j) { return (i >= j ? i : j); } /* * open_fp: * Assocatiate a FILE * with the given FILEDESC. */ static void open_fp(FILEDESC *fp) { if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) { perror(fp->path); exit(1); } } /* * open_dat: * Open up the dat file if we need to. */ static void open_dat(FILEDESC *fp) { if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, O_RDONLY)) < 0) { perror(fp->datfile); exit(1); } } /* * get_pos: * Get the position from the pos file, if there is one. If not, * return a random number. */ static void get_pos(FILEDESC *fp) { int fd; assert(fp->read_tbl); if (fp->pos == POS_UNKNOWN) { if (WriteToDisk) { if ((fd = open(fp->posfile, O_RDONLY)) < 0 || read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos) fp->pos = arc4random_uniform(fp->tbl.str_numstr); else if (fp->pos >= fp->tbl.str_numstr) fp->pos %= fp->tbl.str_numstr; if (fd >= 0) close(fd); } else fp->pos = arc4random_uniform(fp->tbl.str_numstr); } if (++(fp->pos) >= fp->tbl.str_numstr) fp->pos -= fp->tbl.str_numstr; DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, (long)fp->pos)); } /* * get_tbl: * Get the tbl data file the datfile. */ static void get_tbl(FILEDESC *fp) { int fd; FILEDESC *child; if (fp->read_tbl) return; if (fp->child == NULL) { if ((fd = open(fp->datfile, O_RDONLY)) < 0) { perror(fp->datfile); exit(1); } if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) { (void)fprintf(stderr, "fortune: %s corrupted\n", fp->path); exit(1); } /* fp->tbl.str_version = be32toh(fp->tbl.str_version); */ fp->tbl.str_numstr = be32toh(fp->tbl.str_numstr); fp->tbl.str_longlen = be32toh(fp->tbl.str_longlen); fp->tbl.str_shortlen = be32toh(fp->tbl.str_shortlen); fp->tbl.str_flags = be32toh(fp->tbl.str_flags); (void) close(fd); } else { zero_tbl(&fp->tbl); for (child = fp->child; child != NULL; child = child->next) { get_tbl(child); sum_tbl(&fp->tbl, &child->tbl); } } fp->read_tbl = TRUE; } /* * zero_tbl: * Zero out the fields we care about in a tbl structure. */ static void zero_tbl(STRFILE *tp) { tp->str_numstr = 0; tp->str_longlen = 0; tp->str_shortlen = ~0; } /* * sum_tbl: * Merge the tbl data of t2 into t1. */ static void sum_tbl(STRFILE *t1, STRFILE *t2) { t1->str_numstr += t2->str_numstr; if (t1->str_longlen < t2->str_longlen) t1->str_longlen = t2->str_longlen; if (t1->str_shortlen > t2->str_shortlen) t1->str_shortlen = t2->str_shortlen; } #define STR(str) ((str) == NULL ? "NULL" : (str)) /* * print_file_list: * Print out the file list */ static void print_file_list(void) { print_list(File_list, 0); } /* * print_list: * Print out the actual list, recursively. */ static void print_list(FILEDESC *list, int lev) { while (list != NULL) { fprintf(stderr, "%*s", lev * 4, ""); if (list->percent == NO_PROB) fprintf(stderr, "___%%"); else fprintf(stderr, "%3d%%", list->percent); fprintf(stderr, " %s", STR(list->name)); DPRINTF(1, (stderr, " (%s, %s, %s)", STR(list->path), STR(list->datfile), STR(list->posfile))); fprintf(stderr, "\n"); if (list->child != NULL) print_list(list->child, lev + 1); list = list->next; } } /* * conv_pat: * Convert the pattern to an ignore-case equivalent. */ static char * conv_pat(char *orig) { char *sp; unsigned int cnt; char *new; cnt = 1; /* allow for '\0' */ for (sp = orig; *sp != '\0'; sp++) if (isalpha((unsigned char)*sp)) cnt += 4; else cnt++; if ((new = malloc(cnt)) == NULL) { fprintf(stderr, "pattern too long for ignoring case\n"); exit(1); } for (sp = new; *orig != '\0'; orig++) { if (islower((unsigned char)*orig)) { *sp++ = '['; *sp++ = *orig; *sp++ = toupper((unsigned char)*orig); *sp++ = ']'; } else if (isupper((unsigned char)*orig)) { *sp++ = '['; *sp++ = *orig; *sp++ = tolower((unsigned char)*orig); *sp++ = ']'; } else *sp++ = *orig; } *sp = '\0'; return (new); } /* * find_matches: * Find all the fortunes which match the pattern we've been given. */ static int find_matches(void) { Fort_len = maxlen_in_list(File_list); DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len)); /* extra length, "%\n" is appended */ Fortbuf = do_malloc((unsigned int) Fort_len + 10); Found_one = FALSE; matches_in_list(File_list); return (Found_one); } /* * maxlen_in_list * Return the maximum fortune len in the file list. */ static int maxlen_in_list(FILEDESC *list) { FILEDESC *fp; int len, maxlen; maxlen = 0; for (fp = list; fp != NULL; fp = fp->next) { if (fp->child != NULL) { if ((len = maxlen_in_list(fp->child)) > maxlen) maxlen = len; } else { get_tbl(fp); if (fp->tbl.str_longlen > (unsigned int)maxlen) maxlen = fp->tbl.str_longlen; } } return (maxlen); } /* * matches_in_list * Print out the matches from the files in the list. */ static void matches_in_list(FILEDESC *list) { char *sp, *p; FILEDESC *fp; int in_file; unsigned char ch; for (fp = list; fp != NULL; fp = fp->next) { if (fp->child != NULL) { matches_in_list(fp->child); continue; } DPRINTF(1, (stderr, "searching in %s\n", fp->path)); open_fp(fp); sp = Fortbuf; in_file = FALSE; while (fgets(sp, Fort_len, fp->inf) != NULL) if (fp->tbl.str_flags & STR_COMMENTS && sp[0] == fp->tbl.str_delim && sp[1] == fp->tbl.str_delim) continue; else if (!STR_ENDSTRING(sp, fp->tbl)) sp += strlen(sp); else { *sp = '\0'; if (fp->tbl.str_flags & STR_ROTATED) for (p = Fortbuf; (ch = *p) != '\0'; ++p) { if (isascii(ch)) { if (isupper(ch)) *p = 'A' + (ch - 'A' + 13) % 26; else if (islower(ch)) *p = 'a' + (ch - 'a' + 13) % 26; } } if (regexec(&Re_pat, Fortbuf, 0, NULL, 0) != REG_NOMATCH) { printf("%c%c", fp->tbl.str_delim, fp->tbl.str_delim); if (!in_file) { printf(" (%s)", fp->name); Found_one = TRUE; in_file = TRUE; } putchar('\n'); (void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout); } sp = Fortbuf; } } } static void usage(void) { (void) fprintf(stderr, "fortune [-a"); #ifdef DEBUG (void) fprintf(stderr, "D"); #endif /* DEBUG */ (void) fprintf(stderr, "efilosw]"); (void) fprintf(stderr, " [-m pattern]"); (void) fprintf(stderr, " [[N%%] file/directory/all]\n"); exit(1); } /* * getpath - * Set up file search patch from environment var FORTUNE_PATH; + * Set up file search path from environment var FORTUNE_PATH; * if not set, use the compiled in FORTDIR. */ static void getpath(void) { int nstr, foundenv; char *pch, **ppch, *str, *path; foundenv = 1; Fortune_path = getenv("FORTUNE_PATH"); if (Fortune_path == NULL) { Fortune_path = FORTDIR; foundenv = 0; } path = strdup(Fortune_path); for (nstr = 2, pch = path; *pch != '\0'; pch++) { if (*pch == ':') nstr++; } ppch = Fortune_path_arr = (char **)calloc(nstr, sizeof(char *)); nstr = 0; str = strtok(path, ":"); while (str) { if (is_dir(str)) { nstr++; *ppch++ = str; } str = strtok(NULL, ":"); } if (nstr == 0) { if (foundenv == 1) { fprintf(stderr, "fortune: FORTUNE_PATH: None of the specified " "directories found.\n"); exit(1); } free(path); Fortune_path_arr[0] = strdup(FORTDIR); } }