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