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