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