Annotation of src/usr.bin/less/edit.c, Revision 1.18
1.1 etheisen 1: /*
1.10 shadchin 2: * Copyright (C) 1984-2012 Mark Nudelman
1.13 nicm 3: * Modified for use with illumos by Garrett D'Amore.
4: * Copyright 2014 Garrett D'Amore <garrett@damore.org>
1.1 etheisen 5: *
1.4 millert 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.
1.1 etheisen 8: *
1.10 shadchin 9: * For more information, see the README file.
1.12 nicm 10: */
1.1 etheisen 11:
12: #include "less.h"
1.8 shadchin 13: #include <sys/stat.h>
1.1 etheisen 14:
1.12 nicm 15: static int fd0 = 0;
1.1 etheisen 16:
17: extern int new_file;
18: extern int errmsgs;
19: extern char *every_first_cmd;
20: extern int any_display;
21: extern int force_open;
22: extern int is_tty;
1.9 millert 23: extern volatile sig_atomic_t sigs;
1.1 etheisen 24: extern IFILE curr_ifile;
25: extern IFILE old_ifile;
26: extern struct scrpos initial_scrpos;
1.12 nicm 27: extern void *ml_examine;
1.4 millert 28: extern char openquote;
29: extern char closequote;
1.12 nicm 30: extern int less_is_more;
1.1 etheisen 31: extern int logfile;
32: extern int force_logfile;
33: extern char *namelogfile;
34:
1.12 nicm 35: dev_t curr_dev;
36: ino_t curr_ino;
1.8 shadchin 37:
1.1 etheisen 38: char *curr_altfilename = NULL;
39: static void *curr_altpipe;
40:
1.12 nicm 41:
1.1 etheisen 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: */
1.12 nicm 49: void
50: init_textlist(struct textlist *tlist, char *str)
1.1 etheisen 51: {
52: char *s;
1.4 millert 53: int meta_quoted = 0;
54: int delim_quoted = 0;
55: char *esc = get_meta_escape();
56: int esclen = strlen(esc);
1.12 nicm 57:
1.1 etheisen 58: tlist->string = skipsp(str);
59: tlist->endstring = tlist->string + strlen(tlist->string);
1.12 nicm 60: for (s = str; s < tlist->endstring; s++) {
61: if (meta_quoted) {
1.4 millert 62: meta_quoted = 0;
63: } else if (esclen > 0 && s + esclen < tlist->endstring &&
1.12 nicm 64: strncmp(s, esc, esclen) == 0) {
1.4 millert 65: meta_quoted = 1;
66: s += esclen - 1;
1.12 nicm 67: } else if (delim_quoted) {
1.4 millert 68: if (*s == closequote)
69: delim_quoted = 0;
1.12 nicm 70: } else /* (!delim_quoted) */ {
1.4 millert 71: if (*s == openquote)
72: delim_quoted = 1;
73: else if (*s == ' ')
74: *s = '\0';
75: }
1.1 etheisen 76: }
77: }
78:
1.12 nicm 79: char *
80: forw_textlist(struct textlist *tlist, char *prev)
1.1 etheisen 81: {
82: char *s;
1.12 nicm 83:
1.1 etheisen 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:
1.12 nicm 101: char *
102: back_textlist(struct textlist *tlist, char *prev)
1.1 etheisen 103: {
104: char *s;
1.12 nicm 105:
1.1 etheisen 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: */
1.12 nicm 128: static void
129: close_file(void)
1.1 etheisen 130: {
131: struct scrpos scrpos;
1.12 nicm 132:
1.1 etheisen 133: if (curr_ifile == NULL_IFILE)
134: return;
1.4 millert 135:
1.1 etheisen 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);
1.12 nicm 141: if (scrpos.pos != -1) {
1.1 etheisen 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: */
1.12 nicm 153: if (curr_altfilename != NULL) {
1.1 etheisen 154: close_altfile(curr_altfilename, get_filename(curr_ifile),
1.12 nicm 155: curr_altpipe);
1.1 etheisen 156: free(curr_altfilename);
157: curr_altfilename = NULL;
158: }
159: curr_ifile = NULL_IFILE;
1.8 shadchin 160: curr_ino = curr_dev = 0;
1.1 etheisen 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: */
1.12 nicm 168: int
169: edit(char *filename)
1.1 etheisen 170: {
171: if (filename == NULL)
172: return (edit_ifile(NULL_IFILE));
173: return (edit_ifile(get_ifile(filename, curr_ifile)));
174: }
1.12 nicm 175:
1.1 etheisen 176: /*
177: * Edit a new file (given its IFILE).
178: * ifile == NULL means just close the current file.
179: */
1.12 nicm 180: int
181: edit_ifile(IFILE ifile)
1.1 etheisen 182: {
183: int f;
184: int answer;
185: int no_display;
186: int chflags;
187: char *filename;
188: char *open_filename;
1.4 millert 189: char *qopen_filename;
1.1 etheisen 190: char *alt_filename;
191: void *alt_pipe;
192: IFILE was_curr_ifile;
193: PARG parg;
1.12 nicm 194:
195: if (ifile == curr_ifile) {
1.1 etheisen 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();
1.4 millert 210: was_curr_ifile = save_curr_ifile();
1.12 nicm 211: if (curr_ifile != NULL_IFILE) {
1.4 millert 212: chflags = ch_getflags();
1.1 etheisen 213: close_file();
1.12 nicm 214: if ((chflags & CH_HELPFILE) &&
215: held_ifile(was_curr_ifile) <= 1) {
1.8 shadchin 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: }
1.1 etheisen 222: }
223:
1.12 nicm 224: if (ifile == NULL_IFILE) {
1.1 etheisen 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: */
1.4 millert 231: unsave_ifile(was_curr_ifile);
1.1 etheisen 232: return (0);
233: }
234:
1.14 tedu 235: filename = estrdup(get_filename(ifile));
1.1 etheisen 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;
1.4 millert 242: qopen_filename = shell_unquote(open_filename);
1.1 etheisen 243:
244: chflags = 0;
1.11 schwarze 245: if (strcmp(open_filename, helpfile()) == 0)
1.8 shadchin 246: chflags |= CH_HELPFILE;
1.12 nicm 247: if (alt_pipe != NULL) {
1.1 etheisen 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.
1.12 nicm 252: * Keep the file descriptor open because it was opened
1.1 etheisen 253: * via popen(), and pclose() wants to close it.
254: */
255: chflags |= CH_POPENED;
1.12 nicm 256: } else if (strcmp(open_filename, "-") == 0) {
257: /*
1.1 etheisen 258: * Use standard input.
259: * Keep the file descriptor open because we can't reopen it.
260: */
261: f = fd0;
262: chflags |= CH_KEEPOPEN;
1.12 nicm 263: } else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0) {
1.10 shadchin 264: f = -1;
265: chflags |= CH_NODATA;
1.12 nicm 266: } else if ((parg.p_string = bad_file(open_filename)) != NULL) {
1.1 etheisen 267: /*
268: * It looks like a bad file. Don't try to open it.
269: */
270: error("%s", &parg);
271: free(parg.p_string);
1.12 nicm 272: err1:
273: if (alt_filename != NULL) {
1.1 etheisen 274: close_altfile(alt_filename, filename, alt_pipe);
275: free(alt_filename);
276: }
277: del_ifile(ifile);
1.4 millert 278: free(qopen_filename);
279: free(filename);
1.1 etheisen 280: /*
281: * Re-open the current file.
282: */
1.12 nicm 283: if (was_curr_ifile == ifile) {
1.8 shadchin 284: /*
1.12 nicm 285: * Whoops. The "current" ifile is the one we just
286: * deleted. Just give up.
1.8 shadchin 287: */
288: quit(QUIT_ERROR);
289: }
1.4 millert 290: reedit_ifile(was_curr_ifile);
1.1 etheisen 291: return (1);
1.12 nicm 292: } else if ((f = open(qopen_filename, O_RDONLY)) < 0) {
1.1 etheisen 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);
1.12 nicm 299: goto err1;
300: } else {
1.4 millert 301: chflags |= CH_CANSEEK;
1.12 nicm 302: if (!force_open && !opened(ifile) && bin_file(f)) {
1.4 millert 303: /*
1.12 nicm 304: * Looks like a binary file.
1.4 millert 305: * Ask user if we should proceed.
306: */
307: parg.p_string = filename;
1.12 nicm 308: answer = query("\"%s\" may be a binary file. "
309: "See it anyway? ", &parg);
310: if (answer != 'y' && answer != 'Y') {
311: (void) close(f);
1.4 millert 312: goto err1;
313: }
1.1 etheisen 314: }
315: }
316:
317: /*
318: * Get the new ifile.
319: * Get the saved position for the file.
320: */
1.12 nicm 321: if (was_curr_ifile != NULL_IFILE) {
1.1 etheisen 322: old_ifile = was_curr_ifile;
1.4 millert 323: unsave_ifile(was_curr_ifile);
324: }
1.1 etheisen 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);
1.4 millert 332:
1.12 nicm 333: if (!(chflags & CH_HELPFILE)) {
334: struct stat statbuf;
335: int r;
336:
1.8 shadchin 337: if (namelogfile != NULL && is_tty)
338: use_logfile(namelogfile);
1.12 nicm 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;
1.8 shadchin 344: }
345: if (every_first_cmd != NULL)
346: ungetsc(every_first_cmd);
347: }
348: free(qopen_filename);
1.1 etheisen 349: no_display = !any_display;
350: flush();
351: any_display = TRUE;
352:
1.12 nicm 353: if (is_tty) {
1.1 etheisen 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();
1.4 millert 364: cmd_addhist(ml_examine, filename);
1.12 nicm 365: if (no_display && errmsgs > 0) {
1.1 etheisen 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: }
1.4 millert 376: free(filename);
1.1 etheisen 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: */
1.12 nicm 385: int
386: edit_list(char *filelist)
1.1 etheisen 387: {
1.4 millert 388: IFILE save_ifile;
1.1 etheisen 389: char *good_filename;
390: char *filename;
391: char *gfilelist;
392: char *gfilename;
393: struct textlist tl_files;
394: struct textlist tl_gfiles;
395:
1.4 millert 396: save_ifile = save_curr_ifile();
1.1 etheisen 397: good_filename = NULL;
1.12 nicm 398:
1.1 etheisen 399: /*
400: * Run thru each filename in the list.
1.12 nicm 401: * Try to glob the filename.
1.1 etheisen 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;
1.12 nicm 407: while ((filename = forw_textlist(&tl_files, filename)) != NULL) {
1.4 millert 408: gfilelist = lglob(filename);
1.1 etheisen 409: init_textlist(&tl_gfiles, gfilelist);
410: gfilename = NULL;
1.12 nicm 411: while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) !=
412: NULL) {
1.1 etheisen 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: */
1.12 nicm 421: if (good_filename == NULL) {
1.4 millert 422: unsave_ifile(save_ifile);
1.1 etheisen 423: return (1);
1.4 millert 424: }
1.12 nicm 425: if (get_ifile(good_filename, curr_ifile) == curr_ifile) {
1.1 etheisen 426: /*
427: * Trying to edit the current file; don't reopen it.
428: */
1.4 millert 429: unsave_ifile(save_ifile);
1.1 etheisen 430: return (0);
1.4 millert 431: }
432: reedit_ifile(save_ifile);
1.1 etheisen 433: return (edit(good_filename));
434: }
435:
436: /*
437: * Edit the first file in the command line (ifile) list.
438: */
1.12 nicm 439: int
440: edit_first(void)
1.1 etheisen 441: {
442: curr_ifile = NULL_IFILE;
443: return (edit_next(1));
444: }
445:
446: /*
447: * Edit the last file in the command line (ifile) list.
448: */
1.12 nicm 449: int
450: edit_last(void)
1.1 etheisen 451: {
452: curr_ifile = NULL_IFILE;
453: return (edit_prev(1));
454: }
455:
456:
457: /*
1.8 shadchin 458: * Edit the n-th next or previous file in the command line (ifile) list.
1.1 etheisen 459: */
1.12 nicm 460: static int
461: edit_istep(IFILE h, int n, int dir)
1.1 etheisen 462: {
463: IFILE next;
464:
465: /*
466: * Skip n filenames, then try to edit each filename.
467: */
1.12 nicm 468: for (;;) {
1.4 millert 469: next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
1.12 nicm 470: if (--n < 0) {
1.1 etheisen 471: if (edit_ifile(h) == 0)
472: break;
473: }
1.12 nicm 474: if (next == NULL_IFILE) {
1.1 etheisen 475: /*
476: * Reached end of the ifile list.
477: */
478: return (1);
479: }
1.12 nicm 480: if (ABORT_SIGS()) {
1.4 millert 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: }
1.1 etheisen 487: h = next;
1.12 nicm 488: }
1.1 etheisen 489: /*
490: * Found a file that we can edit.
491: */
492: return (0);
493: }
494:
1.12 nicm 495: static int
496: edit_inext(IFILE h, int n)
1.4 millert 497: {
1.8 shadchin 498: return (edit_istep(h, n, +1));
1.4 millert 499: }
500:
1.12 nicm 501: int
502: edit_next(int n)
1.1 etheisen 503: {
1.12 nicm 504: return (edit_istep(curr_ifile, n, +1));
1.4 millert 505: }
506:
1.12 nicm 507: static int
508: edit_iprev(IFILE h, int n)
1.4 millert 509: {
510: return (edit_istep(h, n, -1));
511: }
1.1 etheisen 512:
1.12 nicm 513: int
514: edit_prev(int n)
1.4 millert 515: {
1.12 nicm 516: return (edit_istep(curr_ifile, n, -1));
1.1 etheisen 517: }
518:
519: /*
520: * Edit a specific file in the command line (ifile) list.
521: */
1.12 nicm 522: int
523: edit_index(int n)
1.1 etheisen 524: {
525: IFILE h;
526:
527: h = NULL_IFILE;
1.12 nicm 528: do {
529: if ((h = next_ifile(h)) == NULL_IFILE) {
1.1 etheisen 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:
1.12 nicm 540: IFILE
541: save_curr_ifile(void)
1.4 millert 542: {
543: if (curr_ifile != NULL_IFILE)
544: hold_ifile(curr_ifile, 1);
545: return (curr_ifile);
546: }
547:
1.12 nicm 548: void
549: unsave_ifile(IFILE save_ifile)
1.4 millert 550: {
551: if (save_ifile != NULL_IFILE)
552: hold_ifile(save_ifile, -1);
553: }
554:
555: /*
556: * Reedit the ifile which was previously open.
557: */
1.12 nicm 558: void
559: reedit_ifile(IFILE save_ifile)
1.4 millert 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_IFILE && 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_IFILE && 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);
1.8 shadchin 589: }
590:
1.12 nicm 591: void
592: reopen_curr_ifile(void)
1.8 shadchin 593: {
594: IFILE save_ifile = save_curr_ifile();
595: close_file();
596: reedit_ifile(save_ifile);
1.4 millert 597: }
598:
1.1 etheisen 599: /*
600: * Edit standard input.
601: */
1.12 nicm 602: int
603: edit_stdin(void)
1.1 etheisen 604: {
1.12 nicm 605: if (isatty(fd0)) {
606: if (less_is_more) {
607: error("Missing filename (\"more -h\" for help)",
1.17 deraadt 608: NULL);
1.12 nicm 609: } else {
610: error("Missing filename (\"less --help\" for help)",
1.17 deraadt 611: NULL);
1.12 nicm 612: }
1.1 etheisen 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: */
1.12 nicm 622: void
623: cat_file(void)
1.1 etheisen 624: {
1.12 nicm 625: int c;
1.1 etheisen 626:
627: while ((c = ch_forw_get()) != EOI)
628: putchr(c);
629: flush();
630: }
631:
632: /*
633: * If the user asked for a log file and our input file
1.12 nicm 634: * is standard input, create the log file.
1.1 etheisen 635: * We take care not to blindly overwrite an existing file.
636: */
1.12 nicm 637: void
638: use_logfile(char *filename)
1.1 etheisen 639: {
1.12 nicm 640: int exists;
641: int answer;
1.1 etheisen 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: */
1.4 millert 653: filename = shell_unquote(filename);
1.15 deraadt 654: exists = open(filename, O_RDONLY);
1.1 etheisen 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: */
1.12 nicm 662: if (!exists || force_logfile) {
1.1 etheisen 663: /*
664: * Overwrite (or create) the log file.
665: */
666: answer = 'O';
1.12 nicm 667: } else {
1.1 etheisen 668: /*
669: * Ask user what to do.
670: */
671: parg.p_string = filename;
1.12 nicm 672: answer = query("Warning: \"%s\" exists; "
673: "Overwrite, Append or Don't log? ", &parg);
1.1 etheisen 674: }
675:
676: loop:
1.12 nicm 677: switch (answer) {
1.1 etheisen 678: case 'O': case 'o':
679: /*
680: * Overwrite: create the file.
681: */
1.16 deraadt 682: logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
1.1 etheisen 683: break;
684: case 'A': case 'a':
685: /*
686: * Append: open the file and seek to the end.
687: */
1.15 deraadt 688: logfile = open(filename, O_WRONLY | O_APPEND);
1.18 ! deraadt 689: if (lseek(logfile, (off_t)0, SEEK_END) == (off_t)-1) {
1.1 etheisen 690: close(logfile);
691: logfile = -1;
692: }
693: break;
694: case 'D': case 'd':
695: /*
696: * Don't do anything.
697: */
1.4 millert 698: free(filename);
1.1 etheisen 699: return;
700: case 'q':
701: quit(QUIT_OK);
702: /*NOTREACHED*/
703: default:
704: /*
705: * Eh?
706: */
1.12 nicm 707: answer = query("Overwrite, Append, or Don't log? "
1.17 deraadt 708: "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL);
1.1 etheisen 709: goto loop;
710: }
711:
1.12 nicm 712: if (logfile < 0) {
1.1 etheisen 713: /*
714: * Error in opening logfile.
715: */
716: parg.p_string = filename;
717: error("Cannot write to \"%s\"", &parg);
1.4 millert 718: free(filename);
719: return;
1.1 etheisen 720: }
1.4 millert 721: free(filename);
1.1 etheisen 722: }