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
21public int fd0 = 0;
22
23extern int new_file;
24extern int cbufs;
25extern char *every_first_cmd;
26extern int force_open;
27extern int is_tty;
28extern int sigs;
29extern int hshift;
30extern int want_filesize;
31extern int consecutive_nulls;
32extern int modelines;
33extern int show_preproc_error;
34extern IFILE curr_ifile;
35extern IFILE old_ifile;
36extern struct scrpos initial_scrpos;
37extern void *ml_examine;
38#if SPACES_IN_FILENAMES
39extern char openquote;
40extern char closequote;
41#endif
42
43#if LOGFILE
44extern int logfile;
45extern int force_logfile;
46extern char *namelogfile;
47#endif
48
49#if HAVE_STAT_INO
50public dev_t curr_dev;
51public 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 */
61public 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
102public 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
123public 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 */
149static 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 */
173static 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 */
192static 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 */
211static 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 */
244static 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 */
264static 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 */
323public 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 */
337public 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 */
348static 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 */
392public 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 */
402static 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 */
431public 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 */
682public 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 */
741public 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 */
752public 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 */
762static 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
800static int edit_inext(IFILE h, int n)
801{
802	return (edit_istep(h, n, +1));
803}
804
805public int edit_next(int n)
806{
807	return edit_istep(curr_ifile, n, +1);
808}
809
810static int edit_iprev(IFILE h, int n)
811{
812	return (edit_istep(h, n, -1));
813}
814
815public 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 */
823public 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
842public IFILE save_curr_ifile(void)
843{
844	if (curr_ifile != NULL_IFILE)
845		hold_ifile(curr_ifile, 1);
846	return (curr_ifile);
847}
848
849public 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 */
858public 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
890public 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 */
900public 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 */
914public 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 */
932public 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
971loop:
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