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