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