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