xref: /openbsd-src/usr.bin/less/edit.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: edit.c,v 1.2 2001/01/29 01:58:01 niklas Exp $	*/
2 
3 /*
4  * Copyright (c) 1984,1985,1989,1994,1995  Mark Nudelman
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice in the documentation and/or other materials provided with
14  *    the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 
30 #include "less.h"
31 
32 public int fd0 = 0;
33 
34 extern int new_file;
35 extern int errmsgs;
36 extern int quit_at_eof;
37 extern int cbufs;
38 extern char *every_first_cmd;
39 extern int any_display;
40 extern int force_open;
41 extern int is_tty;
42 extern IFILE curr_ifile;
43 extern IFILE old_ifile;
44 extern struct scrpos initial_scrpos;
45 
46 #if LOGFILE
47 extern int logfile;
48 extern int force_logfile;
49 extern char *namelogfile;
50 #endif
51 
52 char *curr_altfilename = NULL;
53 static void *curr_altpipe;
54 
55 
56 /*
57  * Textlist functions deal with a list of words separated by spaces.
58  * init_textlist sets up a textlist structure.
59  * forw_textlist uses that structure to iterate thru the list of
60  * words, returning each one as a standard null-terminated string.
61  * back_textlist does the same, but runs thru the list backwards.
62  */
63 	public void
64 init_textlist(tlist, str)
65 	struct textlist *tlist;
66 	char *str;
67 {
68 	char *s;
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 (*s == ' ')
75 			*s = '\0';
76 	}
77 }
78 
79 	public char *
80 forw_textlist(tlist, prev)
81 	struct textlist *tlist;
82 	char *prev;
83 {
84 	char *s;
85 
86 	/*
87 	 * prev == NULL means return the first word in the list.
88 	 * Otherwise, return the word after "prev".
89 	 */
90 	if (prev == NULL)
91 		s = tlist->string;
92 	else
93 		s = prev + strlen(prev);
94 	if (s >= tlist->endstring)
95 		return (NULL);
96 	while (*s == '\0')
97 		s++;
98 	if (s >= tlist->endstring)
99 		return (NULL);
100 	return (s);
101 }
102 
103 	public char *
104 back_textlist(tlist, prev)
105 	struct textlist *tlist;
106 	char *prev;
107 {
108 	char *s;
109 
110 	/*
111 	 * prev == NULL means return the last word in the list.
112 	 * Otherwise, return the word before "prev".
113 	 */
114 	if (prev == NULL)
115 		s = tlist->endstring;
116 	else if (prev <= tlist->string)
117 		return (NULL);
118 	else
119 		s = prev - 1;
120 	while (*s == '\0')
121 		s--;
122 	if (s <= tlist->string)
123 		return (NULL);
124 	while (s[-1] != '\0' && s > tlist->string)
125 		s--;
126 	return (s);
127 }
128 
129 /*
130  * Close the current input file.
131  */
132 	static void
133 close_file()
134 {
135 	struct scrpos scrpos;
136 
137 	if (curr_ifile == NULL_IFILE)
138 		return;
139 	/*
140 	 * Save the current position so that we can return to
141 	 * the same position if we edit this file again.
142 	 */
143 	get_scrpos(&scrpos);
144 	if (scrpos.pos != NULL_POSITION)
145 	{
146 		store_pos(curr_ifile, &scrpos);
147 		lastmark();
148 	}
149 	/*
150 	 * Close the file descriptor, unless it is a pipe.
151 	 */
152 	ch_close();
153 	/*
154 	 * If we opened a file using an alternate name,
155 	 * do special stuff to close it.
156 	 */
157 	if (curr_altfilename != NULL)
158 	{
159 		close_altfile(curr_altfilename, get_filename(curr_ifile),
160 			curr_altpipe);
161 		free(curr_altfilename);
162 		curr_altfilename = NULL;
163 	}
164 	curr_ifile = NULL_IFILE;
165 }
166 
167 /*
168  * Edit a new file (given its name).
169  * Filename == "-" means standard input.
170  * Filename == NULL means just close the current file.
171  */
172 	public int
173 edit(filename)
174 	char *filename;
175 {
176 	if (filename == NULL)
177 		return (edit_ifile(NULL_IFILE));
178 	return (edit_ifile(get_ifile(filename, curr_ifile)));
179 }
180 
181 /*
182  * Edit a new file (given its IFILE).
183  * ifile == NULL means just close the current file.
184  */
185 	public int
186 edit_ifile(ifile)
187 	IFILE ifile;
188 {
189 	int f;
190 	int answer;
191 	int no_display;
192 	int chflags;
193 	char *filename;
194 	char *open_filename;
195 	char *alt_filename;
196 	void *alt_pipe;
197 	IFILE was_curr_ifile;
198 	PARG parg;
199 
200 	if (ifile == curr_ifile)
201 	{
202 		/*
203 		 * Already have the correct file open.
204 		 */
205 		return (0);
206 	}
207 
208 	/*
209 	 * We must close the currently open file now.
210 	 * This is necessary to make the open_altfile/close_altfile pairs
211 	 * nest properly (or rather to avoid nesting at all).
212 	 * {{ Some stupid implementations of popen() mess up if you do:
213 	 *    fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }}
214 	 */
215 #if LOGFILE
216 	end_logfile();
217 #endif
218 	was_curr_ifile = curr_ifile;
219 	if (curr_ifile != NULL_IFILE)
220 	{
221 		close_file();
222 	}
223 
224 	if (ifile == NULL_IFILE)
225 	{
226 		/*
227 		 * No new file to open.
228 		 * (Don't set old_ifile, because if you call edit_ifile(NULL),
229 		 *  you're supposed to have saved curr_ifile yourself,
230 		 *  and you'll restore it if necessary.)
231 		 */
232 		return (0);
233 	}
234 
235 	filename = get_filename(ifile);
236 	/*
237 	 * See if LESSOPEN specifies an "alternate" file to open.
238 	 */
239 	alt_pipe = NULL;
240 	alt_filename = open_altfile(filename, &f, &alt_pipe);
241 	open_filename = (alt_filename != NULL) ? alt_filename : filename;
242 
243 	chflags = 0;
244 	if (alt_pipe != NULL)
245 	{
246 		/*
247 		 * The alternate "file" is actually a pipe.
248 		 * f has already been set to the file descriptor of the pipe
249 		 * in the call to open_altfile above.
250 		 * Keep the file descriptor open because it was opened
251 		 * via popen(), and pclose() wants to close it.
252 		 */
253 		chflags |= CH_POPENED;
254 	} else if (strcmp(open_filename, "-") == 0)
255 	{
256 		/*
257 		 * Use standard input.
258 		 * Keep the file descriptor open because we can't reopen it.
259 		 */
260 		f = fd0;
261 		chflags |= CH_KEEPOPEN;
262 	} else if ((parg.p_string = bad_file(open_filename)) != NULL)
263 	{
264 		/*
265 		 * It looks like a bad file.  Don't try to open it.
266 		 */
267 		error("%s", &parg);
268 		free(parg.p_string);
269 	    err1:
270 		if (alt_filename != NULL)
271 		{
272 			close_altfile(alt_filename, filename, alt_pipe);
273 			free(alt_filename);
274 		}
275 		del_ifile(ifile);
276 		/*
277 		 * Re-open the current file.
278 		 */
279 		(void) edit_ifile(was_curr_ifile);
280 		return (1);
281 	} else if ((f = open(open_filename, OPEN_READ)) < 0)
282 	{
283 		/*
284 		 * Got an error trying to open it.
285 		 */
286 		parg.p_string = errno_message(filename);
287 		error("%s", &parg);
288 		free(parg.p_string);
289 	    	goto err1;
290 	} else if (!force_open && !opened(ifile) && bin_file(f))
291 	{
292 		/*
293 		 * Looks like a binary file.  Ask user if we should proceed.
294 		 */
295 		parg.p_string = filename;
296 		answer = query("\"%s\" may be a binary file.  See it anyway? ",
297 			&parg);
298 		if (answer != 'y' && answer != 'Y')
299 		{
300 			close(f);
301 			goto err1;
302 		}
303 	}
304 
305 	/*
306 	 * Get the new ifile.
307 	 * Get the saved position for the file.
308 	 */
309 	if (was_curr_ifile != NULL_IFILE)
310 		old_ifile = was_curr_ifile;
311 	curr_ifile = ifile;
312 	curr_altfilename = alt_filename;
313 	curr_altpipe = alt_pipe;
314 	set_open(curr_ifile); /* File has been opened */
315 	get_pos(curr_ifile, &initial_scrpos);
316 	new_file = TRUE;
317 	ch_init(f, chflags);
318 #if LOGFILE
319 	if (namelogfile != NULL && is_tty)
320 		use_logfile(namelogfile);
321 #endif
322 
323 	if (every_first_cmd != NULL)
324 		ungetsc(every_first_cmd);
325 
326 	no_display = !any_display;
327 	flush();
328 	any_display = TRUE;
329 
330 	if (is_tty)
331 	{
332 		/*
333 		 * Output is to a real tty.
334 		 */
335 
336 		/*
337 		 * Indicate there is nothing displayed yet.
338 		 */
339 		pos_clear();
340 		clr_linenum();
341 #if HILITE_SEARCH
342 		clr_hilite();
343 #endif
344 		if (no_display && errmsgs > 0)
345 		{
346 			/*
347 			 * We displayed some messages on error output
348 			 * (file descriptor 2; see error() function).
349 			 * Before erasing the screen contents,
350 			 * display the file name and wait for a keystroke.
351 			 */
352 			parg.p_string = filename;
353 			error("%s", &parg);
354 		}
355 	}
356 	return (0);
357 }
358 
359 /*
360  * Edit a space-separated list of files.
361  * For each filename in the list, enter it into the ifile list.
362  * Then edit the first one.
363  */
364 	public int
365 edit_list(filelist)
366 	char *filelist;
367 {
368 	IFILE save_curr_ifile;
369 	char *good_filename;
370 	char *filename;
371 	char *gfilelist;
372 	char *gfilename;
373 	struct textlist tl_files;
374 	struct textlist tl_gfiles;
375 
376 	save_curr_ifile = curr_ifile;
377 	good_filename = NULL;
378 
379 	/*
380 	 * Run thru each filename in the list.
381 	 * Try to glob the filename.
382 	 * If it doesn't expand, just try to open the filename.
383 	 * If it does expand, try to open each name in that list.
384 	 */
385 	init_textlist(&tl_files, filelist);
386 	filename = NULL;
387 	while ((filename = forw_textlist(&tl_files, filename)) != NULL)
388 	{
389 		gfilelist = glob(filename);
390 		init_textlist(&tl_gfiles, gfilelist);
391 		gfilename = NULL;
392 		while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
393 		{
394 			if (edit(gfilename) == 0 && good_filename == NULL)
395 				good_filename = get_filename(curr_ifile);
396 		}
397 		free(gfilelist);
398 	}
399 	/*
400 	 * Edit the first valid filename in the list.
401 	 */
402 	if (good_filename == NULL)
403 		return (1);
404 	if (get_ifile(good_filename, curr_ifile) == curr_ifile)
405 		/*
406 		 * Trying to edit the current file; don't reopen it.
407 		 */
408 		return (0);
409 	if (edit_ifile(save_curr_ifile))
410 		quit(QUIT_ERROR);
411 	return (edit(good_filename));
412 }
413 
414 /*
415  * Edit the first file in the command line (ifile) list.
416  */
417 	public int
418 edit_first()
419 {
420 	curr_ifile = NULL_IFILE;
421 	return (edit_next(1));
422 }
423 
424 /*
425  * Edit the last file in the command line (ifile) list.
426  */
427 	public int
428 edit_last()
429 {
430 	curr_ifile = NULL_IFILE;
431 	return (edit_prev(1));
432 }
433 
434 
435 /*
436  * Edit the next file in the command line (ifile) list.
437  */
438 	public int
439 edit_next(n)
440 	int n;
441 {
442 	IFILE h;
443 	IFILE next;
444 
445 	h = curr_ifile;
446 	/*
447 	 * Skip n filenames, then try to edit each filename.
448 	 */
449 	for (;;)
450 	{
451 		next = next_ifile(h);
452 		if (--n < 0)
453 		{
454 			if (edit_ifile(h) == 0)
455 				break;
456 		}
457 		if (next == NULL_IFILE)
458 		{
459 			/*
460 			 * Reached end of the ifile list.
461 			 */
462 			return (1);
463 		}
464 		h = next;
465 	}
466 	/*
467 	 * Found a file that we can edit.
468 	 */
469 	return (0);
470 }
471 
472 /*
473  * Edit the previous file in the command line list.
474  */
475 	public int
476 edit_prev(n)
477 	int n;
478 {
479 	IFILE h;
480 	IFILE next;
481 
482 	h = curr_ifile;
483 	/*
484 	 * Skip n filenames, then try to edit each filename.
485 	 */
486 	for (;;)
487 	{
488 		next = prev_ifile(h);
489 		if (--n < 0)
490 		{
491 			if (edit_ifile(h) == 0)
492 				break;
493 		}
494 		if (next == NULL_IFILE)
495 		{
496 			/*
497 			 * Reached beginning of the ifile list.
498 			 */
499 			return (1);
500 		}
501 		h = next;
502 	}
503 	/*
504 	 * Found a file that we can edit.
505 	 */
506 	return (0);
507 }
508 
509 /*
510  * Edit a specific file in the command line (ifile) list.
511  */
512 	public int
513 edit_index(n)
514 	int n;
515 {
516 	IFILE h;
517 
518 	h = NULL_IFILE;
519 	do
520 	{
521 		if ((h = next_ifile(h)) == NULL_IFILE)
522 		{
523 			/*
524 			 * Reached end of the list without finding it.
525 			 */
526 			return (1);
527 		}
528 	} while (get_index(h) != n);
529 
530 	return (edit_ifile(h));
531 }
532 
533 /*
534  * Edit standard input.
535  */
536 	public int
537 edit_stdin()
538 {
539 	if (isatty(fd0))
540 	{
541 #if MSOFTC || OS2
542 		error("Missing filename (\"less -?\" for help)", NULL_PARG);
543 #else
544 		error("Missing filename (\"less -\\?\" for help)", NULL_PARG);
545 #endif
546 		quit(QUIT_OK);
547 	}
548 	return (edit("-"));
549 }
550 
551 /*
552  * Copy a file directly to standard output.
553  * Used if standard output is not a tty.
554  */
555 	public void
556 cat_file()
557 {
558 	register int c;
559 
560 	while ((c = ch_forw_get()) != EOI)
561 		putchr(c);
562 	flush();
563 }
564 
565 #if LOGFILE
566 
567 /*
568  * If the user asked for a log file and our input file
569  * is standard input, create the log file.
570  * We take care not to blindly overwrite an existing file.
571  */
572 	public void
573 use_logfile(filename)
574 	char *filename;
575 {
576 	register int exists;
577 	register int answer;
578 	PARG parg;
579 
580 	if (ch_getflags() & CH_CANSEEK)
581 		/*
582 		 * Can't currently use a log file on a file that can seek.
583 		 */
584 		return;
585 
586 	/*
587 	 * {{ We could use access() here. }}
588 	 */
589 	exists = open(filename, OPEN_READ);
590 	close(exists);
591 	exists = (exists >= 0);
592 
593 	/*
594 	 * Decide whether to overwrite the log file or append to it.
595 	 * If it doesn't exist we "overwrite" it.
596 	 */
597 	if (!exists || force_logfile)
598 	{
599 		/*
600 		 * Overwrite (or create) the log file.
601 		 */
602 		answer = 'O';
603 	} else
604 	{
605 		/*
606 		 * Ask user what to do.
607 		 */
608 		parg.p_string = filename;
609 		answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
610 	}
611 
612 loop:
613 	switch (answer)
614 	{
615 	case 'O': case 'o':
616 		/*
617 		 * Overwrite: create the file.
618 		 */
619 		logfile = creat(filename, 0644);
620 		break;
621 	case 'A': case 'a':
622 		/*
623 		 * Append: open the file and seek to the end.
624 		 */
625 		logfile = open(filename, OPEN_APPEND);
626 		if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK)
627 		{
628 			close(logfile);
629 			logfile = -1;
630 		}
631 		break;
632 	case 'D': case 'd':
633 		/*
634 		 * Don't do anything.
635 		 */
636 		return;
637 	case 'q':
638 		quit(QUIT_OK);
639 		/*NOTREACHED*/
640 	default:
641 		/*
642 		 * Eh?
643 		 */
644 		answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
645 		goto loop;
646 	}
647 
648 	if (logfile < 0)
649 	{
650 		/*
651 		 * Error in opening logfile.
652 		 */
653 		parg.p_string = filename;
654 		error("Cannot write to \"%s\"", &parg);
655 	}
656 }
657 
658 #endif
659