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