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