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