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