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