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>
56242576Seadler#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
66242576Seadler#define	TRUE	true
67242576Seadler#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
100227101Sedstatic bool	Found_one;		/* did we find a match? */
101227101Sedstatic bool	Find_files = FALSE;	/* just find a list of proper fortune files */
102227101Sedstatic bool	Fortunes_only = FALSE;	/* check only "fortunes" files */
103227101Sedstatic bool	Wait = FALSE;		/* wait desired after fortune */
104227101Sedstatic bool	Short_only = FALSE;	/* short fortune desired */
105227101Sedstatic bool	Long_only = FALSE;	/* long fortune desired */
106227101Sedstatic bool	Offend = FALSE;		/* offensive fortunes only */
107227101Sedstatic bool	All_forts = FALSE;	/* any fortune allowed */
108227101Sedstatic bool	Equal_probs = FALSE;	/* scatter un-allocted prob equally */
109227101Sedstatic bool	Match = FALSE;		/* dump fortunes matching a pattern */
110242577Seadlerstatic bool	WriteToDisk = false;	/* use files on disk to save state */
1112490Sjkh#ifdef DEBUG
112243036Sdimstatic int	Debug = 0;		/* print debug messages */
1132490Sjkh#endif
1142490Sjkh
115227101Sedstatic char	*Fortbuf = NULL;	/* fortune buffer for -m */
1162490Sjkh
117227101Sedstatic int	Fort_len = 0;
1182490Sjkh
119227101Sedstatic off_t	Seekpts[2];		/* seek pointers to fortunes */
1202490Sjkh
121227101Sedstatic FILEDESC	*File_list = NULL,	/* Head of file list */
1222490Sjkh		*File_tail = NULL;	/* Tail of file list */
123227101Sedstatic FILEDESC	*Fortfile;		/* Fortune file to use */
1242490Sjkh
125227101Sedstatic STRFILE	Noprob_tbl;		/* sum of data for all no prob files */
1262490Sjkh
127227101Sedstatic const char *Fortune_path;
128227101Sedstatic char	**Fortune_path_arr;
129173396Sedwin
130227101Sedstatic int	 add_dir(FILEDESC *);
131227101Sedstatic int	 add_file(int, const char *, const char *, FILEDESC **,
132227101Sed		     FILEDESC **, FILEDESC *);
133227101Sedstatic void	 all_forts(FILEDESC *, char *);
134227101Sedstatic char	*copy(const char *, u_int);
135227101Sedstatic void	 display(FILEDESC *);
136227101Sedstatic void	 do_free(void *);
137227101Sedstatic void	*do_malloc(u_int);
138227101Sedstatic int	 form_file_list(char **, int);
139227101Sedstatic int	 fortlen(void);
140227101Sedstatic void	 get_fort(void);
141227101Sedstatic void	 get_pos(FILEDESC *);
142227101Sedstatic void	 get_tbl(FILEDESC *);
143227101Sedstatic void	 getargs(int, char *[]);
144227101Sedstatic void	 getpath(void);
145227101Sedstatic void	 init_prob(void);
146227101Sedstatic int	 is_dir(const char *);
147227101Sedstatic int	 is_fortfile(const char *, char **, char **, int);
148227101Sedstatic int	 is_off_name(const char *);
149227101Sedstatic int	 max(int, int);
150227101Sedstatic FILEDESC *new_fp(void);
151227101Sedstatic char	*off_name(const char *);
152227101Sedstatic void	 open_dat(FILEDESC *);
153227101Sedstatic void	 open_fp(FILEDESC *);
154227101Sedstatic FILEDESC *pick_child(FILEDESC *);
155227101Sedstatic void	 print_file_list(void);
156227101Sedstatic void	 print_list(FILEDESC *, int);
157227101Sedstatic void	 sum_noprobs(FILEDESC *);
158227101Sedstatic void	 sum_tbl(STRFILE *, STRFILE *);
159227101Sedstatic void	 usage(void);
160227101Sedstatic void	 zero_tbl(STRFILE *);
1612490Sjkh
162227101Sedstatic char	*conv_pat(char *);
163227101Sedstatic int	 find_matches(void);
164227101Sedstatic void	 matches_in_list(FILEDESC *);
165227101Sedstatic int	 maxlen_in_list(FILEDESC *);
1662490Sjkh
167130851Sphkstatic regex_t Re_pat;
1682490Sjkh
1692490Sjkhint
170203922Suqsmain(int argc, char *argv[])
1712490Sjkh{
1722490Sjkh	int	fd;
1732490Sjkh
174242577Seadler	if (getenv("FORTUNE_SAVESTATE") != NULL)
175242577Seadler		WriteToDisk = true;
176242577Seadler
17717537Sache	(void) setlocale(LC_ALL, "");
17815944Sache
179173396Sedwin	getpath();
180203922Suqs	getargs(argc, argv);
1812490Sjkh
1822490Sjkh	if (Match)
1832490Sjkh		exit(find_matches() != 0);
1842490Sjkh
1852490Sjkh	init_prob();
1862490Sjkh	do {
1872490Sjkh		get_fort();
1882490Sjkh	} while ((Short_only && fortlen() > SLEN) ||
1892490Sjkh		 (Long_only && fortlen() <= SLEN));
1902490Sjkh
1912490Sjkh	display(Fortfile);
1922490Sjkh
193242577Seadler	if (WriteToDisk) {
194242577Seadler		if ((fd = creat(Fortfile->posfile, 0666)) < 0) {
195242577Seadler			perror(Fortfile->posfile);
196242577Seadler			exit(1);
197242577Seadler		}
198242577Seadler		/*
199242577Seadler		 * if we can, we exclusive lock, but since it isn't very
200242577Seadler		 * important, we just punt if we don't have easy locking
201242577Seadler		 * available.
202242577Seadler		 */
203242577Seadler		flock(fd, LOCK_EX);
204242577Seadler		write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
205242577Seadler		if (!Fortfile->was_pos_file)
206242577Seadler		chmod(Fortfile->path, 0666);
207242577Seadler		flock(fd, LOCK_UN);
2082490Sjkh	}
2092490Sjkh	if (Wait) {
2102490Sjkh		if (Fort_len == 0)
2112490Sjkh			(void) fortlen();
2122490Sjkh		sleep((unsigned int) max(Fort_len / CPERS, MINW));
2132490Sjkh	}
214203926Suqs
215204178Suqs	exit(0);
2162490Sjkh}
2172490Sjkh
218227101Sedstatic void
219203922Suqsdisplay(FILEDESC *fp)
2202490Sjkh{
22153210Sbillf	char   *p;
22253210Sbillf	unsigned char ch;
2232490Sjkh	char	line[BUFSIZ];
2242490Sjkh
2252490Sjkh	open_fp(fp);
226203926Suqs	fseeko(fp->inf, Seekpts[0], SEEK_SET);
2272490Sjkh	for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
2282490Sjkh	    !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
2292490Sjkh		if (fp->tbl.str_flags & STR_ROTATED)
23015944Sache			for (p = line; (ch = *p) != '\0'; ++p) {
23115944Sache				if (isascii(ch)) {
23215944Sache					if (isupper(ch))
23315944Sache						*p = 'A' + (ch - 'A' + 13) % 26;
23415944Sache					else if (islower(ch))
23515944Sache						*p = 'a' + (ch - 'a' + 13) % 26;
23615944Sache				}
23715944Sache			}
23851863Sdcs		if (fp->tbl.str_flags & STR_COMMENTS
23951863Sdcs		    && line[0] == fp->tbl.str_delim
24051863Sdcs		    && line[1] == fp->tbl.str_delim)
24151863Sdcs			continue;
2422490Sjkh		fputs(line, stdout);
2432490Sjkh	}
2442490Sjkh	(void) fflush(stdout);
2452490Sjkh}
2462490Sjkh
2472490Sjkh/*
2482490Sjkh * fortlen:
2492490Sjkh *	Return the length of the fortune.
2502490Sjkh */
251227101Sedstatic int
252203922Suqsfortlen(void)
2532490Sjkh{
25453210Sbillf	int	nchar;
255173401Sedwin	char	line[BUFSIZ];
2562490Sjkh
2572490Sjkh	if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
258123905Sceri		nchar = (int)(Seekpts[1] - Seekpts[0]);
2592490Sjkh	else {
2602490Sjkh		open_fp(Fortfile);
261203926Suqs		fseeko(Fortfile->inf, Seekpts[0], SEEK_SET);
2622490Sjkh		nchar = 0;
2632490Sjkh		while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
2642490Sjkh		       !STR_ENDSTRING(line, Fortfile->tbl))
2652490Sjkh			nchar += strlen(line);
2662490Sjkh	}
2672490Sjkh	Fort_len = nchar;
268203926Suqs
269203926Suqs	return (nchar);
2702490Sjkh}
2712490Sjkh
2722490Sjkh/*
2732490Sjkh *	This routine evaluates the arguments on the command line
2742490Sjkh */
275227101Sedstatic void
276203922Suqsgetargs(int argc, char *argv[])
2772490Sjkh{
27853210Sbillf	int	ignore_case;
27953210Sbillf	char	*pat;
2802490Sjkh	int ch;
2812490Sjkh
2822490Sjkh	ignore_case = FALSE;
2832490Sjkh	pat = NULL;
2842490Sjkh
285203926Suqs#ifdef DEBUG
28647440Simp	while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1)
2872490Sjkh#else
28847440Simp	while ((ch = getopt(argc, argv, "aefilm:osw")) != -1)
2892490Sjkh#endif /* DEBUG */
2902490Sjkh		switch(ch) {
2912490Sjkh		case 'a':		/* any fortune */
2922490Sjkh			All_forts++;
2932490Sjkh			break;
294203926Suqs#ifdef DEBUG
2952490Sjkh		case 'D':
2962490Sjkh			Debug++;
2972490Sjkh			break;
298203926Suqs#endif /* DEBUG */
2992490Sjkh		case 'e':
3002490Sjkh			Equal_probs++;	/* scatter un-allocted prob equally */
3012490Sjkh			break;
3022490Sjkh		case 'f':		/* find fortune files */
3032490Sjkh			Find_files++;
3042490Sjkh			break;
3052490Sjkh		case 'l':		/* long ones only */
3062490Sjkh			Long_only++;
3072490Sjkh			Short_only = FALSE;
3082490Sjkh			break;
3092490Sjkh		case 'o':		/* offensive ones only */
3102490Sjkh			Offend++;
3112490Sjkh			break;
3122490Sjkh		case 's':		/* short ones only */
3132490Sjkh			Short_only++;
3142490Sjkh			Long_only = FALSE;
3152490Sjkh			break;
3162490Sjkh		case 'w':		/* give time to read */
3172490Sjkh			Wait++;
3182490Sjkh			break;
3192490Sjkh		case 'm':			/* dump out the fortunes */
3202490Sjkh			Match++;
3212490Sjkh			pat = optarg;
3222490Sjkh			break;
3232490Sjkh		case 'i':			/* case-insensitive match */
3242490Sjkh			ignore_case++;
3252490Sjkh			break;
3262490Sjkh		case '?':
3272490Sjkh		default:
3282490Sjkh			usage();
3292490Sjkh		}
3302490Sjkh	argc -= optind;
3312490Sjkh	argv += optind;
3322490Sjkh
3332490Sjkh	if (!form_file_list(argv, argc))
3342490Sjkh		exit(1);	/* errors printed through form_file_list() */
3352490Sjkh	if (Find_files) {
3362490Sjkh		print_file_list();
3372490Sjkh		exit(0);
3382490Sjkh	}
33915957Sache#ifdef DEBUG
34015957Sache	else if (Debug >= 1)
34115957Sache		print_file_list();
34215957Sache#endif /* DEBUG */
3432490Sjkh
3442490Sjkh	if (pat != NULL) {
345130851Sphk		int error;
346130851Sphk
3472490Sjkh		if (ignore_case)
3482490Sjkh			pat = conv_pat(pat);
349130851Sphk		error = regcomp(&Re_pat, pat, REG_BASIC);
350130851Sphk		if (error) {
351130851Sphk			fprintf(stderr, "regcomp(%s) fails\n", pat);
352203926Suqs			exit(1);
3532490Sjkh		}
3542490Sjkh	}
3552490Sjkh}
3562490Sjkh
3572490Sjkh/*
3582490Sjkh * form_file_list:
3592490Sjkh *	Form the file list from the file specifications.
3602490Sjkh */
361227101Sedstatic int
362203922Suqsform_file_list(char **files, int file_cnt)
3632490Sjkh{
36453210Sbillf	int	i, percent;
36553210Sbillf	char	*sp;
366173396Sedwin	char	**pstr;
3672490Sjkh
36849040Sbillf	if (file_cnt == 0) {
36915957Sache		if (Find_files) {
37015957Sache			Fortunes_only = TRUE;
371173396Sedwin			pstr = Fortune_path_arr;
372173396Sedwin			i = 0;
373173396Sedwin			while (*pstr) {
374203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
375173396Sedwin					      &File_list, &File_tail, NULL);
376173396Sedwin			}
37715957Sache			Fortunes_only = FALSE;
378173396Sedwin			if (!i) {
379173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
380173401Sedwin				    Fortune_path);
381173396Sedwin			}
382203926Suqs			return (i != 0);
383173396Sedwin		} else {
384173396Sedwin			pstr = Fortune_path_arr;
385173396Sedwin			i = 0;
386173396Sedwin			while (*pstr) {
387173396Sedwin				i += add_file(NO_PROB, "fortunes", *pstr++,
388173396Sedwin					      &File_list, &File_tail, NULL);
389173396Sedwin			}
390173396Sedwin			if (!i) {
391173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
392173401Sedwin				    Fortune_path);
393173396Sedwin			}
394203926Suqs			return (i != 0);
395173396Sedwin		}
39649040Sbillf	}
3972490Sjkh	for (i = 0; i < file_cnt; i++) {
3982490Sjkh		percent = NO_PROB;
39915944Sache		if (!isdigit((unsigned char)files[i][0]))
4002490Sjkh			sp = files[i];
4012490Sjkh		else {
4022490Sjkh			percent = 0;
40315944Sache			for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
4042490Sjkh				percent = percent * 10 + *sp - '0';
4052490Sjkh			if (percent > 100) {
4062490Sjkh				fprintf(stderr, "percentages must be <= 100\n");
407203926Suqs				return (FALSE);
4082490Sjkh			}
4092490Sjkh			if (*sp == '.') {
4102490Sjkh				fprintf(stderr, "percentages must be integers\n");
411203926Suqs				return (FALSE);
4122490Sjkh			}
4132490Sjkh			/*
4142490Sjkh			 * If the number isn't followed by a '%', then
4152490Sjkh			 * it was not a percentage, just the first part
4162490Sjkh			 * of a file name which starts with digits.
4172490Sjkh			 */
4182490Sjkh			if (*sp != '%') {
4192490Sjkh				percent = NO_PROB;
4202490Sjkh				sp = files[i];
4212490Sjkh			}
4222490Sjkh			else if (*++sp == '\0') {
4232490Sjkh				if (++i >= file_cnt) {
4242490Sjkh					fprintf(stderr, "percentages must precede files\n");
425203926Suqs					return (FALSE);
4262490Sjkh				}
4272490Sjkh				sp = files[i];
4282490Sjkh			}
4292490Sjkh		}
430173396Sedwin		if (strcmp(sp, "all") == 0) {
431173396Sedwin			pstr = Fortune_path_arr;
432173396Sedwin			i = 0;
433173396Sedwin			while (*pstr) {
434203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
435173396Sedwin					      &File_list, &File_tail, NULL);
436173396Sedwin			}
437173396Sedwin			if (!i) {
438173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
439173401Sedwin				    Fortune_path);
440203926Suqs				return (FALSE);
441173396Sedwin			}
442203926Suqs		} else if (!add_file(percent, sp, NULL, &File_list,
443173396Sedwin				     &File_tail, NULL)) {
444203926Suqs 			return (FALSE);
445173396Sedwin		}
4462490Sjkh	}
447203926Suqs
448203926Suqs	return (TRUE);
4492490Sjkh}
4502490Sjkh
4512490Sjkh/*
4522490Sjkh * add_file:
4532490Sjkh *	Add a file to the file list.
4542490Sjkh */
455227101Sedstatic int
456203922Suqsadd_file(int percent, const char *file, const char *dir, FILEDESC **head,
457203922Suqs    FILEDESC **tail, FILEDESC *parent)
4582490Sjkh{
45953210Sbillf	FILEDESC	*fp;
46053210Sbillf	int		fd;
461203922Suqs	const char 	*path;
462203922Suqs	char		*tpath, *offensive;
46353210Sbillf	bool		was_malloc;
46453210Sbillf	bool		isdir;
4652490Sjkh
4662490Sjkh	if (dir == NULL) {
4672490Sjkh		path = file;
468203922Suqs		tpath = NULL;
4692490Sjkh		was_malloc = FALSE;
4702490Sjkh	}
4712490Sjkh	else {
472203922Suqs		tpath = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2));
473203922Suqs		strcat(strcat(strcpy(tpath, dir), "/"), file);
474203922Suqs		path = tpath;
4752490Sjkh		was_malloc = TRUE;
4762490Sjkh	}
4772490Sjkh	if ((isdir = is_dir(path)) && parent != NULL) {
4782490Sjkh		if (was_malloc)
479203922Suqs			free(tpath);
480203926Suqs		return (FALSE);	/* don't recurse */
4812490Sjkh	}
4822490Sjkh	offensive = NULL;
4832490Sjkh	if (!isdir && parent == NULL && (All_forts || Offend) &&
4842490Sjkh	    !is_off_name(path)) {
4852490Sjkh		offensive = off_name(path);
4862490Sjkh		if (Offend) {
4872490Sjkh			if (was_malloc)
488203922Suqs				free(tpath);
4892490Sjkh			path = offensive;
49015957Sache			offensive = NULL;
49115957Sache			was_malloc = TRUE;
49215957Sache			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
4932490Sjkh			file = off_name(file);
4942490Sjkh		}
4952490Sjkh	}
4962490Sjkh
4972490Sjkh	DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
4982490Sjkhover:
499203926Suqs	if ((fd = open(path, O_RDONLY)) < 0) {
5002490Sjkh		/*
5012490Sjkh		 * This is a sneak.  If the user said -a, and if the
5022490Sjkh		 * file we're given isn't a file, we check to see if
5032490Sjkh		 * there is a -o version.  If there is, we treat it as
5042490Sjkh		 * if *that* were the file given.  We only do this for
5052490Sjkh		 * individual files -- if we're scanning a directory,
5062490Sjkh		 * we'll pick up the -o file anyway.
5072490Sjkh		 */
5082490Sjkh		if (All_forts && offensive != NULL) {
5092490Sjkh			if (was_malloc)
510203922Suqs				free(tpath);
51115957Sache			path = offensive;
5122490Sjkh			offensive = NULL;
5132490Sjkh			was_malloc = TRUE;
5142490Sjkh			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
5152490Sjkh			file = off_name(file);
5162490Sjkh			goto over;
5172490Sjkh		}
518173396Sedwin		if (dir == NULL && file[0] != '/') {
519173396Sedwin			int i = 0;
520173396Sedwin			char **pstr = Fortune_path_arr;
521173396Sedwin
522173396Sedwin			while (*pstr) {
523203926Suqs				i += add_file(percent, file, *pstr++,
524173396Sedwin					      head, tail, parent);
525173396Sedwin			}
526173396Sedwin			if (!i) {
527173401Sedwin				fprintf(stderr, "No '%s' found in %s.\n",
528173401Sedwin				    file, Fortune_path);
529173396Sedwin			}
530203926Suqs			return (i != 0);
531173396Sedwin		}
532173396Sedwin		/*
5332490Sjkh		if (parent == NULL)
5342490Sjkh			perror(path);
535173396Sedwin		*/
5362490Sjkh		if (was_malloc)
537203922Suqs			free(tpath);
538203926Suqs		return (FALSE);
5392490Sjkh	}
5402490Sjkh
5412490Sjkh	DPRINTF(2, (stderr, "path = \"%s\"\n", path));
5422490Sjkh
5432490Sjkh	fp = new_fp();
5442490Sjkh	fp->fd = fd;
5452490Sjkh	fp->percent = percent;
5462490Sjkh	fp->name = file;
5472490Sjkh	fp->path = path;
5482490Sjkh	fp->parent = parent;
5492490Sjkh
5502490Sjkh	if ((isdir && !add_dir(fp)) ||
5512490Sjkh	    (!isdir &&
5522490Sjkh	     !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
5532490Sjkh	{
5542490Sjkh		if (parent == NULL)
5552490Sjkh			fprintf(stderr,
5562490Sjkh				"fortune:%s not a fortune file or directory\n",
5572490Sjkh				path);
5582490Sjkh		if (was_malloc)
559203922Suqs			free(tpath);
5602490Sjkh		do_free(fp->datfile);
5612490Sjkh		do_free(fp->posfile);
562203926Suqs		free(fp);
5632490Sjkh		do_free(offensive);
564203926Suqs		return (FALSE);
5652490Sjkh	}
5662490Sjkh	/*
5672490Sjkh	 * If the user said -a, we need to make this node a pointer to
5682490Sjkh	 * both files, if there are two.  We don't need to do this if
5692490Sjkh	 * we are scanning a directory, since the scan will pick up the
5702490Sjkh	 * -o file anyway.
5712490Sjkh	 */
5722490Sjkh	if (All_forts && parent == NULL && !is_off_name(path))
5732490Sjkh		all_forts(fp, offensive);
5742490Sjkh	if (*head == NULL)
5752490Sjkh		*head = *tail = fp;
5762490Sjkh	else if (fp->percent == NO_PROB) {
5772490Sjkh		(*tail)->next = fp;
5782490Sjkh		fp->prev = *tail;
5792490Sjkh		*tail = fp;
5802490Sjkh	}
5812490Sjkh	else {
5822490Sjkh		(*head)->prev = fp;
5832490Sjkh		fp->next = *head;
5842490Sjkh		*head = fp;
5852490Sjkh	}
586242577Seadler	if (WriteToDisk)
587242577Seadler		fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
5882490Sjkh
589203926Suqs	return (TRUE);
5902490Sjkh}
5912490Sjkh
5922490Sjkh/*
5932490Sjkh * new_fp:
5942490Sjkh *	Return a pointer to an initialized new FILEDESC.
5952490Sjkh */
596227101Sedstatic FILEDESC *
597203922Suqsnew_fp(void)
5982490Sjkh{
59953210Sbillf	FILEDESC	*fp;
6002490Sjkh
601203926Suqs	fp = do_malloc(sizeof(*fp));
6022490Sjkh	fp->datfd = -1;
6032490Sjkh	fp->pos = POS_UNKNOWN;
6042490Sjkh	fp->inf = NULL;
6052490Sjkh	fp->fd = -1;
6062490Sjkh	fp->percent = NO_PROB;
6072490Sjkh	fp->read_tbl = FALSE;
6082490Sjkh	fp->next = NULL;
6092490Sjkh	fp->prev = NULL;
6102490Sjkh	fp->child = NULL;
6112490Sjkh	fp->parent = NULL;
6122490Sjkh	fp->datfile = NULL;
6132490Sjkh	fp->posfile = NULL;
614203926Suqs
615203926Suqs	return (fp);
6162490Sjkh}
6172490Sjkh
6182490Sjkh/*
6192490Sjkh * off_name:
6202490Sjkh *	Return a pointer to the offensive version of a file of this name.
6212490Sjkh */
622227101Sedstatic char *
623203922Suqsoff_name(const char *file)
6242490Sjkh{
6252490Sjkh	char	*new;
6262490Sjkh
6272490Sjkh	new = copy(file, (unsigned int) (strlen(file) + 2));
628203926Suqs
629203926Suqs	return (strcat(new, "-o"));
6302490Sjkh}
6312490Sjkh
6322490Sjkh/*
6332490Sjkh * is_off_name:
6342490Sjkh *	Is the file an offensive-style name?
6352490Sjkh */
636227101Sedstatic int
637203922Suqsis_off_name(const char *file)
6382490Sjkh{
6392490Sjkh	int	len;
6402490Sjkh
6412490Sjkh	len = strlen(file);
642203926Suqs
6432490Sjkh	return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
6442490Sjkh}
6452490Sjkh
6462490Sjkh/*
6472490Sjkh * all_forts:
6482490Sjkh *	Modify a FILEDESC element to be the parent of two children if
6492490Sjkh *	there are two children to be a parent of.
6502490Sjkh */
651227101Sedstatic void
652203922Suqsall_forts(FILEDESC *fp, char *offensive)
6532490Sjkh{
65453210Sbillf	char		*sp;
65553210Sbillf	FILEDESC	*scene, *obscene;
65653210Sbillf	int		fd;
657203926Suqs	char		*datfile, *posfile;
6582490Sjkh
6592490Sjkh	if (fp->child != NULL)	/* this is a directory, not a file */
6602490Sjkh		return;
6612490Sjkh	if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
6622490Sjkh		return;
663203926Suqs	if ((fd = open(offensive, O_RDONLY)) < 0)
6642490Sjkh		return;
6652490Sjkh	DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
6662490Sjkh	scene = new_fp();
6672490Sjkh	obscene = new_fp();
6682490Sjkh	*scene = *fp;
6692490Sjkh
6702490Sjkh	fp->num_children = 2;
6712490Sjkh	fp->child = scene;
6722490Sjkh	scene->next = obscene;
6732490Sjkh	obscene->next = NULL;
6742490Sjkh	scene->child = obscene->child = NULL;
6752490Sjkh	scene->parent = obscene->parent = fp;
6762490Sjkh
6772490Sjkh	fp->fd = -1;
6782490Sjkh	scene->percent = obscene->percent = NO_PROB;
6792490Sjkh
6802490Sjkh	obscene->fd = fd;
6812490Sjkh	obscene->inf = NULL;
6822490Sjkh	obscene->path = offensive;
683229403Sed	if ((sp = strrchr(offensive, '/')) == NULL)
6842490Sjkh		obscene->name = offensive;
6852490Sjkh	else
6862490Sjkh		obscene->name = ++sp;
6872490Sjkh	obscene->datfile = datfile;
6882490Sjkh	obscene->posfile = posfile;
689242577Seadler	obscene->read_tbl = false;
690242577Seadler	if (WriteToDisk)
691242577Seadler		obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
6922490Sjkh}
6932490Sjkh
6942490Sjkh/*
6952490Sjkh * add_dir:
6962490Sjkh *	Add the contents of an entire directory.
6972490Sjkh */
698227101Sedstatic int
699203922Suqsadd_dir(FILEDESC *fp)
7002490Sjkh{
70153210Sbillf	DIR		*dir;
70253210Sbillf	struct dirent	*dirent;
703203926Suqs	FILEDESC	*tailp;
704203926Suqs	char		*name;
7052490Sjkh
7062490Sjkh	(void) close(fp->fd);
7072490Sjkh	fp->fd = -1;
7082490Sjkh	if ((dir = opendir(fp->path)) == NULL) {
7092490Sjkh		perror(fp->path);
710203926Suqs		return (FALSE);
7112490Sjkh	}
7122490Sjkh	tailp = NULL;
7132490Sjkh	DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
7142490Sjkh	fp->num_children = 0;
7152490Sjkh	while ((dirent = readdir(dir)) != NULL) {
7162490Sjkh		if (dirent->d_namlen == 0)
7172490Sjkh			continue;
7182490Sjkh		name = copy(dirent->d_name, dirent->d_namlen);
7192490Sjkh		if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
7202490Sjkh			fp->num_children++;
7212490Sjkh		else
7222490Sjkh			free(name);
7232490Sjkh	}
7242490Sjkh	if (fp->num_children == 0) {
7252490Sjkh		(void) fprintf(stderr,
7262490Sjkh		    "fortune: %s: No fortune files in directory.\n", fp->path);
727203926Suqs		return (FALSE);
7282490Sjkh	}
729203926Suqs
730203926Suqs	return (TRUE);
7312490Sjkh}
7322490Sjkh
7332490Sjkh/*
7342490Sjkh * is_dir:
7352490Sjkh *	Return TRUE if the file is a directory, FALSE otherwise.
7362490Sjkh */
737227101Sedstatic int
738203922Suqsis_dir(const char *file)
7392490Sjkh{
740203926Suqs	struct stat	sbuf;
7412490Sjkh
7422490Sjkh	if (stat(file, &sbuf) < 0)
743203926Suqs		return (FALSE);
744203926Suqs
7452490Sjkh	return (sbuf.st_mode & S_IFDIR);
7462490Sjkh}
7472490Sjkh
7482490Sjkh/*
7492490Sjkh * is_fortfile:
7502490Sjkh *	Return TRUE if the file is a fortune database file.  We try and
7512490Sjkh *	exclude files without reading them if possible to avoid
7522490Sjkh *	overhead.  Files which start with ".", or which have "illegal"
7532490Sjkh *	suffixes, as contained in suflist[], are ruled out.
7542490Sjkh */
7552490Sjkh/* ARGSUSED */
756227101Sedstatic int
757203922Suqsis_fortfile(const char *file, char **datp, char **posp, int check_for_offend)
7582490Sjkh{
75953210Sbillf	int	i;
760203922Suqs	const char	*sp;
76153210Sbillf	char	*datfile;
762203922Suqs	static const char *suflist[] = {
763203922Suqs		/* list of "illegal" suffixes" */
764203922Suqs		"dat", "pos", "c", "h", "p", "i", "f",
765203922Suqs		"pas", "ftn", "ins.c", "ins,pas",
766203922Suqs		"ins.ftn", "sml",
767203922Suqs		NULL
768203922Suqs	};
7692490Sjkh
7702490Sjkh	DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
7712490Sjkh
7722490Sjkh	/*
7732490Sjkh	 * Preclude any -o files for offendable people, and any non -o
7742490Sjkh	 * files for completely offensive people.
7752490Sjkh	 */
7762490Sjkh	if (check_for_offend && !All_forts) {
7772490Sjkh		i = strlen(file);
77815957Sache		if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o')) {
77915957Sache			DPRINTF(2, (stderr, "FALSE (offending file)\n"));
780203926Suqs			return (FALSE);
78115957Sache		}
7822490Sjkh	}
7832490Sjkh
784229403Sed	if ((sp = strrchr(file, '/')) == NULL)
7852490Sjkh		sp = file;
7862490Sjkh	else
7872490Sjkh		sp++;
7882490Sjkh	if (*sp == '.') {
7892490Sjkh		DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
790203926Suqs		return (FALSE);
7912490Sjkh	}
79215957Sache	if (Fortunes_only && strncmp(sp, "fortunes", 8) != 0) {
79315957Sache		DPRINTF(2, (stderr, "FALSE (check fortunes only)\n"));
794203926Suqs		return (FALSE);
79515957Sache	}
796229403Sed	if ((sp = strrchr(sp, '.')) != NULL) {
7972490Sjkh		sp++;
7982490Sjkh		for (i = 0; suflist[i] != NULL; i++)
7992490Sjkh			if (strcmp(sp, suflist[i]) == 0) {
8002490Sjkh				DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
801203926Suqs				return (FALSE);
8022490Sjkh			}
8032490Sjkh	}
8042490Sjkh
8052490Sjkh	datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
8062490Sjkh	strcat(datfile, ".dat");
8072490Sjkh	if (access(datfile, R_OK) < 0) {
80844599Sdcs		DPRINTF(2, (stderr, "FALSE (no readable \".dat\" file)\n"));
80944599Sdcs#ifdef DEBUG
81044599Sdcs		if (Debug < 2)
81144599Sdcs			DPRINTF(0, (stderr, "Warning: file \"%s\" unreadable\n", datfile));
81244599Sdcs#endif
8132490Sjkh		free(datfile);
814203926Suqs		return (FALSE);
8152490Sjkh	}
8162490Sjkh	if (datp != NULL)
8172490Sjkh		*datp = datfile;
8182490Sjkh	else
8192490Sjkh		free(datfile);
82015944Sache	if (posp != NULL) {
821242577Seadler		if (WriteToDisk) {
822242577Seadler			*posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
823242577Seadler			strcat(*posp, ".pos");
824242577Seadler		}
825242577Seadler		else {
826242577Seadler			*posp = NULL;
827242577Seadler		}
8282490Sjkh	}
8292490Sjkh	DPRINTF(2, (stderr, "TRUE\n"));
830203926Suqs
831203926Suqs	return (TRUE);
8322490Sjkh}
8332490Sjkh
8342490Sjkh/*
8352490Sjkh * copy:
8362490Sjkh *	Return a malloc()'ed copy of the string
8372490Sjkh */
838227101Sedstatic char *
839203922Suqscopy(const char *str, unsigned int len)
8402490Sjkh{
841203926Suqs	char *new, *sp;
8422490Sjkh
8432490Sjkh	new = do_malloc(len + 1);
8442490Sjkh	sp = new;
8452490Sjkh	do {
8462490Sjkh		*sp++ = *str;
8472490Sjkh	} while (*str++);
848203926Suqs
849203926Suqs	return (new);
8502490Sjkh}
8512490Sjkh
8522490Sjkh/*
8532490Sjkh * do_malloc:
8542490Sjkh *	Do a malloc, checking for NULL return.
8552490Sjkh */
856227101Sedstatic void *
857203922Suqsdo_malloc(unsigned int size)
8582490Sjkh{
859203926Suqs	void *new;
8602490Sjkh
8612490Sjkh	if ((new = malloc(size)) == NULL) {
8622490Sjkh		(void) fprintf(stderr, "fortune: out of memory.\n");
8632490Sjkh		exit(1);
8642490Sjkh	}
865203926Suqs
866203926Suqs	return (new);
8672490Sjkh}
8682490Sjkh
8692490Sjkh/*
8702490Sjkh * do_free:
8712490Sjkh *	Free malloc'ed space, if any.
8722490Sjkh */
873227101Sedstatic void
874203922Suqsdo_free(void *ptr)
8752490Sjkh{
8762490Sjkh	if (ptr != NULL)
8772490Sjkh		free(ptr);
8782490Sjkh}
8792490Sjkh
8802490Sjkh/*
8812490Sjkh * init_prob:
8822490Sjkh *	Initialize the fortune probabilities.
8832490Sjkh */
884227101Sedstatic void
885203922Suqsinit_prob(void)
8862490Sjkh{
88753210Sbillf	FILEDESC       *fp, *last = NULL;
88853210Sbillf	int		percent, num_noprob, frac;
8892490Sjkh
8902490Sjkh	/*
8912490Sjkh	 * Distribute the residual probability (if any) across all
8922490Sjkh	 * files with unspecified probability (i.e., probability of 0)
8932490Sjkh	 * (if any).
8942490Sjkh	 */
8952490Sjkh
8962490Sjkh	percent = 0;
8972490Sjkh	num_noprob = 0;
8982490Sjkh	for (fp = File_tail; fp != NULL; fp = fp->prev)
8992490Sjkh		if (fp->percent == NO_PROB) {
9002490Sjkh			num_noprob++;
9012490Sjkh			if (Equal_probs)
9022490Sjkh				last = fp;
903203926Suqs		} else
9042490Sjkh			percent += fp->percent;
9052490Sjkh	DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
9062490Sjkh		    percent, num_noprob));
9072490Sjkh	if (percent > 100) {
9082490Sjkh		(void) fprintf(stderr,
90915957Sache		    "fortune: probabilities sum to %d%% > 100%%!\n", percent);
9102490Sjkh		exit(1);
911203926Suqs	} else if (percent < 100 && num_noprob == 0) {
9122490Sjkh		(void) fprintf(stderr,
91315957Sache		    "fortune: no place to put residual probability (%d%% < 100%%)\n",
9142490Sjkh		    percent);
9152490Sjkh		exit(1);
916203926Suqs	} else if (percent == 100 && num_noprob != 0) {
9172490Sjkh		(void) fprintf(stderr,
91815957Sache		    "fortune: no probability left to put in residual files (100%%)\n");
9192490Sjkh		exit(1);
9202490Sjkh	}
9212490Sjkh	percent = 100 - percent;
92249040Sbillf	if (Equal_probs) {
9232490Sjkh		if (num_noprob != 0) {
9242490Sjkh			if (num_noprob > 1) {
9252490Sjkh				frac = percent / num_noprob;
9262490Sjkh				DPRINTF(1, (stderr, ", frac = %d%%", frac));
927173396Sedwin				for (fp = File_tail; fp != last; fp = fp->prev)
9282490Sjkh					if (fp->percent == NO_PROB) {
9292490Sjkh						fp->percent = frac;
9302490Sjkh						percent -= frac;
9312490Sjkh					}
9322490Sjkh			}
9332490Sjkh			last->percent = percent;
9342490Sjkh			DPRINTF(1, (stderr, ", residual = %d%%", percent));
9352490Sjkh		}
936203922Suqs		else
937203926Suqs		DPRINTF(1, (stderr,
9382490Sjkh			    ", %d%% distributed over remaining fortunes\n",
9392490Sjkh			    percent));
9402490Sjkh	}
9412490Sjkh	DPRINTF(1, (stderr, "\n"));
9422490Sjkh
9432490Sjkh#ifdef DEBUG
9442490Sjkh	if (Debug >= 1)
9452490Sjkh		print_file_list();
9462490Sjkh#endif
9472490Sjkh}
9482490Sjkh
9492490Sjkh/*
9502490Sjkh * get_fort:
9512490Sjkh *	Get the fortune data file's seek pointer for the next fortune.
9522490Sjkh */
953227101Sedstatic void
954203922Suqsget_fort(void)
9552490Sjkh{
95653210Sbillf	FILEDESC	*fp;
95753210Sbillf	int		choice;
9582490Sjkh
9592490Sjkh	if (File_list->next == NULL || File_list->percent == NO_PROB)
9602490Sjkh		fp = File_list;
9612490Sjkh	else {
962181385Sache		choice = arc4random_uniform(100);
9632490Sjkh		DPRINTF(1, (stderr, "choice = %d\n", choice));
9642490Sjkh		for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
9652490Sjkh			if (choice < fp->percent)
9662490Sjkh				break;
9672490Sjkh			else {
9682490Sjkh				choice -= fp->percent;
9692490Sjkh				DPRINTF(1, (stderr,
9702490Sjkh					    "    skip \"%s\", %d%% (choice = %d)\n",
9712490Sjkh					    fp->name, fp->percent, choice));
9722490Sjkh			}
9732490Sjkh			DPRINTF(1, (stderr,
9742490Sjkh				    "using \"%s\", %d%% (choice = %d)\n",
9752490Sjkh				    fp->name, fp->percent, choice));
9762490Sjkh	}
9772490Sjkh	if (fp->percent != NO_PROB)
9782490Sjkh		get_tbl(fp);
9792490Sjkh	else {
9802490Sjkh		if (fp->next != NULL) {
9812490Sjkh			sum_noprobs(fp);
982181385Sache			choice = arc4random_uniform(Noprob_tbl.str_numstr);
983142022Sru			DPRINTF(1, (stderr, "choice = %d (of %u) \n", choice,
9842490Sjkh				    Noprob_tbl.str_numstr));
985203922Suqs			while ((unsigned int)choice >= fp->tbl.str_numstr) {
9862490Sjkh				choice -= fp->tbl.str_numstr;
9872490Sjkh				fp = fp->next;
9882490Sjkh				DPRINTF(1, (stderr,
989142022Sru					    "    skip \"%s\", %u (choice = %d)\n",
9902490Sjkh					    fp->name, fp->tbl.str_numstr,
9912490Sjkh					    choice));
9922490Sjkh			}
993142022Sru			DPRINTF(1, (stderr, "using \"%s\", %u\n", fp->name,
9942490Sjkh				    fp->tbl.str_numstr));
9952490Sjkh		}
9962490Sjkh		get_tbl(fp);
9972490Sjkh	}
9982490Sjkh	if (fp->child != NULL) {
9992490Sjkh		DPRINTF(1, (stderr, "picking child\n"));
10002490Sjkh		fp = pick_child(fp);
10012490Sjkh	}
10022490Sjkh	Fortfile = fp;
10032490Sjkh	get_pos(fp);
10042490Sjkh	open_dat(fp);
1005203926Suqs	lseek(fp->datfd,
1006203926Suqs	    (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), SEEK_SET);
10072490Sjkh	read(fp->datfd, Seekpts, sizeof Seekpts);
1008142022Sru	Seekpts[0] = be64toh(Seekpts[0]);
1009142022Sru	Seekpts[1] = be64toh(Seekpts[1]);
10102490Sjkh}
10112490Sjkh
10122490Sjkh/*
10132490Sjkh * pick_child
10142490Sjkh *	Pick a child from a chosen parent.
10152490Sjkh */
1016227101Sedstatic FILEDESC *
1017203922Suqspick_child(FILEDESC *parent)
10182490Sjkh{
101953210Sbillf	FILEDESC	*fp;
102053210Sbillf	int		choice;
10212490Sjkh
10222490Sjkh	if (Equal_probs) {
1023181385Sache		choice = arc4random_uniform(parent->num_children);
10242490Sjkh		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
10252490Sjkh			    choice, parent->num_children));
10262490Sjkh		for (fp = parent->child; choice--; fp = fp->next)
10272490Sjkh			continue;
10282490Sjkh		DPRINTF(1, (stderr, "    using %s\n", fp->name));
1029203926Suqs		return (fp);
10302490Sjkh	}
10312490Sjkh	else {
10322490Sjkh		get_tbl(parent);
1033181385Sache		choice = arc4random_uniform(parent->tbl.str_numstr);
1034142022Sru		DPRINTF(1, (stderr, "    choice = %d (of %u)\n",
10352490Sjkh			    choice, parent->tbl.str_numstr));
1036203922Suqs		for (fp = parent->child; (unsigned)choice >= fp->tbl.str_numstr;
10372490Sjkh		     fp = fp->next) {
10382490Sjkh			choice -= fp->tbl.str_numstr;
1039142022Sru			DPRINTF(1, (stderr, "\tskip %s, %u (choice = %d)\n",
10402490Sjkh				    fp->name, fp->tbl.str_numstr, choice));
10412490Sjkh		}
1042142022Sru		DPRINTF(1, (stderr, "    using %s, %u\n", fp->name,
10432490Sjkh			    fp->tbl.str_numstr));
1044203926Suqs		return (fp);
10452490Sjkh	}
10462490Sjkh}
10472490Sjkh
10482490Sjkh/*
10492490Sjkh * sum_noprobs:
10502490Sjkh *	Sum up all the noprob probabilities, starting with fp.
10512490Sjkh */
1052227101Sedstatic void
1053203922Suqssum_noprobs(FILEDESC *fp)
10542490Sjkh{
10552490Sjkh	static bool	did_noprobs = FALSE;
10562490Sjkh
10572490Sjkh	if (did_noprobs)
10582490Sjkh		return;
10592490Sjkh	zero_tbl(&Noprob_tbl);
10602490Sjkh	while (fp != NULL) {
10612490Sjkh		get_tbl(fp);
10622490Sjkh		sum_tbl(&Noprob_tbl, &fp->tbl);
10632490Sjkh		fp = fp->next;
10642490Sjkh	}
10652490Sjkh	did_noprobs = TRUE;
10662490Sjkh}
10672490Sjkh
1068227101Sedstatic int
1069203922Suqsmax(int i, int j)
10702490Sjkh{
10712490Sjkh	return (i >= j ? i : j);
10722490Sjkh}
10732490Sjkh
10742490Sjkh/*
10752490Sjkh * open_fp:
10762490Sjkh *	Assocatiate a FILE * with the given FILEDESC.
10772490Sjkh */
1078227101Sedstatic void
1079203922Suqsopen_fp(FILEDESC *fp)
10802490Sjkh{
10812490Sjkh	if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) {
10822490Sjkh		perror(fp->path);
10832490Sjkh		exit(1);
10842490Sjkh	}
10852490Sjkh}
10862490Sjkh
10872490Sjkh/*
10882490Sjkh * open_dat:
10892490Sjkh *	Open up the dat file if we need to.
10902490Sjkh */
1091227101Sedstatic void
1092203922Suqsopen_dat(FILEDESC *fp)
10932490Sjkh{
1094203926Suqs	if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, O_RDONLY)) < 0) {
10952490Sjkh		perror(fp->datfile);
10962490Sjkh		exit(1);
10972490Sjkh	}
10982490Sjkh}
10992490Sjkh
11002490Sjkh/*
11012490Sjkh * get_pos:
11022490Sjkh *	Get the position from the pos file, if there is one.  If not,
11032490Sjkh *	return a random number.
11042490Sjkh */
1105227101Sedstatic void
1106203922Suqsget_pos(FILEDESC *fp)
11072490Sjkh{
11082490Sjkh	int	fd;
11092490Sjkh
11102490Sjkh	assert(fp->read_tbl);
11112490Sjkh	if (fp->pos == POS_UNKNOWN) {
1112242577Seadler		if (WriteToDisk) {
1113242577Seadler			if ((fd = open(fp->posfile, O_RDONLY)) < 0 ||
1114242577Seadler			    read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
1115242577Seadler				fp->pos = arc4random_uniform(fp->tbl.str_numstr);
1116242577Seadler			else if (fp->pos >= fp->tbl.str_numstr)
1117242577Seadler				fp->pos %= fp->tbl.str_numstr;
1118242577Seadler			if (fd >= 0)
1119242577Seadler				close(fd);
1120242577Seadler		}
1121242577Seadler		else
1122181385Sache			fp->pos = arc4random_uniform(fp->tbl.str_numstr);
11232490Sjkh	}
11242490Sjkh	if (++(fp->pos) >= fp->tbl.str_numstr)
11252490Sjkh		fp->pos -= fp->tbl.str_numstr;
1126142022Sru	DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, (long)fp->pos));
11272490Sjkh}
11282490Sjkh
11292490Sjkh/*
11302490Sjkh * get_tbl:
11312490Sjkh *	Get the tbl data file the datfile.
11322490Sjkh */
1133227101Sedstatic void
1134203922Suqsget_tbl(FILEDESC *fp)
11352490Sjkh{
1136203926Suqs	int		fd;
113753210Sbillf	FILEDESC	*child;
11382490Sjkh
11392490Sjkh	if (fp->read_tbl)
11402490Sjkh		return;
11412490Sjkh	if (fp->child == NULL) {
1142203926Suqs		if ((fd = open(fp->datfile, O_RDONLY)) < 0) {
11432490Sjkh			perror(fp->datfile);
11442490Sjkh			exit(1);
11452490Sjkh		}
11462490Sjkh		if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
11472490Sjkh			(void)fprintf(stderr,
11482490Sjkh			    "fortune: %s corrupted\n", fp->path);
11492490Sjkh			exit(1);
11502490Sjkh		}
1151142022Sru		/* fp->tbl.str_version = be32toh(fp->tbl.str_version); */
1152142022Sru		fp->tbl.str_numstr = be32toh(fp->tbl.str_numstr);
1153142022Sru		fp->tbl.str_longlen = be32toh(fp->tbl.str_longlen);
1154142022Sru		fp->tbl.str_shortlen = be32toh(fp->tbl.str_shortlen);
1155142022Sru		fp->tbl.str_flags = be32toh(fp->tbl.str_flags);
11562490Sjkh		(void) close(fd);
11572490Sjkh	}
11582490Sjkh	else {
11592490Sjkh		zero_tbl(&fp->tbl);
11602490Sjkh		for (child = fp->child; child != NULL; child = child->next) {
11612490Sjkh			get_tbl(child);
11622490Sjkh			sum_tbl(&fp->tbl, &child->tbl);
11632490Sjkh		}
11642490Sjkh	}
11652490Sjkh	fp->read_tbl = TRUE;
11662490Sjkh}
11672490Sjkh
11682490Sjkh/*
11692490Sjkh * zero_tbl:
11702490Sjkh *	Zero out the fields we care about in a tbl structure.
11712490Sjkh */
1172227101Sedstatic void
1173203922Suqszero_tbl(STRFILE *tp)
11742490Sjkh{
11752490Sjkh	tp->str_numstr = 0;
11762490Sjkh	tp->str_longlen = 0;
1177142022Sru	tp->str_shortlen = ~0;
11782490Sjkh}
11792490Sjkh
11802490Sjkh/*
11812490Sjkh * sum_tbl:
11822490Sjkh *	Merge the tbl data of t2 into t1.
11832490Sjkh */
1184227101Sedstatic void
1185203922Suqssum_tbl(STRFILE *t1, STRFILE *t2)
11862490Sjkh{
11872490Sjkh	t1->str_numstr += t2->str_numstr;
11882490Sjkh	if (t1->str_longlen < t2->str_longlen)
11892490Sjkh		t1->str_longlen = t2->str_longlen;
11902490Sjkh	if (t1->str_shortlen > t2->str_shortlen)
11912490Sjkh		t1->str_shortlen = t2->str_shortlen;
11922490Sjkh}
11932490Sjkh
11942490Sjkh#define	STR(str)	((str) == NULL ? "NULL" : (str))
11952490Sjkh
11962490Sjkh/*
11972490Sjkh * print_file_list:
11982490Sjkh *	Print out the file list
11992490Sjkh */
1200227101Sedstatic void
1201203922Suqsprint_file_list(void)
12022490Sjkh{
12032490Sjkh	print_list(File_list, 0);
12042490Sjkh}
12052490Sjkh
12062490Sjkh/*
12072490Sjkh * print_list:
12082490Sjkh *	Print out the actual list, recursively.
12092490Sjkh */
1210227101Sedstatic void
1211203922Suqsprint_list(FILEDESC *list, int lev)
12122490Sjkh{
12132490Sjkh	while (list != NULL) {
12142490Sjkh		fprintf(stderr, "%*s", lev * 4, "");
12152490Sjkh		if (list->percent == NO_PROB)
12162490Sjkh			fprintf(stderr, "___%%");
12172490Sjkh		else
12182490Sjkh			fprintf(stderr, "%3d%%", list->percent);
12192490Sjkh		fprintf(stderr, " %s", STR(list->name));
122015957Sache		DPRINTF(1, (stderr, " (%s, %s, %s)", STR(list->path),
12212490Sjkh			    STR(list->datfile), STR(list->posfile)));
122215957Sache		fprintf(stderr, "\n");
12232490Sjkh		if (list->child != NULL)
12242490Sjkh			print_list(list->child, lev + 1);
12252490Sjkh		list = list->next;
12262490Sjkh	}
12272490Sjkh}
12282490Sjkh
12292490Sjkh/*
12302490Sjkh * conv_pat:
12312490Sjkh *	Convert the pattern to an ignore-case equivalent.
12322490Sjkh */
1233227101Sedstatic char *
1234203922Suqsconv_pat(char *orig)
12352490Sjkh{
123653210Sbillf	char		*sp;
123753210Sbillf	unsigned int	cnt;
123853210Sbillf	char		*new;
12392490Sjkh
12402490Sjkh	cnt = 1;	/* allow for '\0' */
12412490Sjkh	for (sp = orig; *sp != '\0'; sp++)
124215944Sache		if (isalpha((unsigned char)*sp))
12432490Sjkh			cnt += 4;
12442490Sjkh		else
12452490Sjkh			cnt++;
12462490Sjkh	if ((new = malloc(cnt)) == NULL) {
12472490Sjkh		fprintf(stderr, "pattern too long for ignoring case\n");
12482490Sjkh		exit(1);
12492490Sjkh	}
12502490Sjkh
12512490Sjkh	for (sp = new; *orig != '\0'; orig++) {
125215944Sache		if (islower((unsigned char)*orig)) {
12532490Sjkh			*sp++ = '[';
12542490Sjkh			*sp++ = *orig;
125515944Sache			*sp++ = toupper((unsigned char)*orig);
12562490Sjkh			*sp++ = ']';
12572490Sjkh		}
125815944Sache		else if (isupper((unsigned char)*orig)) {
12592490Sjkh			*sp++ = '[';
12602490Sjkh			*sp++ = *orig;
126115944Sache			*sp++ = tolower((unsigned char)*orig);
12622490Sjkh			*sp++ = ']';
12632490Sjkh		}
12642490Sjkh		else
12652490Sjkh			*sp++ = *orig;
12662490Sjkh	}
12672490Sjkh	*sp = '\0';
1268203926Suqs
1269203926Suqs	return (new);
12702490Sjkh}
12712490Sjkh
12722490Sjkh/*
12732490Sjkh * find_matches:
12742490Sjkh *	Find all the fortunes which match the pattern we've been given.
12752490Sjkh */
1276227101Sedstatic int
1277203922Suqsfind_matches(void)
12782490Sjkh{
12792490Sjkh	Fort_len = maxlen_in_list(File_list);
12802490Sjkh	DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
12812490Sjkh	/* extra length, "%\n" is appended */
12822490Sjkh	Fortbuf = do_malloc((unsigned int) Fort_len + 10);
12832490Sjkh
12842490Sjkh	Found_one = FALSE;
12852490Sjkh	matches_in_list(File_list);
1286203926Suqs
1287203926Suqs	return (Found_one);
12882490Sjkh}
12892490Sjkh
12902490Sjkh/*
12912490Sjkh * maxlen_in_list
12922490Sjkh *	Return the maximum fortune len in the file list.
12932490Sjkh */
1294227101Sedstatic int
1295203922Suqsmaxlen_in_list(FILEDESC *list)
12962490Sjkh{
129753210Sbillf	FILEDESC	*fp;
129853210Sbillf	int		len, maxlen;
12992490Sjkh
13002490Sjkh	maxlen = 0;
13012490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
13022490Sjkh		if (fp->child != NULL) {
13032490Sjkh			if ((len = maxlen_in_list(fp->child)) > maxlen)
13042490Sjkh				maxlen = len;
13052490Sjkh		}
13062490Sjkh		else {
13072490Sjkh			get_tbl(fp);
1308203926Suqs			if (fp->tbl.str_longlen > (unsigned int)maxlen)
13092490Sjkh				maxlen = fp->tbl.str_longlen;
13102490Sjkh		}
13112490Sjkh	}
1312203926Suqs
1313203926Suqs	return (maxlen);
13142490Sjkh}
13152490Sjkh
13162490Sjkh/*
13172490Sjkh * matches_in_list
13182490Sjkh *	Print out the matches from the files in the list.
13192490Sjkh */
1320227101Sedstatic void
1321203922Suqsmatches_in_list(FILEDESC *list)
13222490Sjkh{
132353210Sbillf	char           *sp, *p;
132453210Sbillf	FILEDESC	*fp;
1325203926Suqs	int		in_file;
1326203926Suqs	unsigned char	ch;
13272490Sjkh
13282490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
13292490Sjkh		if (fp->child != NULL) {
13302490Sjkh			matches_in_list(fp->child);
13312490Sjkh			continue;
13322490Sjkh		}
13332490Sjkh		DPRINTF(1, (stderr, "searching in %s\n", fp->path));
13342490Sjkh		open_fp(fp);
13352490Sjkh		sp = Fortbuf;
13362490Sjkh		in_file = FALSE;
13372490Sjkh		while (fgets(sp, Fort_len, fp->inf) != NULL)
133851863Sdcs			if (fp->tbl.str_flags & STR_COMMENTS
133951863Sdcs			    && sp[0] == fp->tbl.str_delim
134051863Sdcs			    && sp[1] == fp->tbl.str_delim)
134151863Sdcs				continue;
134251863Sdcs			else if (!STR_ENDSTRING(sp, fp->tbl))
13432490Sjkh				sp += strlen(sp);
13442490Sjkh			else {
13452490Sjkh				*sp = '\0';
134615944Sache				if (fp->tbl.str_flags & STR_ROTATED)
134715944Sache					for (p = Fortbuf; (ch = *p) != '\0'; ++p) {
134815944Sache						if (isascii(ch)) {
134915944Sache							if (isupper(ch))
135015944Sache								*p = 'A' + (ch - 'A' + 13) % 26;
135115944Sache							else if (islower(ch))
135215944Sache								*p = 'a' + (ch - 'a' + 13) % 26;
135315944Sache						}
135415944Sache					}
1355130851Sphk				if (regexec(&Re_pat, Fortbuf, 0, NULL, 0) != REG_NOMATCH) {
13562490Sjkh					printf("%c%c", fp->tbl.str_delim,
13572490Sjkh					    fp->tbl.str_delim);
13582490Sjkh					if (!in_file) {
13592490Sjkh						printf(" (%s)", fp->name);
13602490Sjkh						Found_one = TRUE;
13612490Sjkh						in_file = TRUE;
13622490Sjkh					}
13632490Sjkh					putchar('\n');
13642490Sjkh					(void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
13652490Sjkh				}
13662490Sjkh				sp = Fortbuf;
13672490Sjkh			}
13682490Sjkh	}
13692490Sjkh}
13702490Sjkh
1371227101Sedstatic void
1372203922Suqsusage(void)
13732490Sjkh{
13742490Sjkh	(void) fprintf(stderr, "fortune [-a");
13752490Sjkh#ifdef	DEBUG
13762490Sjkh	(void) fprintf(stderr, "D");
13772490Sjkh#endif	/* DEBUG */
1378141581Sru	(void) fprintf(stderr, "efilosw]");
13792490Sjkh	(void) fprintf(stderr, " [-m pattern]");
1380141581Sru	(void) fprintf(stderr, " [[N%%] file/directory/all]\n");
13812490Sjkh	exit(1);
13822490Sjkh}
1383173396Sedwin
1384173396Sedwin/*
1385173396Sedwin * getpath
1386173396Sedwin * 	Set up file search patch from environment var FORTUNE_PATH;
1387173396Sedwin *	if not set, use the compiled in FORTDIR.
1388173396Sedwin */
1389173396Sedwin
1390227101Sedstatic void
1391173396Sedwingetpath(void)
1392173396Sedwin{
1393173401Sedwin	int	nstr, foundenv;
1394173401Sedwin	char	*pch, **ppch, *str, *path;
1395173396Sedwin
1396173401Sedwin	foundenv = 1;
1397173396Sedwin	Fortune_path = getenv("FORTUNE_PATH");
1398173401Sedwin	if (Fortune_path == NULL) {
1399173401Sedwin		Fortune_path = FORTDIR;
1400173401Sedwin		foundenv = 0;
1401173401Sedwin	}
1402173396Sedwin	path = strdup(Fortune_path);
1403173396Sedwin
1404173396Sedwin	for (nstr = 2, pch = path; *pch != '\0'; pch++) {
1405173396Sedwin		if (*pch == ':')
1406173396Sedwin			nstr++;
1407173396Sedwin	}
1408173396Sedwin
1409173396Sedwin	ppch = Fortune_path_arr = (char **)calloc(nstr, sizeof(char *));
1410173396Sedwin
1411173396Sedwin	nstr = 0;
1412173396Sedwin	str = strtok(path, ":");
1413173396Sedwin	while (str) {
1414173396Sedwin		if (is_dir(str)) {
1415173396Sedwin			nstr++;
1416173396Sedwin			*ppch++ = str;
1417173396Sedwin		}
1418173396Sedwin		str = strtok(NULL, ":");
1419173396Sedwin	}
1420173401Sedwin
1421173396Sedwin	if (nstr == 0) {
1422173401Sedwin		if (foundenv == 1) {
1423173401Sedwin			fprintf(stderr,
1424173401Sedwin			    "fortune: FORTUNE_PATH: None of the specified "
1425173401Sedwin			    "directories found.\n");
1426173401Sedwin			exit(1);
1427173401Sedwin		}
1428173396Sedwin		free(path);
1429203922Suqs		Fortune_path_arr[0] = strdup(FORTDIR);
1430173396Sedwin	}
1431173396Sedwin}
1432