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