ftp.nice.ch/Attic/openStep/unix/games/fortune-mod-9708.m.I.bsd.tgz#/fortune-mod-9708/fortune/fortune.c

This is fortune.c in view mode; [Download] [Up]

/*      $NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $  */

/*-
 * Copyright (c) 1986, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Ken Arnold.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* Modified September, 1995, Amy A. Lewis
 * 1: removed all file-locking dreck.  Unnecessary
 * 2: Fixed bug that made fortune -f report a different list than
 *    fortune with any other parameters, or none, and which forced
 *    the program to read only one file (named 'fortunes')
 * 3: removed the unnecessary print_file_list()
 * 4: Added "OFFDIR" to pathnames.h as the directory in which offensive
 *    fortunes are kept.  This considerably simplifies our life by
 *    permitting us to dispense with a lot of silly tests for the string
 *    "-o" at the end of a filename.
 * 5: I think the problems with trying to find filenames were fixed by
 *    the change in the way that offensive files are defined.  Two birds,
 *    one stone!
 * 6: Calculated probabilities for all files, so that -f will print them.
 */

/* Changes Copyright (c) 1997 Dennis L. Clark.  All rights reserved.
 *
 *    The changes in this file may be freely redistributed, modified or
 *    included in other software, as long as both the above copyright
 *    notice and these conditions appear intact.
 */

/* Modified May 1997, Dennis L. Clark (dbugger@progsoc.uts.edu.au)
 *  + Various portability fixes
 *  + Percent selection of files with -a now works on datafiles which
 *    appear in both unoffensive and offensive directories (see man page
 *    for details)
 *  + The -s and -l options are now more consistant in their
 *    interpretation of fortune length
 *  + The -s and -l options can now be combined wit the -m option
 */

#if 0				/* comment out the stuff here, and get rid of silly warnings */
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1986, 1993\n\
	The Regents of the University of California.  All rights reserved.\n";

#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)fortune.c	8.1 (Berkeley) 5/31/93";

#else
static char rcsid[] = "$NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $";

#endif
#endif /* not lint */
#endif /* killing warnings */

#define		PROGRAM_NAME		"fortune-mod"
#define		PROGRAM_VERSION		"9708"

#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/param.h>
#include	<sys/stat.h>
#include	<netinet/in.h>

#include	<time.h>
#include	<dirent.h>
#include	<fcntl.h>
#include	<assert.h>
#include	<unistd.h>
#include	<stdio.h>
#include	<ctype.h>
#include	<stdlib.h>
#include	<string.h>

#ifdef NeXT
	#include	<libc.h>
	#include	<sys/dir.h>
	#define	strdup	NXCopyStringBuffer
#endif

/* This makes GNU libc to prototype the BSD regex functions */
#ifdef BSD_REGEX
#define	_REGEX_RE_COMP
#endif

#ifdef HAVE_REGEX_H
#include	<regex.h>
#endif
#ifdef HAVE_REGEXP_H
#include	<regexp.h>
#endif
#ifdef HAVE_RX_H
#include	<rx.h>
#endif

#include	"strfile.h"

#ifndef NeXT
	#define	TRUE	1
	#define	FALSE	0
#endif
#define	bool	short

#define	MINW	6		/* minimum wait if desired */
#define	CPERS	20		/* # of chars for each sec */

#define	POS_UNKNOWN	((off_t) -1)	/* pos for file unknown */
#define	NO_PROB		(-1)	/* no prob specified for file */

#ifdef DEBUG
#define	DPRINTF(l,x)	if (Debug >= l) fprintf x;
#undef		NDEBUG
#else
#define	DPRINTF(l,x)
#define	NDEBUG	1
#endif

typedef struct fd
{
    int percent;
    int fd, datfd;
    off_t pos;
    FILE *inf;
    char *name;
    char *path;
    char *datfile, *posfile;
    bool read_tbl;
    bool was_pos_file;
    STRFILE tbl;
    int num_children;
    struct fd *child, *parent;
    struct fd *next, *prev;
}
FILEDESC;

bool Found_one;			/* did we find a match? */
bool Find_files = FALSE;	/* just find a list of proper fortune files */
bool Wait = FALSE;		/* wait desired after fortune */
bool Short_only = FALSE;	/* short fortune desired */
bool Long_only = FALSE;		/* long fortune desired */
bool Offend = FALSE;		/* offensive fortunes only */
bool All_forts = FALSE;		/* any fortune allowed */
bool Equal_probs = FALSE;	/* scatter un-allocated prob equally */

