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