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