#ifndef NO_REGEX
bool Match = FALSE;		/* dump fortunes matching a pattern */

#endif
#ifdef DEBUG
bool Debug = FALSE;		/* print debug messages */

#endif

unsigned char *Fortbuf = NULL;	/* fortune buffer for -m */

int Fort_len = 0, Spec_prob = 0,	/* total prob specified on cmd line */
  Num_files, Num_kids,		/* totals of files and children. */
  SLEN = 160;			/* max. characters in a "short" fortune */

off_t Seekpts[2];		/* seek pointers to fortunes */

FILEDESC *File_list = NULL,	/* Head of file list */
 *File_tail = NULL;		/* Tail of file list */
FILEDESC *Fortfile;		/* Fortune file to use */

STRFILE Noprob_tbl;		/* sum of data for all no prob files */

#ifdef BSD_REGEX

#define	RE_COMP(p)	re_comp(p)
#define	BAD_COMP(f)	((f) != NULL)
#define	RE_EXEC(p)	re_exec(p)

#else

#ifdef POSIX_REGEX
#define	RE_COMP(p)	regcomp(&Re_pat, (p), REG_NOSUB)
#define	BAD_COMP(f)	((f) != 0)
#define	RE_EXEC(p)	(regexec(&Re_pat, (p), 0, NULL, 0) == 0)

regex_t Re_pat;
#else
#define NO_REGEX
#endif /* POSIX_REGEX */

#endif /* BSD_REGEX */

int add_dir(register FILEDESC *);

char *program_version(void)
{
    static char buf[BUFSIZ];
    (void) sprintf(buf, "%s version %s", PROGRAM_NAME, PROGRAM_VERSION);
    return buf;
}

void usage(void)
{
    (void) fprintf(stderr, "%s\n",program_version());
    (void) fprintf(stderr, "fortune [-a");
#ifdef	DEBUG
    (void) fprintf(stderr, "D");
#endif /* DEBUG */
    (void) fprintf(stderr, "f");
#ifndef	NO_REGEX
    (void) fprintf(stderr, "i");
#endif /* NO_REGEX */
    (void) fprintf(stderr, "losw]");
#ifndef	NO_REGEX
    (void) fprintf(stderr, " [-m pattern]");
#endif /* NO_REGEX */
    (void) fprintf(stderr, " [-n number] [ [#%%] file/directory/all]\n");
    exit(1);
}

#define	STR(str)	((str) == NULL ? "NULL" : (str))


/*
 * calc_equal_probs:
 *      Set the global values for number of files/children, to be used
 * in printing probabilities when listing files
 */
void calc_equal_probs(void)
{
    FILEDESC *fiddlylist;

    Num_files = Num_kids = 0;
    fiddlylist = File_list;
    while (fiddlylist != NULL)
    {
	Num_files++;
	Num_kids += fiddlylist->num_children;
	fiddlylist = fiddlylist->next;
    }
}

/*
 * print_list:
 *      Print out the actual list, recursively.
 */
void print_list(register FILEDESC * list, int lev)
{
    while (list != NULL)
    {
	fprintf(stderr, "%*s", lev * 4, "");
	if (list->percent == NO_PROB)
	    if (!Equal_probs)
/* This, with some changes elsewhere, gives proper percentages for every case
 * fprintf(stderr, "___%%"); */
		fprintf(stderr, "%5.2f%%", (100.0 - Spec_prob) *
			list->tbl.str_numstr / Noprob_tbl.str_numstr);
	    else if (lev == 0)
		fprintf(stderr, "%5.2f%%", 100.0 / Num_files);
	    else
		fprintf(stderr, "%5.2f%%", 100.0 / Num_kids);
	else
	    fprintf(stderr, "%5.2f%%", 1.0 * list->percent);
	fprintf(stderr, " %s", STR(list->name));
	DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
		    STR(list->datfile), STR(list->posfile)));
	putc('\n', stderr);
	if (list->child != NULL)
	    print_list(list->child, lev + 1);
	list = list->next;
    }
}

#ifndef	NO_REGEX
/*
 * conv_pat:
 *      Convert the pattern to an ignore-case equivalent.
 */
