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