xref: /minix/external/bsd/less/dist/edit.c (revision 84d9c625)
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 
18 public int fd0 = 0;
19 
20 extern int new_file;
21 extern int errmsgs;
22 extern int cbufs;
23 extern char *every_first_cmd;
24 extern int any_display;
25 extern int force_open;
26 extern int is_tty;
27 extern int sigs;
28 extern IFILE curr_ifile;
29 extern IFILE old_ifile;
30 extern struct scrpos initial_scrpos;
31 extern void *constant ml_examine;
32 #if SPACES_IN_FILENAMES
33 extern char openquote;
34 extern char closequote;
35 #endif
36 
37 #if LOGFILE
38 extern int logfile;
39 extern int force_logfile;
40 extern char *namelogfile;
41 #endif
42 
43 #if HAVE_STAT_INO
44 public dev_t curr_dev;
45 public ino_t curr_ino;
46 #endif
47 
48 char *curr_altfilename = NULL;
49 static void *curr_altpipe;
50 
51 
52 static void close_file __P((void));
53 static int edit_istep __P((IFILE, int, int));
54 static int edit_inext __P((IFILE, int));
55 static 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
init_textlist(tlist,str)65 init_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 *
forw_textlist(tlist,prev)109 forw_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 *
back_textlist(tlist,prev)133 back_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
close_file()162 close_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
edit(filename)206 edit(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
edit_ifile(ifile)219 edit_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
edit_list(filelist)466 edit_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
edit_first()524 edit_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
edit_last()534 edit_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
edit_istep(h,n,dir)545 edit_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
edit_inext(h,n)587 edit_inext(h, n)
588 	IFILE h;
589 	int n;
590 {
591 	return (edit_istep(h, n, +1));
592 }
593 
594 	public int
edit_next(n)595 edit_next(n)
596 	int n;
597 {
598 	return edit_istep(curr_ifile, n, +1);
599 }
600 
601 	static int
edit_iprev(h,n)602 edit_iprev(h, n)
603 	IFILE h;
604 	int n;
605 {
606 	return (edit_istep(h, n, -1));
607 }
608 
609 	public int
edit_prev(n)610 edit_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
edit_index(n)620 edit_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
save_curr_ifile()641 save_curr_ifile()
642 {
643 	if (curr_ifile != NULL_IFILE)
644 		hold_ifile(curr_ifile, 1);
645 	return (curr_ifile);
646 }
647 
648 	public void
unsave_ifile(save_ifile)649 unsave_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
reedit_ifile(save_ifile)660 reedit_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
reopen_curr_ifile()694 reopen_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
edit_stdin()705 edit_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
cat_file()720 cat_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
use_logfile(filename)737 use_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 
778 loop:
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