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 public 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(LM(Input_preprocessor_failed), &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(LM(Input_preprocessor_terminated_X), &parg);
329 }
330 return;
331 }
332 if (status != 0)
333 {
334 parg.p_int = status;
335 error(LM(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 static 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,FILE * altpipe,IFILE ifile)424 static int edit_error(constant char *filename, constant char *alt_filename, FILE *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 FILE *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(LM(X_may_be_a_binary_file), &parg);
574 if (answer == 'q')
575 quit(QUIT_OK);
576 if (answer != 'y' && answer != 'Y')
577 {
578 close(f);
579 return edit_error(filename, alt_filename, altpipe, ifile);
580 }
581 }
582 }
583 }
584 if (!force_open && f >= 0 && isatty(f))
585 {
586 PARG parg;
587 parg.p_string = filename;
588 error(LM(X_is_a_terminal), &parg);
589 return edit_error(filename, alt_filename, altpipe, ifile);
590 }
591 }
592
593 #if LOGFILE
594 end_logfile();
595 #endif
596 was_curr_ifile = save_curr_ifile();
597 if (curr_ifile != NULL_IFILE)
598 {
599 int was_helpfile = (ch_getflags() & CH_HELPFILE);
600 close_file();
601 if (was_helpfile && held_ifile(was_curr_ifile) <= 1)
602 {
603 /*
604 * Don't keep the help file in the ifile list.
605 */
606 del_ifile(was_curr_ifile);
607 was_curr_ifile = NULL_IFILE;
608 }
609 }
610 unsave_ifile(was_curr_ifile);
611
612 if (ifile == NULL_IFILE)
613 {
614 /*
615 * No new file to open.
616 * (Don't set old_ifile, because if you call edit_ifile(NULL),
617 * you're supposed to have saved curr_ifile yourself,
618 * and you'll restore it if necessary.)
619 */
620 return (0);
621 }
622
623 /*
624 * Set up the new ifile.
625 * Get the saved position for the file.
626 */
627 curr_ifile = ifile;
628 soft_eof = NULL_POSITION;
629 set_altfilename(curr_ifile, alt_filename);
630 set_altpipe(curr_ifile, altpipe);
631 set_open(curr_ifile); /* File has been opened */
632 get_pos(curr_ifile, &initial_scrpos);
633 ch_init(f, chflags, nread);
634 check_modelines();
635
636 if (!(chflags & CH_HELPFILE))
637 {
638 if (was_curr_ifile != NULL_IFILE)
639 old_ifile = was_curr_ifile;
640 #if LOGFILE
641 if (namelogfile != NULL && is_tty)
642 use_logfile(namelogfile);
643 #endif
644 #if HAVE_STAT_INO
645 /* Remember the i-number and device of the opened file. */
646 curr_ino = curr_dev = 0;
647 if (!is_fake_pathname(open_filename))
648 {
649 less_stat_t statbuf;
650 int r = less_stat(open_filename, &statbuf);
651 if (r == 0)
652 {
653 curr_ino = statbuf.st_ino;
654 curr_dev = statbuf.st_dev;
655 }
656 }
657 #endif
658 if (every_first_cmd != NULL)
659 {
660 ungetsc(every_first_cmd);
661 ungetcc_end_command();
662 }
663 }
664
665 flush();
666
667 if (is_tty)
668 {
669 /*
670 * Output is to a real tty.
671 */
672
673 /*
674 * Indicate there is nothing displayed yet.
675 */
676 pos_clear();
677 clr_linenum();
678 #if HILITE_SEARCH
679 clr_hilite();
680 #endif
681 undo_osc8();
682 hshift = 0;
683 read_error = FALSE;
684 if (strcmp(filename, FAKE_HELPFILE) && strcmp(filename, FAKE_EMPTYFILE))
685 {
686 char *qfilename = shell_quote(filename);
687 if (qfilename == NULL)
688 cmd_addhist(ml_examine, filename, TRUE);
689 else
690 {
691 cmd_addhist(ml_examine, qfilename, TRUE);
692 free(qfilename);
693 }
694 }
695 if (want_filesize)
696 scan_eof();
697 set_header(ch_zero());
698 }
699 return (0);
700 }
701
702 /*
703 * Edit a space-separated list of files.
704 * For each filename in the list, enter it into the ifile list.
705 * Then edit the first one.
706 */
edit_list(char * filelist)707 public int edit_list(char *filelist)
708 {
709 IFILE save_ifile;
710 constant char *good_filename;
711 constant char *filename;
712 char *gfilelist;
713 constant char *gfilename;
714 char *qfilename;
715 struct textlist tl_files;
716 struct textlist tl_gfiles;
717
718 save_ifile = save_curr_ifile();
719 good_filename = NULL;
720
721 /*
722 * Run thru each filename in the list.
723 * Try to glob the filename.
724 * If it doesn't expand, just try to open the filename.
725 * If it does expand, try to open each name in that list.
726 */
727 init_textlist(&tl_files, filelist);
728 filename = NULL;
729 while ((filename = forw_textlist(&tl_files, filename)) != NULL)
730 {
731 gfilelist = lglob(filename);
732 init_textlist(&tl_gfiles, gfilelist);
733 gfilename = NULL;
734 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL)
735 {
736 qfilename = shell_unquote(gfilename);
737 if (edit(qfilename) == 0 && good_filename == NULL)
738 good_filename = get_filename(curr_ifile);
739 free(qfilename);
740 }
741 free(gfilelist);
742 }
743 /*
744 * Edit the first valid filename in the list.
745 */
746 if (good_filename == NULL)
747 {
748 unsave_ifile(save_ifile);
749 return (1);
750 }
751 if (get_ifile(good_filename, curr_ifile) == curr_ifile)
752 {
753 /*
754 * Trying to edit the current file; don't reopen it.
755 */
756 unsave_ifile(save_ifile);
757 return (0);
758 }
759 reedit_ifile(save_ifile);
760 return (edit(good_filename));
761 }
762
763 /*
764 * Edit the first file in the command line (ifile) list.
765 */
edit_first(void)766 public int edit_first(void)
767 {
768 if (nifile() == 0)
769 return (edit_stdin());
770 curr_ifile = NULL_IFILE;
771 return (edit_next(1));
772 }
773
774 /*
775 * Edit the last file in the command line (ifile) list.
776 */
edit_last(void)777 public int edit_last(void)
778 {
779 curr_ifile = NULL_IFILE;
780 return (edit_prev(1));
781 }
782
783
784 /*
785 * Edit the n-th next or previous file in the command line (ifile) list.
786 */
edit_istep(IFILE h,int n,int dir)787 static int edit_istep(IFILE h, int n, int dir)
788 {
789 IFILE next;
790
791 /*
792 * Skip n filenames, then try to edit each filename.
793 */
794 for (;;)
795 {
796 next = (dir > 0) ? next_ifile(h) : prev_ifile(h);
797 if (--n < 0)
798 {
799 if (edit_ifile(h) == 0)
800 break;
801 }
802 if (next == NULL_IFILE)
803 {
804 /*
805 * Reached end of the ifile list.
806 */
807 return (1);
808 }
809 if (ABORT_SIGS())
810 {
811 /*
812 * Interrupt breaks out, if we're in a long
813 * list of files that can't be opened.
814 */
815 return (1);
816 }
817 h = next;
818 }
819 /*
820 * Found a file that we can edit.
821 */
822 return (0);
823 }
824
edit_inext(IFILE h,int n)825 static int edit_inext(IFILE h, int n)
826 {
827 return (edit_istep(h, n, +1));
828 }
829
edit_next(int n)830 public int edit_next(int n)
831 {
832 return edit_istep(curr_ifile, n, +1);
833 }
834
edit_iprev(IFILE h,int n)835 static int edit_iprev(IFILE h, int n)
836 {
837 return (edit_istep(h, n, -1));
838 }
839
edit_prev(int n)840 public int edit_prev(int n)
841 {
842 return edit_istep(curr_ifile, n, -1);
843 }
844
845 /*
846 * Edit a specific file in the command line (ifile) list.
847 */
edit_index(int n)848 public int edit_index(int n)
849 {
850 IFILE h;
851
852 h = NULL_IFILE;
853 do
854 {
855 if ((h = next_ifile(h)) == NULL_IFILE)
856 {
857 /*
858 * Reached end of the list without finding it.
859 */
860 return (1);
861 }
862 } while (get_index(h) != n);
863
864 return (edit_ifile(h));
865 }
866
save_curr_ifile(void)867 public IFILE save_curr_ifile(void)
868 {
869 if (curr_ifile != NULL_IFILE)
870 hold_ifile(curr_ifile, 1);
871 return (curr_ifile);
872 }
873
unsave_ifile(IFILE save_ifile)874 public void unsave_ifile(IFILE save_ifile)
875 {
876 if (save_ifile != NULL_IFILE)
877 hold_ifile(save_ifile, -1);
878 }
879
880 /*
881 * Reedit the ifile which was previously open.
882 */
reedit_ifile(IFILE save_ifile)883 public void reedit_ifile(IFILE save_ifile)
884 {
885 IFILE next;
886 IFILE prev;
887
888 /*
889 * Try to reopen the ifile.
890 * Note that opening it may fail (maybe the file was removed),
891 * in which case the ifile will be deleted from the list.
892 * So save the next and prev ifiles first.
893 */
894 unsave_ifile(save_ifile);
895 next = next_ifile(save_ifile);
896 prev = prev_ifile(save_ifile);
897 if (edit_ifile(save_ifile) == 0)
898 return;
899 /*
900 * If can't reopen it, open the next input file in the list.
901 */
902 if (next != NULL_IFILE && edit_inext(next, 0) == 0)
903 return;
904 /*
905 * If can't open THAT one, open the previous input file in the list.
906 */
907 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0)
908 return;
909 /*
910 * If can't even open that, we're stuck. Just quit.
911 */
912 quit(QUIT_ERROR);
913 }
914
reopen_curr_ifile(void)915 public void reopen_curr_ifile(void)
916 {
917 IFILE save_ifile = save_curr_ifile();
918 close_file();
919 reedit_ifile(save_ifile);
920 }
921
922 /*
923 * Edit standard input.
924 */
edit_stdin(void)925 public int edit_stdin(void)
926 {
927 if (isatty(fd0))
928 {
929 error(LM(Missing_filename), NULL_PARG);
930 quit(QUIT_ERROR);
931 }
932 return (edit("-"));
933 }
934
935 /*
936 * Copy a file directly to standard output.
937 * Used if standard output is not a tty.
938 */
cat_file(void)939 public void cat_file(void)
940 {
941 int c;
942
943 while ((c = ch_forw_get()) != EOI)
944 putchr(c);
945 flush();
946 }
947
948 #if LOGFILE
949
950 /*
951 * If the user asked for a log file and our input file
952 * is standard input, create the log file.
953 * We take care not to blindly overwrite an existing file.
954 */
use_logfile(constant char * filename)955 public void use_logfile(constant char *filename)
956 {
957 int exists;
958 int answer;
959 PARG parg;
960
961 if (ch_getflags() & CH_CANSEEK)
962 /*
963 * Can't currently use a log file on a file that can seek.
964 */
965 return;
966
967 /*
968 * {{ We could use access() here. }}
969 */
970 exists = open(filename, OPEN_READ);
971 if (exists >= 0)
972 close(exists);
973 exists = (exists >= 0);
974
975 /*
976 * Decide whether to overwrite the log file or append to it.
977 * If it doesn't exist we "overwrite" it.
978 */
979 if (!exists || force_logfile)
980 {
981 /*
982 * Overwrite (or create) the log file.
983 */
984 answer = 'O';
985 } else
986 {
987 /*
988 * Ask user what to do.
989 */
990 parg.p_string = filename;
991 answer = query(LM(X_exists), &parg);
992 }
993
994 loop:
995 switch (answer)
996 {
997 case 'O': case 'o':
998 /*
999 * Overwrite: create the file.
1000 */
1001 logfile = creat(filename, CREAT_RW);
1002 break;
1003 case 'A': case 'a':
1004 /*
1005 * Append: open the file and seek to the end.
1006 */
1007 logfile = open(filename, OPEN_APPEND);
1008 if (less_lseek(logfile, (less_off_t)0, SEEK_END) == BAD_LSEEK)
1009 {
1010 close(logfile);
1011 logfile = -1;
1012 }
1013 break;
1014 case 'D': case 'd':
1015 /*
1016 * Don't do anything.
1017 */
1018 return;
1019 default:
1020 /*
1021 * Eh?
1022 */
1023
1024 answer = query(LM(Overwrite), NULL_PARG);
1025 goto loop;
1026 }
1027
1028 if (logfile < 0)
1029 {
1030 /*
1031 * Error in opening logfile.
1032 */
1033 parg.p_string = filename;
1034 error(LM(Cannot_write_to_X), &parg);
1035 return;
1036 }
1037 SET_BINARY(logfile);
1038 }
1039
1040 #endif
1041