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