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