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