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