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