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