12490Sjkh/*-
22490Sjkh * Copyright (c) 1986, 1993
32490Sjkh *	The Regents of the University of California.  All rights reserved.
42490Sjkh *
52490Sjkh * This code is derived from software contributed to Berkeley by
62490Sjkh * Ken Arnold.
72490Sjkh *
82490Sjkh * Redistribution and use in source and binary forms, with or without
92490Sjkh * modification, are permitted provided that the following conditions
102490Sjkh * are met:
112490Sjkh * 1. Redistributions of source code must retain the above copyright
122490Sjkh *    notice, this list of conditions and the following disclaimer.
132490Sjkh * 2. Redistributions in binary form must reproduce the above copyright
142490Sjkh *    notice, this list of conditions and the following disclaimer in the
152490Sjkh *    documentation and/or other materials provided with the distribution.
16203926Suqs * 3. Neither the name of the University nor the names of its contributors
172490Sjkh *    may be used to endorse or promote products derived from this software
182490Sjkh *    without specific prior written permission.
192490Sjkh *
202490Sjkh * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
212490Sjkh * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
222490Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
232490Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
242490Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
252490Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
262490Sjkh * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
272490Sjkh * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
282490Sjkh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
292490Sjkh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
302490Sjkh * SUCH DAMAGE.
312490Sjkh */
322490Sjkh
33114725Sobrien#if 0
342490Sjkh#ifndef lint
3515944Sachestatic const char copyright[] =
362490Sjkh"@(#) Copyright (c) 1986, 1993\n\
372490Sjkh	The Regents of the University of California.  All rights reserved.\n";
382490Sjkh#endif /* not lint */
392490Sjkh
402490Sjkh#ifndef lint
4115944Sachestatic const char sccsid[] = "@(#)fortune.c   8.1 (Berkeley) 5/31/93";
42114725Sobrien#endif /* not lint */
4353920Sbillf#endif
44114725Sobrien#include <sys/cdefs.h>
45114725Sobrien__FBSDID("$FreeBSD$");
462490Sjkh
47203926Suqs#include <sys/stat.h>
48203926Suqs#include <sys/endian.h>
492490Sjkh
50203926Suqs#include <assert.h>
51203926Suqs#include <ctype.h>
52203926Suqs#include <dirent.h>
53203926Suqs#include <fcntl.h>
54203926Suqs#include <locale.h>
55203926Suqs#include <regex.h>
56242686Seadler#include <stdbool.h>
57203926Suqs#include <stdio.h>
58203926Suqs#include <stdlib.h>
59203926Suqs#include <string.h>
60203926Suqs#include <time.h>
61203926Suqs#include <unistd.h>
622490Sjkh
63203926Suqs#include "strfile.h"
64203926Suqs#include "pathnames.h"
652490Sjkh
66242686Seadler#define	TRUE	true
67242686Seadler#define	FALSE	false
682490Sjkh
69203926Suqs#define	MINW	6		/* minimum wait if desired */
70203926Suqs#define	CPERS	20		/* # of chars for each sec */
71203926Suqs#define	SLEN	160		/* # of chars in short fortune */
722490Sjkh
73203926Suqs#define	POS_UNKNOWN	((uint32_t) -1)	/* pos for file unknown */
74203926Suqs#define	NO_PROB		(-1)		/* no prob specified for file */
752490Sjkh
76203926Suqs#ifdef	DEBUG
77203926Suqs#define	DPRINTF(l,x)	{ if (Debug >= l) fprintf x; }
78203926Suqs#undef	NDEBUG
79203926Suqs#else
80203926Suqs#define	DPRINTF(l,x)
81203926Suqs#define	NDEBUG	1
82203926Suqs#endif
83203926Suqs
842490Sjkhtypedef struct fd {
852490Sjkh	int		percent;
862490Sjkh	int		fd, datfd;
87142022Sru	uint32_t	pos;
882490Sjkh	FILE		*inf;
89203922Suqs	const char	*name;
90203922Suqs	const char	*path;
912490Sjkh	char		*datfile, *posfile;
922490Sjkh	bool		read_tbl;
932490Sjkh	bool		was_pos_file;
942490Sjkh	STRFILE		tbl;
952490Sjkh	int		num_children;
962490Sjkh	struct fd	*child, *parent;
972490Sjkh	struct fd	*next, *prev;
982490Sjkh} FILEDESC;
992490Sjkh
1002490Sjkhbool	Found_one;			/* did we find a match? */
1012490Sjkhbool	Find_files	= FALSE;	/* just find a list of proper fortune files */
102203926Suqsbool	Fortunes_only   = FALSE;	/* check only "fortunes" files */
1032490Sjkhbool	Wait		= FALSE;	/* wait desired after fortune */
1042490Sjkhbool	Short_only	= FALSE;	/* short fortune desired */
1052490Sjkhbool	Long_only	= FALSE;	/* long fortune desired */
1062490Sjkhbool	Offend		= FALSE;	/* offensive fortunes only */
1072490Sjkhbool	All_forts	= FALSE;	/* any fortune allowed */
1082490Sjkhbool	Equal_probs	= FALSE;	/* scatter un-allocted prob equally */
1092490Sjkhbool	Match		= FALSE;	/* dump fortunes matching a pattern */
110242687Seadlerstatic bool	WriteToDisk = false;	/* use files on disk to save state */
1112490Sjkh#ifdef DEBUG
112243197Sdimint	Debug = FALSE;			/* print debug messages */
1132490Sjkh#endif
1142490Sjkh
1152490Sjkhchar	*Fortbuf = NULL;			/* fortune buffer for -m */
1162490Sjkh
1172490Sjkhint	Fort_len = 0;
1182490Sjkh
119142022Sruoff_t	Seekpts[2];                     /* seek pointers to fortunes */
1202490Sjkh
1212490SjkhFILEDESC	*File_list = NULL,	/* Head of file list */
1222490Sjkh		*File_tail = NULL;	/* Tail of file list */
1232490SjkhFILEDESC	*Fortfile;		/* Fortune file to use */
1242490Sjkh
1252490SjkhSTRFILE		Noprob_tbl;		/* sum of data for all no prob files */
1262490Sjkh
127203922Suqsconst char	*Fortune_path;
128173396Sedwinchar	**Fortune_path_arr;
129173396Sedwin
13093063Simpint	 add_dir(FILEDESC *);
131203922Suqsint	 add_file(int, const char *, const char *, FILEDESC **, FILEDESC **,
132203922Suqs	    FILEDESC *);
13393063Simpvoid	 all_forts(FILEDESC *, char *);
134203922Suqschar	*copy(const char *, u_int);
13593063Simpvoid	 display(FILEDESC *);
13693063Simpvoid	 do_free(void *);
13793063Simpvoid	*do_malloc(u_int);
13893063Simpint	 form_file_list(char **, int);
13993063Simpint	 fortlen(void);
14093063Simpvoid	 get_fort(void);
14193063Simpvoid	 get_pos(FILEDESC *);
14293063Simpvoid	 get_tbl(FILEDESC *);
14393063Simpvoid	 getargs(int, char *[]);
144173396Sedwinvoid	 getpath(void);
14593063Simpvoid	 init_prob(void);
146203922Suqsint	 is_dir(const char *);
147203922Suqsint	 is_fortfile(const char *, char **, char **, int);
148203922Suqsint	 is_off_name(const char *);
14993063Simpint	 max(int, int);
1502490SjkhFILEDESC *
15193063Simp	 new_fp(void);
152203922Suqschar	*off_name(const char *);
15393063Simpvoid	 open_dat(FILEDESC *);
15493063Simpvoid	 open_fp(FILEDESC *);
1552490SjkhFILEDESC *
15693063Simp	 pick_child(FILEDESC *);
15793063Simpvoid	 print_file_list(void);
15893063Simpvoid	 print_list(FILEDESC *, int);
15993063Simpvoid	 sum_noprobs(FILEDESC *);
16093063Simpvoid	 sum_tbl(STRFILE *, STRFILE *);
16193063Simpvoid	 usage(void);
16293063Simpvoid	 zero_tbl(STRFILE *);
1632490Sjkh
16493063Simpchar	*conv_pat(char *);
16593063Simpint	 find_matches(void);
16693063Simpvoid	 matches_in_list(FILEDESC *);
16793063Simpint	 maxlen_in_list(FILEDESC *);
1682490Sjkh
169130851Sphkstatic regex_t Re_pat;
1702490Sjkh
1712490Sjkhint
172203922Suqsmain(int argc, char *argv[])
1732490Sjkh{
1742490Sjkh	int	fd;
1752490Sjkh
176242687Seadler	if (getenv("FORTUNE_SAVESTATE") != NULL)
177242687Seadler		WriteToDisk = true;
178242687Seadler
17917537Sache	(void) setlocale(LC_ALL, "");
18015944Sache
181173396Sedwin	getpath();
182203922Suqs	getargs(argc, argv);
1832490Sjkh
1842490Sjkh	if (Match)
1852490Sjkh		exit(find_matches() != 0);
1862490Sjkh
1872490Sjkh	init_prob();
1882490Sjkh	do {
1892490Sjkh		get_fort();
1902490Sjkh	} while ((Short_only && fortlen() > SLEN) ||
1912490Sjkh		 (Long_only && fortlen() <= SLEN));
1922490Sjkh
1932490Sjkh	display(Fortfile);
1942490Sjkh
195242687Seadler	if (WriteToDisk) {
196242687Seadler		if ((fd = creat(Fortfile->posfile, 0666)) < 0) {
197242687Seadler			perror(Fortfile->posfile);
198242687Seadler			exit(1);
199242687Seadler		}
200242687Seadler		/*
201242687Seadler		 * if we can, we exclusive lock, but since it isn't very
202242687Seadler		 * important, we just punt if we don't have easy locking
203242687Seadler		 * available.
204242687Seadler		 */
205242687Seadler		flock(fd, LOCK_EX);
206242687Seadler		write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
207242687Seadler		if (!Fortfile->was_pos_file)
208242687Seadler		chmod(Fortfile->path, 0666);
209242687Seadler		flock(fd, LOCK_UN);
2102490Sjkh	}
2112490Sjkh	if (Wait) {
2122490Sjkh		if (Fort_len == 0)
2132490Sjkh			(void) fortlen();
2142490Sjkh		sleep((unsigned int) max(Fort_len / CPERS, MINW));
2152490Sjkh	}
216203926Suqs
217204178Suqs	exit(0);
2182490Sjkh}
2192490Sjkh
2202490Sjkhvoid
221203922Suqsdisplay(FILEDESC *fp)
2222490Sjkh{
22353210Sbillf	char   *p;
22453210Sbillf	unsigned char ch;
2252490Sjkh	char	line[BUFSIZ];
2262490Sjkh
2272490Sjkh	open_fp(fp);
228203926Suqs	fseeko(fp->inf, Seekpts[0], SEEK_SET);
2292490Sjkh	for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
2302490Sjkh	    !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
2312490Sjkh		if (fp->tbl.str_flags & STR_ROTATED)
23215944Sache			for (p = line; (ch = *p) != '\0'; ++p) {
23315944Sache				if (isascii(ch)) {
23415944Sache					if (isupper(ch))
23515944Sache						*p = 'A' + (ch - 'A' + 13) % 26;
23615944Sache					else if (islower(ch))
23715944Sache						*p = 'a' + (ch - 'a' + 13) % 26;
23815944Sache				}
23915944Sache			}
24051863Sdcs		if (fp->tbl.str_flags & STR_COMMENTS
24151863Sdcs		    && line[0] == fp->tbl.str_delim
24251863Sdcs		    && line[1] == fp->tbl.str_delim)
24351863Sdcs			continue;
2442490Sjkh		fputs(line, stdout);
2452490Sjkh	}
2462490Sjkh	(void) fflush(stdout);
2472490Sjkh}
2482490Sjkh
2492490Sjkh/*
2502490Sjkh * fortlen:
2512490Sjkh *	Return the length of the fortune.
2522490Sjkh */
2532490Sjkhint
254203922Suqsfortlen(void)
2552490Sjkh{
25653210Sbillf	int	nchar;
257173401Sedwin	char	line[BUFSIZ];
2582490Sjkh
2592490Sjkh	if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
260123905Sceri		nchar = (int)(Seekpts[1] - Seekpts[0]);
2612490Sjkh	else {
2622490Sjkh		open_fp(Fortfile);
263203926Suqs		fseeko(Fortfile->inf, Seekpts[0], SEEK_SET);
2642490Sjkh		nchar = 0;
2652490Sjkh		while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
2662490Sjkh		       !STR_ENDSTRING(line, Fortfile->tbl))
2672490Sjkh			nchar += strlen(line);
2682490Sjkh	}
2692490Sjkh	Fort_len = nchar;
270203926Suqs
271203926Suqs	return (nchar);
2722490Sjkh}
2732490Sjkh
2742490Sjkh/*
2752490Sjkh *	This routine evaluates the arguments on the command line
2762490Sjkh */
2772490Sjkhvoid
278203922Suqsgetargs(int argc, char *argv[])
2792490Sjkh{
28053210Sbillf	int	ignore_case;
28153210Sbillf	char	*pat;
2822490Sjkh	int ch;
2832490Sjkh
2842490Sjkh	ignore_case = FALSE;
2852490Sjkh	pat = NULL;
2862490Sjkh
287203926Suqs#ifdef DEBUG
28847440Simp	while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1)
2892490Sjkh#else
29047440Simp	while ((ch = getopt(argc, argv, "aefilm:osw")) != -1)
2912490Sjkh#endif /* DEBUG */
2922490Sjkh		switch(ch) {
2932490Sjkh		case 'a':		/* any fortune */
2942490Sjkh			All_forts++;
2952490Sjkh			break;
296203926Suqs#ifdef DEBUG
2972490Sjkh		case 'D':
2982490Sjkh			Debug++;
2992490Sjkh			break;
300203926Suqs#endif /* DEBUG */
3012490Sjkh		case 'e':
3022490Sjkh			Equal_probs++;	/* scatter un-allocted prob equally */
3032490Sjkh			break;
3042490Sjkh		case 'f':		/* find fortune files */
3052490Sjkh			Find_files++;
3062490Sjkh			break;
3072490Sjkh		case 'l':		/* long ones only */
3082490Sjkh			Long_only++;
3092490Sjkh			Short_only = FALSE;
3102490Sjkh			break;
3112490Sjkh		case 'o':		/* offensive ones only */
3122490Sjkh			Offend++;
3132490Sjkh			break;
3142490Sjkh		case 's':		/* short ones only */
3152490Sjkh			Short_only++;
3162490Sjkh			Long_only = FALSE;
3172490Sjkh			break;
3182490Sjkh		case 'w':		/* give time to read */
3192490Sjkh			Wait++;
3202490Sjkh			break;
3212490Sjkh		case 'm':			/* dump out the fortunes */
3222490Sjkh			Match++;
3232490Sjkh			pat = optarg;
3242490Sjkh			break;
3252490Sjkh		case 'i':			/* case-insensitive match */
3262490Sjkh			ignore_case++;
3272490Sjkh			break;
3282490Sjkh		case '?':
3292490Sjkh		default:
3302490Sjkh			usage();
3312490Sjkh		}
3322490Sjkh	argc -= optind;
3332490Sjkh	argv += optind;
3342490Sjkh
3352490Sjkh	if (!form_file_list(argv, argc))
3362490Sjkh		exit(1);	/* errors printed through form_file_list() */
3372490Sjkh	if (Find_files) {
3382490Sjkh		print_file_list();
3392490Sjkh		exit(0);
3402490Sjkh	}
34115957Sache#ifdef DEBUG
34215957Sache	else if (Debug >= 1)
34315957Sache		print_file_list();
34415957Sache#endif /* DEBUG */
3452490Sjkh
3462490Sjkh	if (pat != NULL) {
347130851Sphk		int error;
348130851Sphk
3492490Sjkh		if (ignore_case)
3502490Sjkh			pat = conv_pat(pat);
351130851Sphk		error = regcomp(&Re_pat, pat, REG_BASIC);
352130851Sphk		if (error) {
353130851Sphk			fprintf(stderr, "regcomp(%s) fails\n", pat);
354203926Suqs			exit(1);
3552490Sjkh		}
3562490Sjkh	}
3572490Sjkh}
3582490Sjkh
3592490Sjkh/*
3602490Sjkh * form_file_list:
3612490Sjkh *	Form the file list from the file specifications.
3622490Sjkh */
3632490Sjkhint
364203922Suqsform_file_list(char **files, int file_cnt)
3652490Sjkh{
36653210Sbillf	int	i, percent;
36753210Sbillf	char	*sp;
368173396Sedwin	char	**pstr;
3692490Sjkh
37049040Sbillf	if (file_cnt == 0) {
37115957Sache		if (Find_files) {
37215957Sache			Fortunes_only = TRUE;
373173396Sedwin			pstr = Fortune_path_arr;
374173396Sedwin			i = 0;
375173396Sedwin			while (*pstr) {
376203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
377173396Sedwin					      &File_list, &File_tail, NULL);
378173396Sedwin			}
37915957Sache			Fortunes_only = FALSE;
380173396Sedwin			if (!i) {
381173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
382173401Sedwin				    Fortune_path);
383173396Sedwin			}
384203926Suqs			return (i != 0);
385173396Sedwin		} else {
386173396Sedwin			pstr = Fortune_path_arr;
387173396Sedwin			i = 0;
388173396Sedwin			while (*pstr) {
389173396Sedwin				i += add_file(NO_PROB, "fortunes", *pstr++,
390173396Sedwin					      &File_list, &File_tail, NULL);
391173396Sedwin			}
392173396Sedwin			if (!i) {
393173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
394173401Sedwin				    Fortune_path);
395173396Sedwin			}
396203926Suqs			return (i != 0);
397173396Sedwin		}
39849040Sbillf	}
3992490Sjkh	for (i = 0; i < file_cnt; i++) {
4002490Sjkh		percent = NO_PROB;
40115944Sache		if (!isdigit((unsigned char)files[i][0]))
4022490Sjkh			sp = files[i];
4032490Sjkh		else {
4042490Sjkh			percent = 0;
40515944Sache			for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
4062490Sjkh				percent = percent * 10 + *sp - '0';
4072490Sjkh			if (percent > 100) {
4082490Sjkh				fprintf(stderr, "percentages must be <= 100\n");
409203926Suqs				return (FALSE);
4102490Sjkh			}
4112490Sjkh			if (*sp == '.') {
4122490Sjkh				fprintf(stderr, "percentages must be integers\n");
413203926Suqs				return (FALSE);
4142490Sjkh			}
4152490Sjkh			/*
4162490Sjkh			 * If the number isn't followed by a '%', then
4172490Sjkh			 * it was not a percentage, just the first part
4182490Sjkh			 * of a file name which starts with digits.
4192490Sjkh			 */
4202490Sjkh			if (*sp != '%') {
4212490Sjkh				percent = NO_PROB;
4222490Sjkh				sp = files[i];
4232490Sjkh			}
4242490Sjkh			else if (*++sp == '\0') {
4252490Sjkh				if (++i >= file_cnt) {
4262490Sjkh					fprintf(stderr, "percentages must precede files\n");
427203926Suqs					return (FALSE);
4282490Sjkh				}
4292490Sjkh				sp = files[i];
4302490Sjkh			}
4312490Sjkh		}
432173396Sedwin		if (strcmp(sp, "all") == 0) {
433173396Sedwin			pstr = Fortune_path_arr;
434173396Sedwin			i = 0;
435173396Sedwin			while (*pstr) {
436203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
437173396Sedwin					      &File_list, &File_tail, NULL);
438173396Sedwin			}
439173396Sedwin			if (!i) {
440173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
441173401Sedwin				    Fortune_path);
442203926Suqs				return (FALSE);
443173396Sedwin			}
444203926Suqs		} else if (!add_file(percent, sp, NULL, &File_list,
445173396Sedwin				     &File_tail, NULL)) {
446203926Suqs 			return (FALSE);
447173396Sedwin		}
4482490Sjkh	}
449203926Suqs
450203926Suqs	return (TRUE);
4512490Sjkh}
4522490Sjkh
4532490Sjkh/*
4542490Sjkh * add_file:
4552490Sjkh *	Add a file to the file list.
4562490Sjkh */
4572490Sjkhint
458203922Suqsadd_file(int percent, const char *file, const char *dir, FILEDESC **head,
459203922Suqs    FILEDESC **tail, FILEDESC *parent)
4602490Sjkh{
46153210Sbillf	FILEDESC	*fp;
46253210Sbillf	int		fd;
463203922Suqs	const char 	*path;
464203922Suqs	char		*tpath, *offensive;
46553210Sbillf	bool		was_malloc;
46653210Sbillf	bool		isdir;
4672490Sjkh
4682490Sjkh	if (dir == NULL) {
4692490Sjkh		path = file;
470203922Suqs		tpath = NULL;
4712490Sjkh		was_malloc = FALSE;
4722490Sjkh	}
4732490Sjkh	else {
474203922Suqs		tpath = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2));
475203922Suqs		strcat(strcat(strcpy(tpath, dir), "/"), file);
476203922Suqs		path = tpath;
4772490Sjkh		was_malloc = TRUE;
4782490Sjkh	}
4792490Sjkh	if ((isdir = is_dir(path)) && parent != NULL) {
4802490Sjkh		if (was_malloc)
481203922Suqs			free(tpath);
482203926Suqs		return (FALSE);	/* don't recurse */
4832490Sjkh	}
4842490Sjkh	offensive = NULL;
4852490Sjkh	if (!isdir && parent == NULL && (All_forts || Offend) &&
4862490Sjkh	    !is_off_name(path)) {
4872490Sjkh		offensive = off_name(path);
4882490Sjkh		if (Offend) {
4892490Sjkh			if (was_malloc)
490203922Suqs				free(tpath);
4912490Sjkh			path = offensive;
49215957Sache			offensive = NULL;
49315957Sache			was_malloc = TRUE;
49415957Sache			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
4952490Sjkh			file = off_name(file);
4962490Sjkh		}
4972490Sjkh	}
4982490Sjkh
4992490Sjkh	DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
5002490Sjkhover:
501203926Suqs	if ((fd = open(path, O_RDONLY)) < 0) {
5022490Sjkh		/*
5032490Sjkh		 * This is a sneak.  If the user said -a, and if the
5042490Sjkh		 * file we're given isn't a file, we check to see if
5052490Sjkh		 * there is a -o version.  If there is, we treat it as
5062490Sjkh		 * if *that* were the file given.  We only do this for
5072490Sjkh		 * individual files -- if we're scanning a directory,
5082490Sjkh		 * we'll pick up the -o file anyway.
5092490Sjkh		 */
5102490Sjkh		if (All_forts && offensive != NULL) {
5112490Sjkh			if (was_malloc)
512203922Suqs				free(tpath);
51315957Sache			path = offensive;
5142490Sjkh			offensive = NULL;
5152490Sjkh			was_malloc = TRUE;
5162490Sjkh			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
5172490Sjkh			file = off_name(file);
5182490Sjkh			goto over;
5192490Sjkh		}
520173396Sedwin		if (dir == NULL && file[0] != '/') {
521173396Sedwin			int i = 0;
522173396Sedwin			char **pstr = Fortune_path_arr;
523173396Sedwin
524173396Sedwin			while (*pstr) {
525203926Suqs				i += add_file(percent, file, *pstr++,
526173396Sedwin					      head, tail, parent);
527173396Sedwin			}
528173396Sedwin			if (!i) {
529173401Sedwin				fprintf(stderr, "No '%s' found in %s.\n",
530173401Sedwin				    file, Fortune_path);
531173396Sedwin			}
532203926Suqs			return (i != 0);
533173396Sedwin		}
534173396Sedwin		/*
5352490Sjkh		if (parent == NULL)
5362490Sjkh			perror(path);
537173396Sedwin		*/
5382490Sjkh		if (was_malloc)
539203922Suqs			free(tpath);
540203926Suqs		return (FALSE);
5412490Sjkh	}
5422490Sjkh
5432490Sjkh	DPRINTF(2, (stderr, "path = \"%s\"\n", path));
5442490Sjkh
5452490Sjkh	fp = new_fp();
5462490Sjkh	fp->fd = fd;
5472490Sjkh	fp->percent = percent;
5482490Sjkh	fp->name = file;
5492490Sjkh	fp->path = path;
5502490Sjkh	fp->parent = parent;
5512490Sjkh
5522490Sjkh	if ((isdir && !add_dir(fp)) ||
5532490Sjkh	    (!isdir &&
5542490Sjkh	     !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
5552490Sjkh	{
5562490Sjkh		if (parent == NULL)
5572490Sjkh			fprintf(stderr,
5582490Sjkh				"fortune:%s not a fortune file or directory\n",
5592490Sjkh				path);
5602490Sjkh		if (was_malloc)
561203922Suqs			free(tpath);
5622490Sjkh		do_free(fp->datfile);
5632490Sjkh		do_free(fp->posfile);
564203926Suqs		free(fp);
5652490Sjkh		do_free(offensive);
566203926Suqs		return (FALSE);
5672490Sjkh	}
5682490Sjkh	/*
5692490Sjkh	 * If the user said -a, we need to make this node a pointer to
5702490Sjkh	 * both files, if there are two.  We don't need to do this if
5712490Sjkh	 * we are scanning a directory, since the scan will pick up the
5722490Sjkh	 * -o file anyway.
5732490Sjkh	 */
5742490Sjkh	if (All_forts && parent == NULL && !is_off_name(path))
5752490Sjkh		all_forts(fp, offensive);
5762490Sjkh	if (*head == NULL)
5772490Sjkh		*head = *tail = fp;
5782490Sjkh	else if (fp->percent == NO_PROB) {
5792490Sjkh		(*tail)->next = fp;
5802490Sjkh		fp->prev = *tail;
5812490Sjkh		*tail = fp;
5822490Sjkh	}
5832490Sjkh	else {
5842490Sjkh		(*head)->prev = fp;
5852490Sjkh		fp->next = *head;
5862490Sjkh		*head = fp;
5872490Sjkh	}
588242687Seadler	if (WriteToDisk)
589242687Seadler		fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
5902490Sjkh
591203926Suqs	return (TRUE);
5922490Sjkh}
5932490Sjkh
5942490Sjkh/*
5952490Sjkh * new_fp:
5962490Sjkh *	Return a pointer to an initialized new FILEDESC.
5972490Sjkh */
5982490SjkhFILEDESC *
599203922Suqsnew_fp(void)
6002490Sjkh{
60153210Sbillf	FILEDESC	*fp;
6022490Sjkh
603203926Suqs	fp = do_malloc(sizeof(*fp));
6042490Sjkh	fp->datfd = -1;
6052490Sjkh	fp->pos = POS_UNKNOWN;
6062490Sjkh	fp->inf = NULL;
6072490Sjkh	fp->fd = -1;
6082490Sjkh	fp->percent = NO_PROB;
6092490Sjkh	fp->read_tbl = FALSE;
6102490Sjkh	fp->next = NULL;
6112490Sjkh	fp->prev = NULL;
6122490Sjkh	fp->child = NULL;
6132490Sjkh	fp->parent = NULL;
6142490Sjkh	fp->datfile = NULL;
6152490Sjkh	fp->posfile = NULL;
616203926Suqs
617203926Suqs	return (fp);
6182490Sjkh}
6192490Sjkh
6202490Sjkh/*
6212490Sjkh * off_name:
6222490Sjkh *	Return a pointer to the offensive version of a file of this name.
6232490Sjkh */
6242490Sjkhchar *
625203922Suqsoff_name(const char *file)
6262490Sjkh{
6272490Sjkh	char	*new;
6282490Sjkh
6292490Sjkh	new = copy(file, (unsigned int) (strlen(file) + 2));
630203926Suqs
631203926Suqs	return (strcat(new, "-o"));
6322490Sjkh}
6332490Sjkh
6342490Sjkh/*
6352490Sjkh * is_off_name:
6362490Sjkh *	Is the file an offensive-style name?
6372490Sjkh */
6382490Sjkhint
639203922Suqsis_off_name(const char *file)
6402490Sjkh{
6412490Sjkh	int	len;
6422490Sjkh
6432490Sjkh	len = strlen(file);
644203926Suqs
6452490Sjkh	return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
6462490Sjkh}
6472490Sjkh
6482490Sjkh/*
6492490Sjkh * all_forts:
6502490Sjkh *	Modify a FILEDESC element to be the parent of two children if
6512490Sjkh *	there are two children to be a parent of.
6522490Sjkh */
6532490Sjkhvoid
654203922Suqsall_forts(FILEDESC *fp, char *offensive)
6552490Sjkh{
65653210Sbillf	char		*sp;
65753210Sbillf	FILEDESC	*scene, *obscene;
65853210Sbillf	int		fd;
659203926Suqs	char		*datfile, *posfile;
6602490Sjkh
6612490Sjkh	if (fp->child != NULL)	/* this is a directory, not a file */
6622490Sjkh		return;
6632490Sjkh	if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
6642490Sjkh		return;
665203926Suqs	if ((fd = open(offensive, O_RDONLY)) < 0)
6662490Sjkh		return;
6672490Sjkh	DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
6682490Sjkh	scene = new_fp();
6692490Sjkh	obscene = new_fp();
6702490Sjkh	*scene = *fp;
6712490Sjkh
6722490Sjkh	fp->num_children = 2;
6732490Sjkh	fp->child = scene;
6742490Sjkh	scene->next = obscene;
6752490Sjkh	obscene->next = NULL;
6762490Sjkh	scene->child = obscene->child = NULL;
6772490Sjkh	scene->parent = obscene->parent = fp;
6782490Sjkh
6792490Sjkh	fp->fd = -1;
6802490Sjkh	scene->percent = obscene->percent = NO_PROB;
6812490Sjkh
6822490Sjkh	obscene->fd = fd;
6832490Sjkh	obscene->inf = NULL;
6842490Sjkh	obscene->path = offensive;
685249828Seadler	if ((sp = strrchr(offensive, '/')) == NULL)
6862490Sjkh		obscene->name = offensive;
6872490Sjkh	else
6882490Sjkh		obscene->name = ++sp;
6892490Sjkh	obscene->datfile = datfile;
6902490Sjkh	obscene->posfile = posfile;
691242687Seadler	obscene->read_tbl = false;
692242687Seadler	if (WriteToDisk)
693242687Seadler		obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
6942490Sjkh}
6952490Sjkh
6962490Sjkh/*
6972490Sjkh * add_dir:
6982490Sjkh *	Add the contents of an entire directory.
6992490Sjkh */
7002490Sjkhint
701203922Suqsadd_dir(FILEDESC *fp)
7022490Sjkh{
70353210Sbillf	DIR		*dir;
70453210Sbillf	struct dirent	*dirent;
705203926Suqs	FILEDESC	*tailp;
706203926Suqs	char		*name;
7072490Sjkh
7082490Sjkh	(void) close(fp->fd);
7092490Sjkh	fp->fd = -1;
7102490Sjkh	if ((dir = opendir(fp->path)) == NULL) {
7112490Sjkh		perror(fp->path);
712203926Suqs		return (FALSE);
7132490Sjkh	}
7142490Sjkh	tailp = NULL;
7152490Sjkh	DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
7162490Sjkh	fp->num_children = 0;
7172490Sjkh	while ((dirent = readdir(dir)) != NULL) {
7182490Sjkh		if (dirent->d_namlen == 0)
7192490Sjkh			continue;
7202490Sjkh		name = copy(dirent->d_name, dirent->d_namlen);
7212490Sjkh		if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
7222490Sjkh			fp->num_children++;
7232490Sjkh		else
7242490Sjkh			free(name);
7252490Sjkh	}
7262490Sjkh	if (fp->num_children == 0) {
7272490Sjkh		(void) fprintf(stderr,
7282490Sjkh		    "fortune: %s: No fortune files in directory.\n", fp->path);
729203926Suqs		return (FALSE);
7302490Sjkh	}
731203926Suqs
732203926Suqs	return (TRUE);
7332490Sjkh}
7342490Sjkh
7352490Sjkh/*
7362490Sjkh * is_dir:
7372490Sjkh *	Return TRUE if the file is a directory, FALSE otherwise.
7382490Sjkh */
7392490Sjkhint
740203922Suqsis_dir(const char *file)
7412490Sjkh{
742203926Suqs	struct stat	sbuf;
7432490Sjkh
7442490Sjkh	if (stat(file, &sbuf) < 0)
745203926Suqs		return (FALSE);
746203926Suqs
7472490Sjkh	return (sbuf.st_mode & S_IFDIR);
7482490Sjkh}
7492490Sjkh
7502490Sjkh/*
7512490Sjkh * is_fortfile:
7522490Sjkh *	Return TRUE if the file is a fortune database file.  We try and
7532490Sjkh *	exclude files without reading them if possible to avoid
7542490Sjkh *	overhead.  Files which start with ".", or which have "illegal"
7552490Sjkh *	suffixes, as contained in suflist[], are ruled out.
7562490Sjkh */
7572490Sjkh/* ARGSUSED */
7582490Sjkhint
759203922Suqsis_fortfile(const char *file, char **datp, char **posp, int check_for_offend)
7602490Sjkh{
76153210Sbillf	int	i;
762203922Suqs	const char	*sp;
76353210Sbillf	char	*datfile;
764203922Suqs	static const char *suflist[] = {
765203922Suqs		/* list of "illegal" suffixes" */
766203922Suqs		"dat", "pos", "c", "h", "p", "i", "f",
767203922Suqs		"pas", "ftn", "ins.c", "ins,pas",
768203922Suqs		"ins.ftn", "sml",
769203922Suqs		NULL
770203922Suqs	};
7712490Sjkh
7722490Sjkh	DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
7732490Sjkh
7742490Sjkh	/*
7752490Sjkh	 * Preclude any -o files for offendable people, and any non -o
7762490Sjkh	 * files for completely offensive people.
7772490Sjkh	 */
7782490Sjkh	if (check_for_offend && !All_forts) {
7792490Sjkh		i = strlen(file);
78015957Sache		if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o')) {
78115957Sache			DPRINTF(2, (stderr, "FALSE (offending file)\n"));
782203926Suqs			return (FALSE);
78315957Sache		}
7842490Sjkh	}
7852490Sjkh
786249828Seadler	if ((sp = strrchr(file, '/')) == NULL)
7872490Sjkh		sp = file;
7882490Sjkh	else
7892490Sjkh		sp++;
7902490Sjkh	if (*sp == '.') {
7912490Sjkh		DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
792203926Suqs		return (FALSE);
7932490Sjkh	}
79415957Sache	if (Fortunes_only && strncmp(sp, "fortunes", 8) != 0) {
79515957Sache		DPRINTF(2, (stderr, "FALSE (check fortunes only)\n"));
796203926Suqs		return (FALSE);
79715957Sache	}
798249828Seadler	if ((sp = strrchr(sp, '.')) != NULL) {
7992490Sjkh		sp++;
8002490Sjkh		for (i = 0; suflist[i] != NULL; i++)
8012490Sjkh			if (strcmp(sp, suflist[i]) == 0) {
8022490Sjkh				DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
803203926Suqs				return (FALSE);
8042490Sjkh			}
8052490Sjkh	}
8062490Sjkh
8072490Sjkh	datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
8082490Sjkh	strcat(datfile, ".dat");
8092490Sjkh	if (access(datfile, R_OK) < 0) {
81044599Sdcs		DPRINTF(2, (stderr, "FALSE (no readable \".dat\" file)\n"));
81144599Sdcs#ifdef DEBUG
81244599Sdcs		if (Debug < 2)
81344599Sdcs			DPRINTF(0, (stderr, "Warning: file \"%s\" unreadable\n", datfile));
81444599Sdcs#endif
8152490Sjkh		free(datfile);
816203926Suqs		return (FALSE);
8172490Sjkh	}
8182490Sjkh	if (datp != NULL)
8192490Sjkh		*datp = datfile;
8202490Sjkh	else
8212490Sjkh		free(datfile);
82215944Sache	if (posp != NULL) {
823242687Seadler		if (WriteToDisk) {
824242687Seadler			*posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
825242687Seadler			strcat(*posp, ".pos");
826242687Seadler		}
827242687Seadler		else {
828242687Seadler			*posp = NULL;
829242687Seadler		}
8302490Sjkh	}
8312490Sjkh	DPRINTF(2, (stderr, "TRUE\n"));
832203926Suqs
833203926Suqs	return (TRUE);
8342490Sjkh}
8352490Sjkh
8362490Sjkh/*
8372490Sjkh * copy:
8382490Sjkh *	Return a malloc()'ed copy of the string
8392490Sjkh */
8402490Sjkhchar *
841203922Suqscopy(const char *str, unsigned int len)
8422490Sjkh{
843203926Suqs	char *new, *sp;
8442490Sjkh
8452490Sjkh	new = do_malloc(len + 1);
8462490Sjkh	sp = new;
8472490Sjkh	do {
8482490Sjkh		*sp++ = *str;
8492490Sjkh	} while (*str++);
850203926Suqs
851203926Suqs	return (new);
8522490Sjkh}
8532490Sjkh
8542490Sjkh/*
8552490Sjkh * do_malloc:
8562490Sjkh *	Do a malloc, checking for NULL return.
8572490Sjkh */
8582490Sjkhvoid *
859203922Suqsdo_malloc(unsigned int size)
8602490Sjkh{
861203926Suqs	void *new;
8622490Sjkh
8632490Sjkh	if ((new = malloc(size)) == NULL) {
8642490Sjkh		(void) fprintf(stderr, "fortune: out of memory.\n");
8652490Sjkh		exit(1);
8662490Sjkh	}
867203926Suqs
868203926Suqs	return (new);
8692490Sjkh}
8702490Sjkh
8712490Sjkh/*
8722490Sjkh * do_free:
8732490Sjkh *	Free malloc'ed space, if any.
8742490Sjkh */
8752490Sjkhvoid
876203922Suqsdo_free(void *ptr)
8772490Sjkh{
8782490Sjkh	if (ptr != NULL)
8792490Sjkh		free(ptr);
8802490Sjkh}
8812490Sjkh
8822490Sjkh/*
8832490Sjkh * init_prob:
8842490Sjkh *	Initialize the fortune probabilities.
8852490Sjkh */
8862490Sjkhvoid
887203922Suqsinit_prob(void)
8882490Sjkh{
88953210Sbillf	FILEDESC       *fp, *last = NULL;
89053210Sbillf	int		percent, num_noprob, frac;
8912490Sjkh
8922490Sjkh	/*
8932490Sjkh	 * Distribute the residual probability (if any) across all
8942490Sjkh	 * files with unspecified probability (i.e., probability of 0)
8952490Sjkh	 * (if any).
8962490Sjkh	 */
8972490Sjkh
8982490Sjkh	percent = 0;
8992490Sjkh	num_noprob = 0;
9002490Sjkh	for (fp = File_tail; fp != NULL; fp = fp->prev)
9012490Sjkh		if (fp->percent == NO_PROB) {
9022490Sjkh			num_noprob++;
9032490Sjkh			if (Equal_probs)
9042490Sjkh				last = fp;
905203926Suqs		} else
9062490Sjkh			percent += fp->percent;
9072490Sjkh	DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
9082490Sjkh		    percent, num_noprob));
9092490Sjkh	if (percent > 100) {
9102490Sjkh		(void) fprintf(stderr,
91115957Sache		    "fortune: probabilities sum to %d%% > 100%%!\n", percent);
9122490Sjkh		exit(1);
913203926Suqs	} else if (percent < 100 && num_noprob == 0) {
9142490Sjkh		(void) fprintf(stderr,
91515957Sache		    "fortune: no place to put residual probability (%d%% < 100%%)\n",
9162490Sjkh		    percent);
9172490Sjkh		exit(1);
918203926Suqs	} else if (percent == 100 && num_noprob != 0) {
9192490Sjkh		(void) fprintf(stderr,
92015957Sache		    "fortune: no probability left to put in residual files (100%%)\n");
9212490Sjkh		exit(1);
9222490Sjkh	}
9232490Sjkh	percent = 100 - percent;
92449040Sbillf	if (Equal_probs) {
9252490Sjkh		if (num_noprob != 0) {
9262490Sjkh			if (num_noprob > 1) {
9272490Sjkh				frac = percent / num_noprob;
9282490Sjkh				DPRINTF(1, (stderr, ", frac = %d%%", frac));
929173396Sedwin				for (fp = File_tail; fp != last; fp = fp->prev)
9302490Sjkh					if (fp->percent == NO_PROB) {
9312490Sjkh						fp->percent = frac;
9322490Sjkh						percent -= frac;
9332490Sjkh					}
9342490Sjkh			}
9352490Sjkh			last->percent = percent;
9362490Sjkh			DPRINTF(1, (stderr, ", residual = %d%%", percent));
9372490Sjkh		}
938203922Suqs		else
939203926Suqs		DPRINTF(1, (stderr,
9402490Sjkh			    ", %d%% distributed over remaining fortunes\n",
9412490Sjkh			    percent));
9422490Sjkh	}
9432490Sjkh	DPRINTF(1, (stderr, "\n"));
9442490Sjkh
9452490Sjkh#ifdef DEBUG
9462490Sjkh	if (Debug >= 1)
9472490Sjkh		print_file_list();
9482490Sjkh#endif
9492490Sjkh}
9502490Sjkh
9512490Sjkh/*
9522490Sjkh * get_fort:
9532490Sjkh *	Get the fortune data file's seek pointer for the next fortune.
9542490Sjkh */
9552490Sjkhvoid
956203922Suqsget_fort(void)
9572490Sjkh{
95853210Sbillf	FILEDESC	*fp;
95953210Sbillf	int		choice;
9602490Sjkh
9612490Sjkh	if (File_list->next == NULL || File_list->percent == NO_PROB)
9622490Sjkh		fp = File_list;
9632490Sjkh	else {
964181385Sache		choice = arc4random_uniform(100);
9652490Sjkh		DPRINTF(1, (stderr, "choice = %d\n", choice));
9662490Sjkh		for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
9672490Sjkh			if (choice < fp->percent)
9682490Sjkh				break;
9692490Sjkh			else {
9702490Sjkh				choice -= fp->percent;
9712490Sjkh				DPRINTF(1, (stderr,
9722490Sjkh					    "    skip \"%s\", %d%% (choice = %d)\n",
9732490Sjkh					    fp->name, fp->percent, choice));
9742490Sjkh			}
9752490Sjkh			DPRINTF(1, (stderr,
9762490Sjkh				    "using \"%s\", %d%% (choice = %d)\n",
9772490Sjkh				    fp->name, fp->percent, choice));
9782490Sjkh	}
9792490Sjkh	if (fp->percent != NO_PROB)
9802490Sjkh		get_tbl(fp);
9812490Sjkh	else {
9822490Sjkh		if (fp->next != NULL) {
9832490Sjkh			sum_noprobs(fp);
984181385Sache			choice = arc4random_uniform(Noprob_tbl.str_numstr);
985142022Sru			DPRINTF(1, (stderr, "choice = %d (of %u) \n", choice,
9862490Sjkh				    Noprob_tbl.str_numstr));
987203922Suqs			while ((unsigned int)choice >= fp->tbl.str_numstr) {
9882490Sjkh				choice -= fp->tbl.str_numstr;
9892490Sjkh				fp = fp->next;
9902490Sjkh				DPRINTF(1, (stderr,
991142022Sru					    "    skip \"%s\", %u (choice = %d)\n",
9922490Sjkh					    fp->name, fp->tbl.str_numstr,
9932490Sjkh					    choice));
9942490Sjkh			}
995142022Sru			DPRINTF(1, (stderr, "using \"%s\", %u\n", fp->name,
9962490Sjkh				    fp->tbl.str_numstr));
9972490Sjkh		}
9982490Sjkh		get_tbl(fp);
9992490Sjkh	}
10002490Sjkh	if (fp->child != NULL) {
10012490Sjkh		DPRINTF(1, (stderr, "picking child\n"));
10022490Sjkh		fp = pick_child(fp);
10032490Sjkh	}
10042490Sjkh	Fortfile = fp;
10052490Sjkh	get_pos(fp);
10062490Sjkh	open_dat(fp);
1007203926Suqs	lseek(fp->datfd,
1008203926Suqs	    (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), SEEK_SET);
10092490Sjkh	read(fp->datfd, Seekpts, sizeof Seekpts);
1010142022Sru	Seekpts[0] = be64toh(Seekpts[0]);
1011142022Sru	Seekpts[1] = be64toh(Seekpts[1]);
10122490Sjkh}
10132490Sjkh
10142490Sjkh/*
10152490Sjkh * pick_child
10162490Sjkh *	Pick a child from a chosen parent.
10172490Sjkh */
10182490SjkhFILEDESC *
1019203922Suqspick_child(FILEDESC *parent)
10202490Sjkh{
102153210Sbillf	FILEDESC	*fp;
102253210Sbillf	int		choice;
10232490Sjkh
10242490Sjkh	if (Equal_probs) {
1025181385Sache		choice = arc4random_uniform(parent->num_children);
10262490Sjkh		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
10272490Sjkh			    choice, parent->num_children));
10282490Sjkh		for (fp = parent->child; choice--; fp = fp->next)
10292490Sjkh			continue;
10302490Sjkh		DPRINTF(1, (stderr, "    using %s\n", fp->name));
1031203926Suqs		return (fp);
10322490Sjkh	}
10332490Sjkh	else {
10342490Sjkh		get_tbl(parent);
1035181385Sache		choice = arc4random_uniform(parent->tbl.str_numstr);
1036142022Sru		DPRINTF(1, (stderr, "    choice = %d (of %u)\n",
10372490Sjkh			    choice, parent->tbl.str_numstr));
1038203922Suqs		for (fp = parent->child; (unsigned)choice >= fp->tbl.str_numstr;
10392490Sjkh		     fp = fp->next) {
10402490Sjkh			choice -= fp->tbl.str_numstr;
1041142022Sru			DPRINTF(1, (stderr, "\tskip %s, %u (choice = %d)\n",
10422490Sjkh				    fp->name, fp->tbl.str_numstr, choice));
10432490Sjkh		}
1044142022Sru		DPRINTF(1, (stderr, "    using %s, %u\n", fp->name,
10452490Sjkh			    fp->tbl.str_numstr));
1046203926Suqs		return (fp);
10472490Sjkh	}
10482490Sjkh}
10492490Sjkh
10502490Sjkh/*
10512490Sjkh * sum_noprobs:
10522490Sjkh *	Sum up all the noprob probabilities, starting with fp.
10532490Sjkh */
10542490Sjkhvoid
1055203922Suqssum_noprobs(FILEDESC *fp)
10562490Sjkh{
10572490Sjkh	static bool	did_noprobs = FALSE;
10582490Sjkh
10592490Sjkh	if (did_noprobs)
10602490Sjkh		return;
10612490Sjkh	zero_tbl(&Noprob_tbl);
10622490Sjkh	while (fp != NULL) {
10632490Sjkh		get_tbl(fp);
10642490Sjkh		sum_tbl(&Noprob_tbl, &fp->tbl);
10652490Sjkh		fp = fp->next;
10662490Sjkh	}
10672490Sjkh	did_noprobs = TRUE;
10682490Sjkh}
10692490Sjkh
10702490Sjkhint
1071203922Suqsmax(int i, int j)
10722490Sjkh{
10732490Sjkh	return (i >= j ? i : j);
10742490Sjkh}
10752490Sjkh
10762490Sjkh/*
10772490Sjkh * open_fp:
10782490Sjkh *	Assocatiate a FILE * with the given FILEDESC.
10792490Sjkh */
10802490Sjkhvoid
1081203922Suqsopen_fp(FILEDESC *fp)
10822490Sjkh{
10832490Sjkh	if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) {
10842490Sjkh		perror(fp->path);
10852490Sjkh		exit(1);
10862490Sjkh	}
10872490Sjkh}
10882490Sjkh
10892490Sjkh/*
10902490Sjkh * open_dat:
10912490Sjkh *	Open up the dat file if we need to.
10922490Sjkh */
10932490Sjkhvoid
1094203922Suqsopen_dat(FILEDESC *fp)
10952490Sjkh{
1096203926Suqs	if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, O_RDONLY)) < 0) {
10972490Sjkh		perror(fp->datfile);
10982490Sjkh		exit(1);
10992490Sjkh	}
11002490Sjkh}
11012490Sjkh
11022490Sjkh/*
11032490Sjkh * get_pos:
11042490Sjkh *	Get the position from the pos file, if there is one.  If not,
11052490Sjkh *	return a random number.
11062490Sjkh */
11072490Sjkhvoid
1108203922Suqsget_pos(FILEDESC *fp)
11092490Sjkh{
11102490Sjkh	int	fd;
11112490Sjkh
11122490Sjkh	assert(fp->read_tbl);
11132490Sjkh	if (fp->pos == POS_UNKNOWN) {
1114242687Seadler		if (WriteToDisk) {
1115242687Seadler			if ((fd = open(fp->posfile, O_RDONLY)) < 0 ||
1116242687Seadler			    read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
1117242687Seadler				fp->pos = arc4random_uniform(fp->tbl.str_numstr);
1118242687Seadler			else if (fp->pos >= fp->tbl.str_numstr)
1119242687Seadler				fp->pos %= fp->tbl.str_numstr;
1120242687Seadler			if (fd >= 0)
1121242687Seadler				close(fd);
1122242687Seadler		}
1123242687Seadler		else
1124181385Sache			fp->pos = arc4random_uniform(fp->tbl.str_numstr);
11252490Sjkh	}
11262490Sjkh	if (++(fp->pos) >= fp->tbl.str_numstr)
11272490Sjkh		fp->pos -= fp->tbl.str_numstr;
1128142022Sru	DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, (long)fp->pos));
11292490Sjkh}
11302490Sjkh
11312490Sjkh/*
11322490Sjkh * get_tbl:
11332490Sjkh *	Get the tbl data file the datfile.
11342490Sjkh */
11352490Sjkhvoid
1136203922Suqsget_tbl(FILEDESC *fp)
11372490Sjkh{
1138203926Suqs	int		fd;
113953210Sbillf	FILEDESC	*child;
11402490Sjkh
11412490Sjkh	if (fp->read_tbl)
11422490Sjkh		return;
11432490Sjkh	if (fp->child == NULL) {
1144203926Suqs		if ((fd = open(fp->datfile, O_RDONLY)) < 0) {
11452490Sjkh			perror(fp->datfile);
11462490Sjkh			exit(1);
11472490Sjkh		}
11482490Sjkh		if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
11492490Sjkh			(void)fprintf(stderr,
11502490Sjkh			    "fortune: %s corrupted\n", fp->path);
11512490Sjkh			exit(1);
11522490Sjkh		}
1153142022Sru		/* fp->tbl.str_version = be32toh(fp->tbl.str_version); */
1154142022Sru		fp->tbl.str_numstr = be32toh(fp->tbl.str_numstr);
1155142022Sru		fp->tbl.str_longlen = be32toh(fp->tbl.str_longlen);
1156142022Sru		fp->tbl.str_shortlen = be32toh(fp->tbl.str_shortlen);
1157142022Sru		fp->tbl.str_flags = be32toh(fp->tbl.str_flags);
11582490Sjkh		(void) close(fd);
11592490Sjkh	}
11602490Sjkh	else {
11612490Sjkh		zero_tbl(&fp->tbl);
11622490Sjkh		for (child = fp->child; child != NULL; child = child->next) {
11632490Sjkh			get_tbl(child);
11642490Sjkh			sum_tbl(&fp->tbl, &child->tbl);
11652490Sjkh		}
11662490Sjkh	}
11672490Sjkh	fp->read_tbl = TRUE;
11682490Sjkh}
11692490Sjkh
11702490Sjkh/*
11712490Sjkh * zero_tbl:
11722490Sjkh *	Zero out the fields we care about in a tbl structure.
11732490Sjkh */
11742490Sjkhvoid
1175203922Suqszero_tbl(STRFILE *tp)
11762490Sjkh{
11772490Sjkh	tp->str_numstr = 0;
11782490Sjkh	tp->str_longlen = 0;
1179142022Sru	tp->str_shortlen = ~0;
11802490Sjkh}
11812490Sjkh
11822490Sjkh/*
11832490Sjkh * sum_tbl:
11842490Sjkh *	Merge the tbl data of t2 into t1.
11852490Sjkh */
11862490Sjkhvoid
1187203922Suqssum_tbl(STRFILE *t1, STRFILE *t2)
11882490Sjkh{
11892490Sjkh	t1->str_numstr += t2->str_numstr;
11902490Sjkh	if (t1->str_longlen < t2->str_longlen)
11912490Sjkh		t1->str_longlen = t2->str_longlen;
11922490Sjkh	if (t1->str_shortlen > t2->str_shortlen)
11932490Sjkh		t1->str_shortlen = t2->str_shortlen;
11942490Sjkh}
11952490Sjkh
11962490Sjkh#define	STR(str)	((str) == NULL ? "NULL" : (str))
11972490Sjkh
11982490Sjkh/*
11992490Sjkh * print_file_list:
12002490Sjkh *	Print out the file list
12012490Sjkh */
12022490Sjkhvoid
1203203922Suqsprint_file_list(void)
12042490Sjkh{
12052490Sjkh	print_list(File_list, 0);
12062490Sjkh}
12072490Sjkh
12082490Sjkh/*
12092490Sjkh * print_list:
12102490Sjkh *	Print out the actual list, recursively.
12112490Sjkh */
12122490Sjkhvoid
1213203922Suqsprint_list(FILEDESC *list, int lev)
12142490Sjkh{
12152490Sjkh	while (list != NULL) {
12162490Sjkh		fprintf(stderr, "%*s", lev * 4, "");
12172490Sjkh		if (list->percent == NO_PROB)
12182490Sjkh			fprintf(stderr, "___%%");
12192490Sjkh		else
12202490Sjkh			fprintf(stderr, "%3d%%", list->percent);
12212490Sjkh		fprintf(stderr, " %s", STR(list->name));
122215957Sache		DPRINTF(1, (stderr, " (%s, %s, %s)", STR(list->path),
12232490Sjkh			    STR(list->datfile), STR(list->posfile)));
122415957Sache		fprintf(stderr, "\n");
12252490Sjkh		if (list->child != NULL)
12262490Sjkh			print_list(list->child, lev + 1);
12272490Sjkh		list = list->next;
12282490Sjkh	}
12292490Sjkh}
12302490Sjkh
12312490Sjkh/*
12322490Sjkh * conv_pat:
12332490Sjkh *	Convert the pattern to an ignore-case equivalent.
12342490Sjkh */
12352490Sjkhchar *
1236203922Suqsconv_pat(char *orig)
12372490Sjkh{
123853210Sbillf	char		*sp;
123953210Sbillf	unsigned int	cnt;
124053210Sbillf	char		*new;
12412490Sjkh
12422490Sjkh	cnt = 1;	/* allow for '\0' */
12432490Sjkh	for (sp = orig; *sp != '\0'; sp++)
124415944Sache		if (isalpha((unsigned char)*sp))
12452490Sjkh			cnt += 4;
12462490Sjkh		else
12472490Sjkh			cnt++;
12482490Sjkh	if ((new = malloc(cnt)) == NULL) {
12492490Sjkh		fprintf(stderr, "pattern too long for ignoring case\n");
12502490Sjkh		exit(1);
12512490Sjkh	}
12522490Sjkh
12532490Sjkh	for (sp = new; *orig != '\0'; orig++) {
125415944Sache		if (islower((unsigned char)*orig)) {
12552490Sjkh			*sp++ = '[';
12562490Sjkh			*sp++ = *orig;
125715944Sache			*sp++ = toupper((unsigned char)*orig);
12582490Sjkh			*sp++ = ']';
12592490Sjkh		}
126015944Sache		else if (isupper((unsigned char)*orig)) {
12612490Sjkh			*sp++ = '[';
12622490Sjkh			*sp++ = *orig;
126315944Sache			*sp++ = tolower((unsigned char)*orig);
12642490Sjkh			*sp++ = ']';
12652490Sjkh		}
12662490Sjkh		else
12672490Sjkh			*sp++ = *orig;
12682490Sjkh	}
12692490Sjkh	*sp = '\0';
1270203926Suqs
1271203926Suqs	return (new);
12722490Sjkh}
12732490Sjkh
12742490Sjkh/*
12752490Sjkh * find_matches:
12762490Sjkh *	Find all the fortunes which match the pattern we've been given.
12772490Sjkh */
12782490Sjkhint
1279203922Suqsfind_matches(void)
12802490Sjkh{
12812490Sjkh	Fort_len = maxlen_in_list(File_list);
12822490Sjkh	DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
12832490Sjkh	/* extra length, "%\n" is appended */
12842490Sjkh	Fortbuf = do_malloc((unsigned int) Fort_len + 10);
12852490Sjkh
12862490Sjkh	Found_one = FALSE;
12872490Sjkh	matches_in_list(File_list);
1288203926Suqs
1289203926Suqs	return (Found_one);
12902490Sjkh}
12912490Sjkh
12922490Sjkh/*
12932490Sjkh * maxlen_in_list
12942490Sjkh *	Return the maximum fortune len in the file list.
12952490Sjkh */
12962490Sjkhint
1297203922Suqsmaxlen_in_list(FILEDESC *list)
12982490Sjkh{
129953210Sbillf	FILEDESC	*fp;
130053210Sbillf	int		len, maxlen;
13012490Sjkh
13022490Sjkh	maxlen = 0;
13032490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
13042490Sjkh		if (fp->child != NULL) {
13052490Sjkh			if ((len = maxlen_in_list(fp->child)) > maxlen)
13062490Sjkh				maxlen = len;
13072490Sjkh		}
13082490Sjkh		else {
13092490Sjkh			get_tbl(fp);
1310203926Suqs			if (fp->tbl.str_longlen > (unsigned int)maxlen)
13112490Sjkh				maxlen = fp->tbl.str_longlen;
13122490Sjkh		}
13132490Sjkh	}
1314203926Suqs
1315203926Suqs	return (maxlen);
13162490Sjkh}
13172490Sjkh
13182490Sjkh/*
13192490Sjkh * matches_in_list
13202490Sjkh *	Print out the matches from the files in the list.
13212490Sjkh */
13222490Sjkhvoid
1323203922Suqsmatches_in_list(FILEDESC *list)
13242490Sjkh{
132553210Sbillf	char           *sp, *p;
132653210Sbillf	FILEDESC	*fp;
1327203926Suqs	int		in_file;
1328203926Suqs	unsigned char	ch;
13292490Sjkh
13302490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
13312490Sjkh		if (fp->child != NULL) {
13322490Sjkh			matches_in_list(fp->child);
13332490Sjkh			continue;
13342490Sjkh		}
13352490Sjkh		DPRINTF(1, (stderr, "searching in %s\n", fp->path));
13362490Sjkh		open_fp(fp);
13372490Sjkh		sp = Fortbuf;
13382490Sjkh		in_file = FALSE;
13392490Sjkh		while (fgets(sp, Fort_len, fp->inf) != NULL)
134051863Sdcs			if (fp->tbl.str_flags & STR_COMMENTS
134151863Sdcs			    && sp[0] == fp->tbl.str_delim
134251863Sdcs			    && sp[1] == fp->tbl.str_delim)
134351863Sdcs				continue;
134451863Sdcs			else if (!STR_ENDSTRING(sp, fp->tbl))
13452490Sjkh				sp += strlen(sp);
13462490Sjkh			else {
13472490Sjkh				*sp = '\0';
134815944Sache				if (fp->tbl.str_flags & STR_ROTATED)
134915944Sache					for (p = Fortbuf; (ch = *p) != '\0'; ++p) {
135015944Sache						if (isascii(ch)) {
135115944Sache							if (isupper(ch))
135215944Sache								*p = 'A' + (ch - 'A' + 13) % 26;
135315944Sache							else if (islower(ch))
135415944Sache								*p = 'a' + (ch - 'a' + 13) % 26;
135515944Sache						}
135615944Sache					}
1357130851Sphk				if (regexec(&Re_pat, Fortbuf, 0, NULL, 0) != REG_NOMATCH) {
13582490Sjkh					printf("%c%c", fp->tbl.str_delim,
13592490Sjkh					    fp->tbl.str_delim);
13602490Sjkh					if (!in_file) {
13612490Sjkh						printf(" (%s)", fp->name);
13622490Sjkh						Found_one = TRUE;
13632490Sjkh						in_file = TRUE;
13642490Sjkh					}
13652490Sjkh					putchar('\n');
13662490Sjkh					(void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
13672490Sjkh				}
13682490Sjkh				sp = Fortbuf;
13692490Sjkh			}
13702490Sjkh	}
13712490Sjkh}
13722490Sjkh
13732490Sjkhvoid
1374203922Suqsusage(void)
13752490Sjkh{
13762490Sjkh	(void) fprintf(stderr, "fortune [-a");
13772490Sjkh#ifdef	DEBUG
13782490Sjkh	(void) fprintf(stderr, "D");
13792490Sjkh#endif	/* DEBUG */
1380141581Sru	(void) fprintf(stderr, "efilosw]");
13812490Sjkh	(void) fprintf(stderr, " [-m pattern]");
1382141581Sru	(void) fprintf(stderr, " [[N%%] file/directory/all]\n");
13832490Sjkh	exit(1);
13842490Sjkh}
1385173396Sedwin
1386173396Sedwin/*
1387173396Sedwin * getpath
1388173396Sedwin * 	Set up file search patch from environment var FORTUNE_PATH;
1389173396Sedwin *	if not set, use the compiled in FORTDIR.
1390173396Sedwin */
1391173396Sedwin
1392173396Sedwinvoid
1393173396Sedwingetpath(void)
1394173396Sedwin{
1395173401Sedwin	int	nstr, foundenv;
1396173401Sedwin	char	*pch, **ppch, *str, *path;
1397173396Sedwin
1398173401Sedwin	foundenv = 1;
1399173396Sedwin	Fortune_path = getenv("FORTUNE_PATH");
1400173401Sedwin	if (Fortune_path == NULL) {
1401173401Sedwin		Fortune_path = FORTDIR;
1402173401Sedwin		foundenv = 0;
1403173401Sedwin	}
1404173396Sedwin	path = strdup(Fortune_path);
1405173396Sedwin
1406173396Sedwin	for (nstr = 2, pch = path; *pch != '\0'; pch++) {
1407173396Sedwin		if (*pch == ':')
1408173396Sedwin			nstr++;
1409173396Sedwin	}
1410173396Sedwin
1411173396Sedwin	ppch = Fortune_path_arr = (char **)calloc(nstr, sizeof(char *));
1412173396Sedwin
1413173396Sedwin	nstr = 0;
1414173396Sedwin	str = strtok(path, ":");
1415173396Sedwin	while (str) {
1416173396Sedwin		if (is_dir(str)) {
1417173396Sedwin			nstr++;
1418173396Sedwin			*ppch++ = str;
1419173396Sedwin		}
1420173396Sedwin		str = strtok(NULL, ":");
1421173396Sedwin	}
1422173401Sedwin
1423173396Sedwin	if (nstr == 0) {
1424173401Sedwin		if (foundenv == 1) {
1425173401Sedwin			fprintf(stderr,
1426173401Sedwin			    "fortune: FORTUNE_PATH: None of the specified "
1427173401Sedwin			    "directories found.\n");
1428173401Sedwin			exit(1);
1429173401Sedwin		}
1430173396Sedwin		free(path);
1431203922Suqs		Fortune_path_arr[0] = strdup(FORTDIR);
1432173396Sedwin	}
1433173396Sedwin}
1434