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