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