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