char *conv_pat(register char *orig)
{
    register char *sp;
    register unsigned int cnt;
    register char *new;

    cnt = 1;			/* allow for '\0' */
    for (sp = orig; *sp != '\0'; sp++)
	if (isalpha(*sp))
	    cnt += 4;
	else
	    cnt++;
    if ((new = malloc(cnt)) == NULL)
    {
	fprintf(stderr, "pattern too long for ignoring case\n");
	exit(1);
    }

    for (sp = new; *orig != '\0'; orig++)
    {
	if (islower(*orig))
	{
	    *sp++ = '[';
	    *sp++ = *orig;
	    *sp++ = toupper(*orig);
	    *sp++ = ']';
	}
	else if (isupper(*orig))
	{
	    *sp++ = '[';
	    *sp++ = *orig;
	    *sp++ = tolower(*orig);
	    *sp++ = ']';
	}
	else
	    *sp++ = *orig;
    }
    *sp = '\0';
    return new;
}
#endif /* NO_REGEX */

/*
 * do_malloc:
 *      Do a malloc, checking for NULL return.
 */
void *do_malloc(unsigned int size)
{
    void *new;

    if ((new = malloc(size)) == NULL)
    {
	(void) fprintf(stderr, "fortune: out of memory.\n");
	exit(1);
    }
    return new;
}

/*
 * do_free:
 *      Free malloc'ed space, if any.
 */
void do_free(void *ptr)
{
    if (ptr != NULL)
	free(ptr);
}

/*
 * copy:
 *      Return a malloc()'ed copy of the string
 */
char *copy(char *str, unsigned int len)
{
    char *new, *sp;

    new = do_malloc(len + 1);
    sp = new;
    do
    {
	*sp++ = *str;
    }
    while (*str++);
    return new;
}

/*
 * new_fp:
 *      Return a pointer to an initialized new FILEDESC.
 */
FILEDESC *new_fp(void)
{
    register FILEDESC *fp;

    fp = (FILEDESC *) do_malloc(sizeof *fp);
    fp->datfd = -1;
    fp->pos = POS_UNKNOWN;
    fp->inf = NULL;
    fp->fd = -1;
    fp->percent = NO_PROB;
    fp->read_tbl = FALSE;
    fp->next = NULL;
    fp->prev = NULL;
    fp->child = NULL;
    fp->parent = NULL;
    fp->datfile = NULL;
    fp->posfile = NULL;
    return fp;
}

/*
 * is_dir:
 *      Return TRUE if the file is a directory, FALSE otherwise.
 */
int is_dir(char *file)
{
    auto struct stat sbuf;

    if (stat(file, &sbuf) < 0)
	return FALSE;
    return (sbuf.st_mode & S_IFDIR);
}

/*
 * is_fortfile:
 *      Return TRUE if the file is a fortune database file.  We try and
 *      exclude files without reading them if possible to avoid
 *      overhead.  Files which start with ".", or which have "illegal"
 *      suffixes, as contained in suflist[], are ruled out.
 */
int is_fortfile(char *file, char **datp, char **posp)
{
    register int i;
    register char *sp;
    register char *datfile;
    static char *suflist[] =
    {				/* list of "illegal" suffixes" */
	"dat", "pos", "c", "h", "p", "i", "f",
	"pas", "ftn", "ins.c", "ins,pas",
	"ins.ftn", "sml",
	NULL
    };

    DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));

    if ((sp = strrchr(file, '/')) == NULL)
	sp = file;
    else
	sp++;
    if (*sp == '.')
    {
	DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
	return FALSE;
    }
    if ((sp = strrchr(sp, '.')) != NULL)
    {
	sp++;
	for (i = 0; suflist[i] != NULL; i++)
	    if (strcmp(sp, suflist[i]) == 0)
	    {
		DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
		return FALSE;
	    }
    }

    datfile = copy(file, (unsigned int) (strlen(file) + 4));	/* +4 for ".dat" */
    strcat(datfile, ".dat");
    if (access(datfile, R_OK) < 0)
    {
	free(datfile);
	DPRINTF(2, (stderr, "FALSE (no \".dat\" file)\n"));
	return FALSE;
    }
    if (datp != NULL)
	*datp = datfile;
    else
	free(datfile);
    DPRINTF(2, (stderr, "TRUE\n"));
    return TRUE;
}

