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