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