Merge branch 'vendor/DHCPCD'
[dragonfly.git] / contrib / less / edit.c
1 /*
2  * Copyright (C) 1984-2019  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(VOID_PARAM)
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                 {
490                         char *qfilename = shell_quote(filename);
491                         cmd_addhist(ml_examine, qfilename, 1);
492                         free(qfilename);
493                 }
494
495                 if (no_display && errmsgs > 0)
496                 {
497                         /*
498                          * We displayed some messages on error output
499                          * (file descriptor 2; see error() function).
500                          * Before erasing the screen contents,
501                          * display the file name and wait for a keystroke.
502                          */
503                         parg.p_string = filename;
504                         error("%s", &parg);
505                 }
506         }
507         free(filename);
508         return (0);
509 }
510
511 /*
512  * Edit a space-separated list of files.
513  * For each filename in the list, enter it into the ifile list.
514  * Then edit the first one.
515  */
516         public int
517 edit_list(filelist)
518         char *filelist;
519 {
520         IFILE save_ifile;
521         char *good_filename;
522         char *filename;
523         char *gfilelist;
524         char *gfilename;
525         char *qfilename;
526         struct textlist tl_files;
527         struct textlist tl_gfiles;
528
529         save_ifile = save_curr_ifile();
530         good_filename = NULL;
531         
532         /*
533          * Run thru each filename in the list.
534          * Try to glob the filename.  
535          * If it doesn't expand, just try to open the filename.
536          * If it does expand, try to open each name in that list.
537          */
538         init_textlist(&tl_files, filelist);
539         filename = NULL;
540         while ((filename = forw_textlist(&tl_files, filename)) != NULL)
541         {
542                 gfilelist = lglob(filename);
543                 init_textlist(&tl_gfiles, gfilelist);
544                 gfilename = NULL;
545                 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
546                 {
547                         qfilename = shell_unquote(gfilename);
548                         if (edit(qfilename) == 0 && good_filename == NULL)
549                                 good_filename = get_filename(curr_ifile);
550                         free(qfilename);
551                 }
552                 free(gfilelist);
553         }
554         /*
555          * Edit the first valid filename in the list.
556          */
557         if (good_filename == NULL)
558         {
559                 unsave_ifile(save_ifile);
560                 return (1);
561         }
562         if (get_ifile(good_filename, curr_ifile) == curr_ifile)
563         {
564                 /*
565                  * Trying to edit the current file; don't reopen it.
566                  */
567                 unsave_ifile(save_ifile);
568                 return (0);
569         }
570         reedit_ifile(save_ifile);
571         return (edit(good_filename));
572 }
573
574 /*
575  * Edit the first file in the command line (ifile) list.
576  */
577         public int
578 edit_first(VOID_PARAM)
579 {
580         if (nifile() == 0)
581                 return (edit_stdin());
582         curr_ifile = NULL_IFILE;
583         return (edit_next(1));
584 }
585
586 /*
587  * Edit the last file in the command line (ifile) list.
588  */
589         public int
590 edit_last(VOID_PARAM)
591 {
592         curr_ifile = NULL_IFILE;
593         return (edit_prev(1));
594 }
595
596
597 /*
598  * Edit the n-th next or previous file in the command line (ifile) list.
599  */
600         static int
601 edit_istep(h, n, dir)
602         IFILE h;
603         int n;
604         int dir;
605 {
606         IFILE next;
607
608         /*
609          * Skip n filenames, then try to edit each filename.
610          */
611         for (;;)
612         {
613                 next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
614                 if (--n < 0)
615                 {
616                         if (edit_ifile(h) == 0)
617                                 break;
618                 }
619                 if (next == NULL_IFILE)
620                 {
621                         /*
622                          * Reached end of the ifile list.
623                          */
624                         return (1);
625                 }
626                 if (ABORT_SIGS())
627                 {
628                         /*
629                          * Interrupt breaks out, if we're in a long
630                          * list of files that can't be opened.
631                          */
632                         return (1);
633                 }
634                 h = next;
635         } 
636         /*
637          * Found a file that we can edit.
638          */
639         return (0);
640 }
641
642         static int
643 edit_inext(h, n)
644         IFILE h;
645         int n;
646 {
647         return (edit_istep(h, n, +1));
648 }
649
650         public int
651 edit_next(n)
652         int n;
653 {
654         return edit_istep(curr_ifile, n, +1);
655 }
656
657         static int
658 edit_iprev(h, n)
659         IFILE h;
660         int n;
661 {
662         return (edit_istep(h, n, -1));
663 }
664
665         public int
666 edit_prev(n)
667         int n;
668 {
669         return edit_istep(curr_ifile, n, -1);
670 }
671
672 /*
673  * Edit a specific file in the command line (ifile) list.
674  */
675         public int
676 edit_index(n)
677         int n;
678 {
679         IFILE h;
680
681         h = NULL_IFILE;
682         do
683         {
684                 if ((h = next_ifile(h)) == NULL_IFILE)
685                 {
686                         /*
687                          * Reached end of the list without finding it.
688                          */
689                         return (1);
690                 }
691         } while (get_index(h) != n);
692
693         return (edit_ifile(h));
694 }
695
696         public IFILE
697 save_curr_ifile(VOID_PARAM)
698 {
699         if (curr_ifile != NULL_IFILE)
700                 hold_ifile(curr_ifile, 1);
701         return (curr_ifile);
702 }
703
704         public void
705 unsave_ifile(save_ifile)
706         IFILE save_ifile;
707 {
708         if (save_ifile != NULL_IFILE)
709                 hold_ifile(save_ifile, -1);
710 }
711
712 /*
713  * Reedit the ifile which was previously open.
714  */
715         public void
716 reedit_ifile(save_ifile)
717         IFILE save_ifile;
718 {
719         IFILE next;
720         IFILE prev;
721
722         /*
723          * Try to reopen the ifile.
724          * Note that opening it may fail (maybe the file was removed),
725          * in which case the ifile will be deleted from the list.
726          * So save the next and prev ifiles first.
727          */
728         unsave_ifile(save_ifile);
729         next = next_ifile(save_ifile);
730         prev = prev_ifile(save_ifile);
731         if (edit_ifile(save_ifile) == 0)
732                 return;
733         /*
734          * If can't reopen it, open the next input file in the list.
735          */
736         if (next != NULL_IFILE && edit_inext(next, 0) == 0)
737                 return;
738         /*
739          * If can't open THAT one, open the previous input file in the list.
740          */
741         if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
742                 return;
743         /*
744          * If can't even open that, we're stuck.  Just quit.
745          */
746         quit(QUIT_ERROR);
747 }
748
749         public void
750 reopen_curr_ifile(VOID_PARAM)
751 {
752         IFILE save_ifile = save_curr_ifile();
753         close_file();
754         reedit_ifile(save_ifile);
755 }
756
757 /*
758  * Edit standard input.
759  */
760         public int
761 edit_stdin(VOID_PARAM)
762 {
763         if (isatty(fd0))
764         {
765                 error("Missing filename (\"less --help\" for help)", NULL_PARG);
766                 quit(QUIT_OK);
767         }
768         return (edit("-"));
769 }
770
771 /*
772  * Copy a file directly to standard output.
773  * Used if standard output is not a tty.
774  */
775         public void
776 cat_file(VOID_PARAM)
777 {
778         int c;
779
780         while ((c = ch_forw_get()) != EOI)
781                 putchr(c);
782         flush();
783 }
784
785 #if LOGFILE
786
787 /*
788  * If the user asked for a log file and our input file
789  * is standard input, create the log file.  
790  * We take care not to blindly overwrite an existing file.
791  */
792         public void
793 use_logfile(filename)
794         char *filename;
795 {
796         int exists;
797         int answer;
798         PARG parg;
799
800         if (ch_getflags() & CH_CANSEEK)
801                 /*
802                  * Can't currently use a log file on a file that can seek.
803                  */
804                 return;
805
806         /*
807          * {{ We could use access() here. }}
808          */
809         exists = open(filename, OPEN_READ);
810         if (exists >= 0)
811                 close(exists);
812         exists = (exists >= 0);
813
814         /*
815          * Decide whether to overwrite the log file or append to it.
816          * If it doesn't exist we "overwrite" it.
817          */
818         if (!exists || force_logfile)
819         {
820                 /*
821                  * Overwrite (or create) the log file.
822                  */
823                 answer = 'O';
824         } else
825         {
826                 /*
827                  * Ask user what to do.
828                  */
829                 parg.p_string = filename;
830                 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg);
831         }
832
833 loop:
834         switch (answer)
835         {
836         case 'O': case 'o':
837                 /*
838                  * Overwrite: create the file.
839                  */
840                 logfile = creat(filename, 0644);
841                 break;
842         case 'A': case 'a':
843                 /*
844                  * Append: open the file and seek to the end.
845                  */
846                 logfile = open(filename, OPEN_APPEND);
847                 if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK)
848                 {
849                         close(logfile);
850                         logfile = -1;
851                 }
852                 break;
853         case 'D': case 'd':
854                 /*
855                  * Don't do anything.
856                  */
857                 free(filename);
858                 return;
859         case 'q':
860                 quit(QUIT_OK);
861                 /*NOTREACHED*/
862         default:
863                 /*
864                  * Eh?
865                  */
866                 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG);
867                 goto loop;
868         }
869
870         if (logfile < 0)
871         {
872                 /*
873                  * Error in opening logfile.
874                  */
875                 parg.p_string = filename;
876                 error("Cannot write to \"%s\"", &parg);
877                 return;
878         }
879         SET_BINARY(logfile);
880 }
881
882 #endif