1 /*
2 * Copyright (C) 1984-2024 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 /*
12 * Routines to mess around with filenames (and files).
13 * Much of this is very OS dependent.
14 */
15
16 #include "less.h"
17 #include "lglob.h"
18 #if MSDOS_COMPILER
19 #include <dos.h>
20 #if MSDOS_COMPILER==WIN32C && !defined(_MSC_VER)
21 #include <dir.h>
22 #endif
23 #if MSDOS_COMPILER==DJGPPC
24 #include <glob.h>
25 #include <dir.h>
26 #define _MAX_PATH PATH_MAX
27 #endif
28 #endif
29 #ifdef _OSK
30 #include <rbf.h>
31 #ifndef _OSK_MWC32
32 #include <modes.h>
33 #endif
34 #endif
35
36 #if HAVE_STAT
37 #include <sys/stat.h>
38 #ifndef S_ISDIR
39 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
40 #endif
41 #ifndef S_ISREG
42 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
43 #endif
44 #endif
45
46 extern int force_open;
47 extern int use_lessopen;
48 extern int ctldisp;
49 extern int utf_mode;
50 extern IFILE curr_ifile;
51 extern IFILE old_ifile;
52 #if SPACES_IN_FILENAMES
53 extern char openquote;
54 extern char closequote;
55 #endif
56 #if HAVE_STAT_INO
57 extern ino_t curr_ino;
58 extern dev_t curr_dev;
59 #endif
60
61 /*
62 * Remove quotes around a filename.
63 */
shell_unquote(constant char * str)64 public char * shell_unquote(constant char *str)
65 {
66 char *name;
67 char *p;
68
69 name = p = (char *) ecalloc(strlen(str)+1, sizeof(char));
70 if (*str == openquote)
71 {
72 str++;
73 while (*str != '\0')
74 {
75 if (*str == closequote)
76 {
77 if (str[1] != closequote)
78 break;
79 str++;
80 }
81 *p++ = *str++;
82 }
83 } else
84 {
85 constant char *esc = get_meta_escape();
86 size_t esclen = strlen(esc);
87 while (*str != '\0')
88 {
89 if (esclen > 0 && strncmp(str, esc, esclen) == 0)
90 str += esclen;
91 *p++ = *str++;
92 }
93 }
94 *p = '\0';
95 return (name);
96 }
97
98 /*
99 * Get the shell's escape character.
100 */
get_meta_escape(void)101 public constant char * get_meta_escape(void)
102 {
103 constant char *s;
104
105 s = lgetenv("LESSMETAESCAPE");
106 if (s == NULL)
107 s = DEF_METAESCAPE;
108 return (s);
109 }
110
111 /*
112 * Get the characters which the shell considers to be "metacharacters".
113 */
metachars(void)114 static constant char * metachars(void)
115 {
116 static constant char *mchars = NULL;
117
118 if (mchars == NULL)
119 {
120 mchars = lgetenv("LESSMETACHARS");
121 if (mchars == NULL)
122 mchars = DEF_METACHARS;
123 }
124 return (mchars);
125 }
126
127 /*
128 * Is this a shell metacharacter?
129 */
metachar(char c)130 static lbool metachar(char c)
131 {
132 return (strchr(metachars(), c) != NULL);
133 }
134
135 /*
136 * Must use quotes rather than escape char for this metachar?
137 */
must_quote(char c)138 static lbool must_quote(char c)
139 {
140 /* {{ Maybe the set of must_quote chars should be configurable? }} */
141 return (c == '\n');
142 }
143
144 /*
145 * Insert a backslash before each metacharacter in a string.
146 */
shell_quoten(constant char * s,size_t slen)147 public char * shell_quoten(constant char *s, size_t slen)
148 {
149 constant char *p;
150 char *np;
151 char *newstr;
152 size_t len;
153 constant char *esc = get_meta_escape();
154 size_t esclen = strlen(esc);
155 lbool use_quotes = FALSE;
156 lbool have_quotes = FALSE;
157
158 /*
159 * Determine how big a string we need to allocate.
160 */
161 len = 1; /* Trailing null byte */
162 for (p = s; p < s + slen; p++)
163 {
164 len++;
165 if (*p == openquote || *p == closequote)
166 have_quotes = TRUE;
167 if (metachar(*p))
168 {
169 if (esclen == 0)
170 {
171 /*
172 * We've got a metachar, but this shell
173 * doesn't support escape chars. Use quotes.
174 */
175 use_quotes = TRUE;
176 } else if (must_quote(*p))
177 {
178 len += 3; /* open quote + char + close quote */
179 } else
180 {
181 /*
182 * Allow space for the escape char.
183 */
184 len += esclen;
185 }
186 }
187 }
188 if (use_quotes)
189 {
190 if (have_quotes)
191 /*
192 * We can't quote a string that contains quotes.
193 */
194 return (NULL);
195 len = slen + 3;
196 }
197 /*
198 * Allocate and construct the new string.
199 */
200 newstr = np = (char *) ecalloc(len, sizeof(char));
201 if (use_quotes)
202 {
203 SNPRINTF4(newstr, len, "%c%.*s%c", openquote, (int) slen, s, closequote);
204 } else
205 {
206 constant char *es = s + slen;
207 while (s < es)
208 {
209 if (!metachar(*s))
210 {
211 *np++ = *s++;
212 } else if (must_quote(*s))
213 {
214 /* Surround the char with quotes. */
215 *np++ = openquote;
216 *np++ = *s++;
217 *np++ = closequote;
218 } else
219 {
220 /* Insert an escape char before the char. */
221 strcpy(np, esc);
222 np += esclen;
223 *np++ = *s++;
224 }
225 }
226 *np = '\0';
227 }
228 return (newstr);
229 }
230
shell_quote(constant char * s)231 public char * shell_quote(constant char *s)
232 {
233 return shell_quoten(s, strlen(s));
234 }
235
236 /*
237 * Return a pathname that points to a specified file in a specified directory.
238 * Return NULL if the file does not exist in the directory.
239 */
dirfile(constant char * dirname,constant char * filename,int must_exist)240 public char * dirfile(constant char *dirname, constant char *filename, int must_exist)
241 {
242 char *pathname;
243 size_t len;
244 int f;
245
246 if (dirname == NULL || *dirname == '\0')
247 return (NULL);
248 /*
249 * Construct the full pathname.
250 */
251 len = strlen(dirname) + strlen(filename) + 2;
252 pathname = (char *) calloc(len, sizeof(char));
253 if (pathname == NULL)
254 return (NULL);
255 SNPRINTF3(pathname, len, "%s%s%s", dirname, PATHNAME_SEP, filename);
256 if (must_exist)
257 {
258 /*
259 * Make sure the file exists.
260 */
261 f = open(pathname, OPEN_READ);
262 if (f < 0)
263 {
264 free(pathname);
265 pathname = NULL;
266 } else
267 {
268 close(f);
269 }
270 }
271 return (pathname);
272 }
273
274 /*
275 * Return the full pathname of the given file in the "home directory".
276 */
homefile(constant char * filename)277 public char * homefile(constant char *filename)
278 {
279 char *pathname;
280
281 /* Try $HOME/filename. */
282 pathname = dirfile(lgetenv("HOME"), filename, 1);
283 if (pathname != NULL)
284 return (pathname);
285 #if OS2
286 /* Try $INIT/filename. */
287 pathname = dirfile(lgetenv("INIT"), filename, 1);
288 if (pathname != NULL)
289 return (pathname);
290 #endif
291 #if MSDOS_COMPILER || OS2
292 /* Look for the file anywhere on search path. */
293 pathname = (char *) ecalloc(_MAX_PATH, sizeof(char));
294 #if MSDOS_COMPILER==DJGPPC
295 {
296 char *res = searchpath(filename);
297 if (res == 0)
298 *pathname = '\0';
299 else
300 strcpy(pathname, res);
301 }
302 #else
303 _searchenv(filename, "PATH", pathname);
304 #endif
305 if (*pathname != '\0')
306 return (pathname);
307 free(pathname);
308 #endif
309 return (NULL);
310 }
311
312 typedef struct xcpy { char *dest; size_t copied; } xcpy;
313
xcpy_char(xcpy * xp,char ch)314 static void xcpy_char(xcpy *xp, char ch)
315 {
316 if (xp->dest != NULL) *(xp->dest)++ = ch;
317 xp->copied++;
318 }
319
xcpy_filename(xcpy * xp,constant char * str)320 static void xcpy_filename(xcpy *xp, constant char *str)
321 {
322 /* If filename contains spaces, quote it
323 * to prevent edit_list from splitting it. */
324 lbool quote = (strchr(str, ' ') != NULL);
325 if (quote)
326 xcpy_char(xp, openquote);
327 for (; *str != '\0'; str++)
328 xcpy_char(xp, *str);
329 if (quote)
330 xcpy_char(xp, closequote);
331 }
332
fexpand_copy(constant char * fr,char * to)333 static size_t fexpand_copy(constant char *fr, char *to)
334 {
335 xcpy xp;
336 xp.copied = 0;
337 xp.dest = to;
338
339 for (; *fr != '\0'; fr++)
340 {
341 lbool expand = FALSE;
342 switch (*fr)
343 {
344 case '%':
345 case '#':
346 if (fr[1] == *fr)
347 {
348 /* Two identical chars. Output just one. */
349 fr += 1;
350 } else
351 {
352 /* Single char. Expand to a (quoted) file name. */
353 expand = TRUE;
354 }
355 break;
356 default:
357 break;
358 }
359 if (expand)
360 {
361 IFILE ifile = (*fr == '%') ? curr_ifile : (*fr == '#') ? old_ifile : NULL_IFILE;
362 if (ifile == NULL_IFILE)
363 xcpy_char(&xp, *fr);
364 else
365 xcpy_filename(&xp, get_filename(ifile));
366 } else
367 {
368 xcpy_char(&xp, *fr);
369 }
370 }
371 xcpy_char(&xp, '\0');
372 return xp.copied;
373 }
374
375 /*
376 * Expand a string, substituting any "%" with the current filename,
377 * and any "#" with the previous filename.
378 * But a string of N "%"s is just replaced with N-1 "%"s.
379 * Likewise for a string of N "#"s.
380 * {{ This is a lot of work just to support % and #. }}
381 */
fexpand(constant char * s)382 public char * fexpand(constant char *s)
383 {
384 size_t n;
385 char *e;
386
387 /*
388 * Make one pass to see how big a buffer we
389 * need to allocate for the expanded string.
390 */
391 n = fexpand_copy(s, NULL);
392 e = (char *) ecalloc(n, sizeof(char));
393
394 /*
395 * Now copy the string, expanding any "%" or "#".
396 */
397 fexpand_copy(s, e);
398 return (e);
399 }
400
401
402 #if TAB_COMPLETE_FILENAME
403
404 /*
405 * Return a blank-separated list of filenames which "complete"
406 * the given string.
407 */
fcomplete(constant char * s)408 public char * fcomplete(constant char *s)
409 {
410 char *fpat;
411 char *qs;
412 char *uqs;
413
414 /* {{ Is this needed? lglob calls secure_allow. }} */
415 if (!secure_allow(SF_GLOB))
416 return (NULL);
417 /*
418 * Complete the filename "s" by globbing "s*".
419 */
420 #if MSDOS_COMPILER && (MSDOS_COMPILER == MSOFTC || MSDOS_COMPILER == BORLANDC)
421 /*
422 * But in DOS, we have to glob "s*.*".
423 * But if the final component of the filename already has
424 * a dot in it, just do "s*".
425 * (Thus, "FILE" is globbed as "FILE*.*",
426 * but "FILE.A" is globbed as "FILE.A*").
427 */
428 {
429 constant char *slash;
430 size_t len;
431 for (slash = s+strlen(s)-1; slash > s; slash--)
432 if (*slash == *PATHNAME_SEP || *slash == '/')
433 break;
434 len = strlen(s) + 4;
435 fpat = (char *) ecalloc(len, sizeof(char));
436 if (strchr(slash, '.') == NULL)
437 SNPRINTF1(fpat, len, "%s*.*", s);
438 else
439 SNPRINTF1(fpat, len, "%s*", s);
440 }
441 #else
442 {
443 size_t len = strlen(s) + 2;
444 fpat = (char *) ecalloc(len, sizeof(char));
445 SNPRINTF1(fpat, len, "%s*", s);
446 }
447 #endif
448 qs = lglob(fpat);
449 uqs = shell_unquote(qs);
450 if (strcmp(uqs, fpat) == 0)
451 {
452 /*
453 * The filename didn't expand.
454 */
455 free(qs);
456 qs = NULL;
457 }
458 free(uqs);
459 free(fpat);
460 return (qs);
461 }
462 #endif
463
464 /*
465 * Try to determine if a file is "binary".
466 * This is just a guess, and we need not try too hard to make it accurate.
467 *
468 * The number of bytes read is returned to the caller, because it will
469 * be used later to compare to st_size from stat(2) to see if the file
470 * is lying about its size.
471 */
bin_file(int f,ssize_t * n)472 public int bin_file(int f, ssize_t *n)
473 {
474 int bin_count = 0;
475 char data[256];
476 constant char* p;
477 constant char* edata;
478
479 if (!seekable(f))
480 return (0);
481 if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
482 return (0);
483 *n = read(f, data, sizeof(data));
484 if (*n <= 0)
485 return (0);
486 edata = &data[*n];
487 for (p = data; p < edata; )
488 {
489 if (utf_mode && !is_utf8_well_formed(p, (int) ptr_diff(edata,p)))
490 {
491 bin_count++;
492 utf_skip_to_lead(&p, edata);
493 } else
494 {
495 LWCHAR c = step_charc(&p, +1, edata);
496 struct ansi_state *pansi;
497 if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(c)) != NULL)
498 {
499 skip_ansi(pansi, &p, edata);
500 ansi_done(pansi);
501 } else if (binary_char(c))
502 bin_count++;
503 }
504 }
505 /*
506 * Call it a binary file if there are more than 5 binary characters
507 * in the first 256 bytes of the file.
508 */
509 return (bin_count > 5);
510 }
511
512 /*
513 * Try to determine the size of a file by seeking to the end.
514 */
seek_filesize(int f)515 static POSITION seek_filesize(int f)
516 {
517 less_off_t spos;
518
519 spos = less_lseek(f, (less_off_t)0, SEEK_END);
520 if (spos == BAD_LSEEK)
521 return (NULL_POSITION);
522 return ((POSITION) spos);
523 }
524
525 #if HAVE_POPEN
526 /*
527 * Read a string from a file.
528 * Return a pointer to the string in memory.
529 */
readfd(FILE * fd)530 public char * readfd(FILE *fd)
531 {
532 struct xbuffer xbuf;
533 xbuf_init(&xbuf);
534 for (;;)
535 {
536 int ch;
537 if ((ch = getc(fd)) == '\n' || ch == EOF)
538 break;
539 xbuf_add_char(&xbuf, (char) ch);
540 }
541 xbuf_add_char(&xbuf, '\0');
542 return (char *) xbuf.data;
543 }
544
545 /*
546 * Execute a shell command.
547 * Return a pointer to a pipe connected to the shell command's standard output.
548 */
shellcmd(constant char * cmd)549 static FILE * shellcmd(constant char *cmd)
550 {
551 FILE *fd;
552
553 #if HAVE_SHELL
554 constant char *shell;
555
556 shell = lgetenv("SHELL");
557 if (!isnullenv(shell))
558 {
559 char *scmd;
560 char *esccmd;
561
562 /*
563 * Read the output of <$SHELL -c cmd>.
564 * Escape any metacharacters in the command.
565 */
566 esccmd = shell_quote(cmd);
567 if (esccmd == NULL)
568 {
569 fd = popen(cmd, "r");
570 } else
571 {
572 size_t len = strlen(shell) + strlen(esccmd) + 5;
573 scmd = (char *) ecalloc(len, sizeof(char));
574 SNPRINTF3(scmd, len, "%s %s %s", shell, shell_coption(), esccmd);
575 free(esccmd);
576 fd = popen(scmd, "r");
577 free(scmd);
578 }
579 } else
580 #endif
581 {
582 fd = popen(cmd, "r");
583 }
584 /*
585 * Redirection in `popen' might have messed with the
586 * standard devices. Restore binary input mode.
587 */
588 SET_BINARY(0);
589 return (fd);
590 }
591
592 #endif /* HAVE_POPEN */
593
594
595 /*
596 * Expand a filename, doing any system-specific metacharacter substitutions.
597 */
lglob(constant char * afilename)598 public char * lglob(constant char *afilename)
599 {
600 char *gfilename;
601 char *filename = fexpand(afilename);
602
603 if (!secure_allow(SF_GLOB))
604 return (filename);
605
606 #ifdef DECL_GLOB_LIST
607 {
608 /*
609 * The globbing function returns a list of names.
610 */
611 size_t length;
612 char *p;
613 char *qfilename;
614 DECL_GLOB_LIST(list)
615
616 GLOB_LIST(filename, list);
617 if (GLOB_LIST_FAILED(list))
618 {
619 return (filename);
620 }
621 length = 1; /* Room for trailing null byte */
622 for (SCAN_GLOB_LIST(list, p))
623 {
624 INIT_GLOB_LIST(list, p);
625 qfilename = shell_quote(p);
626 if (qfilename != NULL)
627 {
628 length += strlen(qfilename) + 1;
629 free(qfilename);
630 }
631 }
632 gfilename = (char *) ecalloc(length, sizeof(char));
633 for (SCAN_GLOB_LIST(list, p))
634 {
635 INIT_GLOB_LIST(list, p);
636 qfilename = shell_quote(p);
637 if (qfilename != NULL)
638 {
639 sprintf(gfilename + strlen(gfilename), "%s ", qfilename);
640 free(qfilename);
641 }
642 }
643 /*
644 * Overwrite the final trailing space with a null terminator.
645 */
646 *--p = '\0';
647 GLOB_LIST_DONE(list);
648 }
649 #else
650 #ifdef DECL_GLOB_NAME
651 {
652 /*
653 * The globbing function returns a single name, and
654 * is called multiple times to walk thru all names.
655 */
656 char *p;
657 size_t len;
658 size_t n;
659 char *pfilename;
660 char *qfilename;
661 DECL_GLOB_NAME(fnd,drive,dir,fname,ext,handle)
662
663 GLOB_FIRST_NAME(filename, &fnd, handle);
664 if (GLOB_FIRST_FAILED(handle))
665 {
666 return (filename);
667 }
668
669 _splitpath(filename, drive, dir, fname, ext);
670 len = 100;
671 gfilename = (char *) ecalloc(len, sizeof(char));
672 p = gfilename;
673 do {
674 n = strlen(drive) + strlen(dir) + strlen(fnd.GLOB_NAME) + 1;
675 pfilename = (char *) ecalloc(n, sizeof(char));
676 SNPRINTF3(pfilename, n, "%s%s%s", drive, dir, fnd.GLOB_NAME);
677 qfilename = shell_quote(pfilename);
678 free(pfilename);
679 if (qfilename != NULL)
680 {
681 n = strlen(qfilename);
682 while (p - gfilename + n + 2 >= len)
683 {
684 /*
685 * No room in current buffer.
686 * Allocate a bigger one.
687 */
688 len *= 2;
689 *p = '\0';
690 p = (char *) ecalloc(len, sizeof(char));
691 strcpy(p, gfilename);
692 free(gfilename);
693 gfilename = p;
694 p = gfilename + strlen(gfilename);
695 }
696 strcpy(p, qfilename);
697 free(qfilename);
698 p += n;
699 *p++ = ' ';
700 }
701 } while (GLOB_NEXT_NAME(handle, &fnd) == 0);
702
703 /*
704 * Overwrite the final trailing space with a null terminator.
705 */
706 *--p = '\0';
707 GLOB_NAME_DONE(handle);
708 }
709 #else
710 #if HAVE_POPEN
711 {
712 /*
713 * We get the shell to glob the filename for us by passing
714 * an "echo" command to the shell and reading its output.
715 */
716 FILE *fd;
717 constant char *s;
718 constant char *lessecho;
719 char *cmd;
720 constant char *esc;
721 char *qesc;
722 size_t len;
723
724 esc = get_meta_escape();
725 if (strlen(esc) == 0)
726 esc = "-";
727 qesc = shell_quote(esc);
728 if (qesc == NULL)
729 {
730 return (filename);
731 }
732 lessecho = lgetenv("LESSECHO");
733 if (isnullenv(lessecho))
734 lessecho = "lessecho";
735 /*
736 * Invoke lessecho, and read its output (a globbed list of filenames).
737 */
738 len = strlen(lessecho) + strlen(filename) + (7*strlen(metachars())) + 24;
739 cmd = (char *) ecalloc(len, sizeof(char));
740 SNPRINTF4(cmd, len, "%s -p0x%x -d0x%x -e%s ", lessecho,
741 (unsigned char) openquote, (unsigned char) closequote, qesc);
742 free(qesc);
743 for (s = metachars(); *s != '\0'; s++)
744 sprintf(cmd + strlen(cmd), "-n0x%x ", (unsigned char) *s);
745 sprintf(cmd + strlen(cmd), "-- %s", filename);
746 fd = shellcmd(cmd);
747 free(cmd);
748 if (fd == NULL)
749 {
750 /*
751 * Cannot create the pipe.
752 * Just return the original (fexpanded) filename.
753 */
754 return (filename);
755 }
756 gfilename = readfd(fd);
757 pclose(fd);
758 if (*gfilename == '\0')
759 {
760 free(gfilename);
761 return (filename);
762 }
763 }
764 #else
765 /*
766 * No globbing functions at all. Just use the fexpanded filename.
767 */
768 gfilename = save(filename);
769 #endif
770 #endif
771 #endif
772 free(filename);
773 return (gfilename);
774 }
775
776 /*
777 * Does path not represent something in the file system?
778 */
is_fake_pathname(constant char * path)779 public lbool is_fake_pathname(constant char *path)
780 {
781 return (strcmp(path, "-") == 0 ||
782 strcmp(path, FAKE_HELPFILE) == 0 || strcmp(path, FAKE_EMPTYFILE) == 0);
783 }
784
785 /*
786 * Return canonical pathname.
787 */
lrealpath(constant char * path)788 public char * lrealpath(constant char *path)
789 {
790 if (!is_fake_pathname(path))
791 {
792 #if HAVE_REALPATH
793 /*
794 * Not all systems support the POSIX.1-2008 realpath() behavior
795 * of allocating when passing a NULL argument. And PATH_MAX is
796 * not required to be defined, or might contain an exceedingly
797 * big value. We assume that if it is not defined (such as on
798 * GNU/Hurd), then realpath() accepts NULL.
799 */
800 #ifndef PATH_MAX
801 char *rpath;
802
803 rpath = realpath(path, NULL);
804 if (rpath != NULL)
805 return (rpath);
806 #else
807 char rpath[PATH_MAX];
808 if (realpath(path, rpath) != NULL)
809 return (save(rpath));
810 #endif
811 #endif
812 }
813 return (save(path));
814 }
815
816 #if HAVE_POPEN
817 /*
818 * Return number of %s escapes in a string.
819 * Return a large number if there are any other % escapes besides %s.
820 */
num_pct_s(constant char * lessopen)821 static int num_pct_s(constant char *lessopen)
822 {
823 int num = 0;
824
825 while (*lessopen != '\0')
826 {
827 if (*lessopen == '%')
828 {
829 if (lessopen[1] == '%')
830 ++lessopen;
831 else if (lessopen[1] == 's')
832 ++num;
833 else
834 return (999);
835 }
836 ++lessopen;
837 }
838 return (num);
839 }
840 #endif
841
842 /*
843 * See if we should open a "replacement file"
844 * instead of the file we're about to open.
845 */
open_altfile(constant char * filename,int * pf,void ** pfd)846 public char * open_altfile(constant char *filename, int *pf, void **pfd)
847 {
848 #if !HAVE_POPEN
849 return (NULL);
850 #else
851 constant char *lessopen;
852 char *qfilename;
853 char *cmd;
854 size_t len;
855 FILE *fd;
856 #if HAVE_FILENO
857 int returnfd = 0;
858 #endif
859
860 if (!secure_allow(SF_LESSOPEN))
861 return (NULL);
862 if (!use_lessopen)
863 return (NULL);
864 ch_ungetchar(-1);
865 if ((lessopen = lgetenv("LESSOPEN")) == NULL)
866 return (NULL);
867 while (*lessopen == '|')
868 {
869 /*
870 * If LESSOPEN starts with a |, it indicates
871 * a "pipe preprocessor".
872 */
873 #if !HAVE_FILENO
874 error("LESSOPEN pipe is not supported", NULL_PARG);
875 return (NULL);
876 #else
877 lessopen++;
878 returnfd++;
879 #endif
880 }
881 if (*lessopen == '-')
882 {
883 /*
884 * Lessopen preprocessor will accept "-" as a filename.
885 */
886 lessopen++;
887 } else
888 {
889 if (strcmp(filename, "-") == 0)
890 return (NULL);
891 }
892 if (num_pct_s(lessopen) != 1)
893 {
894 error("LESSOPEN ignored: must contain exactly one %%s", NULL_PARG);
895 return (NULL);
896 }
897
898 qfilename = shell_quote(filename);
899 len = strlen(lessopen) + strlen(qfilename) + 2;
900 cmd = (char *) ecalloc(len, sizeof(char));
901 SNPRINTF1(cmd, len, lessopen, qfilename);
902 free(qfilename);
903 fd = shellcmd(cmd);
904 free(cmd);
905 if (fd == NULL)
906 {
907 /*
908 * Cannot create the pipe.
909 */
910 return (NULL);
911 }
912 #if HAVE_FILENO
913 if (returnfd)
914 {
915 char c;
916 int f;
917
918 /*
919 * The alt file is a pipe. Read one char
920 * to see if the pipe will produce any data.
921 * If it does, push the char back on the pipe.
922 */
923 f = fileno(fd);
924 SET_BINARY(f);
925 if (read(f, &c, 1) != 1)
926 {
927 /*
928 * Pipe is empty.
929 * If more than 1 pipe char was specified,
930 * the exit status tells whether the file itself
931 * is empty, or if there is no alt file.
932 * If only one pipe char, just assume no alt file.
933 */
934 int status = pclose(fd);
935 if (returnfd > 1 && status == 0) {
936 /* File is empty. */
937 *pfd = NULL;
938 *pf = -1;
939 return (save(FAKE_EMPTYFILE));
940 }
941 /* No alt file. */
942 return (NULL);
943 }
944 /* Alt pipe contains data, so use it. */
945 ch_ungetchar(c);
946 *pfd = (void *) fd;
947 *pf = f;
948 return (save("-"));
949 }
950 #endif
951 /* The alt file is a regular file. Read its name from LESSOPEN. */
952 cmd = readfd(fd);
953 pclose(fd);
954 if (*cmd == '\0')
955 {
956 /*
957 * Pipe is empty. This means there is no alt file.
958 */
959 free(cmd);
960 return (NULL);
961 }
962 return (cmd);
963 #endif /* HAVE_POPEN */
964 }
965
966 /*
967 * Close a replacement file.
968 */
close_altfile(constant char * altfilename,constant char * filename)969 public void close_altfile(constant char *altfilename, constant char *filename)
970 {
971 #if HAVE_POPEN
972 constant char *lessclose;
973 char *qfilename;
974 char *qaltfilename;
975 FILE *fd;
976 char *cmd;
977 size_t len;
978
979 if (!secure_allow(SF_LESSOPEN))
980 return;
981 if ((lessclose = lgetenv("LESSCLOSE")) == NULL)
982 return;
983 if (num_pct_s(lessclose) > 2)
984 {
985 error("LESSCLOSE ignored; must contain no more than 2 %%s", NULL_PARG);
986 return;
987 }
988 qfilename = shell_quote(filename);
989 qaltfilename = shell_quote(altfilename);
990 len = strlen(lessclose) + strlen(qfilename) + strlen(qaltfilename) + 2;
991 cmd = (char *) ecalloc(len, sizeof(char));
992 SNPRINTF2(cmd, len, lessclose, qfilename, qaltfilename);
993 free(qaltfilename);
994 free(qfilename);
995 fd = shellcmd(cmd);
996 free(cmd);
997 if (fd != NULL)
998 pclose(fd);
999 #endif
1000 }
1001
1002 /*
1003 * Is the specified file a directory?
1004 */
is_dir(constant char * filename)1005 public lbool is_dir(constant char *filename)
1006 {
1007 lbool isdir = FALSE;
1008
1009 #if HAVE_STAT
1010 {
1011 int r;
1012 less_stat_t statbuf;
1013
1014 r = less_stat(filename, &statbuf);
1015 isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
1016 }
1017 #else
1018 #ifdef _OSK
1019 {
1020 int f;
1021
1022 f = open(filename, S_IREAD | S_IFDIR);
1023 if (f >= 0)
1024 close(f);
1025 isdir = (f >= 0);
1026 }
1027 #endif
1028 #endif
1029 return (isdir);
1030 }
1031
1032 /*
1033 * Returns NULL if the file can be opened and
1034 * is an ordinary file, otherwise an error message
1035 * (if it cannot be opened or is a directory, etc.)
1036 */
bad_file(constant char * filename)1037 public char * bad_file(constant char *filename)
1038 {
1039 char *m = NULL;
1040
1041 if (!force_open && is_dir(filename))
1042 {
1043 static char is_a_dir[] = " is a directory";
1044
1045 m = (char *) ecalloc(strlen(filename) + sizeof(is_a_dir),
1046 sizeof(char));
1047 strcpy(m, filename);
1048 strcat(m, is_a_dir);
1049 } else
1050 {
1051 #if HAVE_STAT
1052 int r;
1053 less_stat_t statbuf;
1054
1055 r = less_stat(filename, &statbuf);
1056 if (r < 0)
1057 {
1058 m = errno_message(filename);
1059 } else if (force_open)
1060 {
1061 m = NULL;
1062 } else if (!S_ISREG(statbuf.st_mode))
1063 {
1064 static char not_reg[] = " is not a regular file (use -f to see it)";
1065 m = (char *) ecalloc(strlen(filename) + sizeof(not_reg),
1066 sizeof(char));
1067 strcpy(m, filename);
1068 strcat(m, not_reg);
1069 }
1070 #endif
1071 }
1072 return (m);
1073 }
1074
1075 /*
1076 * Return the size of a file, as cheaply as possible.
1077 * In Unix, we can stat the file.
1078 */
filesize(int f)1079 public POSITION filesize(int f)
1080 {
1081 #if HAVE_STAT
1082 less_stat_t statbuf;
1083
1084 if (less_fstat(f, &statbuf) >= 0)
1085 return ((POSITION) statbuf.st_size);
1086 #else
1087 #ifdef _OSK
1088 long size;
1089
1090 if ((size = (long) _gs_size(f)) >= 0)
1091 return ((POSITION) size);
1092 #endif
1093 #endif
1094 return (seek_filesize(f));
1095 }
1096
curr_ifile_changed(void)1097 public lbool curr_ifile_changed(void)
1098 {
1099 #if HAVE_STAT_INO
1100 /*
1101 * If the file's i-number or device has changed,
1102 * or if the file is smaller than it previously was,
1103 * the file must be different.
1104 */
1105 struct stat st;
1106 POSITION curr_pos = ch_tell();
1107 int r = stat(get_filename(curr_ifile), &st);
1108 if (r == 0 && (st.st_ino != curr_ino ||
1109 st.st_dev != curr_dev ||
1110 (curr_pos != NULL_POSITION && st.st_size < curr_pos)))
1111 return (TRUE);
1112 #endif
1113 return (FALSE);
1114 }
1115
1116 /*
1117 *
1118 */
shell_coption(void)1119 public constant char * shell_coption(void)
1120 {
1121 return ("-c");
1122 }
1123
1124 /*
1125 * Return last component of a pathname.
1126 */
last_component(constant char * name)1127 public constant char * last_component(constant char *name)
1128 {
1129 constant char *slash;
1130
1131 for (slash = name + strlen(name); slash > name; )
1132 {
1133 --slash;
1134 if (*slash == *PATHNAME_SEP || *slash == '/')
1135 return (slash + 1);
1136 }
1137 return (name);
1138 }
1139