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