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