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