Annotation of src/usr.bin/less/edit.c, Revision 1.1.1.3
1.1 etheisen 1: /*
1.1.1.3 ! shadchin 2: * Copyright (C) 1984-2011 Mark Nudelman
1.1 etheisen 3: *
1.1.1.2 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.1.1.2 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.1.1.3 ! 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.1.1.2 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.1.1.2 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.1.1.3 ! 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:
50:
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.1.1.2 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.1.1.2 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.1.1.2 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: }
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.1.1.2 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.1.1.2 millert 184: curr_altpipe);
1.1 etheisen 185: free(curr_altfilename);
186: curr_altfilename = NULL;
187: }
188: curr_ifile = NULL_IFILE;
1.1.1.3 ! 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.1.1.2 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.1.1.2 millert 246: was_curr_ifile = save_curr_ifile();
1.1 etheisen 247: if (curr_ifile != NULL_IFILE)
248: {
1.1.1.2 millert 249: chflags = ch_getflags();
1.1 etheisen 250: close_file();
1.1.1.2 millert 251: if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
252: {
253: /*
254: * Don't keep the help file in the ifile list.
255: */
256: del_ifile(was_curr_ifile);
257: was_curr_ifile = old_ifile;
258: }
1.1 etheisen 259: }
260:
261: if (ifile == NULL_IFILE)
262: {
263: /*
264: * No new file to open.
265: * (Don't set old_ifile, because if you call edit_ifile(NULL),
266: * you're supposed to have saved curr_ifile yourself,
267: * and you'll restore it if necessary.)
268: */
1.1.1.2 millert 269: unsave_ifile(was_curr_ifile);
1.1 etheisen 270: return (0);
271: }
272:
1.1.1.2 millert 273: filename = save(get_filename(ifile));
1.1 etheisen 274: /*
275: * See if LESSOPEN specifies an "alternate" file to open.
276: */
277: alt_pipe = NULL;
278: alt_filename = open_altfile(filename, &f, &alt_pipe);
279: open_filename = (alt_filename != NULL) ? alt_filename : filename;
1.1.1.2 millert 280: qopen_filename = shell_unquote(open_filename);
1.1 etheisen 281:
282: chflags = 0;
283: if (alt_pipe != NULL)
284: {
285: /*
286: * The alternate "file" is actually a pipe.
287: * f has already been set to the file descriptor of the pipe
288: * in the call to open_altfile above.
289: * Keep the file descriptor open because it was opened
290: * via popen(), and pclose() wants to close it.
291: */
292: chflags |= CH_POPENED;
293: } else if (strcmp(open_filename, "-") == 0)
294: {
295: /*
296: * Use standard input.
297: * Keep the file descriptor open because we can't reopen it.
298: */
299: f = fd0;
300: chflags |= CH_KEEPOPEN;
1.1.1.2 millert 301: /*
302: * Must switch stdin to BINARY mode.
303: */
304: SET_BINARY(f);
305: #if MSDOS_COMPILER==DJGPPC
306: /*
307: * Setting stdin to binary by default causes
308: * Ctrl-C to not raise SIGINT. We must undo
309: * that side-effect.
310: */
311: __djgpp_set_ctrl_c(1);
312: #endif
313: } else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
314: {
315: f = -1;
316: chflags |= CH_HELPFILE;
1.1 etheisen 317: } else if ((parg.p_string = bad_file(open_filename)) != NULL)
318: {
319: /*
320: * It looks like a bad file. Don't try to open it.
321: */
322: error("%s", &parg);
323: free(parg.p_string);
324: err1:
325: if (alt_filename != NULL)
326: {
327: close_altfile(alt_filename, filename, alt_pipe);
328: free(alt_filename);
329: }
330: del_ifile(ifile);
1.1.1.2 millert 331: free(qopen_filename);
332: free(filename);
1.1 etheisen 333: /*
334: * Re-open the current file.
335: */
1.1.1.3 ! shadchin 336: if (was_curr_ifile == ifile)
! 337: {
! 338: /*
! 339: * Whoops. The "current" ifile is the one we just deleted.
! 340: * Just give up.
! 341: */
! 342: quit(QUIT_ERROR);
! 343: }
1.1.1.2 millert 344: reedit_ifile(was_curr_ifile);
1.1 etheisen 345: return (1);
1.1.1.2 millert 346: } else if ((f = open(qopen_filename, OPEN_READ)) < 0)
1.1 etheisen 347: {
348: /*
349: * Got an error trying to open it.
350: */
351: parg.p_string = errno_message(filename);
352: error("%s", &parg);
353: free(parg.p_string);
354: goto err1;
1.1.1.2 millert 355: } else
1.1 etheisen 356: {
1.1.1.2 millert 357: chflags |= CH_CANSEEK;
358: if (!force_open && !opened(ifile) && bin_file(f))
1.1 etheisen 359: {
1.1.1.2 millert 360: /*
361: * Looks like a binary file.
362: * Ask user if we should proceed.
363: */
364: parg.p_string = filename;
365: answer = query("\"%s\" may be a binary file. See it anyway? ",
366: &parg);
367: if (answer != 'y' && answer != 'Y')
368: {
369: close(f);
370: goto err1;
371: }
1.1 etheisen 372: }
373: }
374:
375: /*
376: * Get the new ifile.
377: * Get the saved position for the file.
378: */
379: if (was_curr_ifile != NULL_IFILE)
1.1.1.2 millert 380: {
1.1 etheisen 381: old_ifile = was_curr_ifile;
1.1.1.2 millert 382: unsave_ifile(was_curr_ifile);
383: }
1.1 etheisen 384: curr_ifile = ifile;
385: curr_altfilename = alt_filename;
386: curr_altpipe = alt_pipe;
387: set_open(curr_ifile); /* File has been opened */
388: get_pos(curr_ifile, &initial_scrpos);
389: new_file = TRUE;
390: ch_init(f, chflags);
1.1.1.2 millert 391:
392: if (!(chflags & CH_HELPFILE))
393: {
1.1 etheisen 394: #if LOGFILE
1.1.1.2 millert 395: if (namelogfile != NULL && is_tty)
396: use_logfile(namelogfile);
1.1 etheisen 397: #endif
1.1.1.3 ! shadchin 398: #if HAVE_STAT_INO
! 399: /* Remember the i-number and device of the opened file. */
! 400: {
! 401: struct stat statbuf;
! 402: int r = stat(qopen_filename, &statbuf);
! 403: if (r == 0)
! 404: {
! 405: curr_ino = statbuf.st_ino;
! 406: curr_dev = statbuf.st_dev;
! 407: }
! 408: }
! 409: #endif
1.1.1.2 millert 410: if (every_first_cmd != NULL)
411: ungetsc(every_first_cmd);
412: }
1.1 etheisen 413:
1.1.1.3 ! shadchin 414: free(qopen_filename);
1.1 etheisen 415: no_display = !any_display;
416: flush();
417: any_display = TRUE;
418:
419: if (is_tty)
420: {
421: /*
422: * Output is to a real tty.
423: */
424:
425: /*
426: * Indicate there is nothing displayed yet.
427: */
428: pos_clear();
429: clr_linenum();
430: #if HILITE_SEARCH
431: clr_hilite();
432: #endif
1.1.1.2 millert 433: cmd_addhist(ml_examine, filename);
1.1 etheisen 434: if (no_display && errmsgs > 0)
435: {
436: /*
437: * We displayed some messages on error output
438: * (file descriptor 2; see error() function).
439: * Before erasing the screen contents,
440: * display the file name and wait for a keystroke.
441: */
442: parg.p_string = filename;
443: error("%s", &parg);
444: }
445: }
1.1.1.2 millert 446: free(filename);
1.1 etheisen 447: return (0);
448: }
449:
450: /*
451: * Edit a space-separated list of files.
452: * For each filename in the list, enter it into the ifile list.
453: * Then edit the first one.
454: */
455: public int
456: edit_list(filelist)
457: char *filelist;
458: {
1.1.1.2 millert 459: IFILE save_ifile;
1.1 etheisen 460: char *good_filename;
461: char *filename;
462: char *gfilelist;
463: char *gfilename;
464: struct textlist tl_files;
465: struct textlist tl_gfiles;
466:
1.1.1.2 millert 467: save_ifile = save_curr_ifile();
1.1 etheisen 468: good_filename = NULL;
469:
470: /*
471: * Run thru each filename in the list.
472: * Try to glob the filename.
473: * If it doesn't expand, just try to open the filename.
474: * If it does expand, try to open each name in that list.
475: */
476: init_textlist(&tl_files, filelist);
477: filename = NULL;
478: while ((filename = forw_textlist(&tl_files, filename)) != NULL)
479: {
1.1.1.2 millert 480: gfilelist = lglob(filename);
1.1 etheisen 481: init_textlist(&tl_gfiles, gfilelist);
482: gfilename = NULL;
483: while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
484: {
485: if (edit(gfilename) == 0 && good_filename == NULL)
486: good_filename = get_filename(curr_ifile);
487: }
488: free(gfilelist);
489: }
490: /*
491: * Edit the first valid filename in the list.
492: */
493: if (good_filename == NULL)
1.1.1.2 millert 494: {
495: unsave_ifile(save_ifile);
1.1 etheisen 496: return (1);
1.1.1.2 millert 497: }
1.1 etheisen 498: if (get_ifile(good_filename, curr_ifile) == curr_ifile)
1.1.1.2 millert 499: {
1.1 etheisen 500: /*
501: * Trying to edit the current file; don't reopen it.
502: */
1.1.1.2 millert 503: unsave_ifile(save_ifile);
1.1 etheisen 504: return (0);
1.1.1.2 millert 505: }
506: reedit_ifile(save_ifile);
1.1 etheisen 507: return (edit(good_filename));
508: }
509:
510: /*
511: * Edit the first file in the command line (ifile) list.
512: */
513: public int
514: edit_first()
515: {
516: curr_ifile = NULL_IFILE;
517: return (edit_next(1));
518: }
519:
520: /*
521: * Edit the last file in the command line (ifile) list.
522: */
523: public int
524: edit_last()
525: {
526: curr_ifile = NULL_IFILE;
527: return (edit_prev(1));
528: }
529:
530:
531: /*
1.1.1.3 ! shadchin 532: * Edit the n-th next or previous file in the command line (ifile) list.
1.1 etheisen 533: */
1.1.1.2 millert 534: static int
535: edit_istep(h, n, dir)
536: IFILE h;
1.1 etheisen 537: int n;
1.1.1.2 millert 538: int dir;
1.1 etheisen 539: {
540: IFILE next;
541:
542: /*
543: * Skip n filenames, then try to edit each filename.
544: */
545: for (;;)
546: {
1.1.1.2 millert 547: next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
1.1 etheisen 548: if (--n < 0)
549: {
550: if (edit_ifile(h) == 0)
551: break;
552: }
553: if (next == NULL_IFILE)
554: {
555: /*
556: * Reached end of the ifile list.
557: */
558: return (1);
559: }
1.1.1.2 millert 560: if (ABORT_SIGS())
561: {
562: /*
563: * Interrupt breaks out, if we're in a long
564: * list of files that can't be opened.
565: */
566: return (1);
567: }
1.1 etheisen 568: h = next;
569: }
570: /*
571: * Found a file that we can edit.
572: */
573: return (0);
574: }
575:
1.1.1.2 millert 576: static int
577: edit_inext(h, n)
578: IFILE h;
579: int n;
580: {
1.1.1.3 ! shadchin 581: return (edit_istep(h, n, +1));
1.1.1.2 millert 582: }
583:
1.1 etheisen 584: public int
1.1.1.2 millert 585: edit_next(n)
1.1 etheisen 586: int n;
587: {
1.1.1.3 ! shadchin 588: return edit_istep(curr_ifile, n, +1);
1.1.1.2 millert 589: }
590:
591: static int
592: edit_iprev(h, n)
1.1 etheisen 593: IFILE h;
1.1.1.2 millert 594: int n;
595: {
596: return (edit_istep(h, n, -1));
597: }
1.1 etheisen 598:
1.1.1.2 millert 599: public int
600: edit_prev(n)
601: int n;
602: {
603: return edit_istep(curr_ifile, n, -1);
1.1 etheisen 604: }
605:
606: /*
607: * Edit a specific file in the command line (ifile) list.
608: */
609: public int
610: edit_index(n)
611: int n;
612: {
613: IFILE h;
614:
615: h = NULL_IFILE;
616: do
617: {
618: if ((h = next_ifile(h)) == NULL_IFILE)
619: {
620: /*
621: * Reached end of the list without finding it.
622: */
623: return (1);
624: }
625: } while (get_index(h) != n);
626:
627: return (edit_ifile(h));
628: }
629:
1.1.1.2 millert 630: public IFILE
631: save_curr_ifile()
632: {
633: if (curr_ifile != NULL_IFILE)
634: hold_ifile(curr_ifile, 1);
635: return (curr_ifile);
636: }
637:
638: public void
639: unsave_ifile(save_ifile)
640: IFILE save_ifile;
641: {
642: if (save_ifile != NULL_IFILE)
643: hold_ifile(save_ifile, -1);
644: }
645:
646: /*
647: * Reedit the ifile which was previously open.
648: */
649: public void
650: reedit_ifile(save_ifile)
651: IFILE save_ifile;
652: {
653: IFILE next;
654: IFILE prev;
655:
656: /*
657: * Try to reopen the ifile.
658: * Note that opening it may fail (maybe the file was removed),
659: * in which case the ifile will be deleted from the list.
660: * So save the next and prev ifiles first.
661: */
662: unsave_ifile(save_ifile);
663: next = next_ifile(save_ifile);
664: prev = prev_ifile(save_ifile);
665: if (edit_ifile(save_ifile) == 0)
666: return;
667: /*
668: * If can't reopen it, open the next input file in the list.
669: */
670: if (next != NULL_IFILE && edit_inext(next, 0) == 0)
671: return;
672: /*
673: * If can't open THAT one, open the previous input file in the list.
674: */
675: if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
676: return;
677: /*
678: * If can't even open that, we're stuck. Just quit.
679: */
680: quit(QUIT_ERROR);
681: }
682:
1.1.1.3 ! shadchin 683: public void
! 684: reopen_curr_ifile()
! 685: {
! 686: IFILE save_ifile = save_curr_ifile();
! 687: close_file();
! 688: reedit_ifile(save_ifile);
! 689: }
! 690:
1.1 etheisen 691: /*
692: * Edit standard input.
693: */
694: public int
695: edit_stdin()
696: {
697: if (isatty(fd0))
698: {
1.1.1.2 millert 699: error("Missing filename (\"less --help\" for help)", NULL_PARG);
1.1 etheisen 700: quit(QUIT_OK);
701: }
702: return (edit("-"));
703: }
704:
705: /*
706: * Copy a file directly to standard output.
707: * Used if standard output is not a tty.
708: */
709: public void
710: cat_file()
711: {
712: register int c;
713:
714: while ((c = ch_forw_get()) != EOI)
715: putchr(c);
716: flush();
717: }
718:
719: #if LOGFILE
720:
721: /*
722: * If the user asked for a log file and our input file
723: * is standard input, create the log file.
724: * We take care not to blindly overwrite an existing file.
725: */
726: public void
727: use_logfile(filename)
728: char *filename;
729: {
730: register int exists;
731: register int answer;
732: PARG parg;
733:
734: if (ch_getflags() & CH_CANSEEK)
735: /*
736: * Can't currently use a log file on a file that can seek.
737: */
738: return;
739:
740: /*
741: * {{ We could use access() here. }}
742: */
1.1.1.2 millert 743: filename = shell_unquote(filename);
1.1 etheisen 744: exists = open(filename, OPEN_READ);
745: close(exists);
746: exists = (exists >= 0);
747:
748: /*
749: * Decide whether to overwrite the log file or append to it.
750: * If it doesn't exist we "overwrite" it.
751: */
752: if (!exists || force_logfile)
753: {
754: /*
755: * Overwrite (or create) the log file.
756: */
757: answer = 'O';
758: } else
759: {
760: /*
761: * Ask user what to do.
762: */
763: parg.p_string = filename;
764: answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
765: }
766:
767: loop:
768: switch (answer)
769: {
770: case 'O': case 'o':
771: /*
772: * Overwrite: create the file.
773: */
774: logfile = creat(filename, 0644);
775: break;
776: case 'A': case 'a':
777: /*
778: * Append: open the file and seek to the end.
779: */
780: logfile = open(filename, OPEN_APPEND);
1.1.1.3 ! shadchin 781: if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
1.1 etheisen 782: {
783: close(logfile);
784: logfile = -1;
785: }
786: break;
787: case 'D': case 'd':
788: /*
789: * Don't do anything.
790: */
1.1.1.2 millert 791: free(filename);
1.1 etheisen 792: return;
793: case 'q':
794: quit(QUIT_OK);
795: /*NOTREACHED*/
796: default:
797: /*
798: * Eh?
799: */
800: answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
801: goto loop;
802: }
803:
804: if (logfile < 0)
805: {
806: /*
807: * Error in opening logfile.
808: */
809: parg.p_string = filename;
810: error("Cannot write to \"%s\"", &parg);
1.1.1.2 millert 811: free(filename);
812: return;
1.1 etheisen 813: }
1.1.1.2 millert 814: free(filename);
815: SET_BINARY(logfile);
1.1 etheisen 816: }
817:
818: #endif