1 /*
2 * Copyright (C) 1984-2026 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 && MSDOS_COMPILER!=WIN32C) || 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 lbool 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 constant int umax = 4;
479
480 if (!seekable(f))
481 return FALSE;
482 if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
483 return FALSE;
484 *n = read(f, data, sizeof(data));
485 if (*n <= umax)
486 return FALSE;
487 edata = &data[*n];
488 for (p = data; p+umax < edata; )
489 {
490 if (utf_mode && !is_utf8_well_formed(p, (int) ptr_diff(edata,p)))
491 {
492 bin_count++;
493 utf_skip_to_lead(&p, edata);
494 } else
495 {
496 LWCHAR c = step_charc(&p, +1, edata);
497 struct ansi_state *pansi;
498 if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(c)) != NULL)
499 {
500 skip_ansi(pansi, c, &p, edata);
501 ansi_done(pansi);
502 } else if (binary_char(c))
503 bin_count++;
504 }
505 }
506 /*
507 * Call it a binary file if there are more than 5 binary characters
508 * in the first 256 bytes of the file.
509 */
510 return (bin_count > 5);
511 }
512
513 /*
514 * Try to determine the size of a file by seeking to the end.
515 */
seek_filesize(int f)516 static POSITION seek_filesize(int f)
517 {
518 less_off_t spos;
519
520 spos = less_lseek(f, (less_off_t)0, SEEK_END);
521 if (spos == BAD_LSEEK)
522 return (NULL_POSITION);
523 return ((POSITION) spos);
524 }
525
526 #if HAVE_POPEN
527 /*
528 * Read a string from a file.
529 * Return a pointer to the string in memory.
530 */
readfd(FILE * fd)531 public char * readfd(FILE *fd)
532 {
533 struct xbuffer xbuf;
534 xbuf_init(&xbuf);
535 for (;;)
536 {
537 int ch;
538 if ((ch = getc(fd)) == '\n' || ch == EOF)
539 break;
540 xbuf_add_char(&xbuf, (char) ch);
541 }
542 xbuf_add_char(&xbuf, '\0');
543 return (char *) xbuf.data;
544 }
545
546 /*
547 * Execute a shell command.
548 * Return a pointer to a pipe connected to the shell command's standard output.
549 */
shellcmd(constant char * cmd)550 static FILE * shellcmd(constant char *cmd)
551 {
552 FILE *fd;
553
554 #if HAVE_SHELL
555 constant char *shell;
556
557 shell = lgetenv("SHELL");
558 if (!isnullenv(shell))
559 {
560 char *scmd;
561 char *esccmd;
562
563 /*
564 * Read the output of <$SHELL -c cmd>.
565 * Escape any metacharacters in the command.
566 */
567 esccmd = shell_quote(cmd);
568 if (esccmd == NULL)
569 {
570 fd = popen(cmd, "r");
571 } else
572 {
573 constant char *copt = shell_coption();
574 size_t len = strlen(shell) + strlen(esccmd) + strlen(copt) + 3;
575 scmd = (char *) ecalloc(len, sizeof(char));
576 SNPRINTF3(scmd, len, "%s %s %s", shell, copt, esccmd);
577 free(esccmd);
578 fd = popen(scmd, "r");
579 free(scmd);
580 }
581 } else
582 #endif
583 {
584 fd = popen(cmd, "r");
585 }
586 /*
587 * Redirection in `popen' might have messed with the
588 * standard devices. Restore binary input mode.
589 */
590 SET_BINARY(0);
591 return (fd);
592 }
593
594 #endif /* HAVE_POPEN */
595
596
597 /*
598 * Expand a filename, doing any system-specific metacharacter substitutions.
599 */
lglob(constant char * afilename)600 public char * lglob(constant char *afilename)
601 {
602 char *gfilename;
603 char *filename = fexpand(afilename);
604
605 if (!secure_allow(SF_GLOB))
606 return (filename);
607
608 #ifdef DECL_GLOB_LIST
609 {
610 /*
611 * The globbing function returns a list of names.
612 */
613 size_t length;
614 char *p;
615 char *qfilename;
616 DECL_GLOB_LIST(list)
617
618 GLOB_LIST(filename, list);
619 if (GLOB_LIST_FAILED(list))
620 {
621 return (filename);
622 }
623 length = 1; /* Room for trailing null byte */
624 for (SCAN_GLOB_LIST(list, p))
625 {
626 INIT_GLOB_LIST(list, p);
627 qfilename = shell_quote(p);
628 if (qfilename != NULL)
629 {
630 length += strlen(qfilename) + 1;
631 free(qfilename);
632 }
633 }
634 gfilename = (char *) ecalloc(length, sizeof(char));
635 for (SCAN_GLOB_LIST(list, p))
636 {
637 INIT_GLOB_LIST(list, p);
638 qfilename = shell_quote(p);
639 if (qfilename != NULL)
640 {
641 sprintf(gfilename + strlen(gfilename), "%s ", qfilename);
642 free(qfilename);
643 }
644 }
645 /*
646 * Overwrite the final trailing space with a null terminator.
647 */
648 *--p = '\0';
649 GLOB_LIST_DONE(list);
650 }
651 #else
652 #ifdef DECL_GLOB_NAME
653 {
654 /*
655 * The globbing function returns a single name, and
656 * is called multiple times to walk thru all names.
657 */
658 char *p;
659 size_t len;
660 size_t n;
661 char *pfilename;
662 char *qfilename;
663 DECL_GLOB_NAME(fnd,drive,dir,fname,ext,handle)
664
665 GLOB_FIRST_NAME(filename, &fnd, handle);
666 if (GLOB_FIRST_FAILED(handle))
667 {
668 return (filename);
669 }
670
671 _splitpath(filename, drive, dir, fname, ext);
672 len = 100;
673 gfilename = (char *) ecalloc(len, sizeof(char));
674 p = gfilename;
675 do {
676 n = strlen(drive) + strlen(dir) + strlen(fnd.GLOB_NAME) + 1;
677 pfilename = (char *) ecalloc(n, sizeof(char));
678 SNPRINTF3(pfilename, n, "%s%s%s", drive, dir, fnd.GLOB_NAME);
679 qfilename = shell_quote(pfilename);
680 free(pfilename);
681 if (qfilename != NULL)
682 {
683 n = strlen(qfilename);
684 while (p - gfilename + n + 2 >= len)
685 {
686 /*
687 * No room in current buffer.
688 * Allocate a bigger one.
689 */
690 len *= 2;
691 *p = '\0';
692 p = (char *) ecalloc(len, sizeof(char));
693 strcpy(p, gfilename);
694 free(gfilename);
695 gfilename = p;
696 p = gfilename + strlen(gfilename);
697 }
698 strcpy(p, qfilename);
699 free(qfilename);
700 p += n;
701 *p++ = ' ';
702 }
703 } while (GLOB_NEXT_NAME(handle, &fnd) == 0);
704
705 /*
706 * Overwrite the final trailing space with a null terminator.
707 */
708 *--p = '\0';
709 GLOB_NAME_DONE(handle);
710 }
711 #else
712 #if HAVE_POPEN
713 {
714 /*
715 * We get the shell to glob the filename for us by passing
716 * an "echo" command to the shell and reading its output.
717 */
718 FILE *fd;
719 constant char *s;
720 constant char *lessecho;
721 char *cmd;
722 constant char *esc;
723 char *qesc;
724 size_t len;
725
726 esc = get_meta_escape();
727 if (strlen(esc) == 0)
728 esc = "-";
729 qesc = shell_quote(esc);
730 if (qesc == NULL)
731 {
732 return (filename);
733 }
734 lessecho = lgetenv("LESSECHO");
735 if (isnullenv(lessecho))
736 lessecho = "lessecho";
737 /*
738 * Invoke lessecho, and read its output (a globbed list of filenames).
739 */
740 len = strlen(lessecho) + strlen(filename) + (7*strlen(metachars())) + 24;
741 cmd = (char *) ecalloc(len, sizeof(char));
742 SNPRINTF4(cmd, len, "%s -p0x%x -d0x%x -e%s ", lessecho,
743 (unsigned char) openquote, (unsigned char) closequote, qesc);
744 free(qesc);
745 for (s = metachars(); *s != '\0'; s++)
746 sprintf(cmd + strlen(cmd), "-n0x%x ", (unsigned char) *s);
747 sprintf(cmd + strlen(cmd), "-- %s", filename);
748 fd = shellcmd(cmd);
749 free(cmd);
750 if (fd == NULL)
751 {
752 /*
753 * Cannot create the pipe.
754 * Just return the original (fexpanded) filename.
755 */
756 return (filename);
757 }
758 gfilename = readfd(fd);
759 pclose(fd);
760 if (*gfilename == '\0')
761 {
762 free(gfilename);
763 return (filename);
764 }
765 }
766 #else
767 /*
768 * No globbing functions at all. Just use the fexpanded filename.
769 */
770 gfilename = save(filename);
771 #endif
772 #endif
773 #endif
774 free(filename);
775 return (gfilename);
776 }
777
778 /*
779 * Does path not represent something in the file system?
780 */
is_fake_pathname(constant char * path)781 public lbool is_fake_pathname(constant char *path)
782 {
783 return (strcmp(path, "-") == 0 ||
784 strcmp(path, FAKE_HELPFILE) == 0 || strcmp(path, FAKE_EMPTYFILE) == 0);
785 }
786
787 /*
788 * Return canonical pathname.
789 */
lrealpath(constant char * path)790 public char * lrealpath(constant char *path)
791 {
792 if (!is_fake_pathname(path))
793 {
794 #if HAVE_REALPATH
795 /*
796 * Not all systems support the POSIX.1-2008 realpath() behavior
797 * of allocating when passing a NULL argument. And PATH_MAX is
798 * not required to be defined, or might contain an exceedingly
799 * big value. We assume that if it is not defined (such as on
800 * GNU/Hurd), then realpath() accepts NULL.
801 */
802 #ifndef PATH_MAX
803 char *rpath;
804
805 rpath = realpath(path, NULL);
806 if (rpath != NULL)
807 return (rpath);
808 #else
809 char rpath[PATH_MAX];
810 if (realpath(path, rpath) != NULL)
811 return (save(rpath));
812 #endif
813 #endif
814 }
815 return (save(path));
816 }
817
818 #if HAVE_POPEN
819 /*
820 * Return number of %s escapes in a string.
821 * Return a large number if there are any other % escapes besides %s.
822 */
num_pct_s(constant char * lessopen)823 static int num_pct_s(constant char *lessopen)
824 {
825 int num = 0;
826
827 while (*lessopen != '\0')
828 {
829 if (*lessopen == '%')
830 {
831 if (lessopen[1] == '%')
832 ++lessopen;
833 else if (lessopen[1] == 's')
834 ++num;
835 else
836 return (999);
837 }
838 ++lessopen;
839 }
840 return (num);
841 }
842 #endif
843
844 /*
845 * See if we should open a "replacement file"
846 * instead of the file we're about to open.
847 */
open_altfile(constant char * filename,int * pf,void ** pfd)848 public char * open_altfile(constant char *filename, int *pf, void **pfd)
849 {
850 #if !HAVE_POPEN
851 return (NULL);
852 #else
853 constant char *lessopen;
854 char *qfilename;
855 char *cmd;
856 size_t len;
857 FILE *fd;
858 #if HAVE_FILENO
859 int returnfd = 0;
860 #endif
861
862 if (!secure_allow(SF_LESSOPEN))
863 return (NULL);
864 if (!use_lessopen)
865 return (NULL);
866 ch_ungetchar(-1);
867 if ((lessopen = lgetenv("LESSOPEN")) == NULL)
868 return (NULL);
869 while (*lessopen == '|')
870 {
871 /*
872 * If LESSOPEN starts with a |, it indicates
873 * a "pipe preprocessor".
874 */
875 #if !HAVE_FILENO
876 error("LESSOPEN pipe is not supported", NULL_PARG);
877 return (NULL);
878 #else
879 lessopen++;
880 returnfd++;
881 #endif
882 }
883 if (*lessopen == '-')
884 {
885 /*
886 * Lessopen preprocessor will accept "-" as a filename.
887 */
888 lessopen++;
889 } else
890 {
891 if (strcmp(filename, "-") == 0)
892 return (NULL);
893 }
894 if (num_pct_s(lessopen) != 1)
895 {
896 error("LESSOPEN ignored: must contain exactly one %%s", NULL_PARG);
897 return (NULL);
898 }
899
900 qfilename = shell_quote(filename);
901 len = strlen(lessopen) + strlen(qfilename) + 2;
902 cmd = (char *) ecalloc(len, sizeof(char));
903 SNPRINTF1(cmd, len, lessopen, qfilename);
904 free(qfilename);
905 fd = shellcmd(cmd);
906 free(cmd);
907 if (fd == NULL)
908 {
909 /*
910 * Cannot create the pipe.
911 */
912 return (NULL);
913 }
914 #if HAVE_FILENO
915 if (returnfd)
916 {
917 unsigned char c;
918 int f;
919
920 /*
921 * The alt file is a pipe. Read one char
922 * to see if the pipe will produce any data.
923 * If it does, push the char back on the pipe.
924 */
925 f = fileno(fd);
926 SET_BINARY(f);
927 if (read(f, &c, 1) != 1)
928 {
929 /*
930 * Pipe is empty.
931 * If more than 1 pipe char was specified,
932 * the exit status tells whether the file itself
933 * is empty, or if there is no alt file.
934 * If only one pipe char, just assume no alt file.
935 */
936 int status = pclose(fd);
937 if (returnfd > 1 && status == 0) {
938 /* File is empty. */
939 *pfd = NULL;
940 *pf = -1;
941 return (save(FAKE_EMPTYFILE));
942 }
943 /* No alt file. */
944 return (NULL);
945 }
946 /* Alt pipe contains data, so use it. */
947 ch_ungetchar(c);
948 *pfd = (void *) fd;
949 *pf = f;
950 return (save("-"));
951 }
952 #endif
953 /* The alt file is a regular file. Read its name from LESSOPEN. */
954 cmd = readfd(fd);
955 pclose(fd);
956 if (*cmd == '\0')
957 {
958 /*
959 * Pipe is empty. This means there is no alt file.
960 */
961 free(cmd);
962 return (NULL);
963 }
964 return (cmd);
965 #endif /* HAVE_POPEN */
966 }
967
968 /*
969 * Close a replacement file.
970 */
close_altfile(constant char * altfilename,constant char * filename)971 public void close_altfile(constant char *altfilename, constant char *filename)
972 {
973 #if HAVE_POPEN
974 constant char *lessclose;
975 char *qfilename;
976 char *qaltfilename;
977 FILE *fd;
978 char *cmd;
979 size_t len;
980
981 if (!secure_allow(SF_LESSOPEN))
982 return;
983 if ((lessclose = lgetenv("LESSCLOSE")) == NULL)
984 return;
985 if (num_pct_s(lessclose) > 2)
986 {
987 error("LESSCLOSE ignored; must contain no more than 2 %%s", NULL_PARG);
988 return;
989 }
990 qfilename = shell_quote(filename);
991 qaltfilename = shell_quote(altfilename);
992 len = strlen(lessclose) + strlen(qfilename) + strlen(qaltfilename) + 2;
993 cmd = (char *) ecalloc(len, sizeof(char));
994 SNPRINTF2(cmd, len, lessclose, qfilename, qaltfilename);
995 free(qaltfilename);
996 free(qfilename);
997 fd = shellcmd(cmd);
998 free(cmd);
999 if (fd != NULL)
1000 pclose(fd);
1001 #endif
1002 }
1003
1004 /*
1005 * Is the specified file a directory?
1006 */
is_dir(constant char * filename)1007 public lbool is_dir(constant char *filename)
1008 {
1009 lbool isdir = FALSE;
1010
1011 #if HAVE_STAT
1012 {
1013 int r;
1014 less_stat_t statbuf;
1015
1016 r = less_stat(filename, &statbuf);
1017 isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
1018 }
1019 #else
1020 #ifdef _OSK
1021 {
1022 int f;
1023
1024 f = open(filename, S_IREAD | S_IFDIR);
1025 if (f >= 0)
1026 close(f);
1027 isdir = (f >= 0);
1028 }
1029 #endif
1030 #endif
1031 return (isdir);
1032 }
1033
1034 /*
1035 * Returns NULL if the file can be opened and
1036 * is an ordinary file, otherwise an error message
1037 * (if it cannot be opened or is a directory, etc.)
1038 */
bad_file(constant char * filename)1039 public char * bad_file(constant char *filename)
1040 {
1041 char *m = NULL;
1042
1043 if (!force_open && is_dir(filename))
1044 {
1045 static char is_a_dir[] = " is a directory";
1046
1047 m = (char *) ecalloc(strlen(filename) + sizeof(is_a_dir),
1048 sizeof(char));
1049 strcpy(m, filename);
1050 strcat(m, is_a_dir);
1051 } else
1052 {
1053 #if HAVE_STAT
1054 int r;
1055 less_stat_t statbuf;
1056
1057 r = less_stat(filename, &statbuf);
1058 if (r < 0)
1059 {
1060 m = errno_message(filename);
1061 } else if (force_open)
1062 {
1063 m = NULL;
1064 } else if (!S_ISREG(statbuf.st_mode))
1065 {
1066 static char not_reg[] = " is not a regular file (use -f to see it)";
1067 m = (char *) ecalloc(strlen(filename) + sizeof(not_reg),
1068 sizeof(char));
1069 strcpy(m, filename);
1070 strcat(m, not_reg);
1071 }
1072 #endif
1073 }
1074 return (m);
1075 }
1076
1077 /*
1078 * Return the size of a file, as cheaply as possible.
1079 * In Unix, we can stat the file.
1080 */
filesize(int f)1081 public POSITION filesize(int f)
1082 {
1083 #if HAVE_STAT
1084 less_stat_t statbuf;
1085
1086 if (less_fstat(f, &statbuf) >= 0)
1087 return ((POSITION) statbuf.st_size);
1088 #else
1089 #ifdef _OSK
1090 long size;
1091
1092 if ((size = (long) _gs_size(f)) >= 0)
1093 return ((POSITION) size);
1094 #endif
1095 #endif
1096 return (seek_filesize(f));
1097 }
1098
curr_ifile_changed(void)1099 public lbool curr_ifile_changed(void)
1100 {
1101 #if HAVE_STAT_INO
1102 /*
1103 * If the file's i-number or device has changed,
1104 * or if the file is smaller than it previously was,
1105 * the file must be different.
1106 */
1107 struct stat st;
1108 POSITION curr_pos = ch_tell();
1109 int r = stat(get_filename(curr_ifile), &st);
1110 if (r == 0 && (st.st_ino != curr_ino ||
1111 st.st_dev != curr_dev ||
1112 (curr_pos != NULL_POSITION && st.st_size < curr_pos)))
1113 return (TRUE);
1114 #endif
1115 return (FALSE);
1116 }
1117
1118 /*
1119 *
1120 */
shell_coption(void)1121 public constant char * shell_coption(void)
1122 {
1123 return ("-c");
1124 }
1125
1126 /*
1127 * Return last component of a pathname.
1128 */
last_component(constant char * name)1129 public constant char * last_component(constant char *name)
1130 {
1131 constant char *slash;
1132
1133 for (slash = name + strlen(name); slash > name; )
1134 {
1135 --slash;
1136 if (*slash == *PATHNAME_SEP || *slash == '/')
1137 return (slash + 1);
1138 }
1139 return (name);
1140 }
1141