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