/*
 * add_file:
 *      Add a file to the file list.
 */
int add_file(int percent, register char *file, char *dir,
	     FILEDESC ** head, FILEDESC ** tail, FILEDESC * parent)
{
    register FILEDESC *fp;
    register int fd;
    register char *path;
    register bool was_malloc;
    register bool isdir;
    auto char *sp;
    auto bool found;

    if (dir == NULL)
    {
	path = file;
	was_malloc = FALSE;
    }
    else
    {
	path = do_malloc((unsigned int) (strlen(dir) + strlen(file) + 2));
	(void) strcat(strcat(strcpy(path, dir), "/"), file);
	was_malloc = TRUE;
    }
    if ((isdir = is_dir(path)) && parent != NULL)
    {
	if (was_malloc)
	    free(path);
	return FALSE;		/* don't recurse */
    }

    DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
    if ((fd = open(path, 0)) < 0)
    {
	found = FALSE;
	if (dir == NULL && (strchr(file,'/') == NULL))
	    if ( ((sp = strrchr(file,'-')) != NULL) && (strcmp(sp,"-o") == 0) )
	    {
		/* BSD-style '-o' offensive file suffix */
		*sp = '\0';
		found = add_file(percent, file, OFFDIR, head, tail, parent);
		/* put the suffix back in for better identification later */
		*sp = '-';
	    }
	    else if (All_forts)
		found = (add_file(percent, file, FORTDIR, head, tail, parent)
		    || add_file(percent, file, OFFDIR, head, tail, parent));
	    else if (Offend)
		found = add_file(percent, file, OFFDIR, head, tail, parent);
	    else
		found = add_file(percent, file, FORTDIR, head, tail, parent);
	if (!found && parent == NULL && dir == NULL)
	    perror(path);
	if (was_malloc)
	    free(path);
	return found;
    }

    DPRINTF(2, (stderr, "path = \"%s\"\n", path));

    fp = new_fp();
    fp->fd = fd;
    fp->percent = percent;
    fp->name = file;
    fp->path = path;
    fp->parent = parent;

    if ((isdir && !add_dir(fp)) ||
	(!isdir &&
	 !is_fortfile(path, &fp->datfile, &fp->posfile)))
    {
	if (parent == NULL)
	    fprintf(stderr,
		    "fortune:%s not a fortune file or directory\n",
		    path);
	if (was_malloc)
	    free(path);
	do_free(fp->datfile);
	do_free(fp->posfile);
	if (fp->fd >= 0) close(fp->fd);
	free(fp);
	return FALSE;
    }
    if (*head == NULL)
	*head = *tail = fp;
    else if (fp->percent == NO_PROB)
    {
	(*tail)->next = fp;
	fp->prev = *tail;
	*tail = fp;
    }
    else
    {
	(*head)->prev = fp;
	fp->next = *head;
	*head = fp;
    }

    return TRUE;
}

/*
 * add_dir:
 *      Add the contents of an entire directory.
 */
int add_dir(register FILEDESC * fp)
{
    register DIR *dir;
#ifdef NeXT
	register struct direct *dirent;
#else
    register struct dirent *dirent;
#endif
    auto FILEDESC *tailp;
    auto char *name;

    close(fp->fd);
    fp->fd = -1;
    if ((dir = opendir(fp->path)) == NULL)
    {
	perror(fp->path);
	return FALSE;
    }
    tailp = NULL;
    DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
    fp->num_children = 0;
    while ((dirent = readdir(dir)) != NULL)
    {
	if (dirent->d_name[0] == 0)
	    continue;
	name = strdup(dirent->d_name);
	if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
	    fp->num_children++;
	else
	    free(name);
    }
    if (fp->num_children == 0)
    {
	fprintf(stderr,
		"fortune: %s: No fortune files in directory.\n", fp->path);
	return FALSE;
    }
    return TRUE;
}

/*
 * form_file_list:
 *      Form the file list from the file specifications.
 */
