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