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