/* Copyright (c) 1998 Shevek
* All rights reserved
*
* GNU fortune 1.0
*
* From an idea in BSD fortune.c - I think that this file has been redesigned
* sufficiently including major flow control modifications and a brand new
* memory model. I have tried to maintain the BSD style of the code. However
* it has been ENTIRELY rewritten and typed from scratch and is NOT a
* modified copy of fortune.c.
*
* This file is Copyright (c) to Shevek <shevek@anarres.org> and
* is distributed under the GNU Public License version 2, or such later
* version as may come into effect.
*
* 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 SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
/* View this file with tabstops set to 4 spaces.
* less -x4 gfortune.c for less
* :set ts=4 for vi
* expand -t4 gfortune.c for expand
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <strings.h>
#include <dirent.h> /* For opendir(3) et al */
#include <assert.h> /* For assert(3) */
#include <ctype.h> /* For character classification */
#include <regex.h> /* For regcomp, regexec */
#include <time.h> /* For getting a random number seed */
#include <sys/types.h> /* For char class et al */
#include <sys/stat.h> /* For stat(3) */
#include <netinet/in.h> /* For ntohl(3) */
# include "strfile.h"
/* Declarations */
#ifndef FORTDIR
#define FORTDIR "/var/lib/games/fortunes"
#endif
#ifdef DEBUG
# undef NDEBUG
int Debug = 0;
# define DPRINTF(l,x) if (Debug >= l) { fprintf (stderr, "<%d>", l); fprintf x; }
#else /* DEBUG */
# define NDEBUG
# define DPRINTF(l,x)
#endif /* DEBUG */
/* All these are in the wrong or non-canonical order. SEP. */
/* General declarations */
# define TRUE 1
# define FALSE 0
# define bool short
/* Environment declarations */
/* Need to define min wait, short length, chars per second etc */
# define NO_PROB (-1) /* No probability for file */
/* Check this one out... WTF is it? */
# define POS_UNKNOWN ((unsigned long) -1) /* pos for file unknown */
#define CPERS 40
#define MIN_WAIT 6
/* System-dependent declarations */
#ifdef SYSV
# define index strchr
# define rindex strrchr
#endif /* SYSV */
/* I don't know anything about SYSV anyhow. This is Linux software. */
/* I don't care. */
typedef struct fd {
int percent;
int fd, datfd;
unsigned long 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;
unsigned long num_files;
unsigned long is_offensive;
} FILEDESC;
typedef struct _args {
bool all_fortunes;
int technique;
bool print_list;
bool ignore_case;
bool long_only;
bool match_pattern;
bool offensive;
bool no_recurse;
bool short_only;
bool pause;
char *reg_pattern;
int long_length;
int short_length;
int max_lines;
char *prog_name;
} ARGS;
/* Globals */
ARGS args;
/* Prototypes */
int add_dir (FILEDESC *fp);
int
max (i, j)
int i;
int j;
{
return (i >= j ? i : j);
}
char *
do_malloc(size)
unsigned int size;
{
char *new;
if ((new = (char *)malloc(size)) == NULL) {
fprintf(stderr, "%s: out of memory\n", args.prog_name);
exit(1);
}
return new;
}
char *
copy(str, len)
char *str;
unsigned int len;
{
char *new, *sp;
new = do_malloc(len + 1);
sp = new;
strcpy (new, str);
return new;
}
inline void
zero_tbl (tblp)
STRFILE *tblp;
{
tblp->str_numstr = 0;
tblp->str_longlen = 0;
tblp->str_shortlen = -1;
}
FILEDESC *
new_fp()
{
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;
fp->num_files = 0; /* We need to set this to 1 in add_file */
fp->num_children = 0; /* Surely this should be here? */
fp->is_offensive = 0;
#ifdef DEBUG
zero_tbl (&fp->tbl);
#endif /* DEBUG */
return fp;
}
int
is_dir(file)
char *file;
{
struct stat sbuf;
if (stat(file, &sbuf) < 0)
return FALSE;
return (sbuf.st_mode & S_IFDIR);
}
// S_IROTH to test for world readability
inline void
open_fp (fp)
FILEDESC *fp;
{
if (fp->inf == NULL && (fp->inf = fopen(fp->path, "r")) == NULL) {
perror (fp->path);
exit(1);
}
}
inline void
open_dat (fp)
FILEDESC *fp;
{
if ((fp->datfd = open (fp->datfile, O_RDONLY)) < 0) {
perror (fp->datfile);
exit(1);
}
}
int
is_fortfile (file, datp, posp, check_for_offend)
char *file;
char **datp, **posp;
int check_for_offend;
{
int i;
char *sp;
char *datfile;
static char *suflist[] = { /* List of illegal suffices */
/* Is this still true? */
"dat", "pos",
#ifdef DEBUG /* I don't think these are relevant nowadays */
"c", "h", "p", "i", "f",
"pas", "ftn", "ins", "pas", "sml",
#endif /* DEBUG */
NULL
};
DPRINTF (3, (stderr, " Running is_fortfile on \"%s\"\n", file));
/* We no longer check for offensiveness here */
/* Point sp to the first char of the filename a la` basename(1) */
if ((sp = rindex (file, '/')) == NULL)
sp = file;
else
sp++;
/* Extension check */
if ((sp = rindex(sp, '.')) != NULL) {
sp++;
for (i=0; suflist[i] != NULL; i++)
if (strcmp(sp, suflist[i]) == 0) {
DPRINTF (4, (stderr, " FALSE (file has extension \".%s\")\n", sp));
return FALSE;
}
}
DPRINTF (4, (stderr, " Extension OK \"%s\"\n", file));
datfile = copy (file, (unsigned int)(strlen(file) + 4));
strcat (datfile, ".dat");
DPRINTF (4, (stderr, " Datfile name is \"%s\"\n", datfile));
/* Check permissions and existence of .dat file via access(2) */
if (access(datfile, R_OK) <0) {
DPRINTF (2, (stderr, " is_fortfile() returns FALSE (\"%s\" is unreadable)\n", datfile));
free (datfile);
return FALSE;
}
/* I don't know what this lot does yet, maybe I'll find out later.
For now I'll just copy and paster verbatim */
if (datp != NULL) /* If the pointer exists? */
*datp = datfile; /* Presumably this is a part of *fp */
else
free(datfile);
/* Something's up here... this shouldn't happen IMHO */
/* I'm not going to do a disk writing version, so IMHO no posp */
DPRINTF (2, (stderr, " is_fortfile() returns TRUE\n"));
return TRUE;
}
int
add_file(percent, file, dir, head, tail, parent)
/* This function is now * DIFFERENT to before, previous fortune
* coders BEWARE. */
int percent;
char *file;
char *dir;
FILEDESC **head, **tail;
FILEDESC *parent;
{
bool was_malloc;
char *path;
FILEDESC *fp;
bool is_offensive;
if (*file == '.') {
DPRINTF(3, (stderr, " File is a dot file, ignored\n"));
/* Is this exhaustive if *file == NULL? */
/* *file != NULL for all *file */
return FALSE;
}
/* Offensiveness test has moved house down 30 lines */
/* Get the full filename in *path */
if (dir == NULL) {
path = file;
was_malloc = FALSE;
} else {
/* Cast the size_t to an unsigned int */
path = do_malloc ((unsigned int)(strlen(dir) + strlen(file) + 2));
strcat (strcat (strcpy (path, dir), "/"), file);
was_malloc = TRUE;
}
DPRINTF (3, (stderr, " add_file got file \"%s\" with \"%s\"\n", path, file));
/* If file isn't readable */
if (access (path, R_OK) <0) {
/* Ignore the hack for the -o default suffix, BYEBYE GOTO! */
/* Now if the file is not absolute, read FORTDIR instead */
if ((dir == NULL) && (file[0] != '/'))
return add_file (percent, file, FORTDIR, head, tail, parent);
DPRINTF (4, (stderr, "Returning false at point 0\n"));
/* Single file given and that dosen't exist. */
if (parent->parent == NULL)
perror (path);
/* XXX Now we have a master object, this won't work.
* However parent->parent should still give NULL. */
if (was_malloc)
free(path);
return FALSE;
}
DPRINTF (3, (stderr, " Path = \"%s\"\n", path));
/* Offensiveness check is here, before we allocate memory */
/* This is our offensive test. Unless *file is a complete absolute
* reference in which case we've been passed it as a command line
* argument and the user is perverted anyhow, this will work. */
/* It catches both FILES and DIRECTORIES
* Needs to match first 9 characters ONLY - this relies on a
* version of strncmp that OKs if strlen(str1) < n */
if (!(is_offensive = parent->is_offensive))
if (*file == 'o')
is_offensive = (strncmp(file, "offensive", (size_t)(9)) == 0);
DPRINTF (2, (stderr, "is_offensive on \"%s\" gives %d\n", file, is_offensive));
if (is_offensive) {
DPRINTF (1, (stderr, "Offensive file \"%s\" detected. TAKE COVER.\n", file));
/* XXX Need to fix this */
}
if (!args.all_fortunes && parent->parent != NULL) {
DPRINTF (4, (stderr, "Parent->parent test passed for \"%s\"\n", path));
if ((!args.offensive && is_offensive) ||
(args.offensive && !is_dir (path) && !is_offensive)) {
DPRINTF (3, (stderr, "File \"%s\" rejected: wrong offensiveness.\n", file));
return FALSE;
}
}
else {
DPRINTF (3, (stderr, "Parent test failed for \"%s\"\n", path));
}
/* Create a new file thingy. This might have to be messed around
with to allow the program to open N+1 fortune files. */
fp = new_fp();
fp->percent = percent;
fp->name = file;
fp->path = path;
fp->parent = parent;
fp->is_offensive = is_offensive;
/* We need to check for dir first I think, not sure if it matters */
if (is_dir(path)) {
/* XXX Really we want full recursion to be a command line option */
if (!add_dir (fp)) {
DPRINTF (2, (stderr, "Discarding bad directory \"%s\"\n", fp->path));
free (fp);
return FALSE;
}
}
/* XXX This can be cleaned up later, I don't care again */
else if (!is_fortfile(path, &fp->datfile, &fp->posfile, NULL)) {
DPRINTF(2, (stderr, " File \"%s\" is not a fortune file or directory.\n", path));
return FALSE;
}
else
fp->num_files = 1; /* The file actually exists and needs counting */
/* vi is behaving today. :-) */
/* We can't run this here, so we MUST run get_tbl for technique 2 */
// parent->num_files += fp->num_files;
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;
}
/* We now trap the bad conclusions above */
return TRUE;
}
int
add_dir (fp)
FILEDESC *fp;
{
/* I suppose an ifdef SYSV would be appropriate here XXX */
struct dirent *dirent; /* NIH? WTF is that? */
DIR *dir; /* File stream thingy for the dir */
char *name; /* Filename in question */
FILEDESC *tailp; /* Pointer to last tail in list */
/* I'm gonna leave out the rest of the junk till I know it does
* anything */
/* The obvious snafu */
if ((dir = opendir (fp->path)) == NULL) {
perror (fp->path);
return FALSE;
}
if (args.no_recurse)
if (fp->parent != NULL) /* Play this safe, rely on optimiser */
if (fp->parent->parent != NULL) {
/* We are lower level subdir below master's child. */
DPRINTF (2, (stderr, "Stopping recursion at \"%s\"\n", fp->name));
return FALSE;
}
tailp = NULL;
DPRINTF (1, (stderr, "Adding dir \"%s\"\n", fp->path));
while ((dirent = readdir(dir)) != NULL) {
if (dirent->d_reclen == 0)
continue; /* Wot? */
name = copy (dirent->d_name, dirent->d_reclen);
DPRINTF(2, (stderr, "Trying file \"%s\"\n", name));
if (add_file (NO_PROB, name, fp->path, &fp->child, &tailp, fp)) {
DPRINTF(2, (stderr, "File \"%s\" added from dir.\n", name));
fp->num_children++;
} else
free(name);
}
closedir (dir); /* Har har the original forgot this *smug* */
/* Now what happens if we have no files in the dir? */
if (fp->num_children == 0) {
if (fp->parent->parent == NULL) /* If it was specified, report. */
fprintf (stderr, "%s: %s: No fortune files in directory\n", args.prog_name, fp->path);
// free (fp); /* XXX This might fubar. */
/* This free is now done in add_file and the optimiser can sort it out */
return FALSE;
}
return TRUE;
}
inline void
sum_tbl (t1, t2)
STRFILE *t1;
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;
}
int /* XXX Can't this be a void? */
get_tbl (fp)
FILEDESC *fp;
{
FILEDESC *list;
/* Getting all the wossnames is a bit inefficient but will do ATM */
DPRINTF (3, (stderr, " Testing %s in get_tbl.\n", fp->name));
if (fp->read_tbl)
return TRUE;
DPRINTF (3, (stderr, " Non-trivial %s in get_tbl.\n", fp->name));
if (fp->child == NULL) {
DPRINTF (3, (stderr, " %s has no children.\n", fp->name));
DPRINTF (4, (stderr, " About to open %s\n", fp->datfile));
open_dat (fp);
DPRINTF (4, (stderr, " Dat file open for %s.\n", fp->name));
if (read (fp->datfd, (char *) &fp->tbl, sizeof (fp->tbl)) != sizeof (fp->tbl)) {
fprintf (stderr, "%s: %s corrupted\n", args.prog_name, fp->datfile);
exit(1); /* ^^ hee hee another bug... they said fp->path */
}
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 (fp->datfd);
}
else {
zero_tbl (&fp->tbl);
for (list = fp->child; list != NULL; list = list->next) {
get_tbl (list);
sum_tbl (&fp->tbl, &list->tbl);
/* XXX We need to do this here when we build tables. */
fp->num_files += list->num_files;
}
}
fp->read_tbl = TRUE;
/* We can just return TRUE from here as all errors are exit(3)ed */
return TRUE;
}
#ifdef OLD_REGEX /* For old style regex pattern matching. */
char *
conv_pat (orig)
char *orig;
{
char *sp;
char *new;
unsigned int cnt = 1;
for (sp = orig; *sp != '\0'; sp++)
if (isalpha (*sp))
cnt += 4;
else
cnt++;
if ((new = malloc(cnt)) == NULL) {
fprintf(stderr, "%s: pattern too long to ignore case\n", args.prog_name);
exit(1);
}
for (sp = new; *orig != '\0'; orig++) {
if (islower(*orig)) {
/* I know it's weird but it knocks 64 bytes off the code size */
// sprintf (sp, "[%c%c]", toupper (*orig), *orig);
// sp+=4;
*sp++='[';
*sp++=toupper(*orig);
*sp++=*orig;
*sp++=']';
}
else if (isupper(*orig)) {
//sprintf (sp, "[%c%c]", *orig, tolower (*orig));
//sp+=4;
*sp++='[';
*sp++=*orig;
*sp++=tolower(*orig);
*sp++=']';
}
else
*sp++ = *orig;
}
*sp = '\0';
return new;
}
#endif /* OLD_REGEX */
bool
matches_in_list (object, preg, fortbuf, max_length)
FILEDESC *object;
regex_t *preg;
char *fortbuf;
int max_length;
/* I can ditch max_length as soon as I work out what that +10 hack does. */
{
FILEDESC *fp;
char *sp;
bool in_file;
bool found_one = FALSE;
long num_lines;
long length;
/* Here I've renamed my variables to match the original a bit,
* normally I have used list as the variable and fp as the object. */
for (fp = object; fp != NULL; fp = fp->next) {
if (fp->child != NULL) {
matches_in_list (fp->child, preg, fortbuf, max_length);
continue; /* Not my style, but if it works, don't fix it. */
}
DPRINTF (1, (stderr, "Searching for pattern in \"%s\"\n", fp->path));
open_fp (fp);
sp = fortbuf;
num_lines = 0;
length = 0;
in_file = FALSE;
/* We've assigned Fort_len to be the max fortune size in the
* original. This is OK as fgets stops at newline or EOF */
while (fgets(sp, max_length, fp->inf) != NULL) {
if (!STR_ENDSTRING(sp, fp->tbl)) {
sp += strlen(sp);
num_lines++;
}
else {
*sp = '\0';
/* XXX Here we need our RX compiled and ready */
if (regexec (preg, fortbuf, (size_t) (0), NULL, 0) == 0) {
/* Messy... */
length = sp - fortbuf;
/* This is a bit messy but should work. I think I'm
* reverting to LISP layout here. Sorry BSD! */
if (!(
(args.short_only &&
(length > args.short_length))
||
(args.long_only &&
(length < args.long_length))
||
((args.max_lines < 999) &&
(num_lines > args.max_lines))
))
{
printf ("%c%c", fp->tbl.str_delim, fp->tbl.str_delim);
/* Can I optimise this with %*s? */
if (!in_file) {
printf (" (%s)", fp->path);
found_one = TRUE;
in_file = TRUE;
}
putchar('\n');
fwrite (fortbuf, 1, (sp-fortbuf), stdout);
/* Pointer arith. Yuk. */
}
}
sp = fortbuf;
num_lines = 0;
length = 0;
} /* if */
} /* while */
} /* for */
return found_one;
} /* function */
bool
find_matches (fp, preg)
FILEDESC *fp;
regex_t *preg;
{
int max_length;
char *fortbuf;
get_tbl (fp);
/* This looks like a nasty hack, we don't know how long the fortune
* is, we just guess at 10 extra characters, surely we should add
* the newlines in when we do a get_tbl? XXX */
max_length = fp->tbl.str_longlen;
fortbuf = do_malloc((unsigned int) max_length + 10);
/* Actually I don't need this either as I can put it on the stack?
* or is that inefficient to keep messing around with memory? I
* figure I'll just pass the buffer down the stack instead of
* creating a new minimal buffer at each level. */
/* And thus we don't need maxlen_in_list */
return (matches_in_list (fp, preg, fortbuf, max_length));
}
/* Pick child selects a file to print an epigram from. */
/* I suppose also that we need three techniques here. One based on
* file size and num_str, one based on children per parent
* having equal probability, and so children of children having less
* probablility, and one where all children get the same whack at
* the wotsit. */
FILEDESC *
pick_child (parent)
FILEDESC *parent;
{
FILEDESC *fp;
int choice;
int technique = args.technique;
if (parent->num_children == 0)
return parent; /* Textbook base case */
DPRINTF(1, (stderr, "Starting technique %d on %s\n", technique, parent->name));
if (technique == 1) { /* Each file in dir is equal */
choice = random() % parent->num_children;
DPRINTF(1, (stderr, " Choice = %d (of %d)\n",
choice+1, parent->num_children));
for (fp = parent->child; choice--; fp = fp->next)
continue;
DPRINTF(1, (stderr, " Using %s\n", fp->name));
return pick_child (fp);
}
else if (technique == 2) { /* Each file everywhere is equal */
/* XXX Needs get_tbl to be run on the root node */
choice = random() % parent->num_files;
DPRINTF(1, (stderr, " Choice is %d of %ld\n", choice,
parent->num_files));
for (fp = parent->child; choice > fp->num_files; fp = fp->next)
choice -= fp->num_files;
DPRINTF (1, (stderr, " File number is %d\n", choice));
DPRINTF (1, (stderr, " File is %s\n", fp->name));
return pick_child (fp); /* I think */
}
else if (technique == 3) { /* Each fortune is equal (ish) */
/* XXX We MUST get_tbl() before running technique 3 */
/* Actually on consideration, we've already ditched all percentage
* considerations, so we might as well use num_str here, hence
* get shot of virtual entirely. Wonder if it works? */
/* We now need get_tbl for technique 3 */
choice = random() % parent->tbl.str_numstr;
DPRINTF (1, (stderr, " Choice is %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, " Offset is %d\n", choice));
DPRINTF (1, (stderr, " File is %s\n", fp->name));
return pick_child (fp); /* I think */
}
#ifdef DEBUG
else {
fprintf (stderr, "%s: invalid selection technique\n", args.prog_name);
exit(1);
}
#endif /* DEBUG */
}
inline void
get_pos (fp)
FILEDESC *fp;
{
#ifdef DEBUG
assert (fp->read_tbl); /* XXX Do I need this?? */
#endif /* DEBUG */
/* Only if technique 1 was used. */
if (fp->pos == POS_UNKNOWN)
fp->pos = random() % fp->tbl.str_numstr;
/* We don't need to increment here we already have a value in
* the range [0, tbl.str_numstr - 1] */
DPRINTF (1, (stderr, "pos for file %s is %ld of %ld\n",
fp->name, fp->pos, fp->tbl.str_numstr));
}
FILEDESC *
get_fort_fp (fp)
FILEDESC *fp; /* As ever, the top of the tree */
{
FILEDESC *list;
int choice;
/* I don't know why we have 2 here, we only need 0 and 1 AFAIK */
/* Actually, as we now have a master object, we can ask for it's
* number of children and hence if anything was specified on
* command line it will be either first child or there will be a
* list thereof. Thus. */
/* I wonder how much I can cane off the code if I lose the assert(3) */
#ifdef DEBUG
assert (fp->num_children >= 1); /* This is for safety XXX */
#endif /* DEBUG */
if (fp->num_children == 1 || fp->child->percent == NO_PROB) {
DPRINTF (2, (stderr, "No probability or no next (v2)\n"));
/* Must select a random object here, we need the master object */
/* list = fp ; and then select child from list */
/* Maybe I'll assign it to fp instead. l8r. */
list = fp;
}
else {
choice = random() % 100;
DPRINTF (1, (stderr, "get_fort: Choice = %d\n", choice));
for (list = fp->child; list->percent != NO_PROB; list = list->next) {
if (choice < list->percent)
break;
else {
choice -= list->percent;
DPRINTF (1, (stderr, " skip \"%s\", %d%%\n",
list->name, list->percent));
}
}
DPRINTF (1, (stderr, " Using \"%s\", %d%%\n",
list->name, list->percent));
}
/* Now we build techniques etc etc and get tables */
DPRINTF (1, (stderr, "Picking child from %s\n", list->name));
get_tbl(list); /* For tech 2 and 3 */
if (list->tbl.str_numstr == 0) {
fprintf (stderr, "%s: no fortunes found\n", args.prog_name);
exit(1);
}
list = pick_child(list);
DPRINTF(1, (stderr, "Using file \"%s\" for fortune.\n", list->name));
return list;
}
long
fort_len (fp, seekpts)
FILEDESC *fp;
off_t * seekpts;
{
char line[BUFSIZ];
long length = 0;
if (!(fp->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
length = (seekpts[1] - seekpts[0]);
else {
open_fp (fp);
fseek (fp->inf, seekpts[0], SEEK_SET);
while (fgets (line, sizeof (line), fp->inf) != NULL &&
!STR_ENDSTRING(line, fp->tbl))
length += strlen (line);
}
return length;
}
long
fort_lines (fp, seekpts)
FILEDESC *fp;
off_t * seekpts;
{
char line[BUFSIZ];
long numlines = 0;
open_fp (fp);
fseek (fp->inf, seekpts[0], SEEK_SET);
while (fgets (line, sizeof (line), fp->inf) != NULL &&
!STR_ENDSTRING(line, fp->tbl))
numlines++;
return numlines;
}
void
display (fp, seekpts)
FILEDESC *fp;
off_t * seekpts;
{
char *p, ch;
char line[BUFSIZ];
long length;
open_fp (fp);
fseek (fp->inf, seekpts[0], SEEK_SET);
/* Length seems to be number of lines here. But we must already
* have evaluated fort_len so why not use it? and also number
* of lines. Lessee... as soon as I write a decent -S and -L */
/* XXX Mustn't evaluate this twice */
for (length = 0; fgets (line, sizeof (line), fp->inf) != NULL &&
!STR_ENDSTRING(line, fp->tbl); length++) {
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);
}
bool
get_fort (fp)
FILEDESC *fp;
{
off_t seekpts[2];
get_pos(fp);
open_dat(fp);
lseek (fp->datfd,
(off_t) (sizeof (fp->tbl) + fp->pos * sizeof (seekpts [0])),
SEEK_SET);
read (fp->datfd, seekpts, sizeof (seekpts));
close (fp->datfd); /* >:-) */
seekpts[0] = ntohl(seekpts[0]);
seekpts[1] = ntohl(seekpts[1]);
if (args.short_only &&
(fort_len (fp, seekpts) > args.short_length))
return FALSE;
else if (args.long_only &&
(fort_len (fp, seekpts) < args.long_length))
return FALSE;
else if ((args.max_lines < 999) &&
(fort_lines (fp, seekpts) > args.max_lines))
return FALSE;
/* Display_fortune needs fp and seekpts */
display (fp, seekpts);
if (args.pause)
sleep ((unsigned int)
max(fort_len (fp, seekpts) / CPERS, MIN_WAIT));
return TRUE;
}
void
print_list_format (list, level)
FILEDESC *list;
int level;
{
/* XXX Remove this when the program is finished */
while (list != NULL) {
fprintf (stderr, "%*s", (level * 6), "");
if (level == 1)
if (list->percent == NO_PROB)
fprintf (stderr, "---%%");
else
fprintf (stderr, "%3d%%", list->percent);
else
fprintf (stderr, " ");
if (list->child != NULL) {
fprintf (stderr, " %-16s", strcat(strcat(calloc(strlen(list->name)+1, 1), list->name), "/"));
} else {
fprintf (stderr, " %-16s", list->name);
}
#ifdef DEBUG
fprintf (stderr, " [%ld %ld %ld]\t {%d %ld}", list->tbl.str_shortlen,
list->tbl.str_longlen, list->tbl.str_numstr,
list->num_children, list->num_files);
#endif /* DEBUG */
putc ('\n', stderr);
/* I've evaluated this IF twice :-( Leave it to the optimiser */
/* XXX More liekly, fix it later because I don't need the above
* printf statement, it's just a glob of debugging junk */
if (list->child != NULL)
print_list_format (list->child, level+1);
list = list->next;
}
}
inline void
print_file_list (start)
FILEDESC *start;
{
#ifdef DEBUG
fprintf(stderr, "Percentage, Filename, [short_len, long_len, num_str]"
"{num_children, num_files}\n");
#endif /* DEBUG */
print_list_format (start, 0);
}
void
zero_args(void)
{
args.all_fortunes = FALSE;
args.technique = 3;
args.print_list = FALSE;
args.ignore_case = FALSE;
args.long_only = FALSE;
args.match_pattern = FALSE;
args.offensive = FALSE;
args.no_recurse = FALSE;
args.short_only = FALSE;
args.pause = FALSE;
args.reg_pattern = NULL;
args.long_length = 160;
args.short_length = 160;
args.max_lines = 999;
}
void
usage (void)
{
fprintf (stderr,
"Usage:
%s [-a"
#ifdef DEBUG
"D"
#endif /* DEBUG */
"eEfilosrw] [-m pattern] [-L num] [-N num] [-S num]
%*s [[#%%] file/directory/all]
", args.prog_name, strlen(args.prog_name), "");
}
void
get_args (argc, argv)
int *argc;
char **argv[];
{
int option;
char *temp;
temp = rindex(*argv[0], '/');
args.prog_name = (temp? ++temp :*argv[0]);
#ifdef DEBUG
while ((option = getopt(*argc, *argv, "aefilm:osrwEL:N:S:Dh?")) != EOF)
#else /* DEBUG */
while ((option = getopt(*argc, *argv, "aefilm:osrwEL:N:S:h?")) != EOF)
#endif /* DEBUG */
switch (option) {
case 'a': /* All fortunes */
args.all_fortunes = TRUE;
break;
case 'e': /* Files are all equal */
args.technique = 2;
break;
case 'f': /* Print file list */
args.print_list = TRUE;
break;
case 'i': /* Ignore case in matches */
args.ignore_case = TRUE;
break;
case 'l': /* Long fortunes only */
args.long_only = TRUE;
args.short_only = FALSE;
break;
case 'm': /* Match pattern */
args.match_pattern = TRUE;
args.reg_pattern = optarg;
break;
case 'o': /* Offensive only */
args.offensive = TRUE;
break;
case 'r':
args.no_recurse = TRUE;
break;
case 's': /* Short fortunes only */
args.short_only = TRUE;
args.long_only = FALSE;
break;
case 'w': /* Pause after display */
args.pause = TRUE;
break;
case 'E': /* Files in directory are equal */
args.technique = 1;
break;
case 'L': /* Set long length */
args.long_length = atoi(optarg);
args.long_only = TRUE;
args.short_only = FALSE;
break;
case 'N': /* Set max lines */
args.max_lines = atoi(optarg);
break;
case 'S': /* Set short length */
args.short_length = atoi(optarg);
args.short_only = TRUE;
args.long_only = FALSE;
break;
case 'W': /* Set wait time */
/* I'm far too lazy to do any half consistent implementation
* of wait times, it would need to set min wait, max wait and
* cps to wait for fortunes. SEP again. */
break;
#ifdef DEBUG
case 'D': /* Debug mode on */
Debug++;
break;
#endif /* DEBUG */
case '?':
case 'h':
default:
usage();
exit(1);
}
*argc-=optind;
*argv+=optind;
}
#ifdef DEBUG
void
print_args ()
{
fprintf(stderr,
"All fortunes is %d
Offensive is %d
Ignore case is %d
Match pattern is %d
Pattern is \"%s\"
Long only is %d
Long length is %d
Short only is %d
Short length is %d
Max lines is %d
Pause is %d
Technique is %d
Print list is %d
Don't recurse is %d
Debug is %d
",
args.all_fortunes,
args.offensive,
args.ignore_case,
args.match_pattern,
(args.reg_pattern ? args.reg_pattern : "(null)"),
args.long_only,
args.long_length,
args.short_only,
args.short_length,
args.max_lines,
args.pause,
args.technique,
args.print_list,
args.no_recurse,
Debug
);
}
#endif /* DEBUG */
FILEDESC *
make_list (argc, argv)
int argc;
char *argv[];
{
FILEDESC *tailp = NULL;
FILEDESC *object;
char *sp;
int i;
int percent;
int total_percent = 0;
object = new_fp();
object->name = copy("Master node", 12);
object->path = NULL;
/* fp->name and fp->path are not set to NULL by new_fp() */
if (argc) {
for (i=0; i<argc; i++) {
DPRINTF (3, (stderr, "Parsing argument %d, value %s\n", i, argv[i]));
if (!isdigit (argv[i][0])) {
percent = NO_PROB;
sp = argv[i];
}
else {
percent = 0;
for (sp = argv[i]; isdigit (*sp); sp++) {
percent = (10 * percent) + *sp - '0';
}
if (percent > 100) {
fprintf (stderr, "Percentages must be <= 100\n");
exit(1);
}
if (*sp == '.') {
fprintf (stderr, "%s: percentages must be integers\n", args.prog_name);
exit(1);
}
/* If not followed by a % then it's a filename. */
if (*sp != '%') {
percent = NO_PROB;
sp = argv[i];
}
else if (*++sp == '\0') {
if (++i >= argc) {
fprintf (stderr, "%s: percentages must precede filenames\n", args.prog_name);
exit(1);
}
total_percent += percent;
sp = argv[i]; /* Point sp to the file (argv[i++]) */
}
}
if (strcmp(sp, "all") == 0)
sp = FORTDIR;
if (!add_file (percent, sp, NULL, &object->child, &tailp, object)) {
fprintf (stderr, "%s: failed to add file \"%s\"\n", args.prog_name, sp);
exit(1);
/* XXX We're about to crash out if we add a dot file manually. */
}
object->num_children++;
}
/* XXX Fix this */
if ( (total_percent > 100) ||
((total_percent < 100) && (tailp->percent != NO_PROB)) ) {
fprintf (stderr, "%s: percentages must total 100%% or include undefined percentages\n", args.prog_name);
exit(1);
}
}
else {
sp = FORTDIR;
add_file (NO_PROB, sp, NULL, &object->child, &tailp, object);
object->num_children++;
}
return object;
}
#ifdef DEBUG
void
stat_fp (fp)
FILEDESC *fp;
{
fprintf (stderr,
"
--- Statting FilePointer ---
Name is %s
Path is %s
Child is %s
Next is %s
Prev is %s
Percent is %d
Number of children is %d
Number of files is %ld
Number of strings is %ld
",
fp->name,
fp->path,
(fp->child==NULL?"NULL":fp->child->path),
(fp->next==NULL?"NULL":fp->next->path),
(fp->prev==NULL?"NULL":fp->prev->path),
fp->percent,
fp->num_children,
fp->num_files,
fp->tbl.str_numstr
);
}
#endif /* DEBUG */
regex_t *
make_regex (void)
{
regex_t *preg;
int re_flags;
int re_error;
char *errbuf = NULL;
int errsize;
preg = (regex_t *)malloc (sizeof (regex_t));
re_flags = (args.ignore_case ? REG_ICASE : 0) |
REG_NOSUB | REG_NEWLINE;
DPRINTF (2, (stderr, "Regular expression flags are %d\n", re_flags));
DPRINTF (1, (stderr, "Compiling regular expression buffer\n"));
re_error = regcomp (preg, args.reg_pattern, re_flags);
DPRINTF (2, (stderr, "Checking for regexp error\n"));
if (re_error) {
errsize = (regerror (re_error, preg, NULL, (size_t)(0)) + 1);
errbuf = malloc (errsize);
regerror (re_error, preg, errbuf, errsize);
fprintf (stderr, "%s: %s\n", args.prog_name, errbuf);
exit(1);
}
return preg;
}
int
main(argc, argv)
int argc;
char *argv[];
{
FILEDESC *object;
int counter;
/* This is good, why not keep it. I can't get gettimeofday(3) to work */
srandom((int)(time((time_t *) NULL) + getpid()));
zero_args();
get_args (&argc, &argv);
#ifdef DEBUG
print_args ();
#endif /* DEBUG */
object = make_list (argc, argv);
if (args.print_list) {
#ifdef DEBUG
if (object->child != NULL)
get_tbl (object);
#endif /* DEBUG */
print_file_list (object);
exit(0);
}
if (object->child == NULL) {
fprintf (stderr, "%s: no files found\n", args.prog_name);
exit(1);
}
if (args.match_pattern) {
DPRINTF (2, (stderr, "Making regexp buffer\n"));
/* Compile regex, checking for ignore case. */
/* find_matches (fp, preg) */
exit (find_matches (object, make_regex()));
}
// stat_fp (object);
/* We don't need to do either of these till we know (a) a technique
* and (b) Which child to generate from (Master if print list) */
// get_tbl (object);
// printf("%s\n", conv_pat("ah237\\z^%&*")); /* This is now OLD_REGEX */
/* Make several tries at this and return FALSE or TRUE from it */
for (counter = 0; counter < 128; counter++)
if ( get_fort (get_fort_fp (object)) )
exit(0);
fprintf (stderr, "%s: too many tries\n", args.prog_name);
exit(1);
}