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