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