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