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