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