* Copyright (c) 1986 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 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
"@(#) Copyright (c) 1986 The Regents of the University of California.\n\
static char sccsid
[] = "@(#)fortune.c 5.13 (Berkeley) 4/8/91";
# include <machine/endian.h>
# define F_OK 0 /* does file exist */
# define X_OK 1 /* is it executable by caller */
# define W_OK 2 /* writable by caller */
# define R_OK 4 /* readable by caller */
# define MINW 6 /* minimum wait if desired */
# define CPERS 20 /* # of chars for each sec */
# define SLEN 160 /* # of chars in short fortune */
# define POS_UNKNOWN ((unsigned long) -1) /* pos for file unknown */
# define NO_PROB (-1) /* no prob specified for file */
# define DPRINTF(l,x) if (Debug >= l) fprintf x; else
struct fd
*child
, *parent
;
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-allocted prob equally */
bool Match
= FALSE
; /* dump fortunes matching a pattern */
bool Debug
= FALSE
; /* print debug messages */
char *Fortbuf
= NULL
; /* fortune buffer for -m */
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 */
char *do_malloc(), *copy(), *off_name();
FILEDESC
*pick_child(), *new_fp();
extern char *malloc(), *index(), *rindex(), *strcpy(), *strcat();
# define RE_COMP(p) (Re_pat = regcmp(p, NULL))
# define BAD_COMP(f) ((f) == NULL)
# define RE_EXEC(p) regex(Re_pat, (p))
char *regcmp(), *regex();
# define RE_COMP(p) (p = re_comp(p))
# define BAD_COMP(f) ((f) != NULL)
# define RE_EXEC(p) re_exec(p)
#endif /* OK_TO_WRITE_DISK */
exit(find_matches() != 0);
srandom((int)(time((time_t *) NULL
) + getpid()));
} while ((Short_only
&& fortlen() > SLEN
) ||
(Long_only
&& fortlen() <= SLEN
));
if ((fd
= creat(Fortfile
->posfile
, 0666)) < 0) {
perror(Fortfile
->posfile
);
* if we can, we exclusive lock, but since it isn't very
* important, we just punt if we don't have easy locking
(void) flock(fd
, LOCK_EX
);
write(fd
, (char *) &Fortfile
->pos
, sizeof Fortfile
->pos
);
if (!Fortfile
->was_pos_file
)
(void) chmod(Fortfile
->path
, 0666);
(void) flock(fd
, LOCK_UN
);
#endif /* OK_TO_WRITE_DISK */
sleep((unsigned int) max(Fort_len
/ CPERS
, MINW
));
(void) fseek(fp
->inf
, 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
)
*p
= 'A' + (ch
- 'A' + 13) % 26;
*p
= 'a' + (ch
- 'a' + 13) % 26;
* Return the length of the fortune.
if (!(Fortfile
->tbl
.str_flags
& (STR_RANDOM
| STR_ORDERED
)))
nchar
= (Seekpts
[1] - Seekpts
[0] <= SLEN
);
(void) fseek(Fortfile
->inf
, Seekpts
[0], 0);
while (fgets(line
, sizeof line
, Fortfile
->inf
) != NULL
&&
!STR_ENDSTRING(line
, Fortfile
->tbl
))
* This routine evaluates the arguments on the command line
register int ignore_case
;
while ((ch
= getopt(argc
, argv
, "aDefilm:osw")) != EOF
)
while ((ch
= getopt(argc
, argv
, "aefilm:osw")) != EOF
)
case 'a': /* any fortune */
Equal_probs
++; /* scatter un-allocted prob equally */
case 'f': /* find fortune files */
case 'l': /* long ones only */
case 'o': /* offensive ones only */
case 's': /* short ones only */
case 'w': /* give time to read */
case 'i': /* case-insensitive match */
case 'm': /* dump out the fortunes */
"fortune: can't match fortunes on this system (Sorry)\n");
case 'm': /* dump out the fortunes */
case 'i': /* case-insensitive match */
if (!form_file_list(argv
, argc
))
exit(1); /* errors printed through form_file_list() */
if (BAD_COMP(RE_COMP(pat
))) {
fprintf(stderr
, "%s\n", pat
);
fprintf(stderr
, "bad pattern: %s\n", pat
);
* Form the file list from the file specifications.
form_file_list(files
, file_cnt
)
return add_file(NO_PROB
, FORTDIR
, NULL
, &File_list
,
return add_file(NO_PROB
, "fortunes", FORTDIR
,
&File_list
, &File_tail
, NULL
);
for (i
= 0; i
< file_cnt
; i
++) {
if (!isdigit(files
[i
][0]))
for (sp
= files
[i
]; isdigit(*sp
); sp
++)
percent
= percent
* 10 + *sp
- '0';
fprintf(stderr
, "percentages must be <= 100\n");
fprintf(stderr
, "percentages must be integers\n");
* 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.
else if (*++sp
== '\0') {
fprintf(stderr
, "percentages must precede files\n");
if (strcmp(sp
, "all") == 0)
if (!add_file(percent
, sp
, NULL
, &File_list
, &File_tail
, NULL
))
* Add a file to the file list.
add_file(percent
, file
, dir
, head
, tail
, parent
)
register char *path
, *offensive
;
register bool was_malloc
;
path
= do_malloc((unsigned int) (strlen(dir
) + strlen(file
) + 2));
(void) strcat(strcat(strcpy(path
, dir
), "/"), file
);
if ((isdir
= is_dir(path
)) && parent
!= NULL
) {
return FALSE
; /* don't recurse */
if (!isdir
&& parent
== NULL
&& (All_forts
|| Offend
) &&
offensive
= off_name(path
);
DPRINTF(1, (stderr
, "adding file \"%s\"\n", path
));
if ((fd
= open(path
, 0)) < 0) {
* This is a sneak. If the user said -a, and if the
* file we're given isn't a file, we check to see if
* there is a -o version. If there is, we treat it as
* if *that* were the file given. We only do this for
* individual files -- if we're scanning a directory,
* we'll pick up the -o file anyway.
if (All_forts
&& offensive
!= NULL
) {
DPRINTF(1, (stderr
, "\ttrying \"%s\"\n", path
));
if (dir
== NULL
&& file
[0] != '/')
return add_file(percent
, file
, FORTDIR
, head
, tail
,
DPRINTF(2, (stderr
, "path = \"%s\"\n", path
));
if ((isdir
&& !add_dir(fp
)) ||
!is_fortfile(path
, &fp
->datfile
, &fp
->posfile
, (parent
!= NULL
))))
"fortune:%s not a fortune file or directory\n",
* If the user said -a, we need to make this node a pointer to
* both files, if there are two. We don't need to do this if
* we are scanning a directory, since the scan will pick up the
if (All_forts
&& parent
== NULL
&& !is_off_name(path
))
all_forts(fp
, offensive
);
else if (fp
->percent
== NO_PROB
) {
fp
->was_pos_file
= (access(fp
->posfile
, W_OK
) >= 0);
#endif /* OK_TO_WRITE_DISK */
* Return a pointer to an initialized new FILEDESC.
fp
= (FILEDESC
*) do_malloc(sizeof *fp
);
* Return a pointer to the offensive version of a file of this name.
new = copy(file
, (unsigned int) (strlen(file
) + 2));
return strcat(new, "-o");
* Is the file an offensive-style name?
return (len
>= 3 && file
[len
- 2] == '-' && file
[len
- 1] == 'o');
* Modify a FILEDESC element to be the parent of two children if
* there are two children to be a parent of.
register FILEDESC
*scene
, *obscene
;
auto char *datfile
, *posfile
;
if (fp
->child
!= NULL
) /* this is a directory, not a file */
if (!is_fortfile(offensive
, &datfile
, &posfile
, FALSE
))
if ((fd
= open(offensive
, 0)) < 0)
DPRINTF(1, (stderr
, "adding \"%s\" because of -a\n", offensive
));
scene
->child
= obscene
->child
= NULL
;
scene
->parent
= obscene
->parent
= fp
;
scene
->percent
= obscene
->percent
= NO_PROB
;
obscene
->path
= offensive
;
if ((sp
= rindex(offensive
, '/')) == NULL
)
obscene
->name
= offensive
;
obscene
->datfile
= datfile
;
obscene
->posfile
= posfile
;
obscene
->read_tbl
= FALSE
;
obscene
->was_pos_file
= (access(obscene
->posfile
, W_OK
) >= 0);
#endif /* OK_TO_WRITE_DISK */
* Add the contents of an entire directory.
register struct dirent
*dirent
; /* NIH, of course! */
register struct direct
*dirent
;
if ((dir
= opendir(fp
->path
)) == NULL
) {
DPRINTF(1, (stderr
, "adding dir \"%s\"\n", fp
->path
));
while ((dirent
= readdir(dir
)) != NULL
) {
if (dirent
->d_namlen
== 0)
name
= copy(dirent
->d_name
, dirent
->d_namlen
);
if (add_file(NO_PROB
, name
, fp
->path
, &fp
->child
, &tailp
, fp
))
if (fp
->num_children
== 0) {
"fortune: %s: No fortune files in directory.\n", fp
->path
);
* Return TRUE if the file is a directory, FALSE otherwise.
if (stat(file
, &sbuf
) < 0)
return (sbuf
.st_mode
& S_IFDIR
);
* 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.
is_fortfile(file
, datp
, posp
, check_for_offend
)
static char *suflist
[] = { /* list of "illegal" suffixes" */
"dat", "pos", "c", "h", "p", "i", "f",
"pas", "ftn", "ins.c", "ins,pas",
DPRINTF(2, (stderr
, "is_fortfile(%s) returns ", file
));
* Preclude any -o files for offendable people, and any non -o
* files for completely offensive people.
if (check_for_offend
&& !All_forts
) {
if (Offend
^ (file
[i
- 2] == '-' && file
[i
- 1] == 'o'))
if ((sp
= rindex(file
, '/')) == NULL
)
DPRINTF(2, (stderr
, "FALSE (file starts with '.')\n"));
if ((sp
= rindex(sp
, '.')) != NULL
) {
for (i
= 0; suflist
[i
] != NULL
; i
++)
if (strcmp(sp
, suflist
[i
]) == 0) {
DPRINTF(2, (stderr
, "FALSE (file has suffix \".%s\")\n", sp
));
datfile
= copy(file
, (unsigned int) (strlen(file
) + 4)); /* +4 for ".dat" */
if (access(datfile
, R_OK
) < 0) {
DPRINTF(2, (stderr
, "FALSE (no \".dat\" file)\n"));
*posp
= copy(file
, (unsigned int) (strlen(file
) + 4)); /* +4 for ".dat" */
(void) strcat(*posp
, ".pos");
#endif /* OK_TO_WRITE_DISK */
DPRINTF(2, (stderr
, "TRUE\n"));
* Return a malloc()'ed copy of the string
new = do_malloc(len
+ 1);
* Do a malloc, checking for NULL return.
if ((new = malloc(size
)) == NULL
) {
(void) fprintf(stderr
, "fortune: out of memory.\n");
* Free malloc'ed space, if any.
* Initialize the fortune probabilities.
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)
for (fp
= File_tail
; fp
!= NULL
; fp
= fp
->prev
)
if (fp
->percent
== NO_PROB
) {
DPRINTF(1, (stderr
, "summing probabilities:%d%% with %d NO_PROB's",
"fortune: probabilities sum to %d%%!\n", percent
);
else if (percent
< 100 && num_noprob
== 0) {
"fortune: no place to put residual probability (%d%%)\n",
else if (percent
== 100 && num_noprob
!= 0) {
"fortune: no probability left to put in residual files\n");
frac
= percent
/ num_noprob
;
DPRINTF(1, (stderr
, ", frac = %d%%", frac
));
for (fp
= File_list
; fp
!= last
; fp
= fp
->next
)
if (fp
->percent
== NO_PROB
) {
DPRINTF(1, (stderr
, ", residual = %d%%", percent
));
", %d%% distributed over remaining fortunes\n",
DPRINTF(1, (stderr
, "\n"));
* Get the fortune data file's seek pointer for the next fortune.
if (File_list
->next
== NULL
|| File_list
->percent
== NO_PROB
)
DPRINTF(1, (stderr
, "choice = %d\n", choice
));
for (fp
= File_list
; fp
->percent
!= NO_PROB
; fp
= fp
->next
)
if (choice
< fp
->percent
)
" skip \"%s\", %d%% (choice = %d)\n",
fp
->name
, fp
->percent
, choice
));
"using \"%s\", %d%% (choice = %d)\n",
fp
->name
, fp
->percent
, choice
));
if (fp
->percent
!= NO_PROB
)
choice
= random() % Noprob_tbl
.str_numstr
;
DPRINTF(1, (stderr
, "choice = %d (of %d) \n", choice
,
while (choice
>= fp
->tbl
.str_numstr
) {
choice
-= fp
->tbl
.str_numstr
;
" skip \"%s\", %d (choice = %d)\n",
fp
->name
, fp
->tbl
.str_numstr
,
DPRINTF(1, (stderr
, "using \"%s\", %d\n", fp
->name
,
DPRINTF(1, (stderr
, "picking child\n"));
(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]);
* Pick a child from a chosen parent.
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
)
DPRINTF(1, (stderr
, " using %s\n", fp
->name
));
choice
= random() % parent
->tbl
.str_numstr
;
DPRINTF(1, (stderr
, " choice = %d (of %d)\n",
choice
, parent
->tbl
.str_numstr
));
for (fp
= parent
->child
; choice
>= fp
->tbl
.str_numstr
;
choice
-= fp
->tbl
.str_numstr
;
DPRINTF(1, (stderr
, "\tskip %s, %d (choice = %d)\n",
fp
->name
, fp
->tbl
.str_numstr
, choice
));
DPRINTF(1, (stderr
, " using %s, %d\n", fp
->name
,
* Sum up all the noprob probabilities, starting with fp.
static bool did_noprobs
= FALSE
;
sum_tbl(&Noprob_tbl
, &fp
->tbl
);
* Assocatiate a FILE * with the given FILEDESC.
if (fp
->inf
== NULL
&& (fp
->inf
= fdopen(fp
->fd
, "r")) == NULL
) {
* Open up the dat file if we need to.
if (fp
->datfd
< 0 && (fp
->datfd
= open(fp
->datfile
, 0)) < 0) {
* Get the position from the pos file, if there is one. If not,
* return a random number.
#endif /* OK_TO_WRITE_DISK */
if (fp
->pos
== POS_UNKNOWN
) {
if ((fd
= open(fp
->posfile
, 0)) < 0 ||
read(fd
, &fp
->pos
, sizeof fp
->pos
) != sizeof fp
->pos
)
fp
->pos
= random() % fp
->tbl
.str_numstr
;
else if (fp
->pos
>= fp
->tbl
.str_numstr
)
fp
->pos
%= fp
->tbl
.str_numstr
;
fp
->pos
= random() % fp
->tbl
.str_numstr
;
#endif /* OK_TO_WRITE_DISK */
if (++(fp
->pos
) >= fp
->tbl
.str_numstr
)
fp
->pos
-= fp
->tbl
.str_numstr
;
DPRINTF(1, (stderr
, "pos for %s is %d\n", fp
->name
, fp
->pos
));
* Get the tbl data file the datfile.
register FILEDESC
*child
;
if ((fd
= open(fp
->datfile
, 0)) < 0) {
if (read(fd
, (char *) &fp
->tbl
, sizeof fp
->tbl
) != sizeof fp
->tbl
) {
"fortune: %s corrupted\n", fp
->path
);
/* 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
);
for (child
= fp
->child
; child
!= NULL
; child
= child
->next
) {
sum_tbl(&fp
->tbl
, &child
->tbl
);
* Zero out the fields we care about in a tbl structure.
* Merge the tbl data of t2 into t1.
register STRFILE
*t1
, *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
;
#define STR(str) ((str) == NULL ? "NULL" : (str))
* Print out the file list
print_list(File_list
, 0);
* Print out the actual list, recursively.
fprintf(stderr
, "%*s", lev
* 4, "");
if (list
->percent
== NO_PROB
)
fprintf(stderr
, "___%%");
fprintf(stderr
, "%3d%%", list
->percent
);
fprintf(stderr
, " %s", STR(list
->name
));
DPRINTF(1, (stderr
, " (%s, %s, %s)\n", STR(list
->path
),
STR(list
->datfile
), STR(list
->posfile
)));
print_list(list
->child
, lev
+ 1);
* Convert the pattern to an ignore-case equivalent.
register unsigned int cnt
;
cnt
= 1; /* allow for '\0' */
for (sp
= orig
; *sp
!= '\0'; sp
++)
if ((new = malloc(cnt
)) == NULL
) {
fprintf(stderr
, "pattern too long for ignoring case\n");
for (sp
= new; *orig
!= '\0'; orig
++) {
else if (isupper(*orig
)) {
* Find all the fortunes which match the pattern we've been given.
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);
matches_in_list(File_list
);
* Return the maximum fortune len in the file list.
register int len
, maxlen
;
for (fp
= list
; fp
!= NULL
; fp
= fp
->next
) {
if ((len
= maxlen_in_list(fp
->child
)) > maxlen
)
if (fp
->tbl
.str_longlen
> maxlen
)
maxlen
= fp
->tbl
.str_longlen
;
* Print out the matches from the files in the list.
for (fp
= list
; fp
!= NULL
; fp
= fp
->next
) {
matches_in_list(fp
->child
);
DPRINTF(1, (stderr
, "searching in %s\n", fp
->path
));
while (fgets(sp
, Fort_len
, fp
->inf
) != NULL
)
if (!STR_ENDSTRING(sp
, fp
->tbl
))
printf("%c%c", fp
->tbl
.str_delim
,
printf(" (%s)", fp
->name
);
(void) fwrite(Fortbuf
, 1, (sp
- Fortbuf
), stdout
);
(void) fprintf(stderr
, "fortune [-a");
(void) fprintf(stderr
, "D");
(void) fprintf(stderr
, "f");
(void) fprintf(stderr
, "i");
(void) fprintf(stderr
, "losw]");
(void) fprintf(stderr
, " [-m pattern]");
(void) fprintf(stderr
, "[ [#%%] file/directory/all]\n");