1 /* 2 * Copyright (C) 1984-2002 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 about less, or for information on how to 8 * contact the author, see the README file. 9 */ 10 11 12 #include "less.h" 13 14 public int fd0 = 0; 15 16 extern int new_file; 17 extern int errmsgs; 18 extern int cbufs; 19 extern char *every_first_cmd; 20 extern int any_display; 21 extern int force_open; 22 extern int is_tty; 23 extern int sigs; 24 extern IFILE curr_ifile; 25 extern IFILE old_ifile; 26 extern struct scrpos initial_scrpos; 27 extern void constant *ml_examine; 28 #if SPACES_IN_FILENAMES 29 extern char openquote; 30 extern char closequote; 31 #endif 32 33 #if LOGFILE 34 extern int logfile; 35 extern int force_logfile; 36 extern char *namelogfile; 37 #endif 38 39 char *curr_altfilename = NULL; 40 static void *curr_altpipe; 41 42 43 /* 44 * Textlist functions deal with a list of words separated by spaces. 45 * init_textlist sets up a textlist structure. 46 * forw_textlist uses that structure to iterate thru the list of 47 * words, returning each one as a standard null-terminated string. 48 * back_textlist does the same, but runs thru the list backwards. 49 */ 50 public void 51 init_textlist(tlist, str) 52 struct textlist *tlist; 53 char *str; 54 { 55 char *s; 56 #if SPACES_IN_FILENAMES 57 int meta_quoted = 0; 58 int delim_quoted = 0; 59 char *esc = get_meta_escape(); 60 int esclen = strlen(esc); 61 #endif 62 63 tlist->string = skipsp(str); 64 tlist->endstring = tlist->string + strlen(tlist->string); 65 for (s = str; s < tlist->endstring; s++) 66 { 67 #if SPACES_IN_FILENAMES 68 if (meta_quoted) 69 { 70 meta_quoted = 0; 71 } else if (esclen > 0 && s + esclen < tlist->endstring && 72 strncmp(s, esc, esclen) == 0) 73 { 74 meta_quoted = 1; 75 s += esclen - 1; 76 } else if (delim_quoted) 77 { 78 if (*s == closequote) 79 delim_quoted = 0; 80 } else /* (!delim_quoted) */ 81 { 82 if (*s == openquote) 83 delim_quoted = 1; 84 else if (*s == ' ') 85 *s = '\0'; 86 } 87 #else 88 if (*s == ' ') 89 *s = '\0'; 90 #endif 91 } 92 } 93 94 public char * 95 forw_textlist(tlist, prev) 96 struct textlist *tlist; 97 char *prev; 98 { 99 char *s; 100 101 /* 102 * prev == NULL means return the first word in the list. 103 * Otherwise, return the word after "prev". 104 */ 105 if (prev == NULL) 106 s = tlist->string; 107 else 108 s = prev + strlen(prev); 109 if (s >= tlist->endstring) 110 return (NULL); 111 while (*s == '\0') 112 s++; 113 if (s >= tlist->endstring) 114 return (NULL); 115 return (s); 116 } 117 118 public char * 119 back_textlist(tlist, prev) 120 struct textlist *tlist; 121 char *prev; 122 { 123 char *s; 124 125 /* 126 * prev == NULL means return the last word in the list. 127 * Otherwise, return the word before "prev". 128 */ 129 if (prev == NULL) 130 s = tlist->endstring; 131 else if (prev <= tlist->string) 132 return (NULL); 133 else 134 s = prev - 1; 135 while (*s == '\0') 136 s--; 137 if (s <= tlist->string) 138 return (NULL); 139 while (s[-1] != '\0' && s > tlist->string) 140 s--; 141 return (s); 142 } 143 144 /* 145 * Close the current input file. 146 */ 147 static void 148 close_file() 149 { 150 struct scrpos scrpos; 151 152 if (curr_ifile == NULL_IFILE) 153 return; 154 155 /* 156 * Save the current position so that we can return to 157 * the same position if we edit this file again. 158 */ 159 get_scrpos(&scrpos); 160 if (scrpos.pos != NULL_POSITION) 161 { 162 store_pos(curr_ifile, &scrpos); 163 lastmark(); 164 } 165 /* 166 * Close the file descriptor, unless it is a pipe. 167 */ 168 ch_close(); 169 /* 170 * If we opened a file using an alternate name, 171 * do special stuff to close it. 172 */ 173 if (curr_altfilename != NULL) 174 { 175 close_altfile(curr_altfilename, get_filename(curr_ifile), 176 curr_altpipe); 177 free(curr_altfilename); 178 curr_altfilename = NULL; 179 } 180 curr_ifile = NULL_IFILE; 181 } 182 183 /* 184 * Edit a new file (given its name). 185 * Filename == "-" means standard input. 186 * Filename == NULL means just close the current file. 187 */ 188 public int 189 edit(filename) 190 char *filename; 191 { 192 if (filename == NULL) 193 return (edit_ifile(NULL_IFILE)); 194 return (edit_ifile(get_ifile(filename, curr_ifile))); 195 } 196 197 /* 198 * Edit a new file (given its IFILE). 199 * ifile == NULL means just close the current file. 200 */ 201 public int 202 edit_ifile(ifile) 203 IFILE ifile; 204 { 205 int f; 206 int answer; 207 int no_display; 208 int chflags; 209 char *filename; 210 char *open_filename; 211 char *qopen_filename; 212 char *alt_filename; 213 void *alt_pipe; 214 IFILE was_curr_ifile; 215 PARG parg; 216 217 if (ifile == curr_ifile) 218 { 219 /* 220 * Already have the correct file open. 221 */ 222 return (0); 223 } 224 225 /* 226 * We must close the currently open file now. 227 * This is necessary to make the open_altfile/close_altfile pairs 228 * nest properly (or rather to avoid nesting at all). 229 * {{ Some stupid implementations of popen() mess up if you do: 230 * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }} 231 */ 232 #if LOGFILE 233 end_logfile(); 234 #endif 235 was_curr_ifile = save_curr_ifile(); 236 if (curr_ifile != NULL_IFILE) 237 { 238 chflags = ch_getflags(); 239 close_file(); 240 if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1) 241 { 242 /* 243 * Don't keep the help file in the ifile list. 244 */ 245 del_ifile(was_curr_ifile); 246 was_curr_ifile = old_ifile; 247 } 248 } 249 250 if (ifile == NULL_IFILE) 251 { 252 /* 253 * No new file to open. 254 * (Don't set old_ifile, because if you call edit_ifile(NULL), 255 * you're supposed to have saved curr_ifile yourself, 256 * and you'll restore it if necessary.) 257 */ 258 unsave_ifile(was_curr_ifile); 259 return (0); 260 } 261 262 filename = save(get_filename(ifile)); 263 /* 264 * See if LESSOPEN specifies an "alternate" file to open. 265 */ 266 alt_pipe = NULL; 267 alt_filename = open_altfile(filename, &f, &alt_pipe); 268 open_filename = (alt_filename != NULL) ? alt_filename : filename; 269 qopen_filename = shell_unquote(open_filename); 270 271 chflags = 0; 272 if (alt_pipe != NULL) 273 { 274 /* 275 * The alternate "file" is actually a pipe. 276 * f has already been set to the file descriptor of the pipe 277 * in the call to open_altfile above. 278 * Keep the file descriptor open because it was opened 279 * via popen(), and pclose() wants to close it. 280 */ 281 chflags |= CH_POPENED; 282 } else if (strcmp(open_filename, "-") == 0) 283 { 284 /* 285 * Use standard input. 286 * Keep the file descriptor open because we can't reopen it. 287 */ 288 f = fd0; 289 chflags |= CH_KEEPOPEN; 290 /* 291 * Must switch stdin to BINARY mode. 292 */ 293 SET_BINARY(f); 294 #if MSDOS_COMPILER==DJGPPC 295 /* 296 * Setting stdin to binary by default causes 297 * Ctrl-C to not raise SIGINT. We must undo 298 * that side-effect. 299 */ 300 __djgpp_set_ctrl_c(1); 301 #endif 302 } else if (strcmp(open_filename, FAKE_HELPFILE) == 0) 303 { 304 f = -1; 305 chflags |= CH_HELPFILE; 306 } else if ((parg.p_string = bad_file(open_filename)) != NULL) 307 { 308 /* 309 * It looks like a bad file. Don't try to open it. 310 */ 311 error("%s", &parg); 312 free(parg.p_string); 313 err1: 314 if (alt_filename != NULL) 315 { 316 close_altfile(alt_filename, filename, alt_pipe); 317 free(alt_filename); 318 } 319 del_ifile(ifile); 320 free(qopen_filename); 321 free(filename); 322 /* 323 * Re-open the current file. 324 */ 325 reedit_ifile(was_curr_ifile); 326 return (1); 327 } else if ((f = open(qopen_filename, OPEN_READ)) < 0) 328 { 329 /* 330 * Got an error trying to open it. 331 */ 332 parg.p_string = errno_message(filename); 333 error("%s", &parg); 334 free(parg.p_string); 335 goto err1; 336 } else 337 { 338 chflags |= CH_CANSEEK; 339 if (!force_open && !opened(ifile) && bin_file(f)) 340 { 341 /* 342 * Looks like a binary file. 343 * Ask user if we should proceed. 344 */ 345 parg.p_string = filename; 346 answer = query("\"%s\" may be a binary file. See it anyway? ", 347 &parg); 348 if (answer != 'y' && answer != 'Y') 349 { 350 close(f); 351 goto err1; 352 } 353 } 354 } 355 free(qopen_filename); 356 357 /* 358 * Get the new ifile. 359 * Get the saved position for the file. 360 */ 361 if (was_curr_ifile != NULL_IFILE) 362 { 363 old_ifile = was_curr_ifile; 364 unsave_ifile(was_curr_ifile); 365 } 366 curr_ifile = ifile; 367 curr_altfilename = alt_filename; 368 curr_altpipe = alt_pipe; 369 set_open(curr_ifile); /* File has been opened */ 370 get_pos(curr_ifile, &initial_scrpos); 371 new_file = TRUE; 372 ch_init(f, chflags); 373 374 if (!(chflags & CH_HELPFILE)) 375 { 376 #if LOGFILE 377 if (namelogfile != NULL && is_tty) 378 use_logfile(namelogfile); 379 #endif 380 if (every_first_cmd != NULL) 381 ungetsc(every_first_cmd); 382 } 383 384 no_display = !any_display; 385 flush(); 386 any_display = TRUE; 387 388 if (is_tty) 389 { 390 /* 391 * Output is to a real tty. 392 */ 393 394 /* 395 * Indicate there is nothing displayed yet. 396 */ 397 pos_clear(); 398 clr_linenum(); 399 #if HILITE_SEARCH 400 clr_hilite(); 401 #endif 402 cmd_addhist(ml_examine, filename); 403 if (no_display && errmsgs > 0) 404 { 405 /* 406 * We displayed some messages on error output 407 * (file descriptor 2; see error() function). 408 * Before erasing the screen contents, 409 * display the file name and wait for a keystroke. 410 */ 411 parg.p_string = filename; 412 error("%s", &parg); 413 } 414 } 415 free(filename); 416 return (0); 417 } 418 419 /* 420 * Edit a space-separated list of files. 421 * For each filename in the list, enter it into the ifile list. 422 * Then edit the first one. 423 */ 424 public int 425 edit_list(filelist) 426 char *filelist; 427 { 428 IFILE save_ifile; 429 char *good_filename; 430 char *filename; 431 char *gfilelist; 432 char *gfilename; 433 struct textlist tl_files; 434 struct textlist tl_gfiles; 435 436 save_ifile = save_curr_ifile(); 437 good_filename = NULL; 438 439 /* 440 * Run thru each filename in the list. 441 * Try to glob the filename. 442 * If it doesn't expand, just try to open the filename. 443 * If it does expand, try to open each name in that list. 444 */ 445 init_textlist(&tl_files, filelist); 446 filename = NULL; 447 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 448 { 449 gfilelist = lglob(filename); 450 init_textlist(&tl_gfiles, gfilelist); 451 gfilename = NULL; 452 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 453 { 454 if (edit(gfilename) == 0 && good_filename == NULL) 455 good_filename = get_filename(curr_ifile); 456 } 457 free(gfilelist); 458 } 459 /* 460 * Edit the first valid filename in the list. 461 */ 462 if (good_filename == NULL) 463 { 464 unsave_ifile(save_ifile); 465 return (1); 466 } 467 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 468 { 469 /* 470 * Trying to edit the current file; don't reopen it. 471 */ 472 unsave_ifile(save_ifile); 473 return (0); 474 } 475 reedit_ifile(save_ifile); 476 return (edit(good_filename)); 477 } 478 479 /* 480 * Edit the first file in the command line (ifile) list. 481 */ 482 public int 483 edit_first() 484 { 485 curr_ifile = NULL_IFILE; 486 return (edit_next(1)); 487 } 488 489 /* 490 * Edit the last file in the command line (ifile) list. 491 */ 492 public int 493 edit_last() 494 { 495 curr_ifile = NULL_IFILE; 496 return (edit_prev(1)); 497 } 498 499 500 /* 501 * Edit the next or previous file in the command line (ifile) list. 502 */ 503 static int 504 edit_istep(h, n, dir) 505 IFILE h; 506 int n; 507 int dir; 508 { 509 IFILE next; 510 511 /* 512 * Skip n filenames, then try to edit each filename. 513 */ 514 for (;;) 515 { 516 next = (dir > 0) ? next_ifile(h) : prev_ifile(h); 517 if (--n < 0) 518 { 519 if (edit_ifile(h) == 0) 520 break; 521 } 522 if (next == NULL_IFILE) 523 { 524 /* 525 * Reached end of the ifile list. 526 */ 527 return (1); 528 } 529 if (ABORT_SIGS()) 530 { 531 /* 532 * Interrupt breaks out, if we're in a long 533 * list of files that can't be opened. 534 */ 535 return (1); 536 } 537 h = next; 538 } 539 /* 540 * Found a file that we can edit. 541 */ 542 return (0); 543 } 544 545 static int 546 edit_inext(h, n) 547 IFILE h; 548 int n; 549 { 550 return (edit_istep(h, n, 1)); 551 } 552 553 public int 554 edit_next(n) 555 int n; 556 { 557 return edit_istep(curr_ifile, n, 1); 558 } 559 560 static int 561 edit_iprev(h, n) 562 IFILE h; 563 int n; 564 { 565 return (edit_istep(h, n, -1)); 566 } 567 568 public int 569 edit_prev(n) 570 int n; 571 { 572 return edit_istep(curr_ifile, n, -1); 573 } 574 575 /* 576 * Edit a specific file in the command line (ifile) list. 577 */ 578 public int 579 edit_index(n) 580 int n; 581 { 582 IFILE h; 583 584 h = NULL_IFILE; 585 do 586 { 587 if ((h = next_ifile(h)) == NULL_IFILE) 588 { 589 /* 590 * Reached end of the list without finding it. 591 */ 592 return (1); 593 } 594 } while (get_index(h) != n); 595 596 return (edit_ifile(h)); 597 } 598 599 public IFILE 600 save_curr_ifile() 601 { 602 if (curr_ifile != NULL_IFILE) 603 hold_ifile(curr_ifile, 1); 604 return (curr_ifile); 605 } 606 607 public void 608 unsave_ifile(save_ifile) 609 IFILE save_ifile; 610 { 611 if (save_ifile != NULL_IFILE) 612 hold_ifile(save_ifile, -1); 613 } 614 615 /* 616 * Reedit the ifile which was previously open. 617 */ 618 public void 619 reedit_ifile(save_ifile) 620 IFILE save_ifile; 621 { 622 IFILE next; 623 IFILE prev; 624 625 /* 626 * Try to reopen the ifile. 627 * Note that opening it may fail (maybe the file was removed), 628 * in which case the ifile will be deleted from the list. 629 * So save the next and prev ifiles first. 630 */ 631 unsave_ifile(save_ifile); 632 next = next_ifile(save_ifile); 633 prev = prev_ifile(save_ifile); 634 if (edit_ifile(save_ifile) == 0) 635 return; 636 /* 637 * If can't reopen it, open the next input file in the list. 638 */ 639 if (next != NULL_IFILE && edit_inext(next, 0) == 0) 640 return; 641 /* 642 * If can't open THAT one, open the previous input file in the list. 643 */ 644 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0) 645 return; 646 /* 647 * If can't even open that, we're stuck. Just quit. 648 */ 649 quit(QUIT_ERROR); 650 } 651 652 /* 653 * Edit standard input. 654 */ 655 public int 656 edit_stdin() 657 { 658 if (isatty(fd0)) 659 { 660 error("Missing filename (\"less --help\" for help)", NULL_PARG); 661 quit(QUIT_OK); 662 } 663 return (edit("-")); 664 } 665 666 /* 667 * Copy a file directly to standard output. 668 * Used if standard output is not a tty. 669 */ 670 public void 671 cat_file() 672 { 673 register int c; 674 675 while ((c = ch_forw_get()) != EOI) 676 putchr(c); 677 flush(); 678 } 679 680 #if LOGFILE 681 682 /* 683 * If the user asked for a log file and our input file 684 * is standard input, create the log file. 685 * We take care not to blindly overwrite an existing file. 686 */ 687 public void 688 use_logfile(filename) 689 char *filename; 690 { 691 register int exists; 692 register int answer; 693 PARG parg; 694 695 if (ch_getflags() & CH_CANSEEK) 696 /* 697 * Can't currently use a log file on a file that can seek. 698 */ 699 return; 700 701 /* 702 * {{ We could use access() here. }} 703 */ 704 filename = shell_unquote(filename); 705 exists = open(filename, OPEN_READ); 706 close(exists); 707 exists = (exists >= 0); 708 709 /* 710 * Decide whether to overwrite the log file or append to it. 711 * If it doesn't exist we "overwrite" it. 712 */ 713 if (!exists || force_logfile) 714 { 715 /* 716 * Overwrite (or create) the log file. 717 */ 718 answer = 'O'; 719 } else 720 { 721 /* 722 * Ask user what to do. 723 */ 724 parg.p_string = filename; 725 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg); 726 } 727 728 loop: 729 switch (answer) 730 { 731 case 'O': case 'o': 732 /* 733 * Overwrite: create the file. 734 */ 735 logfile = creat(filename, 0644); 736 break; 737 case 'A': case 'a': 738 /* 739 * Append: open the file and seek to the end. 740 */ 741 logfile = open(filename, OPEN_APPEND); 742 if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK) 743 { 744 close(logfile); 745 logfile = -1; 746 } 747 break; 748 case 'D': case 'd': 749 /* 750 * Don't do anything. 751 */ 752 free(filename); 753 return; 754 case 'q': 755 quit(QUIT_OK); 756 /*NOTREACHED*/ 757 default: 758 /* 759 * Eh? 760 */ 761 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG); 762 goto loop; 763 } 764 765 if (logfile < 0) 766 { 767 /* 768 * Error in opening logfile. 769 */ 770 parg.p_string = filename; 771 error("Cannot write to \"%s\"", &parg); 772 free(filename); 773 return; 774 } 775 free(filename); 776 SET_BINARY(logfile); 777 } 778 779 #endif 780