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