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