1 /*-
2  * Copyright (c) 1986 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Ken Arnold.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 char copyright[] =
13 "@(#) Copyright (c) 1986 The Regents of the University of California.\n\
14  All rights reserved.\n";
15 #endif /* not lint */
16 
17 #ifndef lint
18 static char sccsid[] = "@(#)fortune.c	5.13 (Berkeley) 04/08/91";
19 #endif /* not lint */
20 
21 # include	<machine/endian.h>
22 # include	<sys/param.h>
23 # include	<sys/stat.h>
24 # include	<sys/dir.h>
25 # include	<stdio.h>
26 # include	<assert.h>
27 # include	"strfile.h"
28 # include	"pathnames.h"
29 
30 #ifdef	SYSV
31 # include	<dirent.h>
32 
33 # define	NO_LOCK
34 # define	REGCMP
35 # ifdef	NO_REGEX
36 #	undef	 NO_REGEX
37 # endif	/* NO_REGEX */
38 # define	index	strchr
39 # define	rindex	strrchr
40 #endif	/* SYSV */
41 
42 #ifndef NO_REGEX
43 # include	<ctype.h>
44 #endif	/* NO_REGEX */
45 
46 # ifndef NO_LOCK
47 # include	<sys/file.h>
48 # endif	/* NO_LOCK */
49 
50 # ifndef F_OK
51 /* codes for access() */
52 # define	F_OK		0	/* does file exist */
53 # define	X_OK		1	/* is it executable by caller */
54 # define	W_OK		2	/* writable by caller */
55 # define	R_OK		4	/* readable by caller */
56 # endif	/* F_OK */
57 
58 # define	TRUE	1
59 # define	FALSE	0
60 # define	bool	short
61 
62 # define	MINW	6		/* minimum wait if desired */
63 # define	CPERS	20		/* # of chars for each sec */
64 # define	SLEN	160		/* # of chars in short fortune */
65 
66 # define	POS_UNKNOWN	((unsigned long) -1)	/* pos for file unknown */
67 # define	NO_PROB		(-1)		/* no prob specified for file */
68 
69 # ifdef DEBUG
70 # define	DPRINTF(l,x)	if (Debug >= l) fprintf x; else
71 # undef		NDEBUG
72 # else	/* DEBUG */
73 # define	DPRINTF(l,x)
74 # define	NDEBUG	1
75 # endif	/* DEBUG */
76 
77 typedef struct fd {
78 	int		percent;
79 	int		fd, datfd;
80 	unsigned long	pos;
81 	FILE		*inf;
82 	char		*name;
83 	char		*path;
84 	char		*datfile, *posfile;
85 	bool		read_tbl;
86 	bool		was_pos_file;
87 	STRFILE		tbl;
88 	int		num_children;
89 	struct fd	*child, *parent;
90 	struct fd	*next, *prev;
91 } FILEDESC;
92 
93 bool	Found_one;			/* did we find a match? */
94 bool	Find_files	= FALSE;	/* just find a list of proper fortune files */
95 bool	Wait		= FALSE;	/* wait desired after fortune */
96 bool	Short_only	= FALSE;	/* short fortune desired */
97 bool	Long_only	= FALSE;	/* long fortune desired */
98 bool	Offend		= FALSE;	/* offensive fortunes only */
99 bool	All_forts	= FALSE;	/* any fortune allowed */
100 bool	Equal_probs	= FALSE;	/* scatter un-allocted prob equally */
101 #ifndef NO_REGEX
102 bool	Match		= FALSE;	/* dump fortunes matching a pattern */
103 #endif
104 #ifdef DEBUG
105 bool	Debug = FALSE;			/* print debug messages */
106 #endif
107 
108 char	*Fortbuf = NULL;			/* fortune buffer for -m */
109 
110 int	Fort_len = 0;
111 
112 off_t	Seekpts[2];			/* seek pointers to fortunes */
113 
114 FILEDESC	*File_list = NULL,	/* Head of file list */
115 		*File_tail = NULL;	/* Tail of file list */
116 FILEDESC	*Fortfile;		/* Fortune file to use */
117 
118 STRFILE		Noprob_tbl;		/* sum of data for all no prob files */
119 
120 char	*do_malloc(), *copy(), *off_name();
121 
122 FILEDESC	*pick_child(), *new_fp();
123 
124 extern char	*malloc(), *index(), *rindex(), *strcpy(), *strcat();
125 
126 extern time_t	time();
127 
128 #ifndef NO_REGEX
129 char	*conv_pat();
130 #endif
131 
132 #ifndef NO_REGEX
133 #ifdef REGCMP
134 # define	RE_COMP(p)	(Re_pat = regcmp(p, NULL))
135 # define	BAD_COMP(f)	((f) == NULL)
136 # define	RE_EXEC(p)	regex(Re_pat, (p))
137 
138 char	*Re_pat;
139 
140 char	*regcmp(), *regex();
141 #else
142 # define	RE_COMP(p)	(p = re_comp(p))
143 # define	BAD_COMP(f)	((f) != NULL)
144 # define	RE_EXEC(p)	re_exec(p)
145 
146 char	*re_comp();
147 #ifdef SYSV
148 char	*re_exec();
149 #else
150 int	re_exec();
151 #endif
152 #endif
153 #endif
154 
155 main(ac, av)
156 int	ac;
157 char	*av[];
158 {
159 #ifdef	OK_TO_WRITE_DISK
160 	int	fd;
161 #endif	/* OK_TO_WRITE_DISK */
162 
163 	getargs(ac, av);
164 
165 #ifndef NO_REGEX
166 	if (Match)
167 		exit(find_matches() != 0);
168 #endif
169 
170 	init_prob();
171 	srandom((int)(time((time_t *) NULL) + getpid()));
172 	do {
173 		get_fort();
174 	} while ((Short_only && fortlen() > SLEN) ||
175 		 (Long_only && fortlen() <= SLEN));
176 
177 	display(Fortfile);
178 
179 #ifdef	OK_TO_WRITE_DISK
180 	if ((fd = creat(Fortfile->posfile, 0666)) < 0) {
181 		perror(Fortfile->posfile);
182 		exit(1);
183 	}
184 #ifdef	LOCK_EX
185 	/*
186 	 * if we can, we exclusive lock, but since it isn't very
187 	 * important, we just punt if we don't have easy locking
188 	 * available.
189 	 */
190 	(void) flock(fd, LOCK_EX);
191 #endif	/* LOCK_EX */
192 	write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
193 	if (!Fortfile->was_pos_file)
194 		(void) chmod(Fortfile->path, 0666);
195 #ifdef	LOCK_EX
196 	(void) flock(fd, LOCK_UN);
197 #endif	/* LOCK_EX */
198 #endif	/* OK_TO_WRITE_DISK */
199 	if (Wait) {
200 		if (Fort_len == 0)
201 			(void) fortlen();
202 		sleep((unsigned int) max(Fort_len / CPERS, MINW));
203 	}
204 	exit(0);
205 	/* NOTREACHED */
206 }
207 
208 display(fp)
209 FILEDESC	*fp;
210 {
211 	register char	*p, ch;
212 	char	line[BUFSIZ];
213 
214 	open_fp(fp);
215 	(void) fseek(fp->inf, Seekpts[0], 0);
216 	for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
217 	    !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
218 		if (fp->tbl.str_flags & STR_ROTATED)
219 			for (p = line; ch = *p; ++p)
220 				if (isupper(ch))
221 					*p = 'A' + (ch - 'A' + 13) % 26;
222 				else if (islower(ch))
223 					*p = 'a' + (ch - 'a' + 13) % 26;
224 		fputs(line, stdout);
225 	}
226 	(void) fflush(stdout);
227 }
228 
229 /*
230  * fortlen:
231  *	Return the length of the fortune.
232  */
233 fortlen()
234 {
235 	register int	nchar;
236 	char		line[BUFSIZ];
237 
238 	if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
239 		nchar = (Seekpts[1] - Seekpts[0] <= SLEN);
240 	else {
241 		open_fp(Fortfile);
242 		(void) fseek(Fortfile->inf, Seekpts[0], 0);
243 		nchar = 0;
244 		while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
245 		       !STR_ENDSTRING(line, Fortfile->tbl))
246 			nchar += strlen(line);
247 	}
248 	Fort_len = nchar;
249 	return nchar;
250 }
251 
252 /*
253  *	This routine evaluates the arguments on the command line
254  */
255 getargs(argc, argv)
256 register int	argc;
257 register char	**argv;
258 {
259 	register int	ignore_case;
260 # ifndef NO_REGEX
261 	register char	*pat;
262 # endif	/* NO_REGEX */
263 	extern char *optarg;
264 	extern int optind;
265 	int ch;
266 
267 	ignore_case = FALSE;
268 	pat = NULL;
269 
270 # ifdef DEBUG
271 	while ((ch = getopt(argc, argv, "aDefilm:osw")) != EOF)
272 #else
273 	while ((ch = getopt(argc, argv, "aefilm:osw")) != EOF)
274 #endif /* DEBUG */
275 		switch(ch) {
276 		case 'a':		/* any fortune */
277 			All_forts++;
278 			break;
279 # ifdef DEBUG
280 		case 'D':
281 			Debug++;
282 			break;
283 # endif /* DEBUG */
284 		case 'e':
285 			Equal_probs++;	/* scatter un-allocted prob equally */
286 			break;
287 		case 'f':		/* find fortune files */
288 			Find_files++;
289 			break;
290 		case 'l':		/* long ones only */
291 			Long_only++;
292 			Short_only = FALSE;
293 			break;
294 		case 'o':		/* offensive ones only */
295 			Offend++;
296 			break;
297 		case 's':		/* short ones only */
298 			Short_only++;
299 			Long_only = FALSE;
300 			break;
301 		case 'w':		/* give time to read */
302 			Wait++;
303 			break;
304 # ifdef	NO_REGEX
305 		case 'i':			/* case-insensitive match */
306 		case 'm':			/* dump out the fortunes */
307 			(void) fprintf(stderr,
308 			    "fortune: can't match fortunes on this system (Sorry)\n");
309 			exit(0);
310 # else	/* NO_REGEX */
311 		case 'm':			/* dump out the fortunes */
312 			Match++;
313 			pat = optarg;
314 			break;
315 		case 'i':			/* case-insensitive match */
316 			ignore_case++;
317 			break;
318 # endif	/* NO_REGEX */
319 		case '?':
320 		default:
321 			usage();
322 		}
323 	argc -= optind;
324 	argv += optind;
325 
326 	if (!form_file_list(argv, argc))
327 		exit(1);	/* errors printed through form_file_list() */
328 #ifdef DEBUG
329 	if (Debug >= 1)
330 		print_file_list();
331 #endif /* DEBUG */
332 	if (Find_files) {
333 		print_file_list();
334 		exit(0);
335 	}
336 
337 # ifndef NO_REGEX
338 	if (pat != NULL) {
339 		if (ignore_case)
340 			pat = conv_pat(pat);
341 		if (BAD_COMP(RE_COMP(pat))) {
342 #ifndef REGCMP
343 			fprintf(stderr, "%s\n", pat);
344 #else	/* REGCMP */
345 			fprintf(stderr, "bad pattern: %s\n", pat);
346 #endif	/* REGCMP */
347 		}
348 	}
349 # endif	/* NO_REGEX */
350 }
351 
352 /*
353  * form_file_list:
354  *	Form the file list from the file specifications.
355  */
356 form_file_list(files, file_cnt)
357 register char	**files;
358 register int	file_cnt;
359 {
360 	register int	i, percent;
361 	register char	*sp;
362 
363 	if (file_cnt == 0)
364 		if (Find_files)
365 			return add_file(NO_PROB, FORTDIR, NULL, &File_list,
366 					&File_tail, NULL);
367 		else
368 			return add_file(NO_PROB, "fortunes", FORTDIR,
369 					&File_list, &File_tail, NULL);
370 	for (i = 0; i < file_cnt; i++) {
371 		percent = NO_PROB;
372 		if (!isdigit(files[i][0]))
373 			sp = files[i];
374 		else {
375 			percent = 0;
376 			for (sp = files[i]; isdigit(*sp); sp++)
377 				percent = percent * 10 + *sp - '0';
378 			if (percent > 100) {
379 				fprintf(stderr, "percentages must be <= 100\n");
380 				return FALSE;
381 			}
382 			if (*sp == '.') {
383 				fprintf(stderr, "percentages must be integers\n");
384 				return FALSE;
385 			}
386 			/*
387 			 * If the number isn't followed by a '%', then
388 			 * it was not a percentage, just the first part
389 			 * of a file name which starts with digits.
390 			 */
391 			if (*sp != '%') {
392 				percent = NO_PROB;
393 				sp = files[i];
394 			}
395 			else if (*++sp == '\0') {
396 				if (++i >= file_cnt) {
397 					fprintf(stderr, "percentages must precede files\n");
398 					return FALSE;
399 				}
400 				sp = files[i];
401 			}
402 		}
403 		if (strcmp(sp, "all") == 0)
404 			sp = FORTDIR;
405 		if (!add_file(percent, sp, NULL, &File_list, &File_tail, NULL))
406 			return FALSE;
407 	}
408 	return TRUE;
409 }
410 
411 /*
412  * add_file:
413  *	Add a file to the file list.
414  */
415 add_file(percent, file, dir, head, tail, parent)
416 int		percent;
417 register char	*file;
418 char		*dir;
419 FILEDESC	**head, **tail;
420 FILEDESC	*parent;
421 {
422 	register FILEDESC	*fp;
423 	register int		fd;
424 	register char		*path, *offensive;
425 	register bool		was_malloc;
426 	register bool		isdir;
427 
428 	if (dir == NULL) {
429 		path = file;
430 		was_malloc = FALSE;
431 	}
432 	else {
433 		path = do_malloc((unsigned int) (strlen(dir) + strlen(file) + 2));
434 		(void) strcat(strcat(strcpy(path, dir), "/"), file);
435 		was_malloc = TRUE;
436 	}
437 	if ((isdir = is_dir(path)) && parent != NULL) {
438 		if (was_malloc)
439 			free(path);
440 		return FALSE;	/* don't recurse */
441 	}
442 	offensive = NULL;
443 	if (!isdir && parent == NULL && (All_forts || Offend) &&
444 	    !is_off_name(path)) {
445 		offensive = off_name(path);
446 		was_malloc = TRUE;
447 		if (Offend) {
448 			if (was_malloc)
449 				free(path);
450 			path = offensive;
451 			file = off_name(file);
452 		}
453 	}
454 
455 	DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
456 over:
457 	if ((fd = open(path, 0)) < 0) {
458 		/*
459 		 * This is a sneak.  If the user said -a, and if the
460 		 * file we're given isn't a file, we check to see if
461 		 * there is a -o version.  If there is, we treat it as
462 		 * if *that* were the file given.  We only do this for
463 		 * individual files -- if we're scanning a directory,
464 		 * we'll pick up the -o file anyway.
465 		 */
466 		if (All_forts && offensive != NULL) {
467 			path = offensive;
468 			if (was_malloc)
469 				free(path);
470 			offensive = NULL;
471 			was_malloc = TRUE;
472 			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
473 			file = off_name(file);
474 			goto over;
475 		}
476 		if (dir == NULL && file[0] != '/')
477 			return add_file(percent, file, FORTDIR, head, tail,
478 					parent);
479 		if (parent == NULL)
480 			perror(path);
481 		if (was_malloc)
482 			free(path);
483 		return FALSE;
484 	}
485 
486 	DPRINTF(2, (stderr, "path = \"%s\"\n", path));
487 
488 	fp = new_fp();
489 	fp->fd = fd;
490 	fp->percent = percent;
491 	fp->name = file;
492 	fp->path = path;
493 	fp->parent = parent;
494 
495 	if ((isdir && !add_dir(fp)) ||
496 	    (!isdir &&
497 	     !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
498 	{
499 		if (parent == NULL)
500 			fprintf(stderr,
501 				"fortune:%s not a fortune file or directory\n",
502 				path);
503 		free((char *) fp);
504 		if (was_malloc)
505 			free(path);
506 		do_free(fp->datfile);
507 		do_free(fp->posfile);
508 		do_free(offensive);
509 		return FALSE;
510 	}
511 	/*
512 	 * If the user said -a, we need to make this node a pointer to
513 	 * both files, if there are two.  We don't need to do this if
514 	 * we are scanning a directory, since the scan will pick up the
515 	 * -o file anyway.
516 	 */
517 	if (All_forts && parent == NULL && !is_off_name(path))
518 		all_forts(fp, offensive);
519 	if (*head == NULL)
520 		*head = *tail = fp;
521 	else if (fp->percent == NO_PROB) {
522 		(*tail)->next = fp;
523 		fp->prev = *tail;
524 		*tail = fp;
525 	}
526 	else {
527 		(*head)->prev = fp;
528 		fp->next = *head;
529 		*head = fp;
530 	}
531 #ifdef	OK_TO_WRITE_DISK
532 	fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
533 #endif	/* OK_TO_WRITE_DISK */
534 
535 	return TRUE;
536 }
537 
538 /*
539  * new_fp:
540  *	Return a pointer to an initialized new FILEDESC.
541  */
542 FILEDESC *
543 new_fp()
544 {
545 	register FILEDESC	*fp;
546 
547 	fp = (FILEDESC *) do_malloc(sizeof *fp);
548 	fp->datfd = -1;
549 	fp->pos = POS_UNKNOWN;
550 	fp->inf = NULL;
551 	fp->fd = -1;
552 	fp->percent = NO_PROB;
553 	fp->read_tbl = FALSE;
554 	fp->next = NULL;
555 	fp->prev = NULL;
556 	fp->child = NULL;
557 	fp->parent = NULL;
558 	fp->datfile = NULL;
559 	fp->posfile = NULL;
560 	return fp;
561 }
562 
563 /*
564  * off_name:
565  *	Return a pointer to the offensive version of a file of this name.
566  */
567 char *
568 off_name(file)
569 char	*file;
570 {
571 	char	*new;
572 
573 	new = copy(file, (unsigned int) (strlen(file) + 2));
574 	return strcat(new, "-o");
575 }
576 
577 /*
578  * is_off_name:
579  *	Is the file an offensive-style name?
580  */
581 is_off_name(file)
582 char	*file;
583 {
584 	int	len;
585 
586 	len = strlen(file);
587 	return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
588 }
589 
590 /*
591  * all_forts:
592  *	Modify a FILEDESC element to be the parent of two children if
593  *	there are two children to be a parent of.
594  */
595 all_forts(fp, offensive)
596 register FILEDESC	*fp;
597 char			*offensive;
598 {
599 	register char		*sp;
600 	register FILEDESC	*scene, *obscene;
601 	register int		fd;
602 	auto char		*datfile, *posfile;
603 
604 	if (fp->child != NULL)	/* this is a directory, not a file */
605 		return;
606 	if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
607 		return;
608 	if ((fd = open(offensive, 0)) < 0)
609 		return;
610 	DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
611 	scene = new_fp();
612 	obscene = new_fp();
613 	*scene = *fp;
614 
615 	fp->num_children = 2;
616 	fp->child = scene;
617 	scene->next = obscene;
618 	obscene->next = NULL;
619 	scene->child = obscene->child = NULL;
620 	scene->parent = obscene->parent = fp;
621 
622 	fp->fd = -1;
623 	scene->percent = obscene->percent = NO_PROB;
624 
625 	obscene->fd = fd;
626 	obscene->inf = NULL;
627 	obscene->path = offensive;
628 	if ((sp = rindex(offensive, '/')) == NULL)
629 		obscene->name = offensive;
630 	else
631 		obscene->name = ++sp;
632 	obscene->datfile = datfile;
633 	obscene->posfile = posfile;
634 	obscene->read_tbl = FALSE;
635 #ifdef	OK_TO_WRITE_DISK
636 	obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
637 #endif	/* OK_TO_WRITE_DISK */
638 }
639 
640 /*
641  * add_dir:
642  *	Add the contents of an entire directory.
643  */
644 add_dir(fp)
645 register FILEDESC	*fp;
646 {
647 	register DIR		*dir;
648 #ifdef SYSV
649 	register struct dirent	*dirent;	/* NIH, of course! */
650 #else
651 	register struct direct	*dirent;
652 #endif
653 	auto FILEDESC		*tailp;
654 	auto char		*name;
655 
656 	(void) close(fp->fd);
657 	fp->fd = -1;
658 	if ((dir = opendir(fp->path)) == NULL) {
659 		perror(fp->path);
660 		return FALSE;
661 	}
662 	tailp = NULL;
663 	DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
664 	fp->num_children = 0;
665 	while ((dirent = readdir(dir)) != NULL) {
666 		if (dirent->d_namlen == 0)
667 			continue;
668 		name = copy(dirent->d_name, dirent->d_namlen);
669 		if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
670 			fp->num_children++;
671 		else
672 			free(name);
673 	}
674 	if (fp->num_children == 0) {
675 		(void) fprintf(stderr,
676 		    "fortune: %s: No fortune files in directory.\n", fp->path);
677 		return FALSE;
678 	}
679 	return TRUE;
680 }
681 
682 /*
683  * is_dir:
684  *	Return TRUE if the file is a directory, FALSE otherwise.
685  */
686 is_dir(file)
687 char	*file;
688 {
689 	auto struct stat	sbuf;
690 
691 	if (stat(file, &sbuf) < 0)
692 		return FALSE;
693 	return (sbuf.st_mode & S_IFDIR);
694 }
695 
696 /*
697  * is_fortfile:
698  *	Return TRUE if the file is a fortune database file.  We try and
699  *	exclude files without reading them if possible to avoid
700  *	overhead.  Files which start with ".", or which have "illegal"
701  *	suffixes, as contained in suflist[], are ruled out.
702  */
703 /* ARGSUSED */
704 is_fortfile(file, datp, posp, check_for_offend)
705 char	*file;
706 char	**datp, **posp;
707 int	check_for_offend;
708 {
709 	register int	i;
710 	register char	*sp;
711 	register char	*datfile;
712 	static char	*suflist[] = {	/* list of "illegal" suffixes" */
713 				"dat", "pos", "c", "h", "p", "i", "f",
714 				"pas", "ftn", "ins.c", "ins,pas",
715 				"ins.ftn", "sml",
716 				NULL
717 			};
718 
719 	DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
720 
721 	/*
722 	 * Preclude any -o files for offendable people, and any non -o
723 	 * files for completely offensive people.
724 	 */
725 	if (check_for_offend && !All_forts) {
726 		i = strlen(file);
727 		if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o'))
728 			return FALSE;
729 	}
730 
731 	if ((sp = rindex(file, '/')) == NULL)
732 		sp = file;
733 	else
734 		sp++;
735 	if (*sp == '.') {
736 		DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
737 		return FALSE;
738 	}
739 	if ((sp = rindex(sp, '.')) != NULL) {
740 		sp++;
741 		for (i = 0; suflist[i] != NULL; i++)
742 			if (strcmp(sp, suflist[i]) == 0) {
743 				DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
744 				return FALSE;
745 			}
746 	}
747 
748 	datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
749 	strcat(datfile, ".dat");
750 	if (access(datfile, R_OK) < 0) {
751 		free(datfile);
752 		DPRINTF(2, (stderr, "FALSE (no \".dat\" file)\n"));
753 		return FALSE;
754 	}
755 	if (datp != NULL)
756 		*datp = datfile;
757 	else
758 		free(datfile);
759 #ifdef	OK_TO_WRITE_DISK
760 	if (posp != NULL) {
761 		*posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
762 		(void) strcat(*posp, ".pos");
763 	}
764 #endif	/* OK_TO_WRITE_DISK */
765 	DPRINTF(2, (stderr, "TRUE\n"));
766 	return TRUE;
767 }
768 
769 /*
770  * copy:
771  *	Return a malloc()'ed copy of the string
772  */
773 char *
774 copy(str, len)
775 char		*str;
776 unsigned int	len;
777 {
778 	char	*new, *sp;
779 
780 	new = do_malloc(len + 1);
781 	sp = new;
782 	do {
783 		*sp++ = *str;
784 	} while (*str++);
785 	return new;
786 }
787 
788 /*
789  * do_malloc:
790  *	Do a malloc, checking for NULL return.
791  */
792 char *
793 do_malloc(size)
794 unsigned int	size;
795 {
796 	char	*new;
797 
798 	if ((new = malloc(size)) == NULL) {
799 		(void) fprintf(stderr, "fortune: out of memory.\n");
800 		exit(1);
801 	}
802 	return new;
803 }
804 
805 /*
806  * do_free:
807  *	Free malloc'ed space, if any.
808  */
809 do_free(ptr)
810 char	*ptr;
811 {
812 	if (ptr != NULL)
813 		free(ptr);
814 }
815 
816 /*
817  * init_prob:
818  *	Initialize the fortune probabilities.
819  */
820 init_prob()
821 {
822 	register FILEDESC	*fp, *last;
823 	register int		percent, num_noprob, frac;
824 
825 	/*
826 	 * Distribute the residual probability (if any) across all
827 	 * files with unspecified probability (i.e., probability of 0)
828 	 * (if any).
829 	 */
830 
831 	percent = 0;
832 	num_noprob = 0;
833 	for (fp = File_tail; fp != NULL; fp = fp->prev)
834 		if (fp->percent == NO_PROB) {
835 			num_noprob++;
836 			if (Equal_probs)
837 				last = fp;
838 		}
839 		else
840 			percent += fp->percent;
841 	DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
842 		    percent, num_noprob));
843 	if (percent > 100) {
844 		(void) fprintf(stderr,
845 		    "fortune: probabilities sum to %d%%!\n", percent);
846 		exit(1);
847 	}
848 	else if (percent < 100 && num_noprob == 0) {
849 		(void) fprintf(stderr,
850 		    "fortune: no place to put residual probability (%d%%)\n",
851 		    percent);
852 		exit(1);
853 	}
854 	else if (percent == 100 && num_noprob != 0) {
855 		(void) fprintf(stderr,
856 		    "fortune: no probability left to put in residual files\n");
857 		exit(1);
858 	}
859 	percent = 100 - percent;
860 	if (Equal_probs)
861 		if (num_noprob != 0) {
862 			if (num_noprob > 1) {
863 				frac = percent / num_noprob;
864 				DPRINTF(1, (stderr, ", frac = %d%%", frac));
865 				for (fp = File_list; fp != last; fp = fp->next)
866 					if (fp->percent == NO_PROB) {
867 						fp->percent = frac;
868 						percent -= frac;
869 					}
870 			}
871 			last->percent = percent;
872 			DPRINTF(1, (stderr, ", residual = %d%%", percent));
873 		}
874 	else {
875 		DPRINTF(1, (stderr,
876 			    ", %d%% distributed over remaining fortunes\n",
877 			    percent));
878 	}
879 	DPRINTF(1, (stderr, "\n"));
880 
881 #ifdef DEBUG
882 	if (Debug >= 1)
883 		print_file_list();
884 #endif
885 }
886 
887 /*
888  * get_fort:
889  *	Get the fortune data file's seek pointer for the next fortune.
890  */
891 get_fort()
892 {
893 	register FILEDESC	*fp;
894 	register int		choice;
895 	long random();
896 
897 	if (File_list->next == NULL || File_list->percent == NO_PROB)
898 		fp = File_list;
899 	else {
900 		choice = random() % 100;
901 		DPRINTF(1, (stderr, "choice = %d\n", choice));
902 		for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
903 			if (choice < fp->percent)
904 				break;
905 			else {
906 				choice -= fp->percent;
907 				DPRINTF(1, (stderr,
908 					    "    skip \"%s\", %d%% (choice = %d)\n",
909 					    fp->name, fp->percent, choice));
910 			}
911 			DPRINTF(1, (stderr,
912 				    "using \"%s\", %d%% (choice = %d)\n",
913 				    fp->name, fp->percent, choice));
914 	}
915 	if (fp->percent != NO_PROB)
916 		get_tbl(fp);
917 	else {
918 		if (fp->next != NULL) {
919 			sum_noprobs(fp);
920 			choice = random() % Noprob_tbl.str_numstr;
921 			DPRINTF(1, (stderr, "choice = %d (of %d) \n", choice,
922 				    Noprob_tbl.str_numstr));
923 			while (choice >= fp->tbl.str_numstr) {
924 				choice -= fp->tbl.str_numstr;
925 				fp = fp->next;
926 				DPRINTF(1, (stderr,
927 					    "    skip \"%s\", %d (choice = %d)\n",
928 					    fp->name, fp->tbl.str_numstr,
929 					    choice));
930 			}
931 			DPRINTF(1, (stderr, "using \"%s\", %d\n", fp->name,
932 				    fp->tbl.str_numstr));
933 		}
934 		get_tbl(fp);
935 	}
936 	if (fp->child != NULL) {
937 		DPRINTF(1, (stderr, "picking child\n"));
938 		fp = pick_child(fp);
939 	}
940 	Fortfile = fp;
941 	get_pos(fp);
942 	open_dat(fp);
943 	(void) lseek(fp->datfd,
944 		     (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), 0);
945 	read(fp->datfd, Seekpts, sizeof Seekpts);
946 	Seekpts[0] = ntohl(Seekpts[0]);
947 	Seekpts[1] = ntohl(Seekpts[1]);
948 }
949 
950 /*
951  * pick_child
952  *	Pick a child from a chosen parent.
953  */
954 FILEDESC *
955 pick_child(parent)
956 FILEDESC	*parent;
957 {
958 	register FILEDESC	*fp;
959 	register int		choice;
960 
961 	if (Equal_probs) {
962 		choice = random() % parent->num_children;
963 		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
964 			    choice, parent->num_children));
965 		for (fp = parent->child; choice--; fp = fp->next)
966 			continue;
967 		DPRINTF(1, (stderr, "    using %s\n", fp->name));
968 		return fp;
969 	}
970 	else {
971 		get_tbl(parent);
972 		choice = random() % parent->tbl.str_numstr;
973 		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
974 			    choice, parent->tbl.str_numstr));
975 		for (fp = parent->child; choice >= fp->tbl.str_numstr;
976 		     fp = fp->next) {
977 			choice -= fp->tbl.str_numstr;
978 			DPRINTF(1, (stderr, "\tskip %s, %d (choice = %d)\n",
979 				    fp->name, fp->tbl.str_numstr, choice));
980 		}
981 		DPRINTF(1, (stderr, "    using %s, %d\n", fp->name,
982 			    fp->tbl.str_numstr));
983 		return fp;
984 	}
985 }
986 
987 /*
988  * sum_noprobs:
989  *	Sum up all the noprob probabilities, starting with fp.
990  */
991 sum_noprobs(fp)
992 register FILEDESC	*fp;
993 {
994 	static bool	did_noprobs = FALSE;
995 
996 	if (did_noprobs)
997 		return;
998 	zero_tbl(&Noprob_tbl);
999 	while (fp != NULL) {
1000 		get_tbl(fp);
1001 		sum_tbl(&Noprob_tbl, &fp->tbl);
1002 		fp = fp->next;
1003 	}
1004 	did_noprobs = TRUE;
1005 }
1006 
1007 max(i, j)
1008 register int	i, j;
1009 {
1010 	return (i >= j ? i : j);
1011 }
1012 
1013 /*
1014  * open_fp:
1015  *	Assocatiate a FILE * with the given FILEDESC.
1016  */
1017 open_fp(fp)
1018 FILEDESC	*fp;
1019 {
1020 	if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) {
1021 		perror(fp->path);
1022 		exit(1);
1023 	}
1024 }
1025 
1026 /*
1027  * open_dat:
1028  *	Open up the dat file if we need to.
1029  */
1030 open_dat(fp)
1031 FILEDESC	*fp;
1032 {
1033 	if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, 0)) < 0) {
1034 		perror(fp->datfile);
1035 		exit(1);
1036 	}
1037 }
1038 
1039 /*
1040  * get_pos:
1041  *	Get the position from the pos file, if there is one.  If not,
1042  *	return a random number.
1043  */
1044 get_pos(fp)
1045 FILEDESC	*fp;
1046 {
1047 #ifdef	OK_TO_WRITE_DISK
1048 	int	fd;
1049 #endif /* OK_TO_WRITE_DISK */
1050 
1051 	assert(fp->read_tbl);
1052 	if (fp->pos == POS_UNKNOWN) {
1053 #ifdef	OK_TO_WRITE_DISK
1054 		if ((fd = open(fp->posfile, 0)) < 0 ||
1055 		    read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
1056 			fp->pos = random() % fp->tbl.str_numstr;
1057 		else if (fp->pos >= fp->tbl.str_numstr)
1058 			fp->pos %= fp->tbl.str_numstr;
1059 		if (fd >= 0)
1060 			(void) close(fd);
1061 #else
1062 		fp->pos = random() % fp->tbl.str_numstr;
1063 #endif /* OK_TO_WRITE_DISK */
1064 	}
1065 	if (++(fp->pos) >= fp->tbl.str_numstr)
1066 		fp->pos -= fp->tbl.str_numstr;
1067 	DPRINTF(1, (stderr, "pos for %s is %d\n", fp->name, fp->pos));
1068 }
1069 
1070 /*
1071  * get_tbl:
1072  *	Get the tbl data file the datfile.
1073  */
1074 get_tbl(fp)
1075 FILEDESC	*fp;
1076 {
1077 	auto int		fd;
1078 	register FILEDESC	*child;
1079 
1080 	if (fp->read_tbl)
1081 		return;
1082 	if (fp->child == NULL) {
1083 		if ((fd = open(fp->datfile, 0)) < 0) {
1084 			perror(fp->datfile);
1085 			exit(1);
1086 		}
1087 		if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
1088 			(void)fprintf(stderr,
1089 			    "fortune: %s corrupted\n", fp->path);
1090 			exit(1);
1091 		}
1092 		/* fp->tbl.str_version = ntohl(fp->tbl.str_version); */
1093 		fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1094 		fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1095 		fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1096 		fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1097 		(void) close(fd);
1098 	}
1099 	else {
1100 		zero_tbl(&fp->tbl);
1101 		for (child = fp->child; child != NULL; child = child->next) {
1102 			get_tbl(child);
1103 			sum_tbl(&fp->tbl, &child->tbl);
1104 		}
1105 	}
1106 	fp->read_tbl = TRUE;
1107 }
1108 
1109 /*
1110  * zero_tbl:
1111  *	Zero out the fields we care about in a tbl structure.
1112  */
1113 zero_tbl(tp)
1114 register STRFILE	*tp;
1115 {
1116 	tp->str_numstr = 0;
1117 	tp->str_longlen = 0;
1118 	tp->str_shortlen = -1;
1119 }
1120 
1121 /*
1122  * sum_tbl:
1123  *	Merge the tbl data of t2 into t1.
1124  */
1125 sum_tbl(t1, t2)
1126 register STRFILE	*t1, *t2;
1127 {
1128 	t1->str_numstr += t2->str_numstr;
1129 	if (t1->str_longlen < t2->str_longlen)
1130 		t1->str_longlen = t2->str_longlen;
1131 	if (t1->str_shortlen > t2->str_shortlen)
1132 		t1->str_shortlen = t2->str_shortlen;
1133 }
1134 
1135 #define	STR(str)	((str) == NULL ? "NULL" : (str))
1136 
1137 /*
1138  * print_file_list:
1139  *	Print out the file list
1140  */
1141 print_file_list()
1142 {
1143 	print_list(File_list, 0);
1144 }
1145 
1146 /*
1147  * print_list:
1148  *	Print out the actual list, recursively.
1149  */
1150 print_list(list, lev)
1151 register FILEDESC	*list;
1152 int			lev;
1153 {
1154 	while (list != NULL) {
1155 		fprintf(stderr, "%*s", lev * 4, "");
1156 		if (list->percent == NO_PROB)
1157 			fprintf(stderr, "___%%");
1158 		else
1159 			fprintf(stderr, "%3d%%", list->percent);
1160 		fprintf(stderr, " %s", STR(list->name));
1161 		DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
1162 			    STR(list->datfile), STR(list->posfile)));
1163 		putc('\n', stderr);
1164 		if (list->child != NULL)
1165 			print_list(list->child, lev + 1);
1166 		list = list->next;
1167 	}
1168 }
1169 
1170 #ifndef	NO_REGEX
1171 /*
1172  * conv_pat:
1173  *	Convert the pattern to an ignore-case equivalent.
1174  */
1175 char *
1176 conv_pat(orig)
1177 register char	*orig;
1178 {
1179 	register char		*sp;
1180 	register unsigned int	cnt;
1181 	register char		*new;
1182 
1183 	cnt = 1;	/* allow for '\0' */
1184 	for (sp = orig; *sp != '\0'; sp++)
1185 		if (isalpha(*sp))
1186 			cnt += 4;
1187 		else
1188 			cnt++;
1189 	if ((new = malloc(cnt)) == NULL) {
1190 		fprintf(stderr, "pattern too long for ignoring case\n");
1191 		exit(1);
1192 	}
1193 
1194 	for (sp = new; *orig != '\0'; orig++) {
1195 		if (islower(*orig)) {
1196 			*sp++ = '[';
1197 			*sp++ = *orig;
1198 			*sp++ = toupper(*orig);
1199 			*sp++ = ']';
1200 		}
1201 		else if (isupper(*orig)) {
1202 			*sp++ = '[';
1203 			*sp++ = *orig;
1204 			*sp++ = tolower(*orig);
1205 			*sp++ = ']';
1206 		}
1207 		else
1208 			*sp++ = *orig;
1209 	}
1210 	*sp = '\0';
1211 	return new;
1212 }
1213 
1214 /*
1215  * find_matches:
1216  *	Find all the fortunes which match the pattern we've been given.
1217  */
1218 find_matches()
1219 {
1220 	Fort_len = maxlen_in_list(File_list);
1221 	DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1222 	/* extra length, "%\n" is appended */
1223 	Fortbuf = do_malloc((unsigned int) Fort_len + 10);
1224 
1225 	Found_one = FALSE;
1226 	matches_in_list(File_list);
1227 	return Found_one;
1228 	/* NOTREACHED */
1229 }
1230 
1231 /*
1232  * maxlen_in_list
1233  *	Return the maximum fortune len in the file list.
1234  */
1235 maxlen_in_list(list)
1236 FILEDESC	*list;
1237 {
1238 	register FILEDESC	*fp;
1239 	register int		len, maxlen;
1240 
1241 	maxlen = 0;
1242 	for (fp = list; fp != NULL; fp = fp->next) {
1243 		if (fp->child != NULL) {
1244 			if ((len = maxlen_in_list(fp->child)) > maxlen)
1245 				maxlen = len;
1246 		}
1247 		else {
1248 			get_tbl(fp);
1249 			if (fp->tbl.str_longlen > maxlen)
1250 				maxlen = fp->tbl.str_longlen;
1251 		}
1252 	}
1253 	return maxlen;
1254 }
1255 
1256 /*
1257  * matches_in_list
1258  *	Print out the matches from the files in the list.
1259  */
1260 matches_in_list(list)
1261 FILEDESC	*list;
1262 {
1263 	register char		*sp;
1264 	register FILEDESC	*fp;
1265 	int			in_file;
1266 
1267 	for (fp = list; fp != NULL; fp = fp->next) {
1268 		if (fp->child != NULL) {
1269 			matches_in_list(fp->child);
1270 			continue;
1271 		}
1272 		DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1273 		open_fp(fp);
1274 		sp = Fortbuf;
1275 		in_file = FALSE;
1276 		while (fgets(sp, Fort_len, fp->inf) != NULL)
1277 			if (!STR_ENDSTRING(sp, fp->tbl))
1278 				sp += strlen(sp);
1279 			else {
1280 				*sp = '\0';
1281 				if (RE_EXEC(Fortbuf)) {
1282 					printf("%c%c", fp->tbl.str_delim,
1283 					    fp->tbl.str_delim);
1284 					if (!in_file) {
1285 						printf(" (%s)", fp->name);
1286 						Found_one = TRUE;
1287 						in_file = TRUE;
1288 					}
1289 					putchar('\n');
1290 					(void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
1291 				}
1292 				sp = Fortbuf;
1293 			}
1294 	}
1295 }
1296 # endif	/* NO_REGEX */
1297 
1298 usage()
1299 {
1300 	(void) fprintf(stderr, "fortune [-a");
1301 #ifdef	DEBUG
1302 	(void) fprintf(stderr, "D");
1303 #endif	/* DEBUG */
1304 	(void) fprintf(stderr, "f");
1305 #ifndef	NO_REGEX
1306 	(void) fprintf(stderr, "i");
1307 #endif	/* NO_REGEX */
1308 	(void) fprintf(stderr, "losw]");
1309 #ifndef	NO_REGEX
1310 	(void) fprintf(stderr, " [-m pattern]");
1311 #endif	/* NO_REGEX */
1312 	(void) fprintf(stderr, "[ [#%%] file/directory/all]\n");
1313 	exit(1);
1314 }
1315