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