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