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