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