xref: /freebsd/contrib/less/edit.c (revision d5cb458b4b58b0f0b3c058a32439f232fd5455ca)
1 /*
2  * Copyright (C) 1984-2025  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 int is_tty;
29 extern int sigs;
30 extern int hshift;
31 extern int want_filesize;
32 extern int consecutive_nulls;
33 extern int modelines;
34 extern int show_preproc_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 = 0;
81 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
82 		           strncmp(s, esc, esclen) == 0)
83 		{
84 			meta_quoted = 1;
85 			s += esclen - 1;
86 		} else if (delim_quoted)
87 		{
88 			if (*s == closequote)
89 				delim_quoted = 0;
90 		} else /* (!delim_quoted) */
91 		{
92 			if (*s == openquote)
93 				delim_quoted = 1;
94 			else if (*s == ' ')
95 				*s = '\0';
96 		}
97 #else
98 		if (*s == ' ')
99 			*s = '\0';
100 #endif
101 	}
102 }
103 
forw_textlist(struct textlist * tlist,constant char * prev)104 public constant char * forw_textlist(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(struct textlist * tlist,constant char * prev)123 public constant char * back_textlist(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 #if HAVE_STRSTR
214 	static constant char *pgms[] = { "less:", "vim:", "vi:", "ex:", NULL };
215 	constant char **pgm;
216 	for (pgm = pgms;  *pgm != NULL;  ++pgm)
217 	{
218 		constant char *pline = line;
219 		for (;;)
220 		{
221 			constant char *str;
222 			pline = strstr(pline, *pgm);
223 			if (pline == NULL) /* pgm is not in this line */
224 				break;
225 			str = skipspc(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  */
check_modelines(void)244 static void check_modelines(void)
245 {
246 	POSITION pos = ch_zero();
247 	int i;
248 	for (i = 0;  i < modelines;  i++)
249 	{
250 		constant char *line;
251 		size_t 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  */
close_pipe(FILE * pipefd)264 static void close_pipe(FILE *pipefd)
265 {
266 	int status;
267 	char *p;
268 	PARG parg;
269 
270 	if (pipefd == NULL)
271 		return;
272 #if OS2
273 	/*
274 	 * The pclose function of OS/2 emx sometimes fails.
275 	 * Send SIGINT to the piped process before closing it.
276 	 */
277 	kill(pipefd->_pid, SIGINT);
278 #endif
279 	status = pclose(pipefd);
280 	if (status == -1)
281 	{
282 		/* An internal error in 'less', not a preprocessor error.  */
283 		p = errno_message("pclose");
284 		parg.p_string = p;
285 		error("%s", &parg);
286 		free(p);
287 		return;
288 	}
289 	if (!show_preproc_error)
290 		return;
291 #if defined WIFEXITED && defined WEXITSTATUS
292 	if (WIFEXITED(status))
293 	{
294 		int s = WEXITSTATUS(status);
295 		if (s != 0)
296 		{
297 			parg.p_int = s;
298 			error("Input preprocessor failed (status %d)", &parg);
299 		}
300 		return;
301 	}
302 #endif
303 #if defined WIFSIGNALED && defined WTERMSIG
304 	if (WIFSIGNALED(status))
305 	{
306 		int sig = WTERMSIG(status);
307 		if (
308 #ifdef SIGPIPE
309 			sig != SIGPIPE ||
310 #endif
311 			ch_length() != NULL_POSITION)
312 		{
313 			parg.p_string = signal_message(sig);
314 			error("Input preprocessor terminated: %s", &parg);
315 		}
316 		return;
317 	}
318 #endif
319 	if (status != 0)
320 	{
321 		parg.p_int = status;
322 		error("Input preprocessor exited with status %x", &parg);
323 	}
324 }
325 
326 /*
327  * Drain and close an input pipe if needed.
328  */
close_altpipe(IFILE ifile)329 public void close_altpipe(IFILE ifile)
330 {
331 	FILE *altpipe = get_altpipe(ifile);
332 	if (altpipe != NULL && !(ch_getflags() & CH_KEEPOPEN))
333 	{
334 		close_pipe(altpipe);
335 		set_altpipe(ifile, NULL);
336 	}
337 }
338 
339 /*
340  * Check for error status from the current altpipe.
341  * May or may not close the pipe.
342  */
check_altpipe_error(void)343 public void check_altpipe_error(void)
344 {
345 	if (!show_preproc_error)
346 		return;
347 	if (curr_ifile != NULL_IFILE && get_altfilename(curr_ifile) != NULL)
348 		close_altpipe(curr_ifile);
349 }
350 
351 /*
352  * Close the current input file.
353  */
close_file(void)354 static void close_file(void)
355 {
356 	struct scrpos scrpos;
357 	constant char *altfilename;
358 
359 	if (curr_ifile == NULL_IFILE)
360 		return;
361 
362 	/*
363 	 * Save the current position so that we can return to
364 	 * the same position if we edit this file again.
365 	 */
366 	get_scrpos(&scrpos, TOP);
367 	if (scrpos.pos != NULL_POSITION)
368 	{
369 		store_pos(curr_ifile, &scrpos);
370 		lastmark();
371 	}
372 	/*
373 	 * Close the file descriptor, unless it is a pipe.
374 	 */
375 	ch_close();
376 	/*
377 	 * If we opened a file using an alternate name,
378 	 * do special stuff to close it.
379 	 */
380 	altfilename = get_altfilename(curr_ifile);
381 	if (altfilename != NULL)
382 	{
383 		close_altpipe(curr_ifile);
384 		close_altfile(altfilename, get_filename(curr_ifile));
385 		set_altfilename(curr_ifile, NULL);
386 	}
387 	curr_ifile = NULL_IFILE;
388 #if HAVE_STAT_INO
389 	curr_ino = curr_dev = 0;
390 #endif
391 }
392 
393 /*
394  * Edit a new file (given its name).
395  * Filename == "-" means standard input.
396  * Filename == NULL means just close the current file.
397  */
edit(constant char * filename)398 public int edit(constant char *filename)
399 {
400 	if (filename == NULL)
401 		return (edit_ifile(NULL_IFILE));
402 	return (edit_ifile(get_ifile(filename, curr_ifile)));
403 }
404 
405 /*
406  * Clean up what edit_ifile did before error return.
407  */
edit_error(constant char * filename,constant char * alt_filename,void * altpipe,IFILE ifile)408 static int edit_error(constant char *filename, constant char *alt_filename, void *altpipe, IFILE ifile)
409 {
410 	if (alt_filename != NULL)
411 	{
412 		close_pipe(altpipe);
413 		close_altfile(alt_filename, filename);
414 		free((char*)alt_filename); /* FIXME: WTF? */
415 	}
416 	del_ifile(ifile);
417 	/*
418 	 * Re-open the current file.
419 	 */
420 	if (curr_ifile == ifile)
421 	{
422 		/*
423 		 * Whoops.  The "current" ifile is the one we just deleted.
424 		 * Just give up.
425 		 */
426 		quit(QUIT_ERROR);
427 	}
428 	return (1);
429 }
430 
431 /*
432  * Edit a new file (given its IFILE).
433  * ifile == NULL means just close the current file.
434  */
edit_ifile(IFILE ifile)435 public int edit_ifile(IFILE ifile)
436 {
437 	int f;
438 	int answer;
439 	int chflags;
440 	constant char *filename;
441 	constant char *open_filename;
442 	char *alt_filename;
443 	void *altpipe;
444 	IFILE was_curr_ifile;
445 	char *p;
446 	PARG parg;
447 	ssize_t nread = 0;
448 
449 	if (ifile == curr_ifile)
450 	{
451 		/*
452 		 * Already have the correct file open.
453 		 */
454 		return (0);
455 	}
456 	new_file = TRUE;
457 
458 	if (ifile != NULL_IFILE)
459 	{
460 		/*
461 		 * See if LESSOPEN specifies an "alternate" file to open.
462 		 */
463 		filename = get_filename(ifile);
464 		altpipe = get_altpipe(ifile);
465 		if (altpipe != NULL)
466 		{
467 			/*
468 			 * File is already open.
469 			 * chflags and f are not used by ch_init if ifile has
470 			 * filestate which should be the case if we're here.
471 			 * Set them here to avoid uninitialized variable warnings.
472 			 */
473 			chflags = 0;
474 			f = -1;
475 			alt_filename = get_altfilename(ifile);
476 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
477 		} else
478 		{
479 			if (strcmp(filename, FAKE_HELPFILE) == 0 ||
480 				strcmp(filename, FAKE_EMPTYFILE) == 0)
481 				alt_filename = NULL;
482 			else
483 				alt_filename = open_altfile(filename, &f, &altpipe);
484 
485 			open_filename = (alt_filename != NULL) ? alt_filename : filename;
486 
487 			chflags = 0;
488 			if (altpipe != NULL)
489 			{
490 				/*
491 				 * The alternate "file" is actually a pipe.
492 				 * f has already been set to the file descriptor of the pipe
493 				 * in the call to open_altfile above.
494 				 * Keep the file descriptor open because it was opened
495 				 * via popen(), and pclose() wants to close it.
496 				 */
497 				chflags |= CH_POPENED;
498 				if (strcmp(filename, "-") == 0)
499 					chflags |= CH_KEEPOPEN;
500 			} else if (strcmp(filename, "-") == 0)
501 			{
502 				/*
503 				 * Use standard input.
504 				 * Keep the file descriptor open because we can't reopen it.
505 				 */
506 				f = fd0;
507 				chflags |= CH_KEEPOPEN;
508 				/*
509 				 * Must switch stdin to BINARY mode.
510 				 */
511 				SET_BINARY(f);
512 #if MSDOS_COMPILER==DJGPPC
513 				/*
514 				 * Setting stdin to binary by default causes
515 				 * Ctrl-C to not raise SIGINT.  We must undo
516 				 * that side-effect.
517 				 */
518 				__djgpp_set_ctrl_c(1);
519 #endif
520 			} else if (strcmp(open_filename, FAKE_EMPTYFILE) == 0)
521 			{
522 				f = -1;
523 				chflags |= CH_NODATA;
524 			} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
525 			{
526 				f = -1;
527 				chflags |= CH_HELPFILE;
528 			} else if ((p = bad_file(open_filename)) != NULL)
529 			{
530 				/*
531 				 * It looks like a bad file.  Don't try to open it.
532 				 */
533 				parg.p_string = p;
534 				error("%s", &parg);
535 				free(p);
536 				return edit_error(filename, alt_filename, altpipe, ifile);
537 			} else if ((f = iopen(open_filename, OPEN_READ)) < 0)
538 			{
539 				/*
540 				 * Got an error trying to open it.
541 				 */
542 				char *p = errno_message(filename);
543 				parg.p_string = p;
544 				error("%s", &parg);
545 				free(p);
546 				return edit_error(filename, alt_filename, altpipe, ifile);
547 			} else
548 			{
549 				chflags |= CH_CANSEEK;
550 				if (bin_file(f, &nread) && !force_open && !opened(ifile))
551 				{
552 					/*
553 					 * Looks like a binary file.
554 					 * Ask user if we should proceed.
555 					 */
556 					parg.p_string = filename;
557 					answer = query("\"%s\" may be a binary file.  See it anyway? ", &parg);
558 					if (answer != 'y' && answer != 'Y')
559 					{
560 						close(f);
561 						return edit_error(filename, alt_filename, altpipe, ifile);
562 					}
563 				}
564 			}
565 		}
566 		if (!force_open && f >= 0 && isatty(f))
567 		{
568 			PARG parg;
569 			parg.p_string = filename;
570 			error("%s is a terminal (use -f to open it)", &parg);
571 			return edit_error(filename, alt_filename, altpipe, ifile);
572 		}
573 	}
574 
575 #if LOGFILE
576 	end_logfile();
577 #endif
578 	was_curr_ifile = save_curr_ifile();
579 	if (curr_ifile != NULL_IFILE)
580 	{
581 		int was_helpfile = (ch_getflags() & CH_HELPFILE);
582 		close_file();
583 		if (was_helpfile && held_ifile(was_curr_ifile) <= 1)
584 		{
585 			/*
586 			 * Don't keep the help file in the ifile list.
587 			 */
588 			del_ifile(was_curr_ifile);
589 			was_curr_ifile = NULL_IFILE;
590 		}
591 	}
592 	unsave_ifile(was_curr_ifile);
593 
594 	if (ifile == NULL_IFILE)
595 	{
596 		/*
597 		 * No new file to open.
598 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
599 		 *  you're supposed to have saved curr_ifile yourself,
600 		 *  and you'll restore it if necessary.)
601 		 */
602 		return (0);
603 	}
604 
605 	/*
606 	 * Set up the new ifile.
607 	 * Get the saved position for the file.
608 	 */
609 	curr_ifile = ifile;
610 	soft_eof = NULL_POSITION;
611 	set_altfilename(curr_ifile, alt_filename);
612 	set_altpipe(curr_ifile, altpipe);
613 	set_open(curr_ifile); /* File has been opened */
614 	get_pos(curr_ifile, &initial_scrpos);
615 	ch_init(f, chflags, nread);
616 	consecutive_nulls = 0;
617 	check_modelines();
618 
619 	if (!(chflags & CH_HELPFILE))
620 	{
621 		if (was_curr_ifile != NULL_IFILE)
622 			old_ifile = was_curr_ifile;
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_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 		undo_osc8();
664 		hshift = 0;
665 		if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
666 		{
667 			char *qfilename = shell_quote(filename);
668 			cmd_addhist(ml_examine, qfilename, 1);
669 			free(qfilename);
670 		}
671 		if (want_filesize)
672 			scan_eof();
673 		set_header(ch_zero());
674 	}
675 	return (0);
676 }
677 
678 /*
679  * Edit a space-separated list of files.
680  * For each filename in the list, enter it into the ifile list.
681  * Then edit the first one.
682  */
edit_list(char * filelist)683 public int edit_list(char *filelist)
684 {
685 	IFILE save_ifile;
686 	constant char *good_filename;
687 	constant char *filename;
688 	char *gfilelist;
689 	constant char *gfilename;
690 	char *qfilename;
691 	struct textlist tl_files;
692 	struct textlist tl_gfiles;
693 
694 	save_ifile = save_curr_ifile();
695 	good_filename = NULL;
696 
697 	/*
698 	 * Run thru each filename in the list.
699 	 * Try to glob the filename.
700 	 * If it doesn't expand, just try to open the filename.
701 	 * If it does expand, try to open each name in that list.
702 	 */
703 	init_textlist(&tl_files, filelist);
704 	filename = NULL;
705 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
706 	{
707 		gfilelist = lglob(filename);
708 		init_textlist(&tl_gfiles, gfilelist);
709 		gfilename = NULL;
710 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
711 		{
712 			qfilename = shell_unquote(gfilename);
713 			if (edit(qfilename) == 0 && good_filename == NULL)
714 				good_filename = get_filename(curr_ifile);
715 			free(qfilename);
716 		}
717 		free(gfilelist);
718 	}
719 	/*
720 	 * Edit the first valid filename in the list.
721 	 */
722 	if (good_filename == NULL)
723 	{
724 		unsave_ifile(save_ifile);
725 		return (1);
726 	}
727 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
728 	{
729 		/*
730 		 * Trying to edit the current file; don't reopen it.
731 		 */
732 		unsave_ifile(save_ifile);
733 		return (0);
734 	}
735 	reedit_ifile(save_ifile);
736 	return (edit(good_filename));
737 }
738 
739 /*
740  * Edit the first file in the command line (ifile) list.
741  */
edit_first(void)742 public int edit_first(void)
743 {
744 	if (nifile() == 0)
745 		return (edit_stdin());
746 	curr_ifile = NULL_IFILE;
747 	return (edit_next(1));
748 }
749 
750 /*
751  * Edit the last file in the command line (ifile) list.
752  */
edit_last(void)753 public int edit_last(void)
754 {
755 	curr_ifile = NULL_IFILE;
756 	return (edit_prev(1));
757 }
758 
759 
760 /*
761  * Edit the n-th next or previous file in the command line (ifile) list.
762  */
edit_istep(IFILE h,int n,int dir)763 static int edit_istep(IFILE h, int n, int dir)
764 {
765 	IFILE next;
766 
767 	/*
768 	 * Skip n filenames, then try to edit each filename.
769 	 */
770 	for (;;)
771 	{
772 		next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
773 		if (--n < 0)
774 		{
775 			if (edit_ifile(h) == 0)
776 				break;
777 		}
778 		if (next == NULL_IFILE)
779 		{
780 			/*
781 			 * Reached end of the ifile list.
782 			 */
783 			return (1);
784 		}
785 		if (ABORT_SIGS())
786 		{
787 			/*
788 			 * Interrupt breaks out, if we're in a long
789 			 * list of files that can't be opened.
790 			 */
791 			return (1);
792 		}
793 		h = next;
794 	}
795 	/*
796 	 * Found a file that we can edit.
797 	 */
798 	return (0);
799 }
800 
edit_inext(IFILE h,int n)801 static int edit_inext(IFILE h, int n)
802 {
803 	return (edit_istep(h, n, +1));
804 }
805 
edit_next(int n)806 public int edit_next(int n)
807 {
808 	return edit_istep(curr_ifile, n, +1);
809 }
810 
edit_iprev(IFILE h,int n)811 static int edit_iprev(IFILE h, int n)
812 {
813 	return (edit_istep(h, n, -1));
814 }
815 
edit_prev(int n)816 public int edit_prev(int n)
817 {
818 	return edit_istep(curr_ifile, n, -1);
819 }
820 
821 /*
822  * Edit a specific file in the command line (ifile) list.
823  */
edit_index(int n)824 public int edit_index(int n)
825 {
826 	IFILE h;
827 
828 	h = NULL_IFILE;
829 	do
830 	{
831 		if ((h = next_ifile(h)) == NULL_IFILE)
832 		{
833 			/*
834 			 * Reached end of the list without finding it.
835 			 */
836 			return (1);
837 		}
838 	} while (get_index(h) != n);
839 
840 	return (edit_ifile(h));
841 }
842 
save_curr_ifile(void)843 public IFILE save_curr_ifile(void)
844 {
845 	if (curr_ifile != NULL_IFILE)
846 		hold_ifile(curr_ifile, 1);
847 	return (curr_ifile);
848 }
849 
unsave_ifile(IFILE save_ifile)850 public void unsave_ifile(IFILE save_ifile)
851 {
852 	if (save_ifile != NULL_IFILE)
853 		hold_ifile(save_ifile, -1);
854 }
855 
856 /*
857  * Reedit the ifile which was previously open.
858  */
reedit_ifile(IFILE save_ifile)859 public void reedit_ifile(IFILE save_ifile)
860 {
861 	IFILE next;
862 	IFILE prev;
863 
864 	/*
865 	 * Try to reopen the ifile.
866 	 * Note that opening it may fail (maybe the file was removed),
867 	 * in which case the ifile will be deleted from the list.
868 	 * So save the next and prev ifiles first.
869 	 */
870 	unsave_ifile(save_ifile);
871 	next = next_ifile(save_ifile);
872 	prev = prev_ifile(save_ifile);
873 	if (edit_ifile(save_ifile) == 0)
874 		return;
875 	/*
876 	 * If can't reopen it, open the next input file in the list.
877 	 */
878 	if (next != NULL_IFILE && edit_inext(next, 0) == 0)
879 		return;
880 	/*
881 	 * If can't open THAT one, open the previous input file in the list.
882 	 */
883 	if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
884 		return;
885 	/*
886 	 * If can't even open that, we're stuck.  Just quit.
887 	 */
888 	quit(QUIT_ERROR);
889 }
890 
reopen_curr_ifile(void)891 public void reopen_curr_ifile(void)
892 {
893 	IFILE save_ifile = save_curr_ifile();
894 	close_file();
895 	reedit_ifile(save_ifile);
896 }
897 
898 /*
899  * Edit standard input.
900  */
edit_stdin(void)901 public int edit_stdin(void)
902 {
903 	if (isatty(fd0))
904 	{
905 		error("Missing filename (\"less --help\" for help)", NULL_PARG);
906 		quit(QUIT_OK);
907 	}
908 	return (edit("-"));
909 }
910 
911 /*
912  * Copy a file directly to standard output.
913  * Used if standard output is not a tty.
914  */
cat_file(void)915 public void cat_file(void)
916 {
917 	int c;
918 
919 	while ((c = ch_forw_get()) != EOI)
920 		putchr(c);
921 	flush();
922 }
923 
924 #if LOGFILE
925 
926 #define OVERWRITE_OPTIONS "Overwrite, Append, Don't log, or Quit?"
927 
928 /*
929  * If the user asked for a log file and our input file
930  * is standard input, create the log file.
931  * We take care not to blindly overwrite an existing file.
932  */
use_logfile(constant char * filename)933 public void use_logfile(constant char *filename)
934 {
935 	int exists;
936 	int answer;
937 	PARG parg;
938 
939 	if (ch_getflags() & CH_CANSEEK)
940 		/*
941 		 * Can't currently use a log file on a file that can seek.
942 		 */
943 		return;
944 
945 	/*
946 	 * {{ We could use access() here. }}
947 	 */
948 	exists = open(filename, OPEN_READ);
949 	if (exists >= 0)
950 		close(exists);
951 	exists = (exists >= 0);
952 
953 	/*
954 	 * Decide whether to overwrite the log file or append to it.
955 	 * If it doesn't exist we "overwrite" it.
956 	 */
957 	if (!exists || force_logfile)
958 	{
959 		/*
960 		 * Overwrite (or create) the log file.
961 		 */
962 		answer = 'O';
963 	} else
964 	{
965 		/*
966 		 * Ask user what to do.
967 		 */
968 		parg.p_string = filename;
969 		answer = query("Warning: \"%s\" exists; "OVERWRITE_OPTIONS" ", &parg);
970 	}
971 
972 loop:
973 	switch (answer)
974 	{
975 	case 'O': case 'o':
976 		/*
977 		 * Overwrite: create the file.
978 		 */
979 		logfile = creat(filename, CREAT_RW);
980 		break;
981 	case 'A': case 'a':
982 		/*
983 		 * Append: open the file and seek to the end.
984 		 */
985 		logfile = open(filename, OPEN_APPEND);
986 		if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK)
987 		{
988 			close(logfile);
989 			logfile = -1;
990 		}
991 		break;
992 	case 'D': case 'd':
993 		/*
994 		 * Don't do anything.
995 		 */
996 		return;
997 	default:
998 		/*
999 		 * Eh?
1000 		 */
1001 
1002 		answer = query(OVERWRITE_OPTIONS" (Type \"O\", \"A\", \"D\" or \"Q\") ", NULL_PARG);
1003 		goto loop;
1004 	}
1005 
1006 	if (logfile < 0)
1007 	{
1008 		/*
1009 		 * Error in opening logfile.
1010 		 */
1011 		parg.p_string = filename;
1012 		error("Cannot write to \"%s\"", &parg);
1013 		return;
1014 	}
1015 	SET_BINARY(logfile);
1016 }
1017 
1018 #endif
1019