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