int form_file_list(register char **files, register int file_cnt)
{
    register int i, percent;
    register char *sp;

    if (file_cnt == 0)
	if (All_forts)
	    return (add_file(NO_PROB, FORTDIR, NULL, &File_list,
			     &File_tail, NULL)
		    & add_file(NO_PROB, OFFDIR, NULL, &File_list,
			       &File_tail, NULL));
	else if (Offend)
	    return add_file(NO_PROB, OFFDIR, NULL, &File_list,
			    &File_tail, NULL);
	else
	    return add_file(NO_PROB, FORTDIR, NULL, &File_list,
			    &File_tail, NULL);
    for (i = 0; i < file_cnt; i++)
    {
	percent = NO_PROB;
	if (!isdigit(files[i][0]))
	    sp = files[i];
	else
	{
	    percent = 0;
	    for (sp = files[i]; isdigit(*sp); sp++)
		percent = percent * 10 + *sp - '0';
	    if (percent > 100)
	    {
		fprintf(stderr, "percentages must be <= 100\n");
		return FALSE;
	    }
	    if (*sp == '.')
	    {
		fprintf(stderr, "percentages must be integers\n");
		return FALSE;
	    }
	    /*
	     * If the number isn't followed by a '%', then
	     * it was not a percentage, just the first part
	     * of a file name which starts with digits.
	     */
	    if (*sp != '%')
	    {
		percent = NO_PROB;
		sp = files[i];
	    }
	    else if (*++sp == '\0')
	    {
		if (++i >= file_cnt)
		{
		    fprintf(stderr, "percentages must precede files\n");
		    return FALSE;
		}
		sp = files[i];
	    }
	}
	if (strcmp(sp, "all") == 0)
	    sp = FORTDIR;
	if (!add_file(percent, sp, NULL, &File_list, &File_tail, NULL))
	    return FALSE;
    }
    return TRUE;
}

/*
 *    This routine evaluates the arguments on the command line
 */
void getargs(int argc, char **argv)
{
    register int ignore_case;

#ifndef NO_REGEX
    register char *pat = NULL;

#endif /* NO_REGEX */
    extern char *optarg;
    extern int optind;
    int ch;

    ignore_case = FALSE;

#ifdef DEBUG
    while ((ch = getopt(argc, argv, "aDefilm:n:osvw")) != EOF)
#else
    while ((ch = getopt(argc, argv, "aefilm:n:osvw")) != EOF)
#endif /* DEBUG */
	switch (ch)
	  {
	  case 'a':		/* any fortune */
	      All_forts++;
	      break;
#ifdef DEBUG
	  case 'D':
	      Debug++;
	      break;
#endif /* DEBUG */
	  case 'e':
	      Equal_probs++;	/* scatter un-allocted prob equally */
	      break;
	  case 'f':		/* find fortune files */
	      Find_files++;
	      break;
	  case 'l':		/* long ones only */
	      Long_only++;
	      Short_only = FALSE;
	      break;
	  case 'n':
	      SLEN = atoi(optarg);
	      break;
	  case 'o':		/* offensive ones only */
	      Offend++;
	      break;
	  case 's':		/* short ones only */
	      Short_only++;
	      Long_only = FALSE;
	      break;
	  case 'w':		/* give time to read */
	      Wait++;
	      break;
#ifdef	NO_REGEX
	  case 'i':		/* case-insensitive match */
	  case 'm':		/* dump out the fortunes */
	      (void) fprintf(stderr,
		  "fortune: can't match fortunes on this system (Sorry)\n");
	      exit(0);
#else /* NO_REGEX */
	  case 'm':		/* dump out the fortunes */
	      Match++;
	      pat = optarg;
	      break;
	  case 'i':		/* case-insensitive match */
	      ignore_case++;
	      break;
#endif /* NO_REGEX */
	  case 'v':
	      (void) printf("%s\n", program_version());
	      exit(0);
	  case '?':
	  default:
	      usage();
	  }
    argc -= optind;
    argv += optind;

    if (!form_file_list(argv, argc))
	exit(1);		/* errors printed through form_file_list() */
#ifdef DEBUG
/*      if (Debug >= 1)
 * print_list(File_list, 0); */
#endif /* DEBUG */
/* If (Find_files) print_list() moved to main */
#ifndef NO_REGEX
    if (pat != NULL)
    {
	if (ignore_case)
	    pat = conv_pat(pat);
	if (BAD_COMP(RE_COMP(pat)))
	{
#ifndef REGCMP
	    fprintf(stderr, "%s\n", pat);
#else /* REGCMP */
	    fprintf(stderr, "bad pattern: %s\n", pat);
#endif /* REGCMP */
	}
    }
#endif /* NO_REGEX */
}

