1 /* 2 * Copyright (C) 1984-2005 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 if (was_curr_ifile == ifile) 326 { 327 /* 328 * Whoops. The "current" ifile is the one we just deleted. 329 * Just give up. 330 */ 331 quit(QUIT_ERROR); 332 } 333 reedit_ifile(was_curr_ifile); 334 return (1); 335 } else if ((f = open(qopen_filename, OPEN_READ)) < 0) 336 { 337 /* 338 * Got an error trying to open it. 339 */ 340 parg.p_string = errno_message(filename); 341 error("%s", &parg); 342 free(parg.p_string); 343 goto err1; 344 } else 345 { 346 chflags |= CH_CANSEEK; 347 if (!force_open && !opened(ifile) && bin_file(f)) 348 { 349 /* 350 * Looks like a binary file. 351 * Ask user if we should proceed. 352 */ 353 parg.p_string = filename; 354 answer = query("\"%s\" may be a binary file. See it anyway? ", 355 &parg); 356 if (answer != 'y' && answer != 'Y') 357 { 358 close(f); 359 goto err1; 360 } 361 } 362 } 363 free(qopen_filename); 364 365 /* 366 * Get the new ifile. 367 * Get the saved position for the file. 368 */ 369 if (was_curr_ifile != NULL_IFILE) 370 { 371 old_ifile = was_curr_ifile; 372 unsave_ifile(was_curr_ifile); 373 } 374 curr_ifile = ifile; 375 curr_altfilename = alt_filename; 376 curr_altpipe = alt_pipe; 377 set_open(curr_ifile); /* File has been opened */ 378 get_pos(curr_ifile, &initial_scrpos); 379 new_file = TRUE; 380 ch_init(f, chflags); 381 382 if (!(chflags & CH_HELPFILE)) 383 { 384 #if LOGFILE 385 if (namelogfile != NULL && is_tty) 386 use_logfile(namelogfile); 387 #endif 388 if (every_first_cmd != NULL) 389 ungetsc(every_first_cmd); 390 } 391 392 no_display = !any_display; 393 flush(); 394 any_display = TRUE; 395 396 if (is_tty) 397 { 398 /* 399 * Output is to a real tty. 400 */ 401 402 /* 403 * Indicate there is nothing displayed yet. 404 */ 405 pos_clear(); 406 clr_linenum(); 407 #if HILITE_SEARCH 408 clr_hilite(); 409 #endif 410 cmd_addhist(ml_examine, filename); 411 if (no_display && errmsgs > 0) 412 { 413 /* 414 * We displayed some messages on error output 415 * (file descriptor 2; see error() function). 416 * Before erasing the screen contents, 417 * display the file name and wait for a keystroke. 418 */ 419 parg.p_string = filename; 420 error("%s", &parg); 421 } 422 } 423 free(filename); 424 return (0); 425 } 426 427 /* 428 * Edit a space-separated list of files. 429 * For each filename in the list, enter it into the ifile list. 430 * Then edit the first one. 431 */ 432 public int 433 edit_list(filelist) 434 char *filelist; 435 { 436 IFILE save_ifile; 437 char *good_filename; 438 char *filename; 439 char *gfilelist; 440 char *gfilename; 441 struct textlist tl_files; 442 struct textlist tl_gfiles; 443 444 save_ifile = save_curr_ifile(); 445 good_filename = NULL; 446 447 /* 448 * Run thru each filename in the list. 449 * Try to glob the filename. 450 * If it doesn't expand, just try to open the filename. 451 * If it does expand, try to open each name in that list. 452 */ 453 init_textlist(&tl_files, filelist); 454 filename = NULL; 455 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 456 { 457 gfilelist = lglob(filename); 458 init_textlist(&tl_gfiles, gfilelist); 459 gfilename = NULL; 460 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 461 { 462 if (edit(gfilename) == 0 && good_filename == NULL) 463 good_filename = get_filename(curr_ifile); 464 } 465 free(gfilelist); 466 } 467 /* 468 * Edit the first valid filename in the list. 469 */ 470 if (good_filename == NULL) 471 { 472 unsave_ifile(save_ifile); 473 return (1); 474 } 475 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 476 { 477 /* 478 * Trying to edit the current file; don't reopen it. 479 */ 480 unsave_ifile(save_ifile); 481 return (0); 482 } 483 reedit_ifile(save_ifile); 484 return (edit(good_filename)); 485 } 486 487 /* 488 * Edit the first file in the command line (ifile) list. 489 */ 490 public int 491 edit_first() 492 { 493 curr_ifile = NULL_IFILE; 494 return (edit_next(1)); 495 } 496 497 /* 498 * Edit the last file in the command line (ifile) list. 499 */ 500 public int 501 edit_last() 502 { 503 curr_ifile = NULL_IFILE; 504 return (edit_prev(1)); 505 } 506 507 508 /* 509 * Edit the n-th next or previous file in the command line (ifile) list. 510 */ 511 static int 512 edit_istep(h, n, dir) 513 IFILE h; 514 int n; 515 int dir; 516 { 517 IFILE next; 518 519 /* 520 * Skip n filenames, then try to edit each filename. 521 */ 522 for (;;) 523 { 524 next = (dir > 0) ? next_ifile(h) : prev_ifile(h); 525 if (--n < 0) 526 { 527 if (edit_ifile(h) == 0) 528 break; 529 } 530 if (next == NULL_IFILE) 531 { 532 /* 533 * Reached end of the ifile list. 534 */ 535 return (1); 536 } 537 if (ABORT_SIGS()) 538 { 539 /* 540 * Interrupt breaks out, if we're in a long 541 * list of files that can't be opened. 542 */ 543 return (1); 544 } 545 h = next; 546 } 547 /* 548 * Found a file that we can edit. 549 */ 550 return (0); 551 } 552 553 static int 554 edit_inext(h, n) 555 IFILE h; 556 int n; 557 { 558 return (edit_istep(h, n, +1)); 559 } 560 561 public int 562 edit_next(n) 563 int n; 564 { 565 return edit_istep(curr_ifile, n, +1); 566 } 567 568 static int 569 edit_iprev(h, n) 570 IFILE h; 571 int n; 572 { 573 return (edit_istep(h, n, -1)); 574 } 575 576 public int 577 edit_prev(n) 578 int n; 579 { 580 return edit_istep(curr_ifile, n, -1); 581 } 582 583 /* 584 * Edit a specific file in the command line (ifile) list. 585 */ 586 public int 587 edit_index(n) 588 int n; 589 { 590 IFILE h; 591 592 h = NULL_IFILE; 593 do 594 { 595 if ((h = next_ifile(h)) == NULL_IFILE) 596 { 597 /* 598 * Reached end of the list without finding it. 599 */ 600 return (1); 601 } 602 } while (get_index(h) != n); 603 604 return (edit_ifile(h)); 605 } 606 607 public IFILE 608 save_curr_ifile() 609 { 610 if (curr_ifile != NULL_IFILE) 611 hold_ifile(curr_ifile, 1); 612 return (curr_ifile); 613 } 614 615 public void 616 unsave_ifile(save_ifile) 617 IFILE save_ifile; 618 { 619 if (save_ifile != NULL_IFILE) 620 hold_ifile(save_ifile, -1); 621 } 622 623 /* 624 * Reedit the ifile which was previously open. 625 */ 626 public void 627 reedit_ifile(save_ifile) 628 IFILE save_ifile; 629 { 630 IFILE next; 631 IFILE prev; 632 633 /* 634 * Try to reopen the ifile. 635 * Note that opening it may fail (maybe the file was removed), 636 * in which case the ifile will be deleted from the list. 637 * So save the next and prev ifiles first. 638 */ 639 unsave_ifile(save_ifile); 640 next = next_ifile(save_ifile); 641 prev = prev_ifile(save_ifile); 642 if (edit_ifile(save_ifile) == 0) 643 return; 644 /* 645 * If can't reopen it, open the next input file in the list. 646 */ 647 if (next != NULL_IFILE && edit_inext(next, 0) == 0) 648 return; 649 /* 650 * If can't open THAT one, open the previous input file in the list. 651 */ 652 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0) 653 return; 654 /* 655 * If can't even open that, we're stuck. Just quit. 656 */ 657 quit(QUIT_ERROR); 658 } 659 660 /* 661 * Edit standard input. 662 */ 663 public int 664 edit_stdin() 665 { 666 if (isatty(fd0)) 667 { 668 error("Missing filename (\"less --help\" for help)", NULL_PARG); 669 quit(QUIT_OK); 670 } 671 return (edit("-")); 672 } 673 674 /* 675 * Copy a file directly to standard output. 676 * Used if standard output is not a tty. 677 */ 678 public void 679 cat_file() 680 { 681 register int c; 682 683 while ((c = ch_forw_get()) != EOI) 684 putchr(c); 685 flush(); 686 } 687 688 #if LOGFILE 689 690 /* 691 * If the user asked for a log file and our input file 692 * is standard input, create the log file. 693 * We take care not to blindly overwrite an existing file. 694 */ 695 public void 696 use_logfile(filename) 697 char *filename; 698 { 699 register int exists; 700 register int answer; 701 PARG parg; 702 703 if (ch_getflags() & CH_CANSEEK) 704 /* 705 * Can't currently use a log file on a file that can seek. 706 */ 707 return; 708 709 /* 710 * {{ We could use access() here. }} 711 */ 712 filename = shell_unquote(filename); 713 exists = open(filename, OPEN_READ); 714 close(exists); 715 exists = (exists >= 0); 716 717 /* 718 * Decide whether to overwrite the log file or append to it. 719 * If it doesn't exist we "overwrite" it. 720 */ 721 if (!exists || force_logfile) 722 { 723 /* 724 * Overwrite (or create) the log file. 725 */ 726 answer = 'O'; 727 } else 728 { 729 /* 730 * Ask user what to do. 731 */ 732 parg.p_string = filename; 733 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg); 734 } 735 736 loop: 737 switch (answer) 738 { 739 case 'O': case 'o': 740 /* 741 * Overwrite: create the file. 742 */ 743 logfile = creat(filename, 0644); 744 break; 745 case 'A': case 'a': 746 /* 747 * Append: open the file and seek to the end. 748 */ 749 logfile = open(filename, OPEN_APPEND); 750 if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK) 751 { 752 close(logfile); 753 logfile = -1; 754 } 755 break; 756 case 'D': case 'd': 757 /* 758 * Don't do anything. 759 */ 760 free(filename); 761 return; 762 case 'q': 763 quit(QUIT_OK); 764 /*NOTREACHED*/ 765 default: 766 /* 767 * Eh? 768 */ 769 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG); 770 goto loop; 771 } 772 773 if (logfile < 0) 774 { 775 /* 776 * Error in opening logfile. 777 */ 778 parg.p_string = filename; 779 error("Cannot write to \"%s\"", &parg); 780 free(filename); 781 return; 782 } 783 free(filename); 784 SET_BINARY(logfile); 785 } 786 787 #endif 788