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