xref: /netbsd-src/external/bsd/less/dist/edit.c (revision 824a88bbba654121cac7ca08a3c38a4a435dcdb3)
1 /*	$NetBSD: edit.c,v 1.3 2011/07/03 20:14:12 tron Exp $	*/
2 
3 /*
4  * Copyright (C) 1984-2011  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 about less, or for information on how to
10  * contact the author, see the README file.
11  */
12 
13 
14 #include "less.h"
15 #if HAVE_STAT
16 #include <sys/stat.h>
17 #endif
18 
19 public int fd0 = 0;
20 
21 extern int new_file;
22 extern int errmsgs;
23 extern int cbufs;
24 extern char *every_first_cmd;
25 extern int any_display;
26 extern int force_open;
27 extern int is_tty;
28 extern int sigs;
29 extern IFILE curr_ifile;
30 extern IFILE old_ifile;
31 extern struct scrpos initial_scrpos;
32 extern void *constant ml_examine;
33 #if SPACES_IN_FILENAMES
34 extern char openquote;
35 extern char closequote;
36 #endif
37 
38 #if LOGFILE
39 extern int logfile;
40 extern int force_logfile;
41 extern char *namelogfile;
42 #endif
43 
44 #if HAVE_STAT_INO
45 public dev_t curr_dev;
46 public ino_t curr_ino;
47 #endif
48 
49 char *curr_altfilename = NULL;
50 static void *curr_altpipe;
51 
52 
53 static void close_file __P((void));
54 static int edit_istep __P((IFILE, int, int));
55 static int edit_inext __P((IFILE, int));
56 static int edit_iprev __P((IFILE, int));
57 
58 /*
59  * Textlist functions deal with a list of words separated by spaces.
60  * init_textlist sets up a textlist structure.
61  * forw_textlist uses that structure to iterate thru the list of
62  * words, returning each one as a standard null-terminated string.
63  * back_textlist does the same, but runs thru the list backwards.
64  */
65 	public void
66 init_textlist(tlist, str)
67 	struct textlist *tlist;
68 	char *str;
69 {
70 	char *s;
71 #if SPACES_IN_FILENAMES
72 	int meta_quoted = 0;
73 	int delim_quoted = 0;
74 	char *esc = get_meta_escape();
75 	int esclen = strlen(esc);
76 #endif
77 
78 	tlist->string = skipsp(str);
79 	tlist->endstring = tlist->string + strlen(tlist->string);
80 	for (s = str;  s < tlist->endstring;  s++)
81 	{
82 #if SPACES_IN_FILENAMES
83 		if (meta_quoted)
84 		{
85 			meta_quoted = 0;
86 		} else if (esclen > 0 && s + esclen < tlist->endstring &&
87 		           strncmp(s, esc, esclen) == 0)
88 		{
89 			meta_quoted = 1;
90 			s += esclen - 1;
91 		} else if (delim_quoted)
92 		{
93 			if (*s == closequote)
94 				delim_quoted = 0;
95 		} else /* (!delim_quoted) */
96 		{
97 			if (*s == openquote)
98 				delim_quoted = 1;
99 			else if (*s == ' ')
100 				*s = '\0';
101 		}
102 #else
103 		if (*s == ' ')
104 			*s = '\0';
105 #endif
106 	}
107 }
108 
109 	public char *
110 forw_textlist(tlist, prev)
111 	struct textlist *tlist;
112 	char *prev;
113 {
114 	char *s;
115 
116 	/*
117 	 * prev == NULL means return the first word in the list.
118 	 * Otherwise, return the word after "prev".
119 	 */
120 	if (prev == NULL)
121 		s = tlist->string;
122 	else
123 		s = prev + strlen(prev);
124 	if (s >= tlist->endstring)
125 		return (NULL);
126 	while (*s == '\0')
127 		s++;
128 	if (s >= tlist->endstring)
129 		return (NULL);
130 	return (s);
131 }
132 
133 	public char *
134 back_textlist(tlist, prev)
135 	struct textlist *tlist;
136 	char *prev;
137 {
138 	char *s;
139 
140 	/*
141 	 * prev == NULL means return the last word in the list.
142 	 * Otherwise, return the word before "prev".
143 	 */
144 	if (prev == NULL)
145 		s = tlist->endstring;
146 	else if (prev <= tlist->string)
147 		return (NULL);
148 	else
149 		s = prev - 1;
150 	while (*s == '\0')
151 		s--;
152 	if (s <= tlist->string)
153 		return (NULL);
154 	while (s[-1] != '\0' && s > tlist->string)
155 		s--;
156 	return (s);
157 }
158 
159 /*
160  * Close the current input file.
161  */
162 	static void
163 close_file()
164 {
165 	struct scrpos scrpos;
166 
167 	if (curr_ifile == NULL_IFILE)
168 		return;
169 
170 	/*
171 	 * Save the current position so that we can return to
172 	 * the same position if we edit this file again.
173 	 */
174 	get_scrpos(&scrpos);
175 	if (scrpos.pos != NULL_POSITION)
176 	{
177 		store_pos(curr_ifile, &scrpos);
178 		lastmark();
179 	}
180 	/*
181 	 * Close the file descriptor, unless it is a pipe.
182 	 */
183 	ch_close();
184 	/*
185 	 * If we opened a file using an alternate name,
186 	 * do special stuff to close it.
187 	 */
188 	if (curr_altfilename != NULL)
189 	{
190 		close_altfile(curr_altfilename, get_filename(curr_ifile),
191 				curr_altpipe);
192 		free(curr_altfilename);
193 		curr_altfilename = NULL;
194 	}
195 	curr_ifile = NULL_IFILE;
196 #if HAVE_STAT_INO
197 	curr_ino = curr_dev = 0;
198 #endif
199 }
200 
201 /*
202  * Edit a new file (given its name).
203  * Filename == "-" means standard input.
204  * Filename == NULL means just close the current file.
205  */
206 	public int
207 edit(filename)
208 	char *filename;
209 {
210 	if (filename == NULL)
211 		return (edit_ifile(NULL_IFILE));
212 	return (edit_ifile(get_ifile(filename, curr_ifile)));
213 }
214 
215 /*
216  * Edit a new file (given its IFILE).
217  * ifile == NULL means just close the current file.
218  */
219 	public int
220 edit_ifile(ifile)
221 	IFILE ifile;
222 {
223 	int f;
224 	int answer;
225 	int no_display;
226 	int chflags;
227 	char *filename;
228 	char *open_filename;
229 	char *qopen_filename;
230 	char *alt_filename;
231 	void *alt_pipe;
232 	IFILE was_curr_ifile;
233 	PARG parg;
234 
235 	if (ifile == curr_ifile)
236 	{
237 		/*
238 		 * Already have the correct file open.
239 		 */
240 		return (0);
241 	}
242 
243 	/*
244 	 * We must close the currently open file now.
245 	 * This is necessary to make the open_altfile/close_altfile pairs
246 	 * nest properly (or rather to avoid nesting at all).
247 	 * {{ Some stupid implementations of popen() mess up if you do:
248 	 *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
249 	 */
250 #if LOGFILE
251 	end_logfile();
252 #endif
253 	was_curr_ifile = save_curr_ifile();
254 	if (curr_ifile != NULL_IFILE)
255 	{
256 		chflags = ch_getflags();
257 		close_file();
258 		if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1)
259 		{
260 			/*
261 			 * Don't keep the help file in the ifile list.
262 			 */
263 			del_ifile(was_curr_ifile);
264 			was_curr_ifile = old_ifile;
265 		}
266 	}
267 
268 	if (ifile == NULL_IFILE)
269 	{
270 		/*
271 		 * No new file to open.
272 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
273 		 *  you're supposed to have saved curr_ifile yourself,
274 		 *  and you'll restore it if necessary.)
275 		 */
276 		unsave_ifile(was_curr_ifile);
277 		return (0);
278 	}
279 
280 	filename = save(get_filename(ifile));
281 	/*
282 	 * See if LESSOPEN specifies an "alternate" file to open.
283 	 */
284 	alt_pipe = NULL;
285 	alt_filename = open_altfile(filename, &f, &alt_pipe);
286 	open_filename = (alt_filename != NULL) ? alt_filename : filename;
287 	qopen_filename = shell_unquote(open_filename);
288 
289 	chflags = 0;
290 	if (alt_pipe != NULL)
291 	{
292 		/*
293 		 * The alternate "file" is actually a pipe.
294 		 * f has already been set to the file descriptor of the pipe
295 		 * in the call to open_altfile above.
296 		 * Keep the file descriptor open because it was opened
297 		 * via popen(), and pclose() wants to close it.
298 		 */
299 		chflags |= CH_POPENED;
300 	} else if (strcmp(open_filename, "-") == 0)
301 	{
302 		/*
303 		 * Use standard input.
304 		 * Keep the file descriptor open because we can't reopen it.
305 		 */
306 		f = fd0;
307 		chflags |= CH_KEEPOPEN;
308 		/*
309 		 * Must switch stdin to BINARY mode.
310 		 */
311 		SET_BINARY(f);
312 #if MSDOS_COMPILER==DJGPPC
313 		/*
314 		 * Setting stdin to binary by default causes
315 		 * Ctrl-C to not raise SIGINT.  We must undo
316 		 * that side-effect.
317 		 */
318 		__djgpp_set_ctrl_c(1);
319 #endif
320 	} else if (strcmp(open_filename, FAKE_HELPFILE) == 0)
321 	{
322 		f = -1;
323 		chflags |= CH_HELPFILE;
324 	} else if ((parg.p_string = bad_file(open_filename)) != NULL)
325 	{
326 		/*
327 		 * It looks like a bad file.  Don't try to open it.
328 		 */
329 		error("%s", &parg);
330 		free((void *)parg.p_string);
331 	    err1:
332 		if (alt_filename != NULL)
333 		{
334 			close_altfile(alt_filename, filename, alt_pipe);
335 			free(alt_filename);
336 		}
337 		del_ifile(ifile);
338 		free(qopen_filename);
339 		free(filename);
340 		/*
341 		 * Re-open the current file.
342 		 */
343 		if (was_curr_ifile == ifile)
344 		{
345 			/*
346 			 * Whoops.  The "current" ifile is the one we just deleted.
347 			 * Just give up.
348 			 */
349 			quit(QUIT_ERROR);
350 		}
351 		reedit_ifile(was_curr_ifile);
352 		return (1);
353 	} else if ((f = open(qopen_filename, OPEN_READ)) < 0)
354 	{
355 		/*
356 		 * Got an error trying to open it.
357 		 */
358 		parg.p_string = errno_message(filename);
359 		error("%s", &parg);
360 		free((void *)parg.p_string);
361 	    	goto err1;
362 	} else
363 	{
364 		chflags |= CH_CANSEEK;
365 		if (!force_open && !opened(ifile) && bin_file(f))
366 		{
367 			/*
368 			 * Looks like a binary file.
369 			 * Ask user if we should proceed.
370 			 */
371 			parg.p_string = filename;
372 			answer = query("\"%s\" may be a binary file.  See it anyway? ",
373 				&parg);
374 			if (answer != 'y' && answer != 'Y')
375 			{
376 				close(f);
377 				goto err1;
378 			}
379 		}
380 	}
381 
382 	/*
383 	 * Get the new ifile.
384 	 * Get the saved position for the file.
385 	 */
386 	if (was_curr_ifile != NULL_IFILE)
387 	{
388 		old_ifile = was_curr_ifile;
389 		unsave_ifile(was_curr_ifile);
390 	}
391 	curr_ifile = ifile;
392 	curr_altfilename = alt_filename;
393 	curr_altpipe = alt_pipe;
394 	set_open(curr_ifile); /* File has been opened */
395 	get_pos(curr_ifile, &initial_scrpos);
396 	new_file = TRUE;
397 	ch_init(f, chflags);
398 
399 	if (!(chflags & CH_HELPFILE))
400 	{
401 #if LOGFILE
402 		if (namelogfile != NULL && is_tty)
403 			use_logfile(namelogfile);
404 #endif
405 #if HAVE_STAT_INO
406 		/* Remember the i-number and device of the opened file. */
407 		{
408 			struct stat statbuf;
409 			int r = stat(qopen_filename, &statbuf);
410 			if (r == 0)
411 			{
412 				curr_ino = statbuf.st_ino;
413 				curr_dev = statbuf.st_dev;
414 			}
415 		}
416 #endif
417 		if (every_first_cmd != NULL)
418 			ungetsc(every_first_cmd);
419 	}
420 
421 	free(qopen_filename);
422 	no_display = !any_display;
423 	flush();
424 	any_display = TRUE;
425 
426 	if (is_tty)
427 	{
428 		/*
429 		 * Output is to a real tty.
430 		 */
431 
432 		/*
433 		 * Indicate there is nothing displayed yet.
434 		 */
435 		pos_clear();
436 		clr_linenum();
437 #if HILITE_SEARCH
438 		clr_hilite();
439 #endif
440 		cmd_addhist(ml_examine, filename);
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
463 edit_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
521 edit_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
531 edit_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
542 edit_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
584 edit_inext(h, n)
585 	IFILE h;
586 	int n;
587 {
588 	return (edit_istep(h, n, +1));
589 }
590 
591 	public int
592 edit_next(n)
593 	int n;
594 {
595 	return edit_istep(curr_ifile, n, +1);
596 }
597 
598 	static int
599 edit_iprev(h, n)
600 	IFILE h;
601 	int n;
602 {
603 	return (edit_istep(h, n, -1));
604 }
605 
606 	public int
607 edit_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
617 edit_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
638 save_curr_ifile()
639 {
640 	if (curr_ifile != NULL_IFILE)
641 		hold_ifile(curr_ifile, 1);
642 	return (curr_ifile);
643 }
644 
645 	public void
646 unsave_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
657 reedit_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
691 reopen_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
702 edit_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
717 cat_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
734 use_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 
775 loop:
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