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