/*      $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.
 */

#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 */


#include        <db.h>
#include        <sys/types.h>
#include        <strings.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>
/*#include        <rx.h>*/
#include        <regexp.h>
#include        "strfile.h"
#include        "pathnames.h"

#define TRUE    1
#define FALSE   0
#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;                   /* 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 */

#ifndef NO_REGEX
#ifdef REGCMP
#define RE_COMP(p)      (Re_pat = regcmp(p, NULL))
#define BAD_COMP(f)     ((f) == NULL)
#define RE_EXEC(p)      regex(Re_pat, (p))

char *Re_pat;

char *regcmp(), *regex();

#else
/*#define RE_COMP(p)      (p = re_comp(p))*/
#define RE_COMP(p)      (Re_pat = regcomp(p, NULL))
/*#define BAD_COMP(f)     ((f) != NULL)*/
#define BAD_COMP(f)     ((f) == NULL)
/*#define RE_EXEC(p)      re_exec(p)*/
#define RE_EXEC(p)      regexec(Re_pat, (p))
regexp* Re_pat;

#endif
#endif

int add_dir __P((register FILEDESC *));

void usage(void)
{
    (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 = rindex(file, '/')) == NULL)
        sp = file;
    else
        sp++;
    if (*sp == '.')
    {
        DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
        return FALSE;
    }
    if ((sp = rindex(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;

    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, "adding file \"%s\"\n", path));
    if ((!is_dir(path) && (fd = open(path, 0)) < 0))
    {
        if (dir == NULL && file[0] != '/')
            if (All_forts)
                return !(add_file(percent, file, FORTDIR, head, tail, parent)
                     & add_file(percent, file, OFFDIR, head, tail, parent));
            else if (Offend)
                return add_file(percent, file, OFFDIR, head, tail, parent);
            else
                return add_file(percent, file, FORTDIR, head, tail, parent);
        if (parent == NULL)
            perror(path);
        /* we really don't want an error message if the file might be in the other directory */
        if (was_malloc)
            free(path);
        return FALSE;
    } close(fd);

    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);
        free((char *) fp);
        if (was_malloc)
            free(path);
        do_free(fp->datfile);
        do_free(fp->posfile);
        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;
    register struct dirent *dirent;
    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_namlen == 0)
            continue;
        name = copy(dirent->d_name, dirent->d_namlen);
        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:osw")) != EOF)
#else
    while ((ch = getopt(argc, argv, "aefilm:n:osw")) != 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 '?':
          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",
                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(fp->path, 0);
    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;

    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));
        fp->fd=open(fp->path, 0);
        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';
                if (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;
            }
        close(fp->fd);
    }
}

/*
 * 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]);      /* <= SLEN); huh? */
    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();
        close(Fortfile->fd);
    }
    while ((Short_only && fortlen() > SLEN) ||
           (Long_only && fortlen() <= SLEN));

    open(Fortfile->path, 0);
    display(Fortfile);

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