Merge commit 'db1c88f6dab43484b6c33636600ac4596ff4c354'
[unleashed.git] / bin / less / edit.c
blob6d08adcc74cfab5dd49cb7019ee5740bf324c1fe
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>
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.
9 * For more information, see the README file.
12 #include <sys/stat.h>
14 #include "less.h"
16 static int fd0 = 0;
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 volatile sig_atomic_t sigs;
25 extern IFILE curr_ifile;
26 extern IFILE old_ifile;
27 extern struct scrpos initial_scrpos;
28 extern void *ml_examine;
29 extern char openquote;
30 extern char closequote;
31 extern int less_is_more;
32 extern int logfile;
33 extern int force_logfile;
34 extern char *namelogfile;
36 dev_t curr_dev;
37 ino_t curr_ino;
39 char *curr_altfilename = NULL;
40 static void *curr_altpipe;
44 * Textlist functions deal with a list of words separated by spaces.
45 * init_textlist sets up a textlist structure.
46 * forw_textlist uses that structure to iterate thru the list of
47 * words, returning each one as a standard null-terminated string.
48 * back_textlist does the same, but runs thru the list backwards.
50 void
51 init_textlist(struct textlist *tlist, char *str)
53 char *s;
54 int meta_quoted = 0;
55 int delim_quoted = 0;
56 char *esc = get_meta_escape();
57 int esclen = strlen(esc);
59 tlist->string = skipsp(str);
60 tlist->endstring = tlist->string + strlen(tlist->string);
61 for (s = str; s < tlist->endstring; s++) {
62 if (meta_quoted) {
63 meta_quoted = 0;
64 } else if (esclen > 0 && s + esclen < tlist->endstring &&
65 strncmp(s, esc, esclen) == 0) {
66 meta_quoted = 1;
67 s += esclen - 1;
68 } else if (delim_quoted) {
69 if (*s == closequote)
70 delim_quoted = 0;
71 } else /* (!delim_quoted) */ {
72 if (*s == openquote)
73 delim_quoted = 1;
74 else if (*s == ' ')
75 *s = '\0';
80 char *
81 forw_textlist(struct textlist *tlist, char *prev)
83 char *s;
86 * prev == NULL means return the first word in the list.
87 * Otherwise, return the word after "prev".
89 if (prev == NULL)
90 s = tlist->string;
91 else
92 s = prev + strlen(prev);
93 if (s >= tlist->endstring)
94 return (NULL);
95 while (*s == '\0')
96 s++;
97 if (s >= tlist->endstring)
98 return (NULL);
99 return (s);
102 char *
103 back_textlist(struct textlist *tlist, char *prev)
105 char *s;
108 * prev == NULL means return the last word in the list.
109 * Otherwise, return the word before "prev".
111 if (prev == NULL)
112 s = tlist->endstring;
113 else if (prev <= tlist->string)
114 return (NULL);
115 else
116 s = prev - 1;
117 while (*s == '\0')
118 s--;
119 if (s <= tlist->string)
120 return (NULL);
121 while (s[-1] != '\0' && s > tlist->string)
122 s--;
123 return (s);
127 * Close the current input file.
129 static void
130 close_file(void)
132 struct scrpos scrpos;
134 if (curr_ifile == NULL)
135 return;
138 * Save the current position so that we can return to
139 * the same position if we edit this file again.
141 get_scrpos(&scrpos);
142 if (scrpos.pos != -1) {
143 store_pos(curr_ifile, &scrpos);
144 lastmark();
147 * Close the file descriptor, unless it is a pipe.
149 ch_close();
151 * If we opened a file using an alternate name,
152 * do special stuff to close it.
154 if (curr_altfilename != NULL) {
155 close_altfile(curr_altfilename, get_filename(curr_ifile),
156 curr_altpipe);
157 free(curr_altfilename);
158 curr_altfilename = NULL;
160 curr_ifile = NULL;
161 curr_ino = curr_dev = 0;
165 * Edit a new file (given its name).
166 * Filename == "-" means standard input.
167 * Filename == NULL means just close the current file.
170 edit(char *filename)
172 if (filename == NULL)
173 return (edit_ifile(NULL));
174 return (edit_ifile(get_ifile(filename, curr_ifile)));
178 * Edit a new file (given its IFILE).
179 * ifile == NULL means just close the current file.
182 edit_ifile(IFILE ifile)
184 int f;
185 int answer;
186 int no_display;
187 int chflags;
188 char *filename;
189 char *open_filename;
190 char *qopen_filename;
191 char *alt_filename;
192 void *alt_pipe;
193 IFILE was_curr_ifile;
194 PARG parg;
196 if (ifile == curr_ifile) {
198 * Already have the correct file open.
200 return (0);
204 * We must close the currently open file now.
205 * This is necessary to make the open_altfile/close_altfile pairs
206 * nest properly (or rather to avoid nesting at all).
207 * {{ Some stupid implementations of popen() mess up if you do:
208 * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
210 end_logfile();
211 was_curr_ifile = save_curr_ifile();
212 if (curr_ifile != NULL) {
213 chflags = ch_getflags();
214 close_file();
215 if ((chflags & CH_HELPFILE) &&
216 held_ifile(was_curr_ifile) <= 1) {
218 * Don't keep the help file in the ifile list.
220 del_ifile(was_curr_ifile);
221 was_curr_ifile = old_ifile;
225 if (ifile == NULL) {
227 * No new file to open.
228 * (Don't set old_ifile, because if you call edit_ifile(NULL),
229 * you're supposed to have saved curr_ifile yourself,
230 * and you'll restore it if necessary.)
232 unsave_ifile(was_curr_ifile);
233 return (0);
236 filename = estrdup(get_filename(ifile));
238 * See if LESSOPEN specifies an "alternate" file to open.
240 alt_pipe = NULL;
241 alt_filename = open_altfile(filename, &f, &alt_pipe);
242 open_filename = (alt_filename != NULL) ? alt_filename : filename;
243 qopen_filename = shell_unquote(open_filename);
245 chflags = 0;
246 if (strcmp(open_filename, helpfile()) == 0)
247 chflags |= CH_HELPFILE;
248 if (alt_pipe != NULL) {
250 * The alternate "file" is actually a pipe.
251 * f has already been set to the file descriptor of the pipe
252 * in the call to open_altfile above.
253 * Keep the file descriptor open because it was opened
254 * via popen(), and pclose() wants to close it.
256 chflags |= CH_POPENED;
257 } else if (strcmp(open_filename, "-") == 0) {
259 * Use standard input.
260 * Keep the file descriptor open because we can't reopen it.
262 f = fd0;
263 chflags |= CH_KEEPOPEN;
264 } else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0) {
265 f = -1;
266 chflags |= CH_NODATA;
267 } else if ((parg.p_string = bad_file(open_filename)) != NULL) {
269 * It looks like a bad file. Don't try to open it.
271 error("%s", &parg);
272 free(parg.p_string);
273 err1:
274 if (alt_filename != NULL) {
275 close_altfile(alt_filename, filename, alt_pipe);
276 free(alt_filename);
278 del_ifile(ifile);
279 free(qopen_filename);
280 free(filename);
282 * Re-open the current file.
284 if (was_curr_ifile == ifile) {
286 * Whoops. The "current" ifile is the one we just
287 * deleted. Just give up.
289 quit(QUIT_ERROR);
291 reedit_ifile(was_curr_ifile);
292 return (1);
293 } else if ((f = open(qopen_filename, O_RDONLY)) < 0) {
295 * Got an error trying to open it.
297 parg.p_string = errno_message(filename);
298 error("%s", &parg);
299 free(parg.p_string);
300 goto err1;
301 } else {
302 chflags |= CH_CANSEEK;
303 if (!force_open && !opened(ifile) && bin_file(f)) {
305 * Looks like a binary file.
306 * Ask user if we should proceed.
308 parg.p_string = filename;
309 answer = query("\"%s\" may be a binary file. "
310 "See it anyway? ", &parg);
311 if (answer != 'y' && answer != 'Y') {
312 (void) close(f);
313 goto err1;
319 * Get the new ifile.
320 * Get the saved position for the file.
322 if (was_curr_ifile != NULL) {
323 old_ifile = was_curr_ifile;
324 unsave_ifile(was_curr_ifile);
326 curr_ifile = ifile;
327 curr_altfilename = alt_filename;
328 curr_altpipe = alt_pipe;
329 set_open(curr_ifile); /* File has been opened */
330 get_pos(curr_ifile, &initial_scrpos);
331 new_file = TRUE;
332 ch_init(f, chflags);
334 if (!(chflags & CH_HELPFILE)) {
335 struct stat statbuf;
336 int r;
338 if (namelogfile != NULL && is_tty)
339 use_logfile(namelogfile);
340 /* Remember the i-number and device of opened file. */
341 r = stat(qopen_filename, &statbuf);
342 if (r == 0) {
343 curr_ino = statbuf.st_ino;
344 curr_dev = statbuf.st_dev;
346 if (every_first_cmd != NULL)
347 ungetsc(every_first_cmd);
349 free(qopen_filename);
350 no_display = !any_display;
351 flush(0);
352 any_display = TRUE;
354 if (is_tty) {
356 * Output is to a real tty.
360 * Indicate there is nothing displayed yet.
362 pos_clear();
363 clr_linenum();
364 clr_hilite();
365 cmd_addhist(ml_examine, filename);
366 if (no_display && errmsgs > 0) {
368 * We displayed some messages on error output
369 * (file descriptor 2; see error() function).
370 * Before erasing the screen contents,
371 * display the file name and wait for a keystroke.
373 parg.p_string = filename;
374 error("%s", &parg);
377 free(filename);
378 return (0);
382 * Edit a space-separated list of files.
383 * For each filename in the list, enter it into the ifile list.
384 * Then edit the first one.
387 edit_list(char *filelist)
389 IFILE save_ifile;
390 char *good_filename;
391 char *filename;
392 char *gfilelist;
393 char *gfilename;
394 struct textlist tl_files;
395 struct textlist tl_gfiles;
397 save_ifile = save_curr_ifile();
398 good_filename = NULL;
401 * Run thru each filename in the list.
402 * Try to glob the filename.
403 * If it doesn't expand, just try to open the filename.
404 * If it does expand, try to open each name in that list.
406 init_textlist(&tl_files, filelist);
407 filename = NULL;
408 while ((filename = forw_textlist(&tl_files, filename)) != NULL) {
409 gfilelist = lglob(filename);
410 init_textlist(&tl_gfiles, gfilelist);
411 gfilename = NULL;
412 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) !=
413 NULL) {
414 if (edit(gfilename) == 0 && good_filename == NULL)
415 good_filename = get_filename(curr_ifile);
417 free(gfilelist);
420 * Edit the first valid filename in the list.
422 if (good_filename == NULL) {
423 unsave_ifile(save_ifile);
424 return (1);
426 if (get_ifile(good_filename, curr_ifile) == curr_ifile) {
428 * Trying to edit the current file; don't reopen it.
430 unsave_ifile(save_ifile);
431 return (0);
433 reedit_ifile(save_ifile);
434 return (edit(good_filename));
438 * Edit the first file in the command line (ifile) list.
441 edit_first(void)
443 curr_ifile = NULL;
444 return (edit_next(1));
448 * Edit the last file in the command line (ifile) list.
451 edit_last(void)
453 curr_ifile = NULL;
454 return (edit_prev(1));
459 * Edit the n-th next or previous file in the command line (ifile) list.
461 static int
462 edit_istep(IFILE h, int n, int dir)
464 IFILE next;
467 * Skip n filenames, then try to edit each filename.
469 for (;;) {
470 next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
471 if (--n < 0) {
472 if (edit_ifile(h) == 0)
473 break;
475 if (next == NULL) {
477 * Reached end of the ifile list.
479 return (1);
481 if (ABORT_SIGS()) {
483 * Interrupt breaks out, if we're in a long
484 * list of files that can't be opened.
486 return (1);
488 h = next;
491 * Found a file that we can edit.
493 return (0);
496 static int
497 edit_inext(IFILE h, int n)
499 return (edit_istep(h, n, +1));
503 edit_next(int n)
505 return (edit_istep(curr_ifile, n, +1));
508 static int
509 edit_iprev(IFILE h, int n)
511 return (edit_istep(h, n, -1));
515 edit_prev(int n)
517 return (edit_istep(curr_ifile, n, -1));
521 * Edit a specific file in the command line (ifile) list.
524 edit_index(int n)
526 IFILE h;
528 h = NULL;
529 do {
530 if ((h = next_ifile(h)) == NULL) {
532 * Reached end of the list without finding it.
534 return (1);
536 } while (get_index(h) != n);
538 return (edit_ifile(h));
541 IFILE
542 save_curr_ifile(void)
544 if (curr_ifile != NULL)
545 hold_ifile(curr_ifile, 1);
546 return (curr_ifile);
549 void
550 unsave_ifile(IFILE save_ifile)
552 if (save_ifile != NULL)
553 hold_ifile(save_ifile, -1);
557 * Reedit the ifile which was previously open.
559 void
560 reedit_ifile(IFILE save_ifile)
562 IFILE next;
563 IFILE prev;
566 * Try to reopen the ifile.
567 * Note that opening it may fail (maybe the file was removed),
568 * in which case the ifile will be deleted from the list.
569 * So save the next and prev ifiles first.
571 unsave_ifile(save_ifile);
572 next = next_ifile(save_ifile);
573 prev = prev_ifile(save_ifile);
574 if (edit_ifile(save_ifile) == 0)
575 return;
577 * If can't reopen it, open the next input file in the list.
579 if (next != NULL && edit_inext(next, 0) == 0)
580 return;
582 * If can't open THAT one, open the previous input file in the list.
584 if (prev != NULL && edit_iprev(prev, 0) == 0)
585 return;
587 * If can't even open that, we're stuck. Just quit.
589 quit(QUIT_ERROR);
592 void
593 reopen_curr_ifile(void)
595 IFILE save_ifile = save_curr_ifile();
596 close_file();
597 reedit_ifile(save_ifile);
601 * Edit standard input.
604 edit_stdin(void)
606 if (isatty(fd0)) {
607 if (less_is_more) {
608 error("Missing filename (\"more -h\" for help)",
609 NULL);
610 } else {
611 error("Missing filename (\"less --help\" for help)",
612 NULL);
614 quit(QUIT_OK);
616 return (edit("-"));
620 * Copy a file directly to standard output.
621 * Used if standard output is not a tty.
623 void
624 cat_file(void)
626 int c;
628 while ((c = ch_forw_get()) != EOI)
629 putchr(c);
630 flush(0);
634 * If the user asked for a log file and our input file
635 * is standard input, create the log file.
636 * We take care not to blindly overwrite an existing file.
638 void
639 use_logfile(char *filename)
641 int exists;
642 int answer;
643 PARG parg;
645 if (ch_getflags() & CH_CANSEEK)
647 * Can't currently use a log file on a file that can seek.
649 return;
652 * {{ We could use access() here. }}
654 filename = shell_unquote(filename);
655 exists = open(filename, O_RDONLY);
656 close(exists);
657 exists = (exists >= 0);
660 * Decide whether to overwrite the log file or append to it.
661 * If it doesn't exist we "overwrite" it.
663 if (!exists || force_logfile) {
665 * Overwrite (or create) the log file.
667 answer = 'O';
668 } else {
670 * Ask user what to do.
672 parg.p_string = filename;
673 answer = query("Warning: \"%s\" exists; "
674 "Overwrite, Append or Don't log? ", &parg);
677 loop:
678 switch (answer) {
679 case 'O': case 'o':
681 * Overwrite: create the file.
683 logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
684 break;
685 case 'A': case 'a':
687 * Append: open the file and seek to the end.
689 logfile = open(filename, O_WRONLY | O_APPEND);
690 if (lseek(logfile, (off_t)0, SEEK_END) == (off_t)-1) {
691 close(logfile);
692 logfile = -1;
694 break;
695 case 'D': case 'd':
697 * Don't do anything.
699 free(filename);
700 return;
701 case 'q':
702 quit(QUIT_OK);
703 default:
705 * Eh?
707 answer = query("Overwrite, Append, or Don't log? "
708 "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL);
709 goto loop;
712 if (logfile < 0) {
714 * Error in opening logfile.
716 parg.p_string = filename;
717 error("Cannot write to \"%s\"", &parg);
718 free(filename);
719 return;
721 free(filename);