Home | History | Annotate | Line # | Download | only in dist
      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  */
     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 
    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 
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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  */
    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 
    804 static int edit_inext(IFILE h, int n)
    805 {
    806 	return (edit_istep(h, n, +1));
    807 }
    808 
    809 public int edit_next(int n)
    810 {
    811 	return edit_istep(curr_ifile, n, +1);
    812 }
    813 
    814 static int edit_iprev(IFILE h, int n)
    815 {
    816 	return (edit_istep(h, n, -1));
    817 }
    818 
    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  */
    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 
    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 
    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  */
    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 
    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  */
    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  */
    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  */
    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