/*
 * init_prob:
 *      Initialize the fortune probabilities.
 */
void init_prob(void)
{
    register FILEDESC *fp, *last;
    register int percent, num_noprob, frac;

    /*
     * Distribute the residual probability (if any) across all
     * files with unspecified probability (i.e., probability of 0)
     * (if any).
     */

    percent = 0;
    num_noprob = 0;
    last = NULL;
    for (fp = File_tail; fp != NULL; fp = fp->prev)
	if (fp->percent == NO_PROB)
	{
	    num_noprob++;
	    if (Equal_probs)
		last = fp;
	}
	else
	    percent += fp->percent;
    DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
		percent, num_noprob));
    if (percent > 100)
    {
	fprintf(stderr,
		"fortune: probabilities sum to %d%%!\n", percent);
	exit(1);
    }
    else if (percent < 100 && num_noprob == 0)
    {
	fprintf(stderr,
		"fortune: no place to put residual probability (%d%%)\n",
		percent);
	exit(1);
    }
    else if (percent == 100 && num_noprob != 0)
    {
	fprintf(stderr,
		"fortune: no probability left to put in residual files\n");
	exit(1);
    }
    Spec_prob = percent;	/* this is for -f when % is specified on cmd line */
    percent = 100 - percent;
    if (Equal_probs)
	if (num_noprob != 0)
	{
	    if (num_noprob > 1)
	    {
		frac = percent / num_noprob;
		DPRINTF(1, (stderr, ", frac = %d%%", frac));
		for (fp = File_list; fp != last; fp = fp->next)
		    if (fp->percent == NO_PROB)
		    {
			fp->percent = frac;
			percent -= frac;
		    }
	    }
	    last->percent = percent;
	    DPRINTF(1, (stderr, ", residual = %d%%", percent));
	}
	else
	{
	    DPRINTF(1, (stderr,
			", %d%% distributed over remaining fortunes\n",
			percent));
	}
    DPRINTF(1, (stderr, "\n"));

#ifdef DEBUG
/*      if (Debug >= 1)
     * print_list(File_list, 0); *//* Causes crash with new %% code */
#endif
}

/*
 * zero_tbl:
 *      Zero out the fields we care about in a tbl structure.
 */
void zero_tbl(register STRFILE * tp)
{
    tp->str_numstr = 0;
    tp->str_longlen = 0;
    tp->str_shortlen = -1;
}

/*
 * sum_tbl:
 *      Merge the tbl data of t2 into t1.
 */
void sum_tbl(register STRFILE * t1, register STRFILE * t2)
{
    t1->str_numstr += t2->str_numstr;
    if (t1->str_longlen < t2->str_longlen)
	t1->str_longlen = t2->str_longlen;
    if (t1->str_shortlen > t2->str_shortlen)
	t1->str_shortlen = t2->str_shortlen;
}

/*
 * get_tbl:
 *      Get the tbl data file the datfile.
 */
void get_tbl(FILEDESC * fp)
{
    auto int fd;
    register FILEDESC *child;

    if (fp->read_tbl)
	return;
    if (fp->child == NULL)
    {
	if ((fd = open(fp->datfile, 0)) < 0)
	{
	    perror(fp->datfile);
	    exit(1);
	}
	if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl)
	{
	    fprintf(stderr,
		    "fortune: %s corrupted\n", fp->path);
	    exit(1);
	}
	/* fp->tbl.str_version = ntohl(fp->tbl.str_version); */
	fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
	fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
	fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
	fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
	close(fd);
    }
    else
    {
	zero_tbl(&fp->tbl);
	for (child = fp->child; child != NULL; child = child->next)
	{
	    get_tbl(child);
	    sum_tbl(&fp->tbl, &child->tbl);
	}
    }
    fp->read_tbl = TRUE;
}

/*
 * sum_noprobs:
 *      Sum up all the noprob probabilities, starting with fp.
 */
void sum_noprobs(register FILEDESC * fp)
{
    static bool did_noprobs = FALSE;

    if (did_noprobs)
	return;
    zero_tbl(&Noprob_tbl);
    while (fp != NULL)
    {
	get_tbl(fp);
	/* This conditional should help us return correct values for -f
	 * when a percentage is specified */
	if (fp->percent == NO_PROB)
	    sum_tbl(&Noprob_tbl, &fp->tbl);
	fp = fp->next;
    }
    did_noprobs = TRUE;
}

