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