xref: /freebsd-src/contrib/less/filename.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
1a5f0fb15SPaul Saab /*
2*c77c4889SXin LI  * Copyright (C) 1984-2024  Mark Nudelman
3a5f0fb15SPaul Saab  *
4a5f0fb15SPaul Saab  * You may distribute under the terms of either the GNU General Public
5a5f0fb15SPaul Saab  * License or the Less License, as specified in the README file.
6a5f0fb15SPaul Saab  *
796e55cc7SXin LI  * For more information, see the README file.
8a5f0fb15SPaul Saab  */
9a5f0fb15SPaul Saab 
10a5f0fb15SPaul Saab 
11a5f0fb15SPaul Saab /*
12a5f0fb15SPaul Saab  * Routines to mess around with filenames (and files).
13a5f0fb15SPaul Saab  * Much of this is very OS dependent.
14a5f0fb15SPaul Saab  */
15a5f0fb15SPaul Saab 
16a5f0fb15SPaul Saab #include "less.h"
17a5f0fb15SPaul Saab #include "lglob.h"
18a5f0fb15SPaul Saab #if MSDOS_COMPILER
19a5f0fb15SPaul Saab #include <dos.h>
20a5f0fb15SPaul Saab #if MSDOS_COMPILER==WIN32C && !defined(_MSC_VER)
21a5f0fb15SPaul Saab #include <dir.h>
22a5f0fb15SPaul Saab #endif
23a5f0fb15SPaul Saab #if MSDOS_COMPILER==DJGPPC
24a5f0fb15SPaul Saab #include <glob.h>
25a5f0fb15SPaul Saab #include <dir.h>
26a5f0fb15SPaul Saab #define _MAX_PATH      PATH_MAX
27a5f0fb15SPaul Saab #endif
28a5f0fb15SPaul Saab #endif
29a5f0fb15SPaul Saab #ifdef _OSK
30a5f0fb15SPaul Saab #include <rbf.h>
31a5f0fb15SPaul Saab #ifndef _OSK_MWC32
32a5f0fb15SPaul Saab #include <modes.h>
33a5f0fb15SPaul Saab #endif
34a5f0fb15SPaul Saab #endif
35a5f0fb15SPaul Saab 
36a5f0fb15SPaul Saab #if HAVE_STAT
37a5f0fb15SPaul Saab #include <sys/stat.h>
38a5f0fb15SPaul Saab #ifndef S_ISDIR
39a5f0fb15SPaul Saab #define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
40a5f0fb15SPaul Saab #endif
41a5f0fb15SPaul Saab #ifndef S_ISREG
42a5f0fb15SPaul Saab #define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
43a5f0fb15SPaul Saab #endif
44a5f0fb15SPaul Saab #endif
45a5f0fb15SPaul Saab 
46a5f0fb15SPaul Saab extern int force_open;
47000ba3e8STim J. Robbins extern int use_lessopen;
487f074f9cSXin LI extern int ctldisp;
497374caaaSXin LI extern int utf_mode;
50a5f0fb15SPaul Saab extern IFILE curr_ifile;
51a5f0fb15SPaul Saab extern IFILE old_ifile;
52a5f0fb15SPaul Saab #if SPACES_IN_FILENAMES
53a5f0fb15SPaul Saab extern char openquote;
54a5f0fb15SPaul Saab extern char closequote;
55a5f0fb15SPaul Saab #endif
56d713e089SXin LI #if HAVE_STAT_INO
57d713e089SXin LI extern ino_t curr_ino;
58d713e089SXin LI extern dev_t curr_dev;
59d713e089SXin LI #endif
60a5f0fb15SPaul Saab 
61a5f0fb15SPaul Saab /*
62a5f0fb15SPaul Saab  * Remove quotes around a filename.
63a5f0fb15SPaul Saab  */
64*c77c4889SXin LI public char * shell_unquote(constant char *str)
65a5f0fb15SPaul Saab {
66a5f0fb15SPaul Saab 	char *name;
67a5f0fb15SPaul Saab 	char *p;
68a5f0fb15SPaul Saab 
69000ba3e8STim J. Robbins 	name = p = (char *) ecalloc(strlen(str)+1, sizeof(char));
70000ba3e8STim J. Robbins 	if (*str == openquote)
71000ba3e8STim J. Robbins 	{
72000ba3e8STim J. Robbins 		str++;
73000ba3e8STim J. Robbins 		while (*str != '\0')
74000ba3e8STim J. Robbins 		{
75000ba3e8STim J. Robbins 			if (*str == closequote)
76000ba3e8STim J. Robbins 			{
77000ba3e8STim J. Robbins 				if (str[1] != closequote)
78000ba3e8STim J. Robbins 					break;
79000ba3e8STim J. Robbins 				str++;
80000ba3e8STim J. Robbins 			}
81000ba3e8STim J. Robbins 			*p++ = *str++;
82000ba3e8STim J. Robbins 		}
83000ba3e8STim J. Robbins 	} else
84000ba3e8STim J. Robbins 	{
85*c77c4889SXin LI 		constant char *esc = get_meta_escape();
86*c77c4889SXin LI 		size_t esclen = strlen(esc);
87000ba3e8STim J. Robbins 		while (*str != '\0')
88000ba3e8STim J. Robbins 		{
89000ba3e8STim J. Robbins 			if (esclen > 0 && strncmp(str, esc, esclen) == 0)
90000ba3e8STim J. Robbins 				str += esclen;
91000ba3e8STim J. Robbins 			*p++ = *str++;
92000ba3e8STim J. Robbins 		}
93000ba3e8STim J. Robbins 	}
94a5f0fb15SPaul Saab 	*p = '\0';
95a5f0fb15SPaul Saab 	return (name);
96000ba3e8STim J. Robbins }
97000ba3e8STim J. Robbins 
98000ba3e8STim J. Robbins /*
99000ba3e8STim J. Robbins  * Get the shell's escape character.
100000ba3e8STim J. Robbins  */
101*c77c4889SXin LI public constant char * get_meta_escape(void)
102000ba3e8STim J. Robbins {
103*c77c4889SXin LI 	constant char *s;
104000ba3e8STim J. Robbins 
105000ba3e8STim J. Robbins 	s = lgetenv("LESSMETAESCAPE");
106000ba3e8STim J. Robbins 	if (s == NULL)
107000ba3e8STim J. Robbins 		s = DEF_METAESCAPE;
108000ba3e8STim J. Robbins 	return (s);
109000ba3e8STim J. Robbins }
110000ba3e8STim J. Robbins 
111000ba3e8STim J. Robbins /*
112000ba3e8STim J. Robbins  * Get the characters which the shell considers to be "metacharacters".
113000ba3e8STim J. Robbins  */
114*c77c4889SXin LI static constant char * metachars(void)
115000ba3e8STim J. Robbins {
116*c77c4889SXin LI 	static constant char *mchars = NULL;
117000ba3e8STim J. Robbins 
118000ba3e8STim J. Robbins 	if (mchars == NULL)
119000ba3e8STim J. Robbins 	{
120000ba3e8STim J. Robbins 		mchars = lgetenv("LESSMETACHARS");
121000ba3e8STim J. Robbins 		if (mchars == NULL)
122000ba3e8STim J. Robbins 			mchars = DEF_METACHARS;
123000ba3e8STim J. Robbins 	}
124000ba3e8STim J. Robbins 	return (mchars);
125000ba3e8STim J. Robbins }
126000ba3e8STim J. Robbins 
127000ba3e8STim J. Robbins /*
128000ba3e8STim J. Robbins  * Is this a shell metacharacter?
129000ba3e8STim J. Robbins  */
130*c77c4889SXin LI static lbool metachar(char c)
131000ba3e8STim J. Robbins {
132000ba3e8STim J. Robbins 	return (strchr(metachars(), c) != NULL);
133000ba3e8STim J. Robbins }
134000ba3e8STim J. Robbins 
135000ba3e8STim J. Robbins /*
136*c77c4889SXin LI  * Must use quotes rather than escape char for this metachar?
137*c77c4889SXin LI  */
138*c77c4889SXin LI static lbool must_quote(char c)
139*c77c4889SXin LI {
140*c77c4889SXin LI 	/* {{ Maybe the set of must_quote chars should be configurable? }} */
141*c77c4889SXin LI 	return (c == '\n');
142*c77c4889SXin LI }
143*c77c4889SXin LI 
144*c77c4889SXin LI /*
145000ba3e8STim J. Robbins  * Insert a backslash before each metacharacter in a string.
146000ba3e8STim J. Robbins  */
147*c77c4889SXin LI public char * shell_quoten(constant char *s, size_t slen)
148000ba3e8STim J. Robbins {
149*c77c4889SXin LI 	constant char *p;
150*c77c4889SXin LI 	char *np;
151000ba3e8STim J. Robbins 	char *newstr;
152*c77c4889SXin LI 	size_t len;
153*c77c4889SXin LI 	constant char *esc = get_meta_escape();
154*c77c4889SXin LI 	size_t esclen = strlen(esc);
155*c77c4889SXin LI 	lbool use_quotes = FALSE;
156*c77c4889SXin LI 	lbool have_quotes = FALSE;
157000ba3e8STim J. Robbins 
158000ba3e8STim J. Robbins 	/*
159000ba3e8STim J. Robbins 	 * Determine how big a string we need to allocate.
160000ba3e8STim J. Robbins 	 */
161000ba3e8STim J. Robbins 	len = 1; /* Trailing null byte */
162*c77c4889SXin LI 	for (p = s;  p < s + slen;  p++)
163000ba3e8STim J. Robbins 	{
164000ba3e8STim J. Robbins 		len++;
165000ba3e8STim J. Robbins 		if (*p == openquote || *p == closequote)
166*c77c4889SXin LI 			have_quotes = TRUE;
167000ba3e8STim J. Robbins 		if (metachar(*p))
168000ba3e8STim J. Robbins 		{
169000ba3e8STim J. Robbins 			if (esclen == 0)
170000ba3e8STim J. Robbins 			{
171000ba3e8STim J. Robbins 				/*
172000ba3e8STim J. Robbins 				 * We've got a metachar, but this shell
173000ba3e8STim J. Robbins 				 * doesn't support escape chars.  Use quotes.
174000ba3e8STim J. Robbins 				 */
175*c77c4889SXin LI 				use_quotes = TRUE;
176*c77c4889SXin LI 			} else if (must_quote(*p))
177*c77c4889SXin LI 			{
178*c77c4889SXin LI 				len += 3; /* open quote + char + close quote */
179000ba3e8STim J. Robbins 			} else
180000ba3e8STim J. Robbins 			{
181000ba3e8STim J. Robbins 				/*
182000ba3e8STim J. Robbins 				 * Allow space for the escape char.
183000ba3e8STim J. Robbins 				 */
184000ba3e8STim J. Robbins 				len += esclen;
185000ba3e8STim J. Robbins 			}
186000ba3e8STim J. Robbins 		}
187000ba3e8STim J. Robbins 	}
188000ba3e8STim J. Robbins 	if (use_quotes)
189000ba3e8STim J. Robbins 	{
190000ba3e8STim J. Robbins 		if (have_quotes)
191000ba3e8STim J. Robbins 			/*
192000ba3e8STim J. Robbins 			 * We can't quote a string that contains quotes.
193000ba3e8STim J. Robbins 			 */
194000ba3e8STim J. Robbins 			return (NULL);
195*c77c4889SXin LI 		len = slen + 3;
196000ba3e8STim J. Robbins 	}
197000ba3e8STim J. Robbins 	/*
198000ba3e8STim J. Robbins 	 * Allocate and construct the new string.
199000ba3e8STim J. Robbins 	 */
200*c77c4889SXin LI 	newstr = np = (char *) ecalloc(len, sizeof(char));
201000ba3e8STim J. Robbins 	if (use_quotes)
202000ba3e8STim J. Robbins 	{
203*c77c4889SXin LI 		SNPRINTF4(newstr, len, "%c%.*s%c", openquote, (int) slen, s, closequote);
204000ba3e8STim J. Robbins 	} else
205000ba3e8STim J. Robbins 	{
206*c77c4889SXin LI 		constant char *es = s + slen;
207*c77c4889SXin LI 		while (s < es)
208000ba3e8STim J. Robbins 		{
209*c77c4889SXin LI 			if (!metachar(*s))
210000ba3e8STim J. Robbins 			{
211*c77c4889SXin LI 				*np++ = *s++;
212*c77c4889SXin LI 			} else if (must_quote(*s))
213*c77c4889SXin LI 			{
214*c77c4889SXin LI 				/* Surround the char with quotes. */
215*c77c4889SXin LI 				*np++ = openquote;
216*c77c4889SXin LI 				*np++ = *s++;
217*c77c4889SXin LI 				*np++ = closequote;
218*c77c4889SXin LI 			} else
219*c77c4889SXin LI 			{
220*c77c4889SXin LI 				/* Insert an escape char before the char. */
221*c77c4889SXin LI 				strcpy(np, esc);
222*c77c4889SXin LI 				np += esclen;
223*c77c4889SXin LI 				*np++ = *s++;
224000ba3e8STim J. Robbins 			}
225000ba3e8STim J. Robbins 		}
226*c77c4889SXin LI 		*np = '\0';
227000ba3e8STim J. Robbins 	}
228000ba3e8STim J. Robbins 	return (newstr);
229a5f0fb15SPaul Saab }
230a5f0fb15SPaul Saab 
231*c77c4889SXin LI public char * shell_quote(constant char *s)
232*c77c4889SXin LI {
233*c77c4889SXin LI 	return shell_quoten(s, strlen(s));
234*c77c4889SXin LI }
235*c77c4889SXin LI 
236a5f0fb15SPaul Saab /*
237a5f0fb15SPaul Saab  * Return a pathname that points to a specified file in a specified directory.
238a5f0fb15SPaul Saab  * Return NULL if the file does not exist in the directory.
239a5f0fb15SPaul Saab  */
240*c77c4889SXin LI public char * dirfile(constant char *dirname, constant char *filename, int must_exist)
241a5f0fb15SPaul Saab {
242a5f0fb15SPaul Saab 	char *pathname;
243*c77c4889SXin LI 	size_t len;
244a5f0fb15SPaul Saab 	int f;
245a5f0fb15SPaul Saab 
246a5f0fb15SPaul Saab 	if (dirname == NULL || *dirname == '\0')
247a5f0fb15SPaul Saab 		return (NULL);
248a5f0fb15SPaul Saab 	/*
249a5f0fb15SPaul Saab 	 * Construct the full pathname.
250a5f0fb15SPaul Saab 	 */
251*c77c4889SXin LI 	len = strlen(dirname) + strlen(filename) + 2;
2526dcb072bSXin LI 	pathname = (char *) calloc(len, sizeof(char));
253a5f0fb15SPaul Saab 	if (pathname == NULL)
254a5f0fb15SPaul Saab 		return (NULL);
2556dcb072bSXin LI 	SNPRINTF3(pathname, len, "%s%s%s", dirname, PATHNAME_SEP, filename);
25630a1828cSXin LI 	if (must_exist)
25730a1828cSXin LI 	{
258a5f0fb15SPaul Saab 		/*
259a5f0fb15SPaul Saab 		 * Make sure the file exists.
260a5f0fb15SPaul Saab 		 */
261b2ea2440SXin LI 		f = open(pathname, OPEN_READ);
262a5f0fb15SPaul Saab 		if (f < 0)
263a5f0fb15SPaul Saab 		{
264a5f0fb15SPaul Saab 			free(pathname);
265a5f0fb15SPaul Saab 			pathname = NULL;
266a5f0fb15SPaul Saab 		} else
267a5f0fb15SPaul Saab 		{
268a5f0fb15SPaul Saab 			close(f);
269a5f0fb15SPaul Saab 		}
27030a1828cSXin LI 	}
271a5f0fb15SPaul Saab 	return (pathname);
272a5f0fb15SPaul Saab }
273a5f0fb15SPaul Saab 
274a5f0fb15SPaul Saab /*
275a5f0fb15SPaul Saab  * Return the full pathname of the given file in the "home directory".
276a5f0fb15SPaul Saab  */
277*c77c4889SXin LI public char * homefile(constant char *filename)
278a5f0fb15SPaul Saab {
2791ea31627SRobert Watson 	char *pathname;
280a5f0fb15SPaul Saab 
28130a1828cSXin LI 	/* Try $HOME/filename. */
28230a1828cSXin LI 	pathname = dirfile(lgetenv("HOME"), filename, 1);
283a5f0fb15SPaul Saab 	if (pathname != NULL)
284a5f0fb15SPaul Saab 		return (pathname);
285a5f0fb15SPaul Saab #if OS2
28630a1828cSXin LI 	/* Try $INIT/filename. */
28730a1828cSXin LI 	pathname = dirfile(lgetenv("INIT"), filename, 1);
288a5f0fb15SPaul Saab 	if (pathname != NULL)
289a5f0fb15SPaul Saab 		return (pathname);
290a5f0fb15SPaul Saab #endif
291a5f0fb15SPaul Saab #if MSDOS_COMPILER || OS2
29230a1828cSXin LI 	/* Look for the file anywhere on search path. */
29330a1828cSXin LI 	pathname = (char *) ecalloc(_MAX_PATH, sizeof(char));
294a5f0fb15SPaul Saab #if MSDOS_COMPILER==DJGPPC
295a5f0fb15SPaul Saab 	{
296a5f0fb15SPaul Saab 		char *res = searchpath(filename);
297a5f0fb15SPaul Saab 		if (res == 0)
298a5f0fb15SPaul Saab 			*pathname = '\0';
299a5f0fb15SPaul Saab 		else
300a5f0fb15SPaul Saab 			strcpy(pathname, res);
301a5f0fb15SPaul Saab 	}
302a5f0fb15SPaul Saab #else
303a5f0fb15SPaul Saab 	_searchenv(filename, "PATH", pathname);
304a5f0fb15SPaul Saab #endif
305a5f0fb15SPaul Saab 	if (*pathname != '\0')
306a5f0fb15SPaul Saab 		return (pathname);
307a5f0fb15SPaul Saab 	free(pathname);
308a5f0fb15SPaul Saab #endif
309a5f0fb15SPaul Saab 	return (NULL);
310a5f0fb15SPaul Saab }
311a5f0fb15SPaul Saab 
312*c77c4889SXin LI typedef struct xcpy { char *dest; size_t copied; } xcpy;
313*c77c4889SXin LI 
314*c77c4889SXin LI static void xcpy_char(xcpy *xp, char ch)
315*c77c4889SXin LI {
316*c77c4889SXin LI 	if (xp->dest != NULL) *(xp->dest)++ = ch;
317*c77c4889SXin LI 	xp->copied++;
318*c77c4889SXin LI }
319*c77c4889SXin LI 
320*c77c4889SXin LI static void xcpy_filename(xcpy *xp, constant char *str)
321*c77c4889SXin LI {
322*c77c4889SXin LI 	/* If filename contains spaces, quote it
323*c77c4889SXin LI 	 * to prevent edit_list from splitting it. */
324*c77c4889SXin LI 	lbool quote = (strchr(str, ' ') != NULL);
325*c77c4889SXin LI 	if (quote)
326*c77c4889SXin LI 		xcpy_char(xp, openquote);
327*c77c4889SXin LI 	for (;  *str != '\0';  str++)
328*c77c4889SXin LI 		xcpy_char(xp, *str);
329*c77c4889SXin LI 	if (quote)
330*c77c4889SXin LI 		xcpy_char(xp, closequote);
331*c77c4889SXin LI }
332*c77c4889SXin LI 
333*c77c4889SXin LI static size_t fexpand_copy(constant char *fr, char *to)
334*c77c4889SXin LI {
335*c77c4889SXin LI 	xcpy xp;
336*c77c4889SXin LI 	xp.copied = 0;
337*c77c4889SXin LI 	xp.dest = to;
338*c77c4889SXin LI 
339*c77c4889SXin LI 	for (;  *fr != '\0';  fr++)
340*c77c4889SXin LI 	{
341*c77c4889SXin LI 		lbool expand = FALSE;
342*c77c4889SXin LI 		switch (*fr)
343*c77c4889SXin LI 		{
344*c77c4889SXin LI 		case '%':
345*c77c4889SXin LI 		case '#':
346*c77c4889SXin LI 			if (fr[1] == *fr)
347*c77c4889SXin LI 			{
348*c77c4889SXin LI 				/* Two identical chars. Output just one. */
349*c77c4889SXin LI 				fr += 1;
350*c77c4889SXin LI 			} else
351*c77c4889SXin LI 			{
352*c77c4889SXin LI 				/* Single char. Expand to a (quoted) file name. */
353*c77c4889SXin LI 				expand = TRUE;
354*c77c4889SXin LI 			}
355*c77c4889SXin LI 			break;
356*c77c4889SXin LI 		default:
357*c77c4889SXin LI 			break;
358*c77c4889SXin LI 		}
359*c77c4889SXin LI 		if (expand)
360*c77c4889SXin LI 		{
361*c77c4889SXin LI 			IFILE ifile = (*fr == '%') ? curr_ifile : (*fr == '#') ? old_ifile : NULL_IFILE;
362*c77c4889SXin LI 			if (ifile == NULL_IFILE)
363*c77c4889SXin LI 				xcpy_char(&xp, *fr);
364*c77c4889SXin LI 			else
365*c77c4889SXin LI 				xcpy_filename(&xp, get_filename(ifile));
366*c77c4889SXin LI 		} else
367*c77c4889SXin LI 		{
368*c77c4889SXin LI 			xcpy_char(&xp, *fr);
369*c77c4889SXin LI 		}
370*c77c4889SXin LI 	}
371*c77c4889SXin LI 	xcpy_char(&xp, '\0');
372*c77c4889SXin LI 	return xp.copied;
373*c77c4889SXin LI }
374*c77c4889SXin LI 
375a5f0fb15SPaul Saab /*
376a5f0fb15SPaul Saab  * Expand a string, substituting any "%" with the current filename,
377a5f0fb15SPaul Saab  * and any "#" with the previous filename.
378a5f0fb15SPaul Saab  * But a string of N "%"s is just replaced with N-1 "%"s.
379a5f0fb15SPaul Saab  * Likewise for a string of N "#"s.
380a5f0fb15SPaul Saab  * {{ This is a lot of work just to support % and #. }}
381a5f0fb15SPaul Saab  */
382*c77c4889SXin LI public char * fexpand(constant char *s)
383a5f0fb15SPaul Saab {
384*c77c4889SXin LI 	size_t n;
3851ea31627SRobert Watson 	char *e;
386a5f0fb15SPaul Saab 
387a5f0fb15SPaul Saab 	/*
388a5f0fb15SPaul Saab 	 * Make one pass to see how big a buffer we
389a5f0fb15SPaul Saab 	 * need to allocate for the expanded string.
390a5f0fb15SPaul Saab 	 */
391*c77c4889SXin LI 	n = fexpand_copy(s, NULL);
392*c77c4889SXin LI 	e = (char *) ecalloc(n, sizeof(char));
393a5f0fb15SPaul Saab 
394a5f0fb15SPaul Saab 	/*
395a5f0fb15SPaul Saab 	 * Now copy the string, expanding any "%" or "#".
396a5f0fb15SPaul Saab 	 */
397*c77c4889SXin LI 	fexpand_copy(s, e);
398a5f0fb15SPaul Saab 	return (e);
399a5f0fb15SPaul Saab }
400a5f0fb15SPaul Saab 
40133096f16SXin LI 
402a5f0fb15SPaul Saab #if TAB_COMPLETE_FILENAME
403a5f0fb15SPaul Saab 
404a5f0fb15SPaul Saab /*
405a5f0fb15SPaul Saab  * Return a blank-separated list of filenames which "complete"
406a5f0fb15SPaul Saab  * the given string.
407a5f0fb15SPaul Saab  */
408*c77c4889SXin LI public char * fcomplete(constant char *s)
409a5f0fb15SPaul Saab {
410a5f0fb15SPaul Saab 	char *fpat;
411000ba3e8STim J. Robbins 	char *qs;
412*c77c4889SXin LI 	char *uqs;
413a5f0fb15SPaul Saab 
414*c77c4889SXin LI 	/* {{ Is this needed? lglob calls secure_allow. }} */
415*c77c4889SXin LI 	if (!secure_allow(SF_GLOB))
416a5f0fb15SPaul Saab 		return (NULL);
417a5f0fb15SPaul Saab 	/*
418a5f0fb15SPaul Saab 	 * Complete the filename "s" by globbing "s*".
419a5f0fb15SPaul Saab 	 */
420a5f0fb15SPaul Saab #if MSDOS_COMPILER && (MSDOS_COMPILER == MSOFTC || MSDOS_COMPILER == BORLANDC)
421a5f0fb15SPaul Saab 	/*
422a5f0fb15SPaul Saab 	 * But in DOS, we have to glob "s*.*".
423a5f0fb15SPaul Saab 	 * But if the final component of the filename already has
424a5f0fb15SPaul Saab 	 * a dot in it, just do "s*".
425a5f0fb15SPaul Saab 	 * (Thus, "FILE" is globbed as "FILE*.*",
426a5f0fb15SPaul Saab 	 *  but "FILE.A" is globbed as "FILE.A*").
427a5f0fb15SPaul Saab 	 */
428a5f0fb15SPaul Saab 	{
429*c77c4889SXin LI 		constant char *slash;
430*c77c4889SXin LI 		size_t len;
431a5f0fb15SPaul Saab 		for (slash = s+strlen(s)-1;  slash > s;  slash--)
432a5f0fb15SPaul Saab 			if (*slash == *PATHNAME_SEP || *slash == '/')
433a5f0fb15SPaul Saab 				break;
434*c77c4889SXin LI 		len = strlen(s) + 4;
4356dcb072bSXin LI 		fpat = (char *) ecalloc(len, sizeof(char));
436a5f0fb15SPaul Saab 		if (strchr(slash, '.') == NULL)
4376dcb072bSXin LI 			SNPRINTF1(fpat, len, "%s*.*", s);
438a5f0fb15SPaul Saab 		else
4396dcb072bSXin LI 			SNPRINTF1(fpat, len, "%s*", s);
440a5f0fb15SPaul Saab 	}
441a5f0fb15SPaul Saab #else
4426dcb072bSXin LI 	{
443*c77c4889SXin LI 	size_t len = strlen(s) + 2;
4446dcb072bSXin LI 	fpat = (char *) ecalloc(len, sizeof(char));
4456dcb072bSXin LI 	SNPRINTF1(fpat, len, "%s*", s);
4466dcb072bSXin LI 	}
447a5f0fb15SPaul Saab #endif
448000ba3e8STim J. Robbins 	qs = lglob(fpat);
449*c77c4889SXin LI 	uqs = shell_unquote(qs);
450*c77c4889SXin LI 	if (strcmp(uqs, fpat) == 0)
451a5f0fb15SPaul Saab 	{
452a5f0fb15SPaul Saab 		/*
453a5f0fb15SPaul Saab 		 * The filename didn't expand.
454a5f0fb15SPaul Saab 		 */
455000ba3e8STim J. Robbins 		free(qs);
456000ba3e8STim J. Robbins 		qs = NULL;
457a5f0fb15SPaul Saab 	}
458*c77c4889SXin LI 	free(uqs);
459a5f0fb15SPaul Saab 	free(fpat);
460000ba3e8STim J. Robbins 	return (qs);
461a5f0fb15SPaul Saab }
462a5f0fb15SPaul Saab #endif
463a5f0fb15SPaul Saab 
464a5f0fb15SPaul Saab /*
465a5f0fb15SPaul Saab  * Try to determine if a file is "binary".
466a5f0fb15SPaul Saab  * This is just a guess, and we need not try too hard to make it accurate.
467*c77c4889SXin LI  *
468*c77c4889SXin LI  * The number of bytes read is returned to the caller, because it will
469*c77c4889SXin LI  * be used later to compare to st_size from stat(2) to see if the file
470*c77c4889SXin LI  * is lying about its size.
471a5f0fb15SPaul Saab  */
472*c77c4889SXin LI public int bin_file(int f, ssize_t *n)
473a5f0fb15SPaul Saab {
4747f074f9cSXin LI 	int bin_count = 0;
4757374caaaSXin LI 	char data[256];
476*c77c4889SXin LI 	constant char* p;
477*c77c4889SXin LI 	constant char* edata;
478a5f0fb15SPaul Saab 
479a5f0fb15SPaul Saab 	if (!seekable(f))
480a5f0fb15SPaul Saab 		return (0);
481*c77c4889SXin LI 	if (less_lseek(f, (less_off_t)0, SEEK_SET) == BAD_LSEEK)
482a5f0fb15SPaul Saab 		return (0);
483*c77c4889SXin LI 	*n = read(f, data, sizeof(data));
484*c77c4889SXin LI 	if (*n <= 0)
485a15691bfSXin LI 		return (0);
486*c77c4889SXin LI 	edata = &data[*n];
487b2ea2440SXin LI 	for (p = data;  p < edata;  )
488a15691bfSXin LI 	{
489*c77c4889SXin LI 		if (utf_mode && !is_utf8_well_formed(p, (int) ptr_diff(edata,p)))
490b2ea2440SXin LI 		{
491b2ea2440SXin LI 			bin_count++;
492b2ea2440SXin LI 			utf_skip_to_lead(&p, edata);
493a15691bfSXin LI 		} else
494a15691bfSXin LI 		{
495*c77c4889SXin LI 			LWCHAR c = step_charc(&p, +1, edata);
4962235c7feSXin LI 			struct ansi_state *pansi;
4972235c7feSXin LI 			if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(c)) != NULL)
4982235c7feSXin LI 			{
4992235c7feSXin LI 				skip_ansi(pansi, &p, edata);
5002235c7feSXin LI 				ansi_done(pansi);
5012235c7feSXin LI 			} else if (binary_char(c))
5027f074f9cSXin LI 				bin_count++;
5037f074f9cSXin LI 		}
504a15691bfSXin LI 	}
5057f074f9cSXin LI 	/*
5067f074f9cSXin LI 	 * Call it a binary file if there are more than 5 binary characters
5077f074f9cSXin LI 	 * in the first 256 bytes of the file.
5087f074f9cSXin LI 	 */
5097f074f9cSXin LI 	return (bin_count > 5);
510a5f0fb15SPaul Saab }
511a5f0fb15SPaul Saab 
512a5f0fb15SPaul Saab /*
513a5f0fb15SPaul Saab  * Try to determine the size of a file by seeking to the end.
514a5f0fb15SPaul Saab  */
515d713e089SXin LI static POSITION seek_filesize(int f)
516a5f0fb15SPaul Saab {
517*c77c4889SXin LI 	less_off_t spos;
518a5f0fb15SPaul Saab 
519*c77c4889SXin LI 	spos = less_lseek(f, (less_off_t)0, SEEK_END);
520a5f0fb15SPaul Saab 	if (spos == BAD_LSEEK)
521a5f0fb15SPaul Saab 		return (NULL_POSITION);
522a5f0fb15SPaul Saab 	return ((POSITION) spos);
523a5f0fb15SPaul Saab }
524a5f0fb15SPaul Saab 
52530a1828cSXin LI #if HAVE_POPEN
526a5f0fb15SPaul Saab /*
527a5f0fb15SPaul Saab  * Read a string from a file.
528a5f0fb15SPaul Saab  * Return a pointer to the string in memory.
529a5f0fb15SPaul Saab  */
530*c77c4889SXin LI public char * readfd(FILE *fd)
531a5f0fb15SPaul Saab {
532*c77c4889SXin LI 	struct xbuffer xbuf;
533*c77c4889SXin LI 	xbuf_init(&xbuf);
534*c77c4889SXin LI 	for (;;)
535*c77c4889SXin LI 	{
536a5f0fb15SPaul Saab 		int ch;
537a5f0fb15SPaul Saab 		if ((ch = getc(fd)) == '\n' || ch == EOF)
538a5f0fb15SPaul Saab 			break;
539*c77c4889SXin LI 		xbuf_add_char(&xbuf, (char) ch);
540a5f0fb15SPaul Saab 	}
541*c77c4889SXin LI 	xbuf_add_char(&xbuf, '\0');
542*c77c4889SXin LI 	return (char *) xbuf.data;
543a5f0fb15SPaul Saab }
544a5f0fb15SPaul Saab 
545a5f0fb15SPaul Saab /*
546a5f0fb15SPaul Saab  * Execute a shell command.
547a5f0fb15SPaul Saab  * Return a pointer to a pipe connected to the shell command's standard output.
548a5f0fb15SPaul Saab  */
549*c77c4889SXin LI static FILE * shellcmd(constant char *cmd)
550a5f0fb15SPaul Saab {
551a5f0fb15SPaul Saab 	FILE *fd;
552a5f0fb15SPaul Saab 
553a5f0fb15SPaul Saab #if HAVE_SHELL
554*c77c4889SXin LI 	constant char *shell;
555a5f0fb15SPaul Saab 
556a5f0fb15SPaul Saab 	shell = lgetenv("SHELL");
557b7780dbeSXin LI 	if (!isnullenv(shell))
558a5f0fb15SPaul Saab 	{
559a5f0fb15SPaul Saab 		char *scmd;
560a5f0fb15SPaul Saab 		char *esccmd;
561a5f0fb15SPaul Saab 
562a5f0fb15SPaul Saab 		/*
563000ba3e8STim J. Robbins 		 * Read the output of <$SHELL -c cmd>.
564000ba3e8STim J. Robbins 		 * Escape any metacharacters in the command.
565a5f0fb15SPaul Saab 		 */
566000ba3e8STim J. Robbins 		esccmd = shell_quote(cmd);
567000ba3e8STim J. Robbins 		if (esccmd == NULL)
568a5f0fb15SPaul Saab 		{
569000ba3e8STim J. Robbins 			fd = popen(cmd, "r");
570a5f0fb15SPaul Saab 		} else
571a5f0fb15SPaul Saab 		{
572*c77c4889SXin LI 			size_t len = strlen(shell) + strlen(esccmd) + 5;
5736dcb072bSXin LI 			scmd = (char *) ecalloc(len, sizeof(char));
5746dcb072bSXin LI 			SNPRINTF3(scmd, len, "%s %s %s", shell, shell_coption(), esccmd);
575a5f0fb15SPaul Saab 			free(esccmd);
576a5f0fb15SPaul Saab 			fd = popen(scmd, "r");
577a5f0fb15SPaul Saab 			free(scmd);
578000ba3e8STim J. Robbins 		}
579a5f0fb15SPaul Saab 	} else
580a5f0fb15SPaul Saab #endif
581a5f0fb15SPaul Saab 	{
582a5f0fb15SPaul Saab 		fd = popen(cmd, "r");
583000ba3e8STim J. Robbins 	}
584a5f0fb15SPaul Saab 	/*
585a5f0fb15SPaul Saab 	 * Redirection in `popen' might have messed with the
586a5f0fb15SPaul Saab 	 * standard devices.  Restore binary input mode.
587a5f0fb15SPaul Saab 	 */
588a5f0fb15SPaul Saab 	SET_BINARY(0);
589a5f0fb15SPaul Saab 	return (fd);
590a5f0fb15SPaul Saab }
591a5f0fb15SPaul Saab 
592a5f0fb15SPaul Saab #endif /* HAVE_POPEN */
593a5f0fb15SPaul Saab 
594a5f0fb15SPaul Saab 
595a5f0fb15SPaul Saab /*
596a5f0fb15SPaul Saab  * Expand a filename, doing any system-specific metacharacter substitutions.
597a5f0fb15SPaul Saab  */
598*c77c4889SXin LI public char * lglob(constant char *afilename)
599a5f0fb15SPaul Saab {
600a5f0fb15SPaul Saab 	char *gfilename;
601*c77c4889SXin LI 	char *filename = fexpand(afilename);
602a5f0fb15SPaul Saab 
603*c77c4889SXin LI 	if (!secure_allow(SF_GLOB))
604b2ea2440SXin LI 		return (filename);
605a5f0fb15SPaul Saab 
606a5f0fb15SPaul Saab #ifdef DECL_GLOB_LIST
607a5f0fb15SPaul Saab {
608a5f0fb15SPaul Saab 	/*
609a5f0fb15SPaul Saab 	 * The globbing function returns a list of names.
610a5f0fb15SPaul Saab 	 */
611*c77c4889SXin LI 	size_t length;
612a5f0fb15SPaul Saab 	char *p;
613000ba3e8STim J. Robbins 	char *qfilename;
614a5f0fb15SPaul Saab 	DECL_GLOB_LIST(list)
615a5f0fb15SPaul Saab 
616a5f0fb15SPaul Saab 	GLOB_LIST(filename, list);
617a5f0fb15SPaul Saab 	if (GLOB_LIST_FAILED(list))
618a5f0fb15SPaul Saab 	{
619b2ea2440SXin LI 		return (filename);
620a5f0fb15SPaul Saab 	}
621a5f0fb15SPaul Saab 	length = 1; /* Room for trailing null byte */
622a5f0fb15SPaul Saab 	for (SCAN_GLOB_LIST(list, p))
623a5f0fb15SPaul Saab 	{
624a5f0fb15SPaul Saab 		INIT_GLOB_LIST(list, p);
625000ba3e8STim J. Robbins 		qfilename = shell_quote(p);
626000ba3e8STim J. Robbins 		if (qfilename != NULL)
627000ba3e8STim J. Robbins 		{
628000ba3e8STim J. Robbins 			length += strlen(qfilename) + 1;
629000ba3e8STim J. Robbins 			free(qfilename);
630000ba3e8STim J. Robbins 		}
631a5f0fb15SPaul Saab 	}
632a5f0fb15SPaul Saab 	gfilename = (char *) ecalloc(length, sizeof(char));
633a5f0fb15SPaul Saab 	for (SCAN_GLOB_LIST(list, p))
634a5f0fb15SPaul Saab 	{
635a5f0fb15SPaul Saab 		INIT_GLOB_LIST(list, p);
636000ba3e8STim J. Robbins 		qfilename = shell_quote(p);
637000ba3e8STim J. Robbins 		if (qfilename != NULL)
638000ba3e8STim J. Robbins 		{
639000ba3e8STim J. Robbins 			sprintf(gfilename + strlen(gfilename), "%s ", qfilename);
640000ba3e8STim J. Robbins 			free(qfilename);
641000ba3e8STim J. Robbins 		}
642a5f0fb15SPaul Saab 	}
643a5f0fb15SPaul Saab 	/*
644a5f0fb15SPaul Saab 	 * Overwrite the final trailing space with a null terminator.
645a5f0fb15SPaul Saab 	 */
646a5f0fb15SPaul Saab 	*--p = '\0';
647a5f0fb15SPaul Saab 	GLOB_LIST_DONE(list);
648a5f0fb15SPaul Saab }
649a5f0fb15SPaul Saab #else
650a5f0fb15SPaul Saab #ifdef DECL_GLOB_NAME
651a5f0fb15SPaul Saab {
652a5f0fb15SPaul Saab 	/*
653a5f0fb15SPaul Saab 	 * The globbing function returns a single name, and
654a5f0fb15SPaul Saab 	 * is called multiple times to walk thru all names.
655a5f0fb15SPaul Saab 	 */
6561ea31627SRobert Watson 	char *p;
657*c77c4889SXin LI 	size_t len;
658*c77c4889SXin LI 	size_t n;
659b2ea2440SXin LI 	char *pfilename;
660b2ea2440SXin LI 	char *qfilename;
661a5f0fb15SPaul Saab 	DECL_GLOB_NAME(fnd,drive,dir,fname,ext,handle)
662a5f0fb15SPaul Saab 
663a5f0fb15SPaul Saab 	GLOB_FIRST_NAME(filename, &fnd, handle);
664a5f0fb15SPaul Saab 	if (GLOB_FIRST_FAILED(handle))
665a5f0fb15SPaul Saab 	{
666b2ea2440SXin LI 		return (filename);
667a5f0fb15SPaul Saab 	}
668a5f0fb15SPaul Saab 
669a5f0fb15SPaul Saab 	_splitpath(filename, drive, dir, fname, ext);
670a5f0fb15SPaul Saab 	len = 100;
671a5f0fb15SPaul Saab 	gfilename = (char *) ecalloc(len, sizeof(char));
672a5f0fb15SPaul Saab 	p = gfilename;
673a5f0fb15SPaul Saab 	do {
674*c77c4889SXin LI 		n = strlen(drive) + strlen(dir) + strlen(fnd.GLOB_NAME) + 1;
675b2ea2440SXin LI 		pfilename = (char *) ecalloc(n, sizeof(char));
676b2ea2440SXin LI 		SNPRINTF3(pfilename, n, "%s%s%s", drive, dir, fnd.GLOB_NAME);
677b2ea2440SXin LI 		qfilename = shell_quote(pfilename);
678b2ea2440SXin LI 		free(pfilename);
679b2ea2440SXin LI 		if (qfilename != NULL)
680a5f0fb15SPaul Saab 		{
681*c77c4889SXin LI 			n = strlen(qfilename);
682a5f0fb15SPaul Saab 			while (p - gfilename + n + 2 >= len)
683a5f0fb15SPaul Saab 			{
684a5f0fb15SPaul Saab 				/*
685000ba3e8STim J. Robbins 				 * No room in current buffer.
686000ba3e8STim J. Robbins 				 * Allocate a bigger one.
687a5f0fb15SPaul Saab 				 */
688a5f0fb15SPaul Saab 				len *= 2;
689a5f0fb15SPaul Saab 				*p = '\0';
690a5f0fb15SPaul Saab 				p = (char *) ecalloc(len, sizeof(char));
691a5f0fb15SPaul Saab 				strcpy(p, gfilename);
692a5f0fb15SPaul Saab 				free(gfilename);
693a5f0fb15SPaul Saab 				gfilename = p;
694a5f0fb15SPaul Saab 				p = gfilename + strlen(gfilename);
695a5f0fb15SPaul Saab 			}
696b2ea2440SXin LI 			strcpy(p, qfilename);
697b2ea2440SXin LI 			free(qfilename);
698a5f0fb15SPaul Saab 			p += n;
699000ba3e8STim J. Robbins 			*p++ = ' ';
700000ba3e8STim J. Robbins 		}
701a5f0fb15SPaul Saab 	} while (GLOB_NEXT_NAME(handle, &fnd) == 0);
702a5f0fb15SPaul Saab 
703a5f0fb15SPaul Saab 	/*
704a5f0fb15SPaul Saab 	 * Overwrite the final trailing space with a null terminator.
705a5f0fb15SPaul Saab 	 */
706a5f0fb15SPaul Saab 	*--p = '\0';
707a5f0fb15SPaul Saab 	GLOB_NAME_DONE(handle);
708a5f0fb15SPaul Saab }
709a5f0fb15SPaul Saab #else
710a5f0fb15SPaul Saab #if HAVE_POPEN
711a5f0fb15SPaul Saab {
712a5f0fb15SPaul Saab 	/*
713a5f0fb15SPaul Saab 	 * We get the shell to glob the filename for us by passing
714a5f0fb15SPaul Saab 	 * an "echo" command to the shell and reading its output.
715a5f0fb15SPaul Saab 	 */
716a5f0fb15SPaul Saab 	FILE *fd;
717*c77c4889SXin LI 	constant char *s;
718*c77c4889SXin LI 	constant char *lessecho;
719a5f0fb15SPaul Saab 	char *cmd;
720*c77c4889SXin LI 	constant char *esc;
721*c77c4889SXin LI 	char *qesc;
722*c77c4889SXin LI 	size_t len;
723a5f0fb15SPaul Saab 
724000ba3e8STim J. Robbins 	esc = get_meta_escape();
725000ba3e8STim J. Robbins 	if (strlen(esc) == 0)
726000ba3e8STim J. Robbins 		esc = "-";
727*c77c4889SXin LI 	qesc = shell_quote(esc);
728*c77c4889SXin LI 	if (qesc == NULL)
729a5f0fb15SPaul Saab 	{
730b2ea2440SXin LI 		return (filename);
731a5f0fb15SPaul Saab 	}
732000ba3e8STim J. Robbins 	lessecho = lgetenv("LESSECHO");
733b7780dbeSXin LI 	if (isnullenv(lessecho))
734000ba3e8STim J. Robbins 		lessecho = "lessecho";
735a5f0fb15SPaul Saab 	/*
736a5f0fb15SPaul Saab 	 * Invoke lessecho, and read its output (a globbed list of filenames).
737a5f0fb15SPaul Saab 	 */
738*c77c4889SXin LI 	len = strlen(lessecho) + strlen(filename) + (7*strlen(metachars())) + 24;
7396dcb072bSXin LI 	cmd = (char *) ecalloc(len, sizeof(char));
74095270f73SXin LI 	SNPRINTF4(cmd, len, "%s -p0x%x -d0x%x -e%s ", lessecho,
741*c77c4889SXin LI 		(unsigned char) openquote, (unsigned char) closequote, qesc);
742*c77c4889SXin LI 	free(qesc);
743000ba3e8STim J. Robbins 	for (s = metachars();  *s != '\0';  s++)
74495270f73SXin LI 		sprintf(cmd + strlen(cmd), "-n0x%x ", (unsigned char) *s);
745b2ea2440SXin LI 	sprintf(cmd + strlen(cmd), "-- %s", filename);
746a5f0fb15SPaul Saab 	fd = shellcmd(cmd);
747a5f0fb15SPaul Saab 	free(cmd);
748a5f0fb15SPaul Saab 	if (fd == NULL)
749a5f0fb15SPaul Saab 	{
750a5f0fb15SPaul Saab 		/*
751a5f0fb15SPaul Saab 		 * Cannot create the pipe.
752a5f0fb15SPaul Saab 		 * Just return the original (fexpanded) filename.
753a5f0fb15SPaul Saab 		 */
754b2ea2440SXin LI 		return (filename);
755a5f0fb15SPaul Saab 	}
756a5f0fb15SPaul Saab 	gfilename = readfd(fd);
757a5f0fb15SPaul Saab 	pclose(fd);
758a5f0fb15SPaul Saab 	if (*gfilename == '\0')
759a5f0fb15SPaul Saab 	{
760a5f0fb15SPaul Saab 		free(gfilename);
761b7780dbeSXin LI 		return (filename);
762a5f0fb15SPaul Saab 	}
763a5f0fb15SPaul Saab }
764a5f0fb15SPaul Saab #else
765a5f0fb15SPaul Saab 	/*
766a5f0fb15SPaul Saab 	 * No globbing functions at all.  Just use the fexpanded filename.
767a5f0fb15SPaul Saab 	 */
768a5f0fb15SPaul Saab 	gfilename = save(filename);
769a5f0fb15SPaul Saab #endif
770a5f0fb15SPaul Saab #endif
771a5f0fb15SPaul Saab #endif
772a5f0fb15SPaul Saab 	free(filename);
773a5f0fb15SPaul Saab 	return (gfilename);
774a5f0fb15SPaul Saab }
775a5f0fb15SPaul Saab 
776a5f0fb15SPaul Saab /*
777d713e089SXin LI  * Does path not represent something in the file system?
778d713e089SXin LI  */
779*c77c4889SXin LI public lbool is_fake_pathname(constant char *path)
780d713e089SXin LI {
781d713e089SXin LI 	return (strcmp(path, "-") == 0 ||
782d713e089SXin LI 	        strcmp(path, FAKE_HELPFILE) == 0 || strcmp(path, FAKE_EMPTYFILE) == 0);
783d713e089SXin LI }
784d713e089SXin LI 
785d713e089SXin LI /*
7866f26c71dSXin LI  * Return canonical pathname.
787b7780dbeSXin LI  */
788*c77c4889SXin LI public char * lrealpath(constant char *path)
789d713e089SXin LI {
790d713e089SXin LI 	if (!is_fake_pathname(path))
791b7780dbeSXin LI 	{
792b7780dbeSXin LI #if HAVE_REALPATH
793*c77c4889SXin LI 		/*
794*c77c4889SXin LI 		 * Not all systems support the POSIX.1-2008 realpath() behavior
795*c77c4889SXin LI 		 * of allocating when passing a NULL argument. And PATH_MAX is
796*c77c4889SXin LI 		 * not required to be defined, or might contain an exceedingly
797*c77c4889SXin LI 		 * big value. We assume that if it is not defined (such as on
798*c77c4889SXin LI 		 * GNU/Hurd), then realpath() accepts NULL.
799*c77c4889SXin LI 		 */
800*c77c4889SXin LI #ifndef PATH_MAX
801*c77c4889SXin LI 		char *rpath;
802*c77c4889SXin LI 
803*c77c4889SXin LI 		rpath = realpath(path, NULL);
804*c77c4889SXin LI 		if (rpath != NULL)
805*c77c4889SXin LI 			return (rpath);
806*c77c4889SXin LI #else
807b7780dbeSXin LI 		char rpath[PATH_MAX];
808b7780dbeSXin LI 		if (realpath(path, rpath) != NULL)
809b7780dbeSXin LI 			return (save(rpath));
810b7780dbeSXin LI #endif
811*c77c4889SXin LI #endif
812d713e089SXin LI 	}
813b7780dbeSXin LI 	return (save(path));
814b7780dbeSXin LI }
815b7780dbeSXin LI 
81630a1828cSXin LI #if HAVE_POPEN
817b7780dbeSXin LI /*
81896e55cc7SXin LI  * Return number of %s escapes in a string.
81996e55cc7SXin LI  * Return a large number if there are any other % escapes besides %s.
82096e55cc7SXin LI  */
821*c77c4889SXin LI static int num_pct_s(constant char *lessopen)
82296e55cc7SXin LI {
823a15691bfSXin LI 	int num = 0;
82496e55cc7SXin LI 
825a15691bfSXin LI 	while (*lessopen != '\0')
82696e55cc7SXin LI 	{
827a15691bfSXin LI 		if (*lessopen == '%')
828a15691bfSXin LI 		{
829a15691bfSXin LI 			if (lessopen[1] == '%')
830a15691bfSXin LI 				++lessopen;
831a15691bfSXin LI 			else if (lessopen[1] == 's')
832a15691bfSXin LI 				++num;
833a15691bfSXin LI 			else
83496e55cc7SXin LI 				return (999);
83596e55cc7SXin LI 		}
836a15691bfSXin LI 		++lessopen;
837a15691bfSXin LI 	}
83896e55cc7SXin LI 	return (num);
83996e55cc7SXin LI }
84030a1828cSXin LI #endif
84196e55cc7SXin LI 
84296e55cc7SXin LI /*
843a5f0fb15SPaul Saab  * See if we should open a "replacement file"
844a5f0fb15SPaul Saab  * instead of the file we're about to open.
845a5f0fb15SPaul Saab  */
846*c77c4889SXin LI public char * open_altfile(constant char *filename, int *pf, void **pfd)
847a5f0fb15SPaul Saab {
848a5f0fb15SPaul Saab #if !HAVE_POPEN
849a5f0fb15SPaul Saab 	return (NULL);
850a5f0fb15SPaul Saab #else
851*c77c4889SXin LI 	constant char *lessopen;
852b2ea2440SXin LI 	char *qfilename;
853a5f0fb15SPaul Saab 	char *cmd;
854*c77c4889SXin LI 	size_t len;
855a5f0fb15SPaul Saab 	FILE *fd;
856a5f0fb15SPaul Saab #if HAVE_FILENO
857a5f0fb15SPaul Saab 	int returnfd = 0;
858a5f0fb15SPaul Saab #endif
859a5f0fb15SPaul Saab 
860*c77c4889SXin LI 	if (!secure_allow(SF_LESSOPEN))
861*c77c4889SXin LI 		return (NULL);
862*c77c4889SXin LI 	if (!use_lessopen)
863a5f0fb15SPaul Saab 		return (NULL);
864a5f0fb15SPaul Saab 	ch_ungetchar(-1);
865a5f0fb15SPaul Saab 	if ((lessopen = lgetenv("LESSOPEN")) == NULL)
866a5f0fb15SPaul Saab 		return (NULL);
86796e55cc7SXin LI 	while (*lessopen == '|')
868a5f0fb15SPaul Saab 	{
869a5f0fb15SPaul Saab 		/*
870a5f0fb15SPaul Saab 		 * If LESSOPEN starts with a |, it indicates
871a5f0fb15SPaul Saab 		 * a "pipe preprocessor".
872a5f0fb15SPaul Saab 		 */
8737374caaaSXin LI #if !HAVE_FILENO
874a5f0fb15SPaul Saab 		error("LESSOPEN pipe is not supported", NULL_PARG);
875a5f0fb15SPaul Saab 		return (NULL);
8767374caaaSXin LI #else
8777374caaaSXin LI 		lessopen++;
87896e55cc7SXin LI 		returnfd++;
879f0be0a1fSXin LI #endif
880f0be0a1fSXin LI 	}
881b2ea2440SXin LI 	if (*lessopen == '-')
882b2ea2440SXin LI 	{
8837374caaaSXin LI 		/*
8847374caaaSXin LI 		 * Lessopen preprocessor will accept "-" as a filename.
8857374caaaSXin LI 		 */
8867374caaaSXin LI 		lessopen++;
887b2ea2440SXin LI 	} else
888b2ea2440SXin LI 	{
8897374caaaSXin LI 		if (strcmp(filename, "-") == 0)
8907374caaaSXin LI 			return (NULL);
8917374caaaSXin LI 	}
892b2ea2440SXin LI 	if (num_pct_s(lessopen) != 1)
89396e55cc7SXin LI 	{
894b2ea2440SXin LI 		error("LESSOPEN ignored: must contain exactly one %%s", NULL_PARG);
89596e55cc7SXin LI 		return (NULL);
89696e55cc7SXin LI 	}
897a5f0fb15SPaul Saab 
898b2ea2440SXin LI 	qfilename = shell_quote(filename);
899*c77c4889SXin LI 	len = strlen(lessopen) + strlen(qfilename) + 2;
9006dcb072bSXin LI 	cmd = (char *) ecalloc(len, sizeof(char));
901b2ea2440SXin LI 	SNPRINTF1(cmd, len, lessopen, qfilename);
902b2ea2440SXin LI 	free(qfilename);
903a5f0fb15SPaul Saab 	fd = shellcmd(cmd);
904a5f0fb15SPaul Saab 	free(cmd);
905a5f0fb15SPaul Saab 	if (fd == NULL)
906a5f0fb15SPaul Saab 	{
907a5f0fb15SPaul Saab 		/*
908a5f0fb15SPaul Saab 		 * Cannot create the pipe.
909a5f0fb15SPaul Saab 		 */
910a5f0fb15SPaul Saab 		return (NULL);
911a5f0fb15SPaul Saab 	}
912a5f0fb15SPaul Saab #if HAVE_FILENO
913a5f0fb15SPaul Saab 	if (returnfd)
914a5f0fb15SPaul Saab 	{
915*c77c4889SXin LI 		unsigned char c;
916b2ea2440SXin LI 		int f;
917a5f0fb15SPaul Saab 
918a5f0fb15SPaul Saab 		/*
9192235c7feSXin LI 		 * The alt file is a pipe. Read one char
920b2ea2440SXin LI 		 * to see if the pipe will produce any data.
921a5f0fb15SPaul Saab 		 * If it does, push the char back on the pipe.
922a5f0fb15SPaul Saab 		 */
923a5f0fb15SPaul Saab 		f = fileno(fd);
924a5f0fb15SPaul Saab 		SET_BINARY(f);
925a5f0fb15SPaul Saab 		if (read(f, &c, 1) != 1)
926a5f0fb15SPaul Saab 		{
927a5f0fb15SPaul Saab 			/*
92896e55cc7SXin LI 			 * Pipe is empty.
92996e55cc7SXin LI 			 * If more than 1 pipe char was specified,
93096e55cc7SXin LI 			 * the exit status tells whether the file itself
93196e55cc7SXin LI 			 * is empty, or if there is no alt file.
93296e55cc7SXin LI 			 * If only one pipe char, just assume no alt file.
933a5f0fb15SPaul Saab 			 */
93496e55cc7SXin LI 			int status = pclose(fd);
93596e55cc7SXin LI 			if (returnfd > 1 && status == 0) {
9362235c7feSXin LI 				/* File is empty. */
93796e55cc7SXin LI 				*pfd = NULL;
93896e55cc7SXin LI 				*pf = -1;
93996e55cc7SXin LI 				return (save(FAKE_EMPTYFILE));
94096e55cc7SXin LI 			}
9412235c7feSXin LI 			/* No alt file. */
942a5f0fb15SPaul Saab 			return (NULL);
943a5f0fb15SPaul Saab 		}
9442235c7feSXin LI 		/* Alt pipe contains data, so use it. */
945a5f0fb15SPaul Saab 		ch_ungetchar(c);
946a5f0fb15SPaul Saab 		*pfd = (void *) fd;
947a5f0fb15SPaul Saab 		*pf = f;
948a5f0fb15SPaul Saab 		return (save("-"));
949a5f0fb15SPaul Saab 	}
950a5f0fb15SPaul Saab #endif
9512235c7feSXin LI 	/* The alt file is a regular file. Read its name from LESSOPEN. */
952000ba3e8STim J. Robbins 	cmd = readfd(fd);
953a5f0fb15SPaul Saab 	pclose(fd);
954000ba3e8STim J. Robbins 	if (*cmd == '\0')
95530a1828cSXin LI 	{
956a5f0fb15SPaul Saab 		/*
957a5f0fb15SPaul Saab 		 * Pipe is empty.  This means there is no alt file.
958a5f0fb15SPaul Saab 		 */
95930a1828cSXin LI 		free(cmd);
960a5f0fb15SPaul Saab 		return (NULL);
96130a1828cSXin LI 	}
962000ba3e8STim J. Robbins 	return (cmd);
963a5f0fb15SPaul Saab #endif /* HAVE_POPEN */
964a5f0fb15SPaul Saab }
965a5f0fb15SPaul Saab 
966a5f0fb15SPaul Saab /*
967a5f0fb15SPaul Saab  * Close a replacement file.
968a5f0fb15SPaul Saab  */
969*c77c4889SXin LI public void close_altfile(constant char *altfilename, constant char *filename)
970a5f0fb15SPaul Saab {
971a5f0fb15SPaul Saab #if HAVE_POPEN
972*c77c4889SXin LI 	constant char *lessclose;
97395270f73SXin LI 	char *qfilename;
97495270f73SXin LI 	char *qaltfilename;
975a5f0fb15SPaul Saab 	FILE *fd;
976a5f0fb15SPaul Saab 	char *cmd;
977*c77c4889SXin LI 	size_t len;
978a5f0fb15SPaul Saab 
979*c77c4889SXin LI 	if (!secure_allow(SF_LESSOPEN))
980a5f0fb15SPaul Saab 		return;
981a5f0fb15SPaul Saab 	if ((lessclose = lgetenv("LESSCLOSE")) == NULL)
982a5f0fb15SPaul Saab 		return;
98396e55cc7SXin LI 	if (num_pct_s(lessclose) > 2)
98496e55cc7SXin LI 	{
985b2ea2440SXin LI 		error("LESSCLOSE ignored; must contain no more than 2 %%s", NULL_PARG);
98696e55cc7SXin LI 		return;
98796e55cc7SXin LI 	}
98895270f73SXin LI 	qfilename = shell_quote(filename);
98995270f73SXin LI 	qaltfilename = shell_quote(altfilename);
990*c77c4889SXin LI 	len = strlen(lessclose) + strlen(qfilename) + strlen(qaltfilename) + 2;
9916dcb072bSXin LI 	cmd = (char *) ecalloc(len, sizeof(char));
99295270f73SXin LI 	SNPRINTF2(cmd, len, lessclose, qfilename, qaltfilename);
99395270f73SXin LI 	free(qaltfilename);
99495270f73SXin LI 	free(qfilename);
995a5f0fb15SPaul Saab 	fd = shellcmd(cmd);
996a5f0fb15SPaul Saab 	free(cmd);
997a5f0fb15SPaul Saab 	if (fd != NULL)
998a5f0fb15SPaul Saab 		pclose(fd);
999a5f0fb15SPaul Saab #endif
1000a5f0fb15SPaul Saab }
1001a5f0fb15SPaul Saab 
1002a5f0fb15SPaul Saab /*
1003a5f0fb15SPaul Saab  * Is the specified file a directory?
1004a5f0fb15SPaul Saab  */
1005*c77c4889SXin LI public lbool is_dir(constant char *filename)
1006a5f0fb15SPaul Saab {
1007*c77c4889SXin LI 	lbool isdir = FALSE;
1008a5f0fb15SPaul Saab 
1009a5f0fb15SPaul Saab #if HAVE_STAT
1010a5f0fb15SPaul Saab {
1011a5f0fb15SPaul Saab 	int r;
1012*c77c4889SXin LI 	less_stat_t statbuf;
1013a5f0fb15SPaul Saab 
1014*c77c4889SXin LI 	r = less_stat(filename, &statbuf);
1015a5f0fb15SPaul Saab 	isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
1016a5f0fb15SPaul Saab }
1017a5f0fb15SPaul Saab #else
1018a5f0fb15SPaul Saab #ifdef _OSK
1019a5f0fb15SPaul Saab {
10201ea31627SRobert Watson 	int f;
1021a5f0fb15SPaul Saab 
1022a5f0fb15SPaul Saab 	f = open(filename, S_IREAD | S_IFDIR);
1023a5f0fb15SPaul Saab 	if (f >= 0)
1024a5f0fb15SPaul Saab 		close(f);
1025a5f0fb15SPaul Saab 	isdir = (f >= 0);
1026a5f0fb15SPaul Saab }
1027a5f0fb15SPaul Saab #endif
1028a5f0fb15SPaul Saab #endif
1029a5f0fb15SPaul Saab 	return (isdir);
1030a5f0fb15SPaul Saab }
1031a5f0fb15SPaul Saab 
1032a5f0fb15SPaul Saab /*
1033a5f0fb15SPaul Saab  * Returns NULL if the file can be opened and
1034a5f0fb15SPaul Saab  * is an ordinary file, otherwise an error message
1035a5f0fb15SPaul Saab  * (if it cannot be opened or is a directory, etc.)
1036a5f0fb15SPaul Saab  */
1037*c77c4889SXin LI public char * bad_file(constant char *filename)
1038a5f0fb15SPaul Saab {
10391ea31627SRobert Watson 	char *m = NULL;
1040a5f0fb15SPaul Saab 
10417f074f9cSXin LI 	if (!force_open && is_dir(filename))
1042a5f0fb15SPaul Saab 	{
1043000ba3e8STim J. Robbins 		static char is_a_dir[] = " is a directory";
1044a5f0fb15SPaul Saab 
1045000ba3e8STim J. Robbins 		m = (char *) ecalloc(strlen(filename) + sizeof(is_a_dir),
1046a5f0fb15SPaul Saab 			sizeof(char));
1047a5f0fb15SPaul Saab 		strcpy(m, filename);
1048000ba3e8STim J. Robbins 		strcat(m, is_a_dir);
1049a5f0fb15SPaul Saab 	} else
1050a5f0fb15SPaul Saab 	{
1051a5f0fb15SPaul Saab #if HAVE_STAT
1052a5f0fb15SPaul Saab 		int r;
1053*c77c4889SXin LI 		less_stat_t statbuf;
1054a5f0fb15SPaul Saab 
1055*c77c4889SXin LI 		r = less_stat(filename, &statbuf);
1056a5f0fb15SPaul Saab 		if (r < 0)
1057a5f0fb15SPaul Saab 		{
1058a5f0fb15SPaul Saab 			m = errno_message(filename);
1059a5f0fb15SPaul Saab 		} else if (force_open)
1060a5f0fb15SPaul Saab 		{
1061a5f0fb15SPaul Saab 			m = NULL;
1062a5f0fb15SPaul Saab 		} else if (!S_ISREG(statbuf.st_mode))
1063a5f0fb15SPaul Saab 		{
1064a5f0fb15SPaul Saab 			static char not_reg[] = " is not a regular file (use -f to see it)";
1065a5f0fb15SPaul Saab 			m = (char *) ecalloc(strlen(filename) + sizeof(not_reg),
1066a5f0fb15SPaul Saab 				sizeof(char));
1067a5f0fb15SPaul Saab 			strcpy(m, filename);
1068a5f0fb15SPaul Saab 			strcat(m, not_reg);
1069a5f0fb15SPaul Saab 		}
1070a5f0fb15SPaul Saab #endif
1071a5f0fb15SPaul Saab 	}
1072a5f0fb15SPaul Saab 	return (m);
1073a5f0fb15SPaul Saab }
1074a5f0fb15SPaul Saab 
1075a5f0fb15SPaul Saab /*
1076a5f0fb15SPaul Saab  * Return the size of a file, as cheaply as possible.
1077a5f0fb15SPaul Saab  * In Unix, we can stat the file.
1078a5f0fb15SPaul Saab  */
1079d713e089SXin LI public POSITION filesize(int f)
1080a5f0fb15SPaul Saab {
1081a5f0fb15SPaul Saab #if HAVE_STAT
1082*c77c4889SXin LI 	less_stat_t statbuf;
1083a5f0fb15SPaul Saab 
1084*c77c4889SXin LI 	if (less_fstat(f, &statbuf) >= 0)
1085a5f0fb15SPaul Saab 		return ((POSITION) statbuf.st_size);
1086a5f0fb15SPaul Saab #else
1087a5f0fb15SPaul Saab #ifdef _OSK
1088a5f0fb15SPaul Saab 	long size;
1089a5f0fb15SPaul Saab 
1090a5f0fb15SPaul Saab 	if ((size = (long) _gs_size(f)) >= 0)
1091a5f0fb15SPaul Saab 		return ((POSITION) size);
1092a5f0fb15SPaul Saab #endif
1093a5f0fb15SPaul Saab #endif
1094a5f0fb15SPaul Saab 	return (seek_filesize(f));
1095a5f0fb15SPaul Saab }
1096a5f0fb15SPaul Saab 
1097*c77c4889SXin LI public lbool curr_ifile_changed(void)
1098d713e089SXin LI {
1099d713e089SXin LI #if HAVE_STAT_INO
1100d713e089SXin LI 	/*
1101d713e089SXin LI 	 * If the file's i-number or device has changed,
1102d713e089SXin LI 	 * or if the file is smaller than it previously was,
1103d713e089SXin LI 	 * the file must be different.
1104d713e089SXin LI 	 */
1105d713e089SXin LI 	struct stat st;
1106d713e089SXin LI 	POSITION curr_pos = ch_tell();
1107d713e089SXin LI 	int r = stat(get_filename(curr_ifile), &st);
1108d713e089SXin LI 	if (r == 0 && (st.st_ino != curr_ino ||
1109d713e089SXin LI 		st.st_dev != curr_dev ||
1110d713e089SXin LI 		(curr_pos != NULL_POSITION && st.st_size < curr_pos)))
1111d713e089SXin LI 		return (TRUE);
1112d713e089SXin LI #endif
1113d713e089SXin LI 	return (FALSE);
1114d713e089SXin LI }
1115d713e089SXin LI 
1116000ba3e8STim J. Robbins /*
1117000ba3e8STim J. Robbins  *
1118000ba3e8STim J. Robbins  */
1119*c77c4889SXin LI public constant char * shell_coption(void)
1120000ba3e8STim J. Robbins {
1121000ba3e8STim J. Robbins 	return ("-c");
1122000ba3e8STim J. Robbins }
112333096f16SXin LI 
112433096f16SXin LI /*
112533096f16SXin LI  * Return last component of a pathname.
112633096f16SXin LI  */
1127*c77c4889SXin LI public constant char * last_component(constant char *name)
112833096f16SXin LI {
1129*c77c4889SXin LI 	constant char *slash;
113033096f16SXin LI 
113133096f16SXin LI 	for (slash = name + strlen(name);  slash > name; )
113233096f16SXin LI 	{
113333096f16SXin LI 		--slash;
113433096f16SXin LI 		if (*slash == *PATHNAME_SEP || *slash == '/')
113533096f16SXin LI 			return (slash + 1);
113633096f16SXin LI 	}
113733096f16SXin LI 	return (name);
113833096f16SXin LI }
1139