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