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