xref: /openbsd-src/usr.bin/less/edit.c (revision 897fc685943471cf985a0fe38ba076ea6fe74fa5)
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>
5  *
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.
8  *
9  * For more information, see the README file.
10  */
11 
12 #include <sys/stat.h>
13 
14 #include "less.h"
15 
16 static int fd0 = 0;
17 
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;
35 
36 dev_t curr_dev;
37 ino_t curr_ino;
38 
39 char *curr_altfilename = NULL;
40 static void *curr_altpipe;
41 
42 
43 /*
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.
49  */
50 void
51 init_textlist(struct textlist *tlist, char *str)
52 {
53 	char *s;
54 	int meta_quoted = 0;
55 	int delim_quoted = 0;
56 	char *esc = get_meta_escape();
57 	int esclen = strlen(esc);
58 
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';
76 		}
77 	}
78 }
79 
80 char *
81 forw_textlist(struct textlist *tlist, char *prev)
82 {
83 	char *s;
84 
85 	/*
86 	 * prev == NULL means return the first word in the list.
87 	 * Otherwise, return the word after "prev".
88 	 */
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);
100 }
101 
102 char *
103 back_textlist(struct textlist *tlist, char *prev)
104 {
105 	char *s;
106 
107 	/*
108 	 * prev == NULL means return the last word in the list.
109 	 * Otherwise, return the word before "prev".
110 	 */
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);
124 }
125 
126 /*
127  * Close the current input file.
128  */
129 static void
130 close_file(void)
131 {
132 	struct scrpos scrpos;
133 
134 	if (curr_ifile == NULL)
135 		return;
136 
137 	/*
138 	 * Save the current position so that we can return to
139 	 * the same position if we edit this file again.
140 	 */
141 	get_scrpos(&scrpos);
142 	if (scrpos.pos != -1) {
143 		store_pos(curr_ifile, &scrpos);
144 		lastmark();
145 	}
146 	/*
147 	 * Close the file descriptor, unless it is a pipe.
148 	 */
149 	ch_close();
150 	/*
151 	 * If we opened a file using an alternate name,
152 	 * do special stuff to close it.
153 	 */
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;
159 	}
160 	curr_ifile = NULL;
161 	curr_ino = curr_dev = 0;
162 }
163 
164 /*
165  * Edit a new file (given its name).
166  * Filename == "-" means standard input.
167  * Filename == NULL means just close the current file.
168  */
169 int
170 edit(char *filename)
171 {
172 	if (filename == NULL)
173 		return (edit_ifile(NULL));
174 	return (edit_ifile(get_ifile(filename, curr_ifile)));
175 }
176 
177 /*
178  * Edit a new file (given its IFILE).
179  * ifile == NULL means just close the current file.
180  */
181 int
182 edit_ifile(IFILE ifile)
183 {
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;
195 
196 	if (ifile == curr_ifile) {
197 		/*
198 		 * Already have the correct file open.
199 		 */
200 		return (0);
201 	}
202 
203 	/*
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); }}
209 	 */
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) {
217 			/*
218 			 * Don't keep the help file in the ifile list.
219 			 */
220 			del_ifile(was_curr_ifile);
221 			was_curr_ifile = old_ifile;
222 		}
223 	}
224 
225 	if (ifile == NULL) {
226 		/*
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.)
231 		 */
232 		unsave_ifile(was_curr_ifile);
233 		return (0);
234 	}
235 
236 	filename = estrdup(get_filename(ifile));
237 	/*
238 	 * See if LESSOPEN specifies an "alternate" file to open.
239 	 */
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);
244 
245 	chflags = 0;
246 	if (strcmp(open_filename, helpfile()) == 0)
247 		chflags |= CH_HELPFILE;
248 	if (alt_pipe != NULL) {
249 		/*
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.
255 		 */
256 		chflags |= CH_POPENED;
257 	} else if (strcmp(open_filename, "-") == 0) {
258 		/*
259 		 * Use standard input.
260 		 * Keep the file descriptor open because we can't reopen it.
261 		 */
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) {
268 		/*
269 		 * It looks like a bad file.  Don't try to open it.
270 		 */
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);
277 		}
278 		del_ifile(ifile);
279 		free(qopen_filename);
280 		free(filename);
281 		/*
282 		 * Re-open the current file.
283 		 */
284 		if (was_curr_ifile == ifile) {
285 			/*
286 			 * Whoops.  The "current" ifile is the one we just
287 			 * deleted. Just give up.
288 			 */
289 			quit(QUIT_ERROR);
290 		}
291 		reedit_ifile(was_curr_ifile);
292 		return (1);
293 	} else if ((f = open(qopen_filename, O_RDONLY)) < 0) {
294 		/*
295 		 * Got an error trying to open it.
296 		 */
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)) {
304 			/*
305 			 * Looks like a binary file.
306 			 * Ask user if we should proceed.
307 			 */
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;
314 			}
315 		}
316 	}
317 
318 	/*
319 	 * Get the new ifile.
320 	 * Get the saved position for the file.
321 	 */
322 	if (was_curr_ifile != NULL) {
323 		old_ifile = was_curr_ifile;
324 		unsave_ifile(was_curr_ifile);
325 	}
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);
333 
334 	if (!(chflags & CH_HELPFILE)) {
335 		struct stat statbuf;
336 		int r;
337 
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;
345 		}
346 		if (every_first_cmd != NULL)
347 			ungetsc(every_first_cmd);
348 	}
349 	free(qopen_filename);
350 	no_display = !any_display;
351 	flush(0);
352 	any_display = TRUE;
353 
354 	if (is_tty) {
355 		/*
356 		 * Output is to a real tty.
357 		 */
358 
359 		/*
360 		 * Indicate there is nothing displayed yet.
361 		 */
362 		pos_clear();
363 		clr_linenum();
364 		clr_hilite();
365 		cmd_addhist(ml_examine, filename);
366 		if (no_display && errmsgs > 0) {
367 			/*
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.
372 			 */
373 			parg.p_string = filename;
374 			error("%s", &parg);
375 		}
376 	}
377 	free(filename);
378 	return (0);
379 }
380 
381 /*
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.
385  */
386 int
387 edit_list(char *filelist)
388 {
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;
396 
397 	save_ifile = save_curr_ifile();
398 	good_filename = NULL;
399 
400 	/*
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.
405 	 */
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);
416 		}
417 		free(gfilelist);
418 	}
419 	/*
420 	 * Edit the first valid filename in the list.
421 	 */
422 	if (good_filename == NULL) {
423 		unsave_ifile(save_ifile);
424 		return (1);
425 	}
426 	if (get_ifile(good_filename, curr_ifile) == curr_ifile) {
427 		/*
428 		 * Trying to edit the current file; don't reopen it.
429 		 */
430 		unsave_ifile(save_ifile);
431 		return (0);
432 	}
433 	reedit_ifile(save_ifile);
434 	return (edit(good_filename));
435 }
436 
437 /*
438  * Edit the first file in the command line (ifile) list.
439  */
440 int
441 edit_first(void)
442 {
443 	curr_ifile = NULL;
444 	return (edit_next(1));
445 }
446 
447 /*
448  * Edit the last file in the command line (ifile) list.
449  */
450 int
451 edit_last(void)
452 {
453 	curr_ifile = NULL;
454 	return (edit_prev(1));
455 }
456 
457 
458 /*
459  * Edit the n-th next or previous file in the command line (ifile) list.
460  */
461 static int
462 edit_istep(IFILE h, int n, int dir)
463 {
464 	IFILE next;
465 
466 	/*
467 	 * Skip n filenames, then try to edit each filename.
468 	 */
469 	for (;;) {
470 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
471 		if (--n < 0) {
472 			if (edit_ifile(h) == 0)
473 				break;
474 		}
475 		if (next == NULL) {
476 			/*
477 			 * Reached end of the ifile list.
478 			 */
479 			return (1);
480 		}
481 		if (ABORT_SIGS()) {
482 			/*
483 			 * Interrupt breaks out, if we're in a long
484 			 * list of files that can't be opened.
485 			 */
486 			return (1);
487 		}
488 		h = next;
489 	}
490 	/*
491 	 * Found a file that we can edit.
492 	 */
493 	return (0);
494 }
495 
496 static int
497 edit_inext(IFILE h, int n)
498 {
499 	return (edit_istep(h, n, +1));
500 }
501 
502 int
503 edit_next(int n)
504 {
505 	return (edit_istep(curr_ifile, n, +1));
506 }
507 
508 static int
509 edit_iprev(IFILE h, int n)
510 {
511 	return (edit_istep(h, n, -1));
512 }
513 
514 int
515 edit_prev(int n)
516 {
517 	return (edit_istep(curr_ifile, n, -1));
518 }
519 
520 /*
521  * Edit a specific file in the command line (ifile) list.
522  */
523 int
524 edit_index(int n)
525 {
526 	IFILE h;
527 
528 	h = NULL;
529 	do {
530 		if ((h = next_ifile(h)) == NULL) {
531 			/*
532 			 * Reached end of the list without finding it.
533 			 */
534 			return (1);
535 		}
536 	} while (get_index(h) != n);
537 
538 	return (edit_ifile(h));
539 }
540 
541 IFILE
542 save_curr_ifile(void)
543 {
544 	if (curr_ifile != NULL)
545 		hold_ifile(curr_ifile, 1);
546 	return (curr_ifile);
547 }
548 
549 void
550 unsave_ifile(IFILE save_ifile)
551 {
552 	if (save_ifile != NULL)
553 		hold_ifile(save_ifile, -1);
554 }
555 
556 /*
557  * Reedit the ifile which was previously open.
558  */
559 void
560 reedit_ifile(IFILE save_ifile)
561 {
562 	IFILE next;
563 	IFILE prev;
564 
565 	/*
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.
570 	 */
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;
576 	/*
577 	 * If can't reopen it, open the next input file in the list.
578 	 */
579 	if (next != NULL && edit_inext(next, 0) == 0)
580 		return;
581 	/*
582 	 * If can't open THAT one, open the previous input file in the list.
583 	 */
584 	if (prev != NULL && edit_iprev(prev, 0) == 0)
585 		return;
586 	/*
587 	 * If can't even open that, we're stuck.  Just quit.
588 	 */
589 	quit(QUIT_ERROR);
590 }
591 
592 void
593 reopen_curr_ifile(void)
594 {
595 	IFILE save_ifile = save_curr_ifile();
596 	close_file();
597 	reedit_ifile(save_ifile);
598 }
599 
600 /*
601  * Edit standard input.
602  */
603 int
604 edit_stdin(void)
605 {
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);
613 		}
614 		quit(QUIT_OK);
615 	}
616 	return (edit("-"));
617 }
618 
619 /*
620  * Copy a file directly to standard output.
621  * Used if standard output is not a tty.
622  */
623 void
624 cat_file(void)
625 {
626 	int c;
627 
628 	while ((c = ch_forw_get()) != EOI)
629 		putchr(c);
630 	flush(0);
631 }
632 
633 /*
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.
637  */
638 void
639 use_logfile(char *filename)
640 {
641 	int exists;
642 	int answer;
643 	PARG parg;
644 
645 	if (ch_getflags() & CH_CANSEEK)
646 		/*
647 		 * Can't currently use a log file on a file that can seek.
648 		 */
649 		return;
650 
651 	/*
652 	 * {{ We could use access() here. }}
653 	 */
654 	filename = shell_unquote(filename);
655 	exists = open(filename, O_RDONLY);
656 	close(exists);
657 	exists = (exists >= 0);
658 
659 	/*
660 	 * Decide whether to overwrite the log file or append to it.
661 	 * If it doesn't exist we "overwrite" it.
662 	 */
663 	if (!exists || force_logfile) {
664 		/*
665 		 * Overwrite (or create) the log file.
666 		 */
667 		answer = 'O';
668 	} else {
669 		/*
670 		 * Ask user what to do.
671 		 */
672 		parg.p_string = filename;
673 		answer = query("Warning: \"%s\" exists; "
674 		    "Overwrite, Append or Don't log? ", &parg);
675 	}
676 
677 loop:
678 	switch (answer) {
679 	case 'O': case 'o':
680 		/*
681 		 * Overwrite: create the file.
682 		 */
683 		logfile = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
684 		break;
685 	case 'A': case 'a':
686 		/*
687 		 * Append: open the file and seek to the end.
688 		 */
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;
693 		}
694 		break;
695 	case 'D': case 'd':
696 		/*
697 		 * Don't do anything.
698 		 */
699 		free(filename);
700 		return;
701 	case 'q':
702 		quit(QUIT_OK);
703 	default:
704 		/*
705 		 * Eh?
706 		 */
707 		answer = query("Overwrite, Append, or Don't log? "
708 		    "(Type \"O\", \"A\", \"D\" or \"q\") ", NULL);
709 		goto loop;
710 	}
711 
712 	if (logfile < 0) {
713 		/*
714 		 * Error in opening logfile.
715 		 */
716 		parg.p_string = filename;
717 		error("Cannot write to \"%s\"", &parg);
718 		free(filename);
719 		return;
720 	}
721 	free(filename);
722 }
723