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