1 /* 2 * Copyright (C) 1984-2025 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 HAVE_SYS_WAIT_H 17 #include <sys/wait.h> 18 #endif 19 #if OS2 || __MVS__ || (defined WIFSIGNALED && defined WTERMSIG) 20 #include <signal.h> 21 #endif 22 23 public int fd0 = 0; 24 25 extern lbool new_file; 26 extern char *every_first_cmd; 27 extern int force_open; 28 extern int is_tty; 29 extern int sigs; 30 extern int hshift; 31 extern int want_filesize; 32 extern int consecutive_nulls; 33 extern int modelines; 34 extern int show_preproc_error; 35 extern IFILE curr_ifile; 36 extern IFILE old_ifile; 37 extern struct scrpos initial_scrpos; 38 extern void *ml_examine; 39 extern POSITION soft_eof; 40 #if SPACES_IN_FILENAMES 41 extern char openquote; 42 extern char closequote; 43 #endif 44 45 #if LOGFILE 46 extern int logfile; 47 extern int force_logfile; 48 extern char *namelogfile; 49 #endif 50 51 #if HAVE_STAT_INO 52 public dev_t curr_dev; 53 public ino_t curr_ino; 54 #endif 55 56 /* 57 * Textlist functions deal with a list of words separated by spaces. 58 * init_textlist sets up a textlist structure. 59 * forw_textlist uses that structure to iterate thru the list of 60 * words, returning each one as a standard null-terminated string. 61 * back_textlist does the same, but runs thru the list backwards. 62 */ 63 public void init_textlist(struct textlist *tlist, mutable char *str) 64 { 65 char *s; 66 #if SPACES_IN_FILENAMES 67 lbool meta_quoted = FALSE; 68 lbool delim_quoted = FALSE; 69 constant char *esc = get_meta_escape(); 70 size_t esclen = strlen(esc); 71 #endif 72 73 tlist->string = skipsp(str); 74 tlist->endstring = tlist->string + strlen(tlist->string); 75 for (s = str; s < tlist->endstring; s++) 76 { 77 #if SPACES_IN_FILENAMES 78 if (meta_quoted) 79 { 80 meta_quoted = 0; 81 } else if (esclen > 0 && s + esclen < tlist->endstring && 82 strncmp(s, esc, esclen) == 0) 83 { 84 meta_quoted = 1; 85 s += esclen - 1; 86 } else if (delim_quoted) 87 { 88 if (*s == closequote) 89 delim_quoted = 0; 90 } else /* (!delim_quoted) */ 91 { 92 if (*s == openquote) 93 delim_quoted = 1; 94 else if (*s == ' ') 95 *s = '\0'; 96 } 97 #else 98 if (*s == ' ') 99 *s = '\0'; 100 #endif 101 } 102 } 103 104 public constant char * forw_textlist(struct textlist *tlist, constant char *prev) 105 { 106 constant 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 constant char * back_textlist(struct textlist *tlist, constant char *prev) 126 { 127 constant char *s; 128 129 /* 130 * prev == NULL means return the last word in the list. 131 * Otherwise, return the word before "prev". 132 */ 133 if (prev == NULL) 134 s = tlist->endstring; 135 else if (prev <= tlist->string) 136 return (NULL); 137 else 138 s = prev - 1; 139 while (*s == '\0') 140 s--; 141 if (s <= tlist->string) 142 return (NULL); 143 while (s[-1] != '\0' && s > tlist->string) 144 s--; 145 return (s); 146 } 147 148 /* 149 * Parse a single option setting in a modeline. 150 */ 151 static void modeline_option(constant char *str, size_t opt_len) 152 { 153 struct mloption { constant char *opt_name; void (*opt_func)(constant char*,size_t); }; 154 struct mloption options[] = { 155 { "ts=", set_tabs }, 156 { "tabstop=", set_tabs }, 157 { NULL, NULL } 158 }; 159 struct mloption *opt; 160 for (opt = options; opt->opt_name != NULL; opt++) 161 { 162 size_t name_len = strlen(opt->opt_name); 163 if (opt_len > name_len && strncmp(str, opt->opt_name, name_len) == 0) 164 { 165 (*opt->opt_func)(str + name_len, opt_len - name_len); 166 break; 167 } 168 } 169 } 170 171 /* 172 * String length, terminated by option separator (space or colon). 173 * Space/colon can be escaped with backspace. 174 */ 175 static size_t modeline_option_len(constant char *str) 176 { 177 lbool esc = FALSE; 178 constant char *s; 179 for (s = str; *s != '\0'; s++) 180 { 181 if (esc) 182 esc = FALSE; 183 else if (*s == '\\') 184 esc = TRUE; 185 else if (*s == ' ' || *s == ':') /* separator */ 186 break; 187 } 188 return ptr_diff(s, str); 189 } 190 191 /* 192 * Parse colon- or space-separated option settings in a modeline. 193 */ 194 static void modeline_options(constant char *str, char end_char) 195 { 196 for (;;) 197 { 198 size_t opt_len; 199 str = skipspc(str); 200 if (*str == '\0' || *str == end_char) 201 break; 202 opt_len = modeline_option_len(str); 203 modeline_option(str, opt_len); 204 str += opt_len; 205 if (*str != '\0') 206 str += 1; /* skip past the separator */ 207 } 208 } 209 210 /* 211 * See if there is a modeline string in a line. 212 */ 213 static void check_modeline(constant char *line) 214 { 215 #if HAVE_STRSTR 216 static constant char *pgms[] = { "less:", "vim:", "vi:", "ex:", NULL }; 217 constant char **pgm; 218 for (pgm = pgms; *pgm != NULL; ++pgm) 219 { 220 constant char *pline = line; 221 for (;;) 222 { 223 constant char *str; 224 pline = strstr(pline, *pgm); 225 if (pline == NULL) /* pgm is not in this line */ 226 break; 227 str = skipspc(pline + strlen(*pgm)); 228 if (pline == line || pline[-1] == ' ') 229 { 230 if (strncmp(str, "set ", 4) == 0) 231 modeline_options(str+4, ':'); 232 else if (pgm != &pgms[0]) /* "less:" requires "set" */ 233 modeline_options(str, '\0'); 234 break; 235 } 236 /* Continue searching the rest of the line. */ 237 pline = str; 238 } 239 } 240 #endif /* HAVE_STRSTR */ 241 } 242 243 /* 244 * Read lines from start of file and check if any are modelines. 245 */ 246 static void check_modelines(void) 247 { 248 POSITION pos = ch_zero(); 249 int i; 250 for (i = 0; i < modelines; i++) 251 { 252 constant char *line; 253 size_t line_len; 254 if (ABORT_SIGS()) 255 return; 256 pos = forw_raw_line(pos, &line, &line_len); 257 if (pos == NULL_POSITION) 258 break; 259 check_modeline(line); 260 } 261 } 262 263 /* 264 * Close a pipe opened via popen. 265 */ 266 static void close_pipe(FILE *pipefd) 267 { 268 int status; 269 char *p; 270 PARG parg; 271 272 if (pipefd == NULL) 273 return; 274 #if OS2 275 /* 276 * The pclose function of OS/2 emx sometimes fails. 277 * Send SIGINT to the piped process before closing it. 278 */ 279 kill(pipefd->_pid, SIGINT); 280 #endif 281 status = pclose(pipefd); 282 if (status == -1) 283 { 284 /* An internal error in 'less', not a preprocessor error. */ 285 p = errno_message("pclose"); 286 parg.p_string = p; 287 error("%s", &parg); 288 free(p); 289 return; 290 } 291 if (!show_preproc_error) 292 return; 293 #if defined WIFEXITED && defined WEXITSTATUS 294 if (WIFEXITED(status)) 295 { 296 int s = WEXITSTATUS(status); 297 if (s != 0) 298 { 299 parg.p_int = s; 300 error("Input preprocessor failed (status %d)", &parg); 301 } 302 return; 303 } 304 #endif 305 #if defined WIFSIGNALED && defined WTERMSIG 306 if (WIFSIGNALED(status)) 307 { 308 int sig = WTERMSIG(status); 309 if (sig != SIGPIPE || ch_length() != NULL_POSITION) 310 { 311 parg.p_string = signal_message(sig); 312 error("Input preprocessor terminated: %s", &parg); 313 } 314 return; 315 } 316 #endif 317 if (status != 0) 318 { 319 parg.p_int = status; 320 error("Input preprocessor exited with status %x", &parg); 321 } 322 } 323 324 /* 325 * Drain and close an input pipe if needed. 326 */ 327 public void close_altpipe(IFILE ifile) 328 { 329 FILE *altpipe = get_altpipe(ifile); 330 if (altpipe != NULL && !(ch_getflags() & CH_KEEPOPEN)) 331 { 332 close_pipe(altpipe); 333 set_altpipe(ifile, NULL); 334 } 335 } 336 337 /* 338 * Check for error status from the current altpipe. 339 * May or may not close the pipe. 340 */ 341 public void check_altpipe_error(void) 342 { 343 if (!show_preproc_error) 344 return; 345 if (curr_ifile != NULL_IFILE && get_altfilename(curr_ifile) != NULL) 346 close_altpipe(curr_ifile); 347 } 348 349 /* 350 * Close the current input file. 351 */ 352 static void close_file(void) 353 { 354 struct scrpos scrpos; 355 constant char *altfilename; 356 357 if (curr_ifile == NULL_IFILE) 358 return; 359 360 /* 361 * Save the current position so that we can return to 362 * the same position if we edit this file again. 363 */ 364 get_scrpos(&scrpos, TOP); 365 if (scrpos.pos != NULL_POSITION) 366 { 367 store_pos(curr_ifile, &scrpos); 368 lastmark(); 369 } 370 /* 371 * Close the file descriptor, unless it is a pipe. 372 */ 373 ch_close(); 374 /* 375 * If we opened a file using an alternate name, 376 * do special stuff to close it. 377 */ 378 altfilename = get_altfilename(curr_ifile); 379 if (altfilename != NULL) 380 { 381 close_altpipe(curr_ifile); 382 close_altfile(altfilename, get_filename(curr_ifile)); 383 set_altfilename(curr_ifile, NULL); 384 } 385 curr_ifile = NULL_IFILE; 386 #if HAVE_STAT_INO 387 curr_ino = curr_dev = 0; 388 #endif 389 } 390 391 /* 392 * Edit a new file (given its name). 393 * Filename == "-" means standard input. 394 * Filename == NULL means just close the current file. 395 */ 396 public int edit(constant char *filename) 397 { 398 if (filename == NULL) 399 return (edit_ifile(NULL_IFILE)); 400 return (edit_ifile(get_ifile(filename, curr_ifile))); 401 } 402 403 /* 404 * Clean up what edit_ifile did before error return. 405 */ 406 static int edit_error(constant char *filename, constant char *alt_filename, void *altpipe, IFILE ifile) 407 { 408 if (alt_filename != NULL) 409 { 410 close_pipe(altpipe); 411 close_altfile(alt_filename, filename); 412 free((char*)alt_filename); /* FIXME: WTF? */ 413 } 414 del_ifile(ifile); 415 /* 416 * Re-open the current file. 417 */ 418 if (curr_ifile == ifile) 419 { 420 /* 421 * Whoops. The "current" ifile is the one we just deleted. 422 * Just give up. 423 */ 424 quit(QUIT_ERROR); 425 } 426 return (1); 427 } 428 429 /* 430 * Edit a new file (given its IFILE). 431 * ifile == NULL means just close the current file. 432 */ 433 public int edit_ifile(IFILE ifile) 434 { 435 int f; 436 int answer; 437 int chflags; 438 constant char *filename; 439 constant char *open_filename; 440 char *alt_filename; 441 void *altpipe; 442 IFILE was_curr_ifile; 443 char *p; 444 PARG parg; 445 ssize_t nread = 0; 446 447 if (ifile == curr_ifile) 448 { 449 /* 450 * Already have the correct file open. 451 */ 452 return (0); 453 } 454 new_file = TRUE; 455 456 if (ifile != NULL_IFILE) 457 { 458 /* 459 * See if LESSOPEN specifies an "alternate" file to open. 460 */ 461 filename = get_filename(ifile); 462 altpipe = get_altpipe(ifile); 463 if (altpipe != NULL) 464 { 465 /* 466 * File is already open. 467 * chflags and f are not used by ch_init if ifile has 468 * filestate which should be the case if we're here. 469 * Set them here to avoid uninitialized variable warnings. 470 */ 471 chflags = 0; 472 f = -1; 473 alt_filename = get_altfilename(ifile); 474 open_filename = (alt_filename != NULL) ? alt_filename : filename; 475 } else 476 { 477 if (strcmp(filename, FAKE_HELPFILE) == 0 || 478 strcmp(filename, FAKE_EMPTYFILE) == 0) 479 alt_filename = NULL; 480 else 481 alt_filename = open_altfile(filename, &f, &altpipe); 482 483 open_filename = (alt_filename != NULL) ? alt_filename : filename; 484 485 chflags = 0; 486 if (altpipe != NULL) 487 { 488 /* 489 * The alternate "file" is actually a pipe. 490 * f has already been set to the file descriptor of the pipe 491 * in the call to open_altfile above. 492 * Keep the file descriptor open because it was opened 493 * via popen(), and pclose() wants to close it. 494 */ 495 chflags |= CH_POPENED; 496 if (strcmp(filename, "-") == 0) 497 chflags |= CH_KEEPOPEN; 498 } else if (strcmp(filename, "-") == 0) 499 { 500 /* 501 * Use standard input. 502 * Keep the file descriptor open because we can't reopen it. 503 */ 504 f = fd0; 505 chflags |= CH_KEEPOPEN; 506 /* 507 * Must switch stdin to BINARY mode. 508 */ 509 SET_BINARY(f); 510 #if MSDOS_COMPILER==DJGPPC 511 /* 512 * Setting stdin to binary by default causes 513 * Ctrl-C to not raise SIGINT. We must undo 514 * that side-effect. 515 */ 516 __djgpp_set_ctrl_c(1); 517 #endif 518 } else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0) 519 { 520 f = -1; 521 chflags |= CH_NODATA; 522 } else if (strcmp(open_filename, FAKE_HELPFILE) == 0) 523 { 524 f = -1; 525 chflags |= CH_HELPFILE; 526 } else if ((p = bad_file(open_filename)) != NULL) 527 { 528 /* 529 * It looks like a bad file. Don't try to open it. 530 */ 531 parg.p_string = p; 532 error("%s", &parg); 533 free(p); 534 return edit_error(filename, alt_filename, altpipe, ifile); 535 } else if ((f = iopen(open_filename, OPEN_READ)) < 0) 536 { 537 /* 538 * Got an error trying to open it. 539 */ 540 char *p = errno_message(filename); 541 parg.p_string = p; 542 error("%s", &parg); 543 free(p); 544 return edit_error(filename, alt_filename, altpipe, ifile); 545 } else 546 { 547 chflags |= CH_CANSEEK; 548 if (bin_file(f, &nread) && !force_open && !opened(ifile)) 549 { 550 /* 551 * Looks like a binary file. 552 * Ask user if we should proceed. 553 */ 554 parg.p_string = filename; 555 answer = query("\"%s\" may be a binary file. See it anyway? ", &parg); 556 if (answer != 'y' && answer != 'Y') 557 { 558 close(f); 559 return edit_error(filename, alt_filename, altpipe, ifile); 560 } 561 } 562 } 563 } 564 if (!force_open && f >= 0 && isatty(f)) 565 { 566 PARG parg; 567 parg.p_string = filename; 568 error("%s is a terminal (use -f to open it)", &parg); 569 return edit_error(filename, alt_filename, altpipe, ifile); 570 } 571 } 572 573 #if LOGFILE 574 end_logfile(); 575 #endif 576 was_curr_ifile = save_curr_ifile(); 577 if (curr_ifile != NULL_IFILE) 578 { 579 int was_helpfile = (ch_getflags() & CH_HELPFILE); 580 close_file(); 581 if (was_helpfile && held_ifile(was_curr_ifile) <= 1) 582 { 583 /* 584 * Don't keep the help file in the ifile list. 585 */ 586 del_ifile(was_curr_ifile); 587 was_curr_ifile = NULL_IFILE; 588 } 589 } 590 unsave_ifile(was_curr_ifile); 591 592 if (ifile == NULL_IFILE) 593 { 594 /* 595 * No new file to open. 596 * (Don't set old_ifile, because if you call edit_ifile(NULL), 597 * you're supposed to have saved curr_ifile yourself, 598 * and you'll restore it if necessary.) 599 */ 600 return (0); 601 } 602 603 /* 604 * Set up the new ifile. 605 * Get the saved position for the file. 606 */ 607 curr_ifile = ifile; 608 soft_eof = NULL_POSITION; 609 set_altfilename(curr_ifile, alt_filename); 610 set_altpipe(curr_ifile, altpipe); 611 set_open(curr_ifile); /* File has been opened */ 612 get_pos(curr_ifile, &initial_scrpos); 613 ch_init(f, chflags, nread); 614 consecutive_nulls = 0; 615 check_modelines(); 616 617 if (!(chflags & CH_HELPFILE)) 618 { 619 if (was_curr_ifile != NULL_IFILE) 620 old_ifile = was_curr_ifile; 621 #if LOGFILE 622 if (namelogfile != NULL && is_tty) 623 use_logfile(namelogfile); 624 #endif 625 #if HAVE_STAT_INO 626 /* Remember the i-number and device of the opened file. */ 627 if (strcmp(open_filename, "-") != 0) 628 { 629 struct stat statbuf; 630 int r = stat(open_filename, &statbuf); 631 if (r == 0) 632 { 633 curr_ino = statbuf.st_ino; 634 curr_dev = statbuf.st_dev; 635 } 636 } 637 #endif 638 if (every_first_cmd != NULL) 639 { 640 ungetsc(every_first_cmd); 641 ungetcc_end_command(); 642 } 643 } 644 645 flush(); 646 647 if (is_tty) 648 { 649 /* 650 * Output is to a real tty. 651 */ 652 653 /* 654 * Indicate there is nothing displayed yet. 655 */ 656 pos_clear(); 657 clr_linenum(); 658 #if HILITE_SEARCH 659 clr_hilite(); 660 #endif 661 undo_osc8(); 662 hshift = 0; 663 if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE)) 664 { 665 char *qfilename = shell_quote(filename); 666 cmd_addhist(ml_examine, qfilename, 1); 667 free(qfilename); 668 } 669 if (want_filesize) 670 scan_eof(); 671 set_header(ch_zero()); 672 } 673 return (0); 674 } 675 676 /* 677 * Edit a space-separated list of files. 678 * For each filename in the list, enter it into the ifile list. 679 * Then edit the first one. 680 */ 681 public int edit_list(char *filelist) 682 { 683 IFILE save_ifile; 684 constant char *good_filename; 685 constant char *filename; 686 char *gfilelist; 687 constant char *gfilename; 688 char *qfilename; 689 struct textlist tl_files; 690 struct textlist tl_gfiles; 691 692 save_ifile = save_curr_ifile(); 693 good_filename = NULL; 694 695 /* 696 * Run thru each filename in the list. 697 * Try to glob the filename. 698 * If it doesn't expand, just try to open the filename. 699 * If it does expand, try to open each name in that list. 700 */ 701 init_textlist(&tl_files, filelist); 702 filename = NULL; 703 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 704 { 705 gfilelist = lglob(filename); 706 init_textlist(&tl_gfiles, gfilelist); 707 gfilename = NULL; 708 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 709 { 710 qfilename = shell_unquote(gfilename); 711 if (edit(qfilename) == 0 && good_filename == NULL) 712 good_filename = get_filename(curr_ifile); 713 free(qfilename); 714 } 715 free(gfilelist); 716 } 717 /* 718 * Edit the first valid filename in the list. 719 */ 720 if (good_filename == NULL) 721 { 722 unsave_ifile(save_ifile); 723 return (1); 724 } 725 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 726 { 727 /* 728 * Trying to edit the current file; don't reopen it. 729 */ 730 unsave_ifile(save_ifile); 731 return (0); 732 } 733 reedit_ifile(save_ifile); 734 return (edit(good_filename)); 735 } 736 737 /* 738 * Edit the first file in the command line (ifile) list. 739 */ 740 public int edit_first(void) 741 { 742 if (nifile() == 0) 743 return (edit_stdin()); 744 curr_ifile = NULL_IFILE; 745 return (edit_next(1)); 746 } 747 748 /* 749 * Edit the last file in the command line (ifile) list. 750 */ 751 public int edit_last(void) 752 { 753 curr_ifile = NULL_IFILE; 754 return (edit_prev(1)); 755 } 756 757 758 /* 759 * Edit the n-th next or previous file in the command line (ifile) list. 760 */ 761 static int edit_istep(IFILE h, int n, int dir) 762 { 763 IFILE next; 764 765 /* 766 * Skip n filenames, then try to edit each filename. 767 */ 768 for (;;) 769 { 770 next = (dir > 0) ? next_ifile(h) : prev_ifile(h); 771 if (--n < 0) 772 { 773 if (edit_ifile(h) == 0) 774 break; 775 } 776 if (next == NULL_IFILE) 777 { 778 /* 779 * Reached end of the ifile list. 780 */ 781 return (1); 782 } 783 if (ABORT_SIGS()) 784 { 785 /* 786 * Interrupt breaks out, if we're in a long 787 * list of files that can't be opened. 788 */ 789 return (1); 790 } 791 h = next; 792 } 793 /* 794 * Found a file that we can edit. 795 */ 796 return (0); 797 } 798 799 static int edit_inext(IFILE h, int n) 800 { 801 return (edit_istep(h, n, +1)); 802 } 803 804 public int edit_next(int n) 805 { 806 return edit_istep(curr_ifile, n, +1); 807 } 808 809 static int edit_iprev(IFILE h, int n) 810 { 811 return (edit_istep(h, n, -1)); 812 } 813 814 public int edit_prev(int n) 815 { 816 return edit_istep(curr_ifile, n, -1); 817 } 818 819 /* 820 * Edit a specific file in the command line (ifile) list. 821 */ 822 public int edit_index(int n) 823 { 824 IFILE h; 825 826 h = NULL_IFILE; 827 do 828 { 829 if ((h = next_ifile(h)) == NULL_IFILE) 830 { 831 /* 832 * Reached end of the list without finding it. 833 */ 834 return (1); 835 } 836 } while (get_index(h) != n); 837 838 return (edit_ifile(h)); 839 } 840 841 public IFILE save_curr_ifile(void) 842 { 843 if (curr_ifile != NULL_IFILE) 844 hold_ifile(curr_ifile, 1); 845 return (curr_ifile); 846 } 847 848 public void unsave_ifile(IFILE save_ifile) 849 { 850 if (save_ifile != NULL_IFILE) 851 hold_ifile(save_ifile, -1); 852 } 853 854 /* 855 * Reedit the ifile which was previously open. 856 */ 857 public void reedit_ifile(IFILE save_ifile) 858 { 859 IFILE next; 860 IFILE prev; 861 862 /* 863 * Try to reopen the ifile. 864 * Note that opening it may fail (maybe the file was removed), 865 * in which case the ifile will be deleted from the list. 866 * So save the next and prev ifiles first. 867 */ 868 unsave_ifile(save_ifile); 869 next = next_ifile(save_ifile); 870 prev = prev_ifile(save_ifile); 871 if (edit_ifile(save_ifile) == 0) 872 return; 873 /* 874 * If can't reopen it, open the next input file in the list. 875 */ 876 if (next != NULL_IFILE && edit_inext(next, 0) == 0) 877 return; 878 /* 879 * If can't open THAT one, open the previous input file in the list. 880 */ 881 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0) 882 return; 883 /* 884 * If can't even open that, we're stuck. Just quit. 885 */ 886 quit(QUIT_ERROR); 887 } 888 889 public void reopen_curr_ifile(void) 890 { 891 IFILE save_ifile = save_curr_ifile(); 892 close_file(); 893 reedit_ifile(save_ifile); 894 } 895 896 /* 897 * Edit standard input. 898 */ 899 public int edit_stdin(void) 900 { 901 if (isatty(fd0)) 902 { 903 error("Missing filename (\"less --help\" for help)", NULL_PARG); 904 quit(QUIT_OK); 905 } 906 return (edit("-")); 907 } 908 909 /* 910 * Copy a file directly to standard output. 911 * Used if standard output is not a tty. 912 */ 913 public void cat_file(void) 914 { 915 int c; 916 917 while ((c = ch_forw_get()) != EOI) 918 putchr(c); 919 flush(); 920 } 921 922 #if LOGFILE 923 924 #define OVERWRITE_OPTIONS "Overwrite, Append, Don't log, or Quit?" 925 926 /* 927 * If the user asked for a log file and our input file 928 * is standard input, create the log file. 929 * We take care not to blindly overwrite an existing file. 930 */ 931 public void use_logfile(constant char *filename) 932 { 933 int exists; 934 int answer; 935 PARG parg; 936 937 if (ch_getflags() & CH_CANSEEK) 938 /* 939 * Can't currently use a log file on a file that can seek. 940 */ 941 return; 942 943 /* 944 * {{ We could use access() here. }} 945 */ 946 exists = open(filename, OPEN_READ); 947 if (exists >= 0) 948 close(exists); 949 exists = (exists >= 0); 950 951 /* 952 * Decide whether to overwrite the log file or append to it. 953 * If it doesn't exist we "overwrite" it. 954 */ 955 if (!exists || force_logfile) 956 { 957 /* 958 * Overwrite (or create) the log file. 959 */ 960 answer = 'O'; 961 } else 962 { 963 /* 964 * Ask user what to do. 965 */ 966 parg.p_string = filename; 967 answer = query("Warning: \"%s\" exists; "OVERWRITE_OPTIONS" ", &parg); 968 } 969 970 loop: 971 switch (answer) 972 { 973 case 'O': case 'o': 974 /* 975 * Overwrite: create the file. 976 */ 977 logfile = creat(filename, CREAT_RW); 978 break; 979 case 'A': case 'a': 980 /* 981 * Append: open the file and seek to the end. 982 */ 983 logfile = open(filename, OPEN_APPEND); 984 if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK) 985 { 986 close(logfile); 987 logfile = -1; 988 } 989 break; 990 case 'D': case 'd': 991 /* 992 * Don't do anything. 993 */ 994 return; 995 default: 996 /* 997 * Eh? 998 */ 999 1000 answer = query(OVERWRITE_OPTIONS" (Type \"O\", \"A\", \"D\" or \"Q\") ", NULL_PARG); 1001 goto loop; 1002 } 1003 1004 if (logfile < 0) 1005 { 1006 /* 1007 * Error in opening logfile. 1008 */ 1009 parg.p_string = filename; 1010 error("Cannot write to \"%s\"", &parg); 1011 return; 1012 } 1013 SET_BINARY(logfile); 1014 } 1015 1016 #endif 1017