xref: /freebsd/contrib/less/edit.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
1 /*
2  * Copyright (C) 1984-2026  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 HAVE_SYS_WAIT_H
17 #include <sys/wait.h>
18 #endif
19 #if OS2 || __MVS__ || (defined WIFSIGNALED && defined WTERMSIG)
20 #include <signal.h>
21 #endif
22 
23 public int fd0 = 0;
24 
25 extern lbool new_file;
26 extern char *every_first_cmd;
27 extern int force_open;
28 extern lbool is_tty;
29 extern int sigs;
30 extern int hshift;
31 extern int want_filesize;
32 extern int modelines;
33 extern int show_preproc_error;
34 extern lbool read_error;
35 extern IFILE curr_ifile;
36 extern IFILE old_ifile;
37 extern struct scrpos initial_scrpos;
38 extern void *ml_examine;
39 extern POSITION soft_eof;
40 #if SPACES_IN_FILENAMES
41 extern char openquote;
42 extern char closequote;
43 #endif
44 
45 #if LOGFILE
46 extern int logfile;
47 extern int force_logfile;
48 extern char *namelogfile;
49 #endif
50 
51 #if HAVE_STAT_INO
52 public dev_t curr_dev;
53 public ino_t curr_ino;
54 #endif
55 
56 /*
57  * Textlist functions deal with a list of words separated by spaces.
58  * init_textlist sets up a textlist structure.
59  * forw_textlist uses that structure to iterate thru the list of
60  * words, returning each one as a standard null-terminated string.
61  * back_textlist does the same, but runs thru the list backwards.
62  */
init_textlist(struct textlist * tlist,mutable char * str)63 public void init_textlist(struct textlist *tlist, mutable char *str)
64 {
65 	char *s;
66 #if SPACES_IN_FILENAMES
67 	lbool meta_quoted = FALSE;
68 	lbool delim_quoted = FALSE;
69 	constant char *esc = get_meta_escape();
70 	size_t esclen = strlen(esc);
71 #endif
72 
73 	tlist->string = skipsp(str);
74 	tlist->endstring = tlist->string + strlen(tlist->string);
75 	for (s = str;  s < tlist->endstring;  s++)
76 	{
77 #if SPACES_IN_FILENAMES
78 		if (meta_quoted)
79 		{
80 			meta_quoted = FALSE;
81 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
82 		           strncmp(s, esc, esclen) == 0)
83 		{
84 			meta_quoted = TRUE;
85 			s += esclen - 1;
86 		} else if (delim_quoted)
87 		{
88 			if (*s == closequote)
89 				delim_quoted = FALSE;
90 		} else /* (!delim_quoted) */
91 		{
92 			if (*s == openquote)
93 				delim_quoted = TRUE;
94 			else if (*s == ' ')
95 				*s = '\0';
96 		}
97 #else
98 		if (*s == ' ')
99 			*s = '\0';
100 #endif
101 	}
102 }
103 
forw_textlist(constant struct textlist * tlist,constant char * prev)104 public constant char * forw_textlist(constant struct textlist *tlist, constant char *prev)
105 {
106 	constant 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 	while (s < tlist->endstring && *s == '\0')
117 		s++;
118 	if (s >= tlist->endstring)
119 		return (NULL);
120 	return (s);
121 }
122 
back_textlist(constant struct textlist * tlist,constant char * prev)123 public constant char * back_textlist(constant struct textlist *tlist, constant char *prev)
124 {
125 	constant char *s;
126 
127 	/*
128 	 * prev == NULL means return the last word in the list.
129 	 * Otherwise, return the word before "prev".
130 	 */
131 	if (prev == NULL)
132 		s = tlist->endstring;
133 	else if (prev <= tlist->string)
134 		return (NULL);
135 	else
136 		s = prev - 1;
137 	while (*s == '\0')
138 		s--;
139 	if (s <= tlist->string)
140 		return (NULL);
141 	while (s[-1] != '\0' && s > tlist->string)
142 		s--;
143 	return (s);
144 }
145 
146 /*
147  * Parse a single option setting in a modeline.
148  */
modeline_option(constant char * str,size_t opt_len)149 static void modeline_option(constant char *str, size_t opt_len)
150 {
151 	struct mloption { constant char *opt_name; void (*opt_func)(constant char*,size_t); };
152 	struct mloption options[] = {
153 		{ "ts=",         set_tabs },
154 		{ "tabstop=",    set_tabs },
155 		{ NULL, NULL }
156 	};
157 	struct mloption *opt;
158 	for (opt = options;  opt->opt_name != NULL;  opt++)
159 	{
160 		size_t name_len = strlen(opt->opt_name);
161 		if (opt_len > name_len && strncmp(str, opt->opt_name, name_len) == 0)
162 		{
163 			(*opt->opt_func)(str + name_len, opt_len - name_len);
164 			break;
165 		}
166 	}
167 }
168 
169 /*
170  * String length, terminated by option separator (space or colon).
171  * Space/colon can be escaped with backspace.
172  */
modeline_option_len(constant char * str)173 static size_t modeline_option_len(constant char *str)
174 {
175 	lbool esc = FALSE;
176 	constant char *s;
177 	for (s = str;  *s != '\0';  s++)
178 	{
179 		if (esc)
180 			esc = FALSE;
181 		else if (*s == '\\')
182 			esc = TRUE;
183 		else if (*s == ' ' || *s == ':') /* separator */
184 			break;
185 	}
186 	return ptr_diff(s, str);
187 }
188 
189 /*
190  * Parse colon- or space-separated option settings in a modeline.
191  */
modeline_options(constant char * str,char end_char)192 static void modeline_options(constant char *str, char end_char)
193 {
194 	for (;;)
195 	{
196 		size_t opt_len;
197 		str = skipspc(str);
198 		if (*str == '\0' || *str == end_char)
199 			break;
200 		opt_len = modeline_option_len(str);
201 		modeline_option(str, opt_len);
202 		str += opt_len;
203 		if (*str != '\0')
204 			str += 1; /* skip past the separator */
205 	}
206 }
207 
208 /*
209  * See if there is a modeline string in a line.
210  */
check_modeline(constant char * line)211 static void check_modeline(constant char *line)
212 {
213 	static constant char *pgms[] = { "less:", "vim:", "vi:", "ex:", NULL };
214 	constant char **pgm;
215 	for (pgm = pgms;  *pgm != NULL;  ++pgm)
216 	{
217 		constant char *pline = line;
218 		for (;;)
219 		{
220 			constant char *str;
221 			pline = strstr(pline, *pgm);
222 			if (pline == NULL) /* pgm is not in this line */
223 				break;
224 			str = skipspc(pline + strlen(*pgm));
225 			if (pline == line || pline[-1] == ' ')
226 			{
227 				if (strncmp(str, "set ", 4) == 0)
228 					modeline_options(str+4, ':');
229 				else if (pgm != &pgms[0]) /* "less:" requires "set" */
230 					modeline_options(str, '\0');
231 				break;
232 			}
233 			/* Continue searching the rest of the line. */
234 			pline = str;
235 		}
236 	}
237 }
238 
239 /*
240  * Read lines from start of file and check if any are modelines.
241  */
check_modelines(void)242 static void check_modelines(void)
243 {
244 	POSITION pos = ch_zero();
245 	int i;
246 	for (i = 0;  i < modelines;  i++)
247 	{
248 		constant char *line;
249 		size_t line_len;
250 		if (ABORT_SIGS())
251 			return;
252 		pos = forw_raw_line(pos, &line, &line_len);
253 		if (pos == NULL_POSITION)
254 			break;
255 		check_modeline(line);
256 	}
257 }
258 
259 /*
260  * Close a pipe opened via popen.
261  */
close_pipe(FILE * pipefd)262 public void close_pipe(FILE *pipefd)
263 {
264 	int status;
265 	char *p;
266 	PARG parg;
267 	int sig = 0;
268 
269 	if (pipefd == NULL)
270 		return;
271 #if OS2
272 	/*
273 	 * The pclose function of OS/2 emx sometimes fails.
274 	 * Send SIGINT to the piped process before closing it.
275 	 */
276 	kill(pipefd->_pid, SIGINT);
277 #endif
278 	status = pclose(pipefd);
279 	if (status == -1)
280 	{
281 		/* An internal error in 'less', not a preprocessor error.  */
282 		p = errno_message("pclose");
283 		parg.p_string = p;
284 		error("%s", &parg);
285 		free(p);
286 		return;
287 	}
288 	if (!show_preproc_error)
289 		return;
290 #if defined WIFEXITED && defined WEXITSTATUS
291 	if (WIFEXITED(status))
292 	{
293 		int s = WEXITSTATUS(status);
294 		if (s == 0)
295 			return;
296 		if (s <= 128)
297 		{
298 			parg.p_int = s;
299 			error(LM(Input_preprocessor_failed), &parg);
300 			return;
301 		}
302 		/*
303 		 * popen invoked the shell, which likely last ran a
304 		 * program that terminated due to a signal.
305 		 * Assume the longstanding tradition (allowed but not
306 		 * required by POSIX) of adding 128 to the signal.
307 		 * Many shells use last command optimization, i.e.,
308 		 * they exec the last command instead of forking and
309 		 * waiting for it, and in that case the more-reliable
310 		 * WIFSIGNALED code below will be used.
311 		 */
312 		sig = s - 128;
313 	}
314 #endif
315 #if defined WIFSIGNALED && defined WTERMSIG
316 	if (WIFSIGNALED(status))
317 		sig = WTERMSIG(status);
318 #endif
319 	if (sig != 0)
320 	{
321 		if (
322 #ifdef SIGPIPE
323 			sig != SIGPIPE ||
324 #endif
325 			ch_length() != NULL_POSITION)
326 		{
327 			parg.p_string = signal_message(sig);
328 			error(LM(Input_preprocessor_terminated_X), &parg);
329 		}
330 		return;
331 	}
332 	if (status != 0)
333 	{
334 		parg.p_int = status;
335 		error(LM(Input_preprocessor_exited_with_status_X), &parg);
336 	}
337 }
338 
339 /*
340  * Drain and close an input pipe if needed.
341  */
close_altpipe(IFILE ifile)342 static void close_altpipe(IFILE ifile)
343 {
344 	FILE *altpipe = get_altpipe(ifile);
345 	if (altpipe != NULL && !(ch_getflags() & CH_KEEPOPEN))
346 	{
347 		close_pipe(altpipe);
348 		set_altpipe(ifile, NULL);
349 	}
350 }
351 
352 /*
353  * Check for error status from the current altpipe.
354  * May or may not close the pipe.
355  */
check_altpipe_error(void)356 public void check_altpipe_error(void)
357 {
358 	if (!show_preproc_error)
359 		return;
360 	if (curr_ifile != NULL_IFILE && get_altfilename(curr_ifile) != NULL)
361 		close_altpipe(curr_ifile);
362 }
363 
364 /*
365  * Close the current input file.
366  */
close_file(void)367 static void close_file(void)
368 {
369 	struct scrpos scrpos;
370 	constant char *altfilename;
371 
372 	if (curr_ifile == NULL_IFILE)
373 		return;
374 
375 	/*
376 	 * Save the current position so that we can return to
377 	 * the same position if we edit this file again.
378 	 */
379 	if (is_tty)
380 	{
381 		get_scrpos(&scrpos, TOP);
382 		if (scrpos.pos != NULL_POSITION)
383 		{
384 			store_pos(curr_ifile, &scrpos);
385 			lastmark();
386 		}
387 	}
388 	/*
389 	 * Close the file descriptor, unless it is a pipe.
390 	 */
391 	ch_close();
392 	/*
393 	 * If we opened a file using an alternate name,
394 	 * do special stuff to close it.
395 	 */
396 	altfilename = get_altfilename(curr_ifile);
397 	if (altfilename != NULL)
398 	{
399 		close_altpipe(curr_ifile);
400 		close_altfile(altfilename, get_filename(curr_ifile));
401 		set_altfilename(curr_ifile, NULL);
402 	}
403 	curr_ifile = NULL_IFILE;
404 #if HAVE_STAT_INO
405 	curr_ino = curr_dev = 0;
406 #endif
407 }
408 
409 /*
410  * Edit a new file (given its name).
411  * Filename == "-" means standard input.
412  * Filename == NULL means just close the current file.
413  */
edit(constant char * filename)414 public int edit(constant char *filename)
415 {
416 	if (filename == NULL)
417 		return (edit_ifile(NULL_IFILE));
418 	return (edit_ifile(get_ifile(filename, curr_ifile)));
419 }
420 
421 /*
422  * Clean up what edit_ifile did before error return.
423  */
edit_error(constant char * filename,constant char * alt_filename,FILE * altpipe,IFILE ifile)424 static int edit_error(constant char *filename, constant char *alt_filename, FILE *altpipe, IFILE ifile)
425 {
426 	if (alt_filename != NULL)
427 	{
428 		close_pipe(altpipe);
429 		close_altfile(alt_filename, filename);
430 		free((char*)alt_filename); /* FIXME: WTF? */
431 	}
432 	del_ifile(ifile);
433 	/*
434 	 * Re-open the current file.
435 	 */
436 	if (curr_ifile == ifile)
437 	{
438 		/*
439 		 * Whoops.  The "current" ifile is the one we just deleted.
440 		 * Just give up.
441 		 */
442 		quit(QUIT_ERROR);
443 	}
444 	return (1);
445 }
446 
447 /*
448  * Edit a new file (given its IFILE).
449  * ifile == NULL means just close the current file.
450  */
edit_ifile(IFILE ifile)451 public int edit_ifile(IFILE ifile)
452 {
453 	int f;
454 	int answer;
455 	int chflags;
456 	constant char *filename;
457 	constant char *open_filename;
458 	char *alt_filename;
459 	FILE *altpipe;
460 	IFILE was_curr_ifile;
461 	char *p;
462 	PARG parg;
463 	ssize_t nread = 0;
464 
465 	if (ifile == curr_ifile)
466 	{
467 		/*
468 		 * Already have the correct file open.
469 		 */
470 		return (0);
471 	}
472 	new_file = TRUE;
473 
474 	if (ifile != NULL_IFILE)
475 	{
476 		/*
477 		 * See if LESSOPEN specifies an "alternate" file to open.
478 		 */
479 		filename = get_filename(ifile);
480 		altpipe = get_altpipe(ifile);
481 		if (altpipe != NULL)
482 		{
483 			/*
484 			 * File is already open.
485 			 * chflags and f are not used by ch_init if ifile has
486 			 * filestate which should be the case if we're here.
487 			 * Set them here to avoid uninitialized variable warnings.
488 			 */
489 			chflags = 0;
490 			f = -1;
491 			alt_filename = get_altfilename(ifile);
492 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
493 		} else
494 		{
495 			if (strcmp(filename, FAKE_HELPFILE) == 0 ||
496 				strcmp(filename, FAKE_EMPTYFILE) == 0)
497 				alt_filename = NULL;
498 			else
499 				alt_filename = open_altfile(filename, &f, &altpipe);
500 
501 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
502 
503 			chflags = 0;
504 			if (altpipe != NULL)
505 			{
506 				/*
507 				 * The alternate "file" is actually a pipe.
508 				 * f has already been set to the file descriptor of the pipe
509 				 * in the call to open_altfile above.
510 				 * Keep the file descriptor open because it was opened
511 				 * via popen(), and pclose() wants to close it.
512 				 */
513 				chflags |= CH_POPENED;
514 				if (strcmp(filename, "-") == 0)
515 					chflags |= CH_KEEPOPEN;
516 			} else if (strcmp(filename, "-") == 0)
517 			{
518 				/*
519 				 * Use standard input.
520 				 * Keep the file descriptor open because we can't reopen it.
521 				 */
522 				f = fd0;
523 				chflags |= CH_KEEPOPEN;
524 				/*
525 				 * Must switch stdin to BINARY mode.
526 				 */
527 				SET_BINARY(f);
528 #if MSDOS_COMPILER==DJGPPC
529 				/*
530 				 * Setting stdin to binary by default causes
531 				 * Ctrl-C to not raise SIGINT.  We must undo
532 				 * that side-effect.
533 				 */
534 				__djgpp_set_ctrl_c(1);
535 #endif
536 			} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
537 			{
538 				f = -1;
539 				chflags |= CH_NODATA;
540 			} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
541 			{
542 				f = -1;
543 				chflags |= CH_HELPFILE;
544 			} else if ((p = bad_file(open_filename)) != NULL)
545 			{
546 				/*
547 				 * It looks like a bad file.  Don't try to open it.
548 				 */
549 				parg.p_string = p;
550 				error("%s", &parg);
551 				free(p);
552 				return edit_error(filename, alt_filename, altpipe, ifile);
553 			} else if ((f = iopen(open_filename, OPEN_READ)) < 0)
554 			{
555 				/*
556 				 * Got an error trying to open it.
557 				 */
558 				char *p = errno_message(filename);
559 				parg.p_string = p;
560 				error("%s", &parg);
561 				free(p);
562 				return edit_error(filename, alt_filename, altpipe, ifile);
563 			} else
564 			{
565 				chflags |= CH_CANSEEK;
566 				if (bin_file(f, &nread) && is_tty && !force_open && !opened(ifile))
567 				{
568 					/*
569 					 * Looks like a binary file.
570 					 * Ask user if we should proceed.
571 					 */
572 					parg.p_string = filename;
573 					answer = query(LM(X_may_be_a_binary_file), &parg);
574 					if (answer == 'q')
575 						quit(QUIT_OK);
576 					if (answer != 'y' && answer != 'Y')
577 					{
578 						close(f);
579 						return edit_error(filename, alt_filename, altpipe, ifile);
580 					}
581 				}
582 			}
583 		}
584 		if (!force_open && f >= 0 && isatty(f))
585 		{
586 			PARG parg;
587 			parg.p_string = filename;
588 			error(LM(X_is_a_terminal), &parg);
589 			return edit_error(filename, alt_filename, altpipe, ifile);
590 		}
591 	}
592 
593 #if LOGFILE
594 	end_logfile();
595 #endif
596 	was_curr_ifile = save_curr_ifile();
597 	if (curr_ifile != NULL_IFILE)
598 	{
599 		int was_helpfile = (ch_getflags() & CH_HELPFILE);
600 		close_file();
601 		if (was_helpfile && held_ifile(was_curr_ifile) <= 1)
602 		{
603 			/*
604 			 * Don't keep the help file in the ifile list.
605 			 */
606 			del_ifile(was_curr_ifile);
607 			was_curr_ifile = NULL_IFILE;
608 		}
609 	}
610 	unsave_ifile(was_curr_ifile);
611 
612 	if (ifile == NULL_IFILE)
613 	{
614 		/*
615 		 * No new file to open.
616 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
617 		 *  you're supposed to have saved curr_ifile yourself,
618 		 *  and you'll restore it if necessary.)
619 		 */
620 		return (0);
621 	}
622 
623 	/*
624 	 * Set up the new ifile.
625 	 * Get the saved position for the file.
626 	 */
627 	curr_ifile = ifile;
628 	soft_eof = NULL_POSITION;
629 	set_altfilename(curr_ifile, alt_filename);
630 	set_altpipe(curr_ifile, altpipe);
631 	set_open(curr_ifile); /* File has been opened */
632 	get_pos(curr_ifile, &initial_scrpos);
633 	ch_init(f, chflags, nread);
634 	check_modelines();
635 
636 	if (!(chflags & CH_HELPFILE))
637 	{
638 		if (was_curr_ifile != NULL_IFILE)
639 			old_ifile = was_curr_ifile;
640 #if LOGFILE
641 		if (namelogfile != NULL && is_tty)
642 			use_logfile(namelogfile);
643 #endif
644 #if HAVE_STAT_INO
645 		/* Remember the i-number and device of the opened file. */
646 		curr_ino = curr_dev = 0;
647 		if (!is_fake_pathname(open_filename))
648 		{
649 			less_stat_t statbuf;
650 			int r = less_stat(open_filename, &statbuf);
651 			if (r == 0)
652 			{
653 				curr_ino = statbuf.st_ino;
654 				curr_dev = statbuf.st_dev;
655 			}
656 		}
657 #endif
658 		if (every_first_cmd != NULL)
659 		{
660 			ungetsc(every_first_cmd);
661 			ungetcc_end_command();
662 		}
663 	}
664 
665 	flush();
666 
667 	if (is_tty)
668 	{
669 		/*
670 		 * Output is to a real tty.
671 		 */
672 
673 		/*
674 		 * Indicate there is nothing displayed yet.
675 		 */
676 		pos_clear();
677 		clr_linenum();
678 #if HILITE_SEARCH
679 		clr_hilite();
680 #endif
681 		undo_osc8();
682 		hshift = 0;
683 		read_error = FALSE;
684 		if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
685 		{
686 			char *qfilename = shell_quote(filename);
687 			if (qfilename == NULL)
688 				cmd_addhist(ml_examine, filename, TRUE);
689 			else
690 			{
691 				cmd_addhist(ml_examine, qfilename, TRUE);
692 				free(qfilename);
693 			}
694 		}
695 		if (want_filesize)
696 			scan_eof();
697 		set_header(ch_zero());
698 	}
699 	return (0);
700 }
701 
702 /*
703  * Edit a space-separated list of files.
704  * For each filename in the list, enter it into the ifile list.
705  * Then edit the first one.
706  */
edit_list(char * filelist)707 public int edit_list(char *filelist)
708 {
709 	IFILE save_ifile;
710 	constant char *good_filename;
711 	constant char *filename;
712 	char *gfilelist;
713 	constant char *gfilename;
714 	char *qfilename;
715 	struct textlist tl_files;
716 	struct textlist tl_gfiles;
717 
718 	save_ifile = save_curr_ifile();
719 	good_filename = NULL;
720 
721 	/*
722 	 * Run thru each filename in the list.
723 	 * Try to glob the filename.
724 	 * If it doesn't expand, just try to open the filename.
725 	 * If it does expand, try to open each name in that list.
726 	 */
727 	init_textlist(&tl_files, filelist);
728 	filename = NULL;
729 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
730 	{
731 		gfilelist = lglob(filename);
732 		init_textlist(&tl_gfiles, gfilelist);
733 		gfilename = NULL;
734 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
735 		{
736 			qfilename = shell_unquote(gfilename);
737 			if (edit(qfilename) == 0 && good_filename == NULL)
738 				good_filename = get_filename(curr_ifile);
739 			free(qfilename);
740 		}
741 		free(gfilelist);
742 	}
743 	/*
744 	 * Edit the first valid filename in the list.
745 	 */
746 	if (good_filename == NULL)
747 	{
748 		unsave_ifile(save_ifile);
749 		return (1);
750 	}
751 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
752 	{
753 		/*
754 		 * Trying to edit the current file; don't reopen it.
755 		 */
756 		unsave_ifile(save_ifile);
757 		return (0);
758 	}
759 	reedit_ifile(save_ifile);
760 	return (edit(good_filename));
761 }
762 
763 /*
764  * Edit the first file in the command line (ifile) list.
765  */
edit_first(void)766 public int edit_first(void)
767 {
768 	if (nifile() == 0)
769 		return (edit_stdin());
770 	curr_ifile = NULL_IFILE;
771 	return (edit_next(1));
772 }
773 
774 /*
775  * Edit the last file in the command line (ifile) list.
776  */
edit_last(void)777 public int edit_last(void)
778 {
779 	curr_ifile = NULL_IFILE;
780 	return (edit_prev(1));
781 }
782 
783 
784 /*
785  * Edit the n-th next or previous file in the command line (ifile) list.
786  */
edit_istep(IFILE h,int n,int dir)787 static int edit_istep(IFILE h, int n, int dir)
788 {
789 	IFILE next;
790 
791 	/*
792 	 * Skip n filenames, then try to edit each filename.
793 	 */
794 	for (;;)
795 	{
796 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
797 		if (--n < 0)
798 		{
799 			if (edit_ifile(h) == 0)
800 				break;
801 		}
802 		if (next == NULL_IFILE)
803 		{
804 			/*
805 			 * Reached end of the ifile list.
806 			 */
807 			return (1);
808 		}
809 		if (ABORT_SIGS())
810 		{
811 			/*
812 			 * Interrupt breaks out, if we're in a long
813 			 * list of files that can't be opened.
814 			 */
815 			return (1);
816 		}
817 		h = next;
818 	}
819 	/*
820 	 * Found a file that we can edit.
821 	 */
822 	return (0);
823 }
824 
edit_inext(IFILE h,int n)825 static int edit_inext(IFILE h, int n)
826 {
827 	return (edit_istep(h, n, +1));
828 }
829 
edit_next(int n)830 public int edit_next(int n)
831 {
832 	return edit_istep(curr_ifile, n, +1);
833 }
834 
edit_iprev(IFILE h,int n)835 static int edit_iprev(IFILE h, int n)
836 {
837 	return (edit_istep(h, n, -1));
838 }
839 
edit_prev(int n)840 public int edit_prev(int n)
841 {
842 	return edit_istep(curr_ifile, n, -1);
843 }
844 
845 /*
846  * Edit a specific file in the command line (ifile) list.
847  */
edit_index(int n)848 public int edit_index(int n)
849 {
850 	IFILE h;
851 
852 	h = NULL_IFILE;
853 	do
854 	{
855 		if ((h = next_ifile(h)) == NULL_IFILE)
856 		{
857 			/*
858 			 * Reached end of the list without finding it.
859 			 */
860 			return (1);
861 		}
862 	} while (get_index(h) != n);
863 
864 	return (edit_ifile(h));
865 }
866 
save_curr_ifile(void)867 public IFILE save_curr_ifile(void)
868 {
869 	if (curr_ifile != NULL_IFILE)
870 		hold_ifile(curr_ifile, 1);
871 	return (curr_ifile);
872 }
873 
unsave_ifile(IFILE save_ifile)874 public void unsave_ifile(IFILE save_ifile)
875 {
876 	if (save_ifile != NULL_IFILE)
877 		hold_ifile(save_ifile, -1);
878 }
879 
880 /*
881  * Reedit the ifile which was previously open.
882  */
reedit_ifile(IFILE save_ifile)883 public void reedit_ifile(IFILE save_ifile)
884 {
885 	IFILE next;
886 	IFILE prev;
887 
888 	/*
889 	 * Try to reopen the ifile.
890 	 * Note that opening it may fail (maybe the file was removed),
891 	 * in which case the ifile will be deleted from the list.
892 	 * So save the next and prev ifiles first.
893 	 */
894 	unsave_ifile(save_ifile);
895 	next = next_ifile(save_ifile);
896 	prev = prev_ifile(save_ifile);
897 	if (edit_ifile(save_ifile) == 0)
898 		return;
899 	/*
900 	 * If can't reopen it, open the next input file in the list.
901 	 */
902 	if (next != NULL_IFILE && edit_inext(next, 0) == 0)
903 		return;
904 	/*
905 	 * If can't open THAT one, open the previous input file in the list.
906 	 */
907 	if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
908 		return;
909 	/*
910 	 * If can't even open that, we're stuck.  Just quit.
911 	 */
912 	quit(QUIT_ERROR);
913 }
914 
reopen_curr_ifile(void)915 public void reopen_curr_ifile(void)
916 {
917 	IFILE save_ifile = save_curr_ifile();
918 	close_file();
919 	reedit_ifile(save_ifile);
920 }
921 
922 /*
923  * Edit standard input.
924  */
edit_stdin(void)925 public int edit_stdin(void)
926 {
927 	if (isatty(fd0))
928 	{
929 		error(LM(Missing_filename), NULL_PARG);
930 		quit(QUIT_ERROR);
931 	}
932 	return (edit("-"));
933 }
934 
935 /*
936  * Copy a file directly to standard output.
937  * Used if standard output is not a tty.
938  */
cat_file(void)939 public void cat_file(void)
940 {
941 	int c;
942 
943 	while ((c = ch_forw_get()) != EOI)
944 		putchr(c);
945 	flush();
946 }
947 
948 #if LOGFILE
949 
950 /*
951  * If the user asked for a log file and our input file
952  * is standard input, create the log file.
953  * We take care not to blindly overwrite an existing file.
954  */
use_logfile(constant char * filename)955 public void use_logfile(constant char *filename)
956 {
957 	int exists;
958 	int answer;
959 	PARG parg;
960 
961 	if (ch_getflags() & CH_CANSEEK)
962 		/*
963 		 * Can't currently use a log file on a file that can seek.
964 		 */
965 		return;
966 
967 	/*
968 	 * {{ We could use access() here. }}
969 	 */
970 	exists = open(filename, OPEN_READ);
971 	if (exists >= 0)
972 		close(exists);
973 	exists = (exists >= 0);
974 
975 	/*
976 	 * Decide whether to overwrite the log file or append to it.
977 	 * If it doesn't exist we "overwrite" it.
978 	 */
979 	if (!exists || force_logfile)
980 	{
981 		/*
982 		 * Overwrite (or create) the log file.
983 		 */
984 		answer = 'O';
985 	} else
986 	{
987 		/*
988 		 * Ask user what to do.
989 		 */
990 		parg.p_string = filename;
991 		answer = query(LM(X_exists), &parg);
992 	}
993 
994 loop:
995 	switch (answer)
996 	{
997 	case 'O': case 'o':
998 		/*
999 		 * Overwrite: create the file.
1000 		 */
1001 		logfile = creat(filename, CREAT_RW);
1002 		break;
1003 	case 'A': case 'a':
1004 		/*
1005 		 * Append: open the file and seek to the end.
1006 		 */
1007 		logfile = open(filename, OPEN_APPEND);
1008 		if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK)
1009 		{
1010 			close(logfile);
1011 			logfile = -1;
1012 		}
1013 		break;
1014 	case 'D': case 'd':
1015 		/*
1016 		 * Don't do anything.
1017 		 */
1018 		return;
1019 	default:
1020 		/*
1021 		 * Eh?
1022 		 */
1023 
1024 		answer = query(LM(Overwrite), NULL_PARG);
1025 		goto loop;
1026 	}
1027 
1028 	if (logfile < 0)
1029 	{
1030 		/*
1031 		 * Error in opening logfile.
1032 		 */
1033 		parg.p_string = filename;
1034 		error(LM(Cannot_write_to_X), &parg);
1035 		return;
1036 	}
1037 	SET_BINARY(logfile);
1038 }
1039 
1040 #endif
1041