/*
 * pick_child
 *      Pick a child from a chosen parent.
 */
FILEDESC *pick_child(FILEDESC * parent)
{
    register FILEDESC *fp;
    register int choice;

    if (Equal_probs)
    {
	choice = random() % parent->num_children;
	DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
		    choice, parent->num_children));
	for (fp = parent->child; choice--; fp = fp->next)
	    continue;
	DPRINTF(1, (stderr, "    using %s\n", fp->name));
	return fp;
    }
    else
    {
	get_tbl(parent);
	choice = random() % parent->tbl.str_numstr;
	DPRINTF(1, (stderr, "    choice = %d (of %ld)\n",
		    choice, parent->tbl.str_numstr));
	for (fp = parent->child; choice >= fp->tbl.str_numstr;
	     fp = fp->next)
	{
	    choice -= fp->tbl.str_numstr;
	    DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n",
			fp->name, fp->tbl.str_numstr, choice));
	}
	DPRINTF(1, (stderr, "    using %s, %ld\n", fp->name,
		    fp->tbl.str_numstr));
	return fp;
    }
}

/*
 * open_dat:
 *      Open up the dat file if we need to.
 */
void open_dat(FILEDESC * fp)
{
    if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, 0)) < 0)
    {
	perror(fp->datfile);
	exit(1);
    }
}

/*
 * get_pos:
 *      Get the position from the pos file, if there is one.  If not,
 *      return a random number.
 */
void get_pos(FILEDESC * fp)
{
    assert(fp->read_tbl);
    if (fp->pos == POS_UNKNOWN)
    {
	fp->pos = random() % fp->tbl.str_numstr;
    }
    if (++(fp->pos) >= fp->tbl.str_numstr)
	fp->pos -= fp->tbl.str_numstr;
    DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
}

/*
 * get_fort:
 *      Get the fortune data file's seek pointer for the next fortune.
 */
void get_fort(void)
{
    register FILEDESC *fp;
    register int choice;

    if (File_list->next == NULL || File_list->percent == NO_PROB)
	fp = File_list;
    else
    {
	choice = random() % 100;
	DPRINTF(1, (stderr, "choice = %d\n", choice));
	for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
	    if (choice < fp->percent)
		break;
	    else
	    {
		choice -= fp->percent;
		DPRINTF(1, (stderr,
			    "    skip \"%s\", %d%% (choice = %d)\n",
			    fp->name, fp->percent, choice));
	    }
	DPRINTF(1, (stderr,
		    "using \"%s\", %d%% (choice = %d)\n",
		    fp->name, fp->percent, choice));
    }
    if (fp->percent != NO_PROB)
	get_tbl(fp);
    else
    {
	if (fp->next != NULL)
	{
	    sum_noprobs(fp);
	    choice = random() % Noprob_tbl.str_numstr;
	    DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
			Noprob_tbl.str_numstr));
	    while (choice >= fp->tbl.str_numstr)
	    {
		choice -= fp->tbl.str_numstr;
		fp = fp->next;
		DPRINTF(1, (stderr,
			    "    skip \"%s\", %ld (choice = %d)\n",
			    fp->name, fp->tbl.str_numstr,
			    choice));
	    }
	    DPRINTF(1, (stderr, "using \"%s\", %ld\n", fp->name,
			fp->tbl.str_numstr));
	}
	get_tbl(fp);
    }
    if (fp->child != NULL)
    {
	DPRINTF(1, (stderr, "picking child\n"));
	fp = pick_child(fp);
    }
    Fortfile = fp;
    get_pos(fp);
    open_dat(fp);
    lseek(fp->datfd,
	  (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), 0);
    read(fp->datfd, Seekpts, sizeof Seekpts);
    Seekpts[0] = ntohl(Seekpts[0]);
    Seekpts[1] = ntohl(Seekpts[1]);
}

/*
 * open_fp:
 *      Assocatiate a FILE * with the given FILEDESC.
 */
void open_fp(FILEDESC * fp)
{
    if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL)
    {
	perror(fp->path);
	exit(1);
    }
}

#ifndef	NO_REGEX
/*
 * maxlen_in_list
 *      Return the maximum fortune len in the file list.
 */
int maxlen_in_list(FILEDESC * list)
{
    register FILEDESC *fp;
    register int len, maxlen;

    maxlen = 0;
    for (fp = list; fp != NULL; fp = fp->next)
    {
	if (fp->child != NULL)
	{
	    if ((len = maxlen_in_list(fp->child)) > maxlen)
		maxlen = len;
	}
	else
	{
	    get_tbl(fp);
	    if (fp->tbl.str_longlen > maxlen)
		maxlen = fp->tbl.str_longlen;
	}
    }
    return maxlen;
}

/*
 * matches_in_list
 *      Print out the matches from the files in the list.
 */
void matches_in_list(FILEDESC * list)
{
    unsigned char *sp;
    register FILEDESC *fp;
    int in_file, nchar;

    for (fp = list; fp != NULL; fp = fp->next)
    {
	if (fp->child != NULL)
	{
	    matches_in_list(fp->child);
	    continue;
	}
	DPRINTF(1, (stderr, "searching in %s\n", fp->path));
	open_fp(fp);
	sp = Fortbuf;
	in_file = FALSE;
	while (fgets(sp, Fort_len, fp->inf) != NULL)
	    if (!STR_ENDSTRING(sp, fp->tbl))
		sp += strlen(sp);
	    else
	    {
		*sp = '\0';
		nchar = sp - Fortbuf;
		DPRINTF(1, (stdout, "nchar = %d\n", nchar));
		if ( (nchar < SLEN || !Short_only) &&
			(nchar > SLEN || !Long_only) &&
			RE_EXEC(Fortbuf) )
		{
		    if (!in_file)
		    {
			fprintf(stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
			Found_one = TRUE;
			in_file = TRUE;
		    }
		    fwrite(Fortbuf, 1, sp - Fortbuf, stdout);
		    printf("%c\n", fp->tbl.str_delim);
		}
		sp = Fortbuf;
	    }
    }
}

/*
 * find_matches:
 *      Find all the fortunes which match the pattern we've been given.
 */
int find_matches(void)
{
    Fort_len = maxlen_in_list(File_list);
    DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
    /* extra length, "%\n" is appended */
    Fortbuf = do_malloc((unsigned int) Fort_len + 10);

    Found_one = FALSE;
    matches_in_list(File_list);
    return Found_one;
    /* NOTREACHED */
}
#endif /* NO_REGEX */

void display(FILEDESC * fp)
{
    register char *p, ch;
    unsigned char line[BUFSIZ];

    open_fp(fp);
    fseek(fp->inf, (long) Seekpts[0], 0);
    for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
	 !STR_ENDSTRING(line, fp->tbl); Fort_len++)
    {
	if (fp->tbl.str_flags & STR_ROTATED)
	    for (p = line; (ch = *p); ++p)
		if (isupper(ch))
		    *p = 'A' + (ch - 'A' + 13) % 26;
		else if (islower(ch))
		    *p = 'a' + (ch - 'a' + 13) % 26;
	fputs(line, stdout);
    }
    fflush(stdout);
}

/*
 * fortlen:
 *      Return the length of the fortune.
 */
int fortlen(void)
{
    register int nchar;
    unsigned char line[BUFSIZ];

    if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
	nchar = (Seekpts[1] - Seekpts[0]) - 2;	/* for %^J delimiter */
    else
    {
	open_fp(Fortfile);
	fseek(Fortfile->inf, (long) Seekpts[0], 0);
	nchar = 0;
	while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
	       !STR_ENDSTRING(line, Fortfile->tbl))
	    nchar += strlen(line);
    }
    Fort_len = nchar;
    return nchar;
}

int max(register int i, register int j)
{
    return (i >= j ? i : j);
}

int main(int ac, char *av[])
{
    getargs(ac, av);

#ifndef NO_REGEX
    if (Match)
	exit(find_matches() != 0);
#endif
    init_prob();
    if (Find_files)
    {
	sum_noprobs(File_list);
	if (Equal_probs)
	    calc_equal_probs();
	print_list(File_list, 0);
	exit(0);
    }
    srandom((int) (time((time_t *) NULL) + getpid()));
    do
    {
	get_fort();
    }
    while ((Short_only && fortlen() > SLEN) ||
	   (Long_only && fortlen() <= SLEN));

    display(Fortfile);

    if (Wait)
    {
        fortlen();
	sleep((unsigned int) max(Fort_len / CPERS, MINW));
    }
    exit(0);
    /* NOTREACHED */
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.