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