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