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