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