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