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