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