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