1 /* 2 * Copyright (C) 1984-2007 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information about less, or for information on how to 8 * contact the author, see the README file. 9 */ 10 11 12 #include "less.h" 13 #if HAVE_STAT 14 #include <sys/stat.h> 15 #endif 16 17 public int fd0 = 0; 18 19 extern int new_file; 20 extern int errmsgs; 21 extern int cbufs; 22 extern char *every_first_cmd; 23 extern int any_display; 24 extern int force_open; 25 extern int is_tty; 26 extern int sigs; 27 extern IFILE curr_ifile; 28 extern IFILE old_ifile; 29 extern struct scrpos initial_scrpos; 30 extern void constant *ml_examine; 31 #if SPACES_IN_FILENAMES 32 extern char openquote; 33 extern char closequote; 34 #endif 35 36 #if LOGFILE 37 extern int logfile; 38 extern int force_logfile; 39 extern char *namelogfile; 40 #endif 41 42 #if HAVE_STAT_INO 43 public dev_t curr_dev; 44 public ino_t curr_ino; 45 #endif 46 47 char *curr_altfilename = NULL; 48 static void *curr_altpipe; 49 50 51 /* 52 * Textlist functions deal with a list of words separated by spaces. 53 * init_textlist sets up a textlist structure. 54 * forw_textlist uses that structure to iterate thru the list of 55 * words, returning each one as a standard null-terminated string. 56 * back_textlist does the same, but runs thru the list backwards. 57 */ 58 public void 59 init_textlist(tlist, str) 60 struct textlist *tlist; 61 char *str; 62 { 63 char *s; 64 #if SPACES_IN_FILENAMES 65 int meta_quoted = 0; 66 int delim_quoted = 0; 67 char *esc = get_meta_escape(); 68 int esclen = strlen(esc); 69 #endif 70 71 tlist->string = skipsp(str); 72 tlist->endstring = tlist->string + strlen(tlist->string); 73 for (s = str; s < tlist->endstring; s++) 74 { 75 #if SPACES_IN_FILENAMES 76 if (meta_quoted) 77 { 78 meta_quoted = 0; 79 } else if (esclen > 0 && s + esclen < tlist->endstring && 80 strncmp(s, esc, esclen) == 0) 81 { 82 meta_quoted = 1; 83 s += esclen - 1; 84 } else if (delim_quoted) 85 { 86 if (*s == closequote) 87 delim_quoted = 0; 88 } else /* (!delim_quoted) */ 89 { 90 if (*s == openquote) 91 delim_quoted = 1; 92 else if (*s == ' ') 93 *s = '\0'; 94 } 95 #else 96 if (*s == ' ') 97 *s = '\0'; 98 #endif 99 } 100 } 101 102 public char * 103 forw_textlist(tlist, prev) 104 struct textlist *tlist; 105 char *prev; 106 { 107 char *s; 108 109 /* 110 * prev == NULL means return the first word in the list. 111 * Otherwise, return the word after "prev". 112 */ 113 if (prev == NULL) 114 s = tlist->string; 115 else 116 s = prev + strlen(prev); 117 if (s >= tlist->endstring) 118 return (NULL); 119 while (*s == '\0') 120 s++; 121 if (s >= tlist->endstring) 122 return (NULL); 123 return (s); 124 } 125 126 public char * 127 back_textlist(tlist, prev) 128 struct textlist *tlist; 129 char *prev; 130 { 131 char *s; 132 133 /* 134 * prev == NULL means return the last word in the list. 135 * Otherwise, return the word before "prev". 136 */ 137 if (prev == NULL) 138 s = tlist->endstring; 139 else if (prev <= tlist->string) 140 return (NULL); 141 else 142 s = prev - 1; 143 while (*s == '\0') 144 s--; 145 if (s <= tlist->string) 146 return (NULL); 147 while (s[-1] != '\0' && s > tlist->string) 148 s--; 149 return (s); 150 } 151 152 /* 153 * Close the current input file. 154 */ 155 static void 156 close_file() 157 { 158 struct scrpos scrpos; 159 160 if (curr_ifile == NULL_IFILE) 161 return; 162 163 /* 164 * Save the current position so that we can return to 165 * the same position if we edit this file again. 166 */ 167 get_scrpos(&scrpos); 168 if (scrpos.pos != NULL_POSITION) 169 { 170 store_pos(curr_ifile, &scrpos); 171 lastmark(); 172 } 173 /* 174 * Close the file descriptor, unless it is a pipe. 175 */ 176 ch_close(); 177 /* 178 * If we opened a file using an alternate name, 179 * do special stuff to close it. 180 */ 181 if (curr_altfilename != NULL) 182 { 183 close_altfile(curr_altfilename, get_filename(curr_ifile), 184 curr_altpipe); 185 free(curr_altfilename); 186 curr_altfilename = NULL; 187 } 188 curr_ifile = NULL_IFILE; 189 #if HAVE_STAT_INO 190 curr_ino = curr_dev = 0; 191 #endif 192 } 193 194 /* 195 * Edit a new file (given its name). 196 * Filename == "-" means standard input. 197 * Filename == NULL means just close the current file. 198 */ 199 public int 200 edit(filename) 201 char *filename; 202 { 203 if (filename == NULL) 204 return (edit_ifile(NULL_IFILE)); 205 return (edit_ifile(get_ifile(filename, curr_ifile))); 206 } 207 208 /* 209 * Edit a new file (given its IFILE). 210 * ifile == NULL means just close the current file. 211 */ 212 public int 213 edit_ifile(ifile) 214 IFILE ifile; 215 { 216 int f; 217 int answer; 218 int no_display; 219 int chflags; 220 char *filename; 221 char *open_filename; 222 char *qopen_filename; 223 char *alt_filename; 224 void *alt_pipe; 225 IFILE was_curr_ifile; 226 PARG parg; 227 228 if (ifile == curr_ifile) 229 { 230 /* 231 * Already have the correct file open. 232 */ 233 return (0); 234 } 235 236 /* 237 * We must close the currently open file now. 238 * This is necessary to make the open_altfile/close_altfile pairs 239 * nest properly (or rather to avoid nesting at all). 240 * {{ Some stupid implementations of popen() mess up if you do: 241 * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }} 242 */ 243 #if LOGFILE 244 end_logfile(); 245 #endif 246 was_curr_ifile = save_curr_ifile(); 247 if (curr_ifile != NULL_IFILE) 248 { 249 chflags = ch_getflags(); 250 close_file(); 251 if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1) 252 { 253 /* 254 * Don't keep the help file in the ifile list. 255 */ 256 del_ifile(was_curr_ifile); 257 was_curr_ifile = old_ifile; 258 } 259 } 260 261 if (ifile == NULL_IFILE) 262 { 263 /* 264 * No new file to open. 265 * (Don't set old_ifile, because if you call edit_ifile(NULL), 266 * you're supposed to have saved curr_ifile yourself, 267 * and you'll restore it if necessary.) 268 */ 269 unsave_ifile(was_curr_ifile); 270 return (0); 271 } 272 273 filename = save(get_filename(ifile)); 274 /* 275 * See if LESSOPEN specifies an "alternate" file to open. 276 */ 277 alt_pipe = NULL; 278 alt_filename = open_altfile(filename, &f, &alt_pipe); 279 open_filename = (alt_filename != NULL) ? alt_filename : filename; 280 qopen_filename = shell_unquote(open_filename); 281 282 chflags = 0; 283 if (alt_pipe != NULL) 284 { 285 /* 286 * The alternate "file" is actually a pipe. 287 * f has already been set to the file descriptor of the pipe 288 * in the call to open_altfile above. 289 * Keep the file descriptor open because it was opened 290 * via popen(), and pclose() wants to close it. 291 */ 292 chflags |= CH_POPENED; 293 } else if (strcmp(open_filename, "-") == 0) 294 { 295 /* 296 * Use standard input. 297 * Keep the file descriptor open because we can't reopen it. 298 */ 299 f = fd0; 300 chflags |= CH_KEEPOPEN; 301 /* 302 * Must switch stdin to BINARY mode. 303 */ 304 SET_BINARY(f); 305 #if MSDOS_COMPILER==DJGPPC 306 /* 307 * Setting stdin to binary by default causes 308 * Ctrl-C to not raise SIGINT. We must undo 309 * that side-effect. 310 */ 311 __djgpp_set_ctrl_c(1); 312 #endif 313 } else if (strcmp(open_filename, FAKE_HELPFILE) == 0) 314 { 315 f = -1; 316 chflags |= CH_HELPFILE; 317 } else if ((parg.p_string = bad_file(open_filename)) != NULL) 318 { 319 /* 320 * It looks like a bad file. Don't try to open it. 321 */ 322 error("%s", &parg); 323 free(parg.p_string); 324 err1: 325 if (alt_filename != NULL) 326 { 327 close_altfile(alt_filename, filename, alt_pipe); 328 free(alt_filename); 329 } 330 del_ifile(ifile); 331 free(qopen_filename); 332 free(filename); 333 /* 334 * Re-open the current file. 335 */ 336 if (was_curr_ifile == ifile) 337 { 338 /* 339 * Whoops. The "current" ifile is the one we just deleted. 340 * Just give up. 341 */ 342 quit(QUIT_ERROR); 343 } 344 reedit_ifile(was_curr_ifile); 345 return (1); 346 } else if ((f = open(qopen_filename, OPEN_READ)) < 0) 347 { 348 /* 349 * Got an error trying to open it. 350 */ 351 parg.p_string = errno_message(filename); 352 error("%s", &parg); 353 free(parg.p_string); 354 goto err1; 355 } else 356 { 357 chflags |= CH_CANSEEK; 358 if (!force_open && !opened(ifile) && bin_file(f)) 359 { 360 /* 361 * Looks like a binary file. 362 * Ask user if we should proceed. 363 */ 364 parg.p_string = filename; 365 answer = query("\"%s\" may be a binary file. See it anyway? ", 366 &parg); 367 if (answer != 'y' && answer != 'Y') 368 { 369 close(f); 370 goto err1; 371 } 372 } 373 } 374 375 /* 376 * Get the new ifile. 377 * Get the saved position for the file. 378 */ 379 if (was_curr_ifile != NULL_IFILE) 380 { 381 old_ifile = was_curr_ifile; 382 unsave_ifile(was_curr_ifile); 383 } 384 curr_ifile = ifile; 385 curr_altfilename = alt_filename; 386 curr_altpipe = alt_pipe; 387 set_open(curr_ifile); /* File has been opened */ 388 get_pos(curr_ifile, &initial_scrpos); 389 new_file = TRUE; 390 ch_init(f, chflags); 391 392 if (!(chflags & CH_HELPFILE)) 393 { 394 #if LOGFILE 395 if (namelogfile != NULL && is_tty) 396 use_logfile(namelogfile); 397 #endif 398 #if HAVE_STAT_INO 399 /* Remember the i-number and device of the opened file. */ 400 { 401 struct stat statbuf; 402 int r = stat(qopen_filename, &statbuf); 403 if (r == 0) 404 { 405 curr_ino = statbuf.st_ino; 406 curr_dev = statbuf.st_dev; 407 } 408 } 409 #endif 410 if (every_first_cmd != NULL) 411 ungetsc(every_first_cmd); 412 } 413 414 free(qopen_filename); 415 no_display = !any_display; 416 flush(); 417 any_display = TRUE; 418 419 if (is_tty) 420 { 421 /* 422 * Output is to a real tty. 423 */ 424 425 /* 426 * Indicate there is nothing displayed yet. 427 */ 428 pos_clear(); 429 clr_linenum(); 430 #if HILITE_SEARCH 431 clr_hilite(); 432 #endif 433 cmd_addhist(ml_examine, filename); 434 if (no_display && errmsgs > 0) 435 { 436 /* 437 * We displayed some messages on error output 438 * (file descriptor 2; see error() function). 439 * Before erasing the screen contents, 440 * display the file name and wait for a keystroke. 441 */ 442 parg.p_string = filename; 443 error("%s", &parg); 444 } 445 } 446 free(filename); 447 return (0); 448 } 449 450 /* 451 * Edit a space-separated list of files. 452 * For each filename in the list, enter it into the ifile list. 453 * Then edit the first one. 454 */ 455 public int 456 edit_list(filelist) 457 char *filelist; 458 { 459 IFILE save_ifile; 460 char *good_filename; 461 char *filename; 462 char *gfilelist; 463 char *gfilename; 464 struct textlist tl_files; 465 struct textlist tl_gfiles; 466 467 save_ifile = save_curr_ifile(); 468 good_filename = NULL; 469 470 /* 471 * Run thru each filename in the list. 472 * Try to glob the filename. 473 * If it doesn't expand, just try to open the filename. 474 * If it does expand, try to open each name in that list. 475 */ 476 init_textlist(&tl_files, filelist); 477 filename = NULL; 478 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 479 { 480 gfilelist = lglob(filename); 481 init_textlist(&tl_gfiles, gfilelist); 482 gfilename = NULL; 483 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 484 { 485 if (edit(gfilename) == 0 && good_filename == NULL) 486 good_filename = get_filename(curr_ifile); 487 } 488 free(gfilelist); 489 } 490 /* 491 * Edit the first valid filename in the list. 492 */ 493 if (good_filename == NULL) 494 { 495 unsave_ifile(save_ifile); 496 return (1); 497 } 498 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 499 { 500 /* 501 * Trying to edit the current file; don't reopen it. 502 */ 503 unsave_ifile(save_ifile); 504 return (0); 505 } 506 reedit_ifile(save_ifile); 507 return (edit(good_filename)); 508 } 509 510 /* 511 * Edit the first file in the command line (ifile) list. 512 */ 513 public int 514 edit_first() 515 { 516 curr_ifile = NULL_IFILE; 517 return (edit_next(1)); 518 } 519 520 /* 521 * Edit the last file in the command line (ifile) list. 522 */ 523 public int 524 edit_last() 525 { 526 curr_ifile = NULL_IFILE; 527 return (edit_prev(1)); 528 } 529 530 531 /* 532 * Edit the n-th next or previous file in the command line (ifile) list. 533 */ 534 static int 535 edit_istep(h, n, dir) 536 IFILE h; 537 int n; 538 int dir; 539 { 540 IFILE next; 541 542 /* 543 * Skip n filenames, then try to edit each filename. 544 */ 545 for (;;) 546 { 547 next = (dir > 0) ? next_ifile(h) : prev_ifile(h); 548 if (--n < 0) 549 { 550 if (edit_ifile(h) == 0) 551 break; 552 } 553 if (next == NULL_IFILE) 554 { 555 /* 556 * Reached end of the ifile list. 557 */ 558 return (1); 559 } 560 if (ABORT_SIGS()) 561 { 562 /* 563 * Interrupt breaks out, if we're in a long 564 * list of files that can't be opened. 565 */ 566 return (1); 567 } 568 h = next; 569 } 570 /* 571 * Found a file that we can edit. 572 */ 573 return (0); 574 } 575 576 static int 577 edit_inext(h, n) 578 IFILE h; 579 int n; 580 { 581 return (edit_istep(h, n, +1)); 582 } 583 584 public int 585 edit_next(n) 586 int n; 587 { 588 return edit_istep(curr_ifile, n, +1); 589 } 590 591 static int 592 edit_iprev(h, n) 593 IFILE h; 594 int n; 595 { 596 return (edit_istep(h, n, -1)); 597 } 598 599 public int 600 edit_prev(n) 601 int n; 602 { 603 return edit_istep(curr_ifile, n, -1); 604 } 605 606 /* 607 * Edit a specific file in the command line (ifile) list. 608 */ 609 public int 610 edit_index(n) 611 int n; 612 { 613 IFILE h; 614 615 h = NULL_IFILE; 616 do 617 { 618 if ((h = next_ifile(h)) == NULL_IFILE) 619 { 620 /* 621 * Reached end of the list without finding it. 622 */ 623 return (1); 624 } 625 } while (get_index(h) != n); 626 627 return (edit_ifile(h)); 628 } 629 630 public IFILE 631 save_curr_ifile() 632 { 633 if (curr_ifile != NULL_IFILE) 634 hold_ifile(curr_ifile, 1); 635 return (curr_ifile); 636 } 637 638 public void 639 unsave_ifile(save_ifile) 640 IFILE save_ifile; 641 { 642 if (save_ifile != NULL_IFILE) 643 hold_ifile(save_ifile, -1); 644 } 645 646 /* 647 * Reedit the ifile which was previously open. 648 */ 649 public void 650 reedit_ifile(save_ifile) 651 IFILE save_ifile; 652 { 653 IFILE next; 654 IFILE prev; 655 656 /* 657 * Try to reopen the ifile. 658 * Note that opening it may fail (maybe the file was removed), 659 * in which case the ifile will be deleted from the list. 660 * So save the next and prev ifiles first. 661 */ 662 unsave_ifile(save_ifile); 663 next = next_ifile(save_ifile); 664 prev = prev_ifile(save_ifile); 665 if (edit_ifile(save_ifile) == 0) 666 return; 667 /* 668 * If can't reopen it, open the next input file in the list. 669 */ 670 if (next != NULL_IFILE && edit_inext(next, 0) == 0) 671 return; 672 /* 673 * If can't open THAT one, open the previous input file in the list. 674 */ 675 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0) 676 return; 677 /* 678 * If can't even open that, we're stuck. Just quit. 679 */ 680 quit(QUIT_ERROR); 681 } 682 683 public void 684 reopen_curr_ifile() 685 { 686 IFILE save_ifile = save_curr_ifile(); 687 close_file(); 688 reedit_ifile(save_ifile); 689 } 690 691 /* 692 * Edit standard input. 693 */ 694 public int 695 edit_stdin() 696 { 697 if (isatty(fd0)) 698 { 699 error("Missing filename (\"less --help\" for help)", NULL_PARG); 700 quit(QUIT_OK); 701 } 702 return (edit("-")); 703 } 704 705 /* 706 * Copy a file directly to standard output. 707 * Used if standard output is not a tty. 708 */ 709 public void 710 cat_file() 711 { 712 register int c; 713 714 while ((c = ch_forw_get()) != EOI) 715 putchr(c); 716 flush(); 717 } 718 719 #if LOGFILE 720 721 /* 722 * If the user asked for a log file and our input file 723 * is standard input, create the log file. 724 * We take care not to blindly overwrite an existing file. 725 */ 726 public void 727 use_logfile(filename) 728 char *filename; 729 { 730 register int exists; 731 register int answer; 732 PARG parg; 733 734 if (ch_getflags() & CH_CANSEEK) 735 /* 736 * Can't currently use a log file on a file that can seek. 737 */ 738 return; 739 740 /* 741 * {{ We could use access() here. }} 742 */ 743 filename = shell_unquote(filename); 744 exists = open(filename, OPEN_READ); 745 close(exists); 746 exists = (exists >= 0); 747 748 /* 749 * Decide whether to overwrite the log file or append to it. 750 * If it doesn't exist we "overwrite" it. 751 */ 752 if (!exists || force_logfile) 753 { 754 /* 755 * Overwrite (or create) the log file. 756 */ 757 answer = 'O'; 758 } else 759 { 760 /* 761 * Ask user what to do. 762 */ 763 parg.p_string = filename; 764 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg); 765 } 766 767 loop: 768 switch (answer) 769 { 770 case 'O': case 'o': 771 /* 772 * Overwrite: create the file. 773 */ 774 logfile = creat(filename, 0644); 775 break; 776 case 'A': case 'a': 777 /* 778 * Append: open the file and seek to the end. 779 */ 780 logfile = open(filename, OPEN_APPEND); 781 if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK) 782 { 783 close(logfile); 784 logfile = -1; 785 } 786 break; 787 case 'D': case 'd': 788 /* 789 * Don't do anything. 790 */ 791 free(filename); 792 return; 793 case 'q': 794 quit(QUIT_OK); 795 /*NOTREACHED*/ 796 default: 797 /* 798 * Eh? 799 */ 800 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG); 801 goto loop; 802 } 803 804 if (logfile < 0) 805 { 806 /* 807 * Error in opening logfile. 808 */ 809 parg.p_string = filename; 810 error("Cannot write to \"%s\"", &parg); 811 free(filename); 812 return; 813 } 814 free(filename); 815 SET_BINARY(logfile); 816 } 817 818 #endif 819