wdvi

network DVI viewer
Log | Files | Refs

filefind.c (45081B)


      1 /*========================================================================*\
      2 
      3 Copyright (c) 1996-2013  Paul Vojta
      4 
      5 Permission is hereby granted, free of charge, to any person obtaining a copy
      6 of this software and associated documentation files (the "Software"), to
      7 deal in the Software without restriction, including without limitation the
      8 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      9 sell copies of the Software, and to permit persons to whom the Software is
     10 furnished to do so, subject to the following conditions:
     11 
     12 The above copyright notice and this permission notice shall be included in
     13 all copies or substantial portions of the Software.
     14 
     15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     18 PAUL VOJTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 
     22 \*========================================================================*/
     23 
     24 /*
     25  *	filefind.c - Routines to perform file searches.
     26  */
     27 
     28 /*
     29  *	We assume that "filf-app.h" includes the following, if appropriate:
     30  *
     31  *		<stdlib.h>
     32  *		<unistd.h>
     33  *		<string.h>
     34  *		<memory.h>
     35  *		<sys/types.h>
     36  *		<sys/stat.h>
     37  *		<setjmp.h>
     38  *		<pwd.h>
     39  */
     40 
     41 #include <sys/types.h>	/* opendir() */
     42 
     43 #include <dirent.h>	/* opendir() */
     44 #include <err.h>
     45 #include <setjmp.h>	/* jmp_buf, longjmp(), setjmp() */
     46 #include <stdlib.h>	/* getenv() */
     47 
     48 #include "filf-app.h"		/* application-related declarations & defs */
     49 #include "filefind.h"
     50 #include "util.h"	/* xfopen(), expandline(), ff_getpw() */
     51 
     52 
     53 /* default list of texmf trees */
     54 #define	DEFAULT_TEXMF_PATH		\
     55 "/usr/local/share/texmf-local:"		\
     56 "/usr/local/share/texmf-dist:"		\
     57 "/usr/local/share/texmf:"		\
     58 "/usr/local/texlive/texmf-local:"	\
     59 "/usr/local/texlive/texmf-dist:"	\
     60 "/usr/local/texlive/texmf:"		\
     61 "/usr/share/texmf-local:"		\
     62 "/usr/share/texmf-dist:"		\
     63 "/usr/share/texmf:"			\
     64 "/usr/share/texlive/texmf-local:"	\
     65 "/usr/share/texlive/texmf-dist:"	\
     66 "/usr/share/texlive/texmf"
     67 
     68 
     69 #if !defined(S_ISREG) && defined(S_IFREG)
     70 #define	S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)
     71 #endif
     72 
     73 #if !defined(S_ISDIR) && defined(S_IFDIR)
     74 #define	S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
     75 #endif
     76 
     77 #if !defined(S_ISLNK) && defined(S_IFLNK)
     78 #define	S_ISLNK(m)	(((m) & S_IFMT) == S_IFLNK)
     79 #endif
     80 
     81 #ifndef	ST_NLINK_TRICK
     82 #define	ST_NLINK_TRICK	1
     83 #endif	/* !defined(ST_NLINK_TRICK) */
     84 
     85 	/* These are defined so that the editor can match braces properly.  */
     86 #define	LBRACE	'{'
     87 #define	RBRACE	'}'
     88 
     89 	/* In configuration files, semicolons are also valid separators.  */
     90 #define	IS_SEP(c)	((c) == ':')
     91 
     92 /*	Local copy of findrec record. */
     93 
     94 static	struct findrec	lrec;
     95 
     96 /*
     97  *	Additional data structures.
     98  */
     99 
    100 struct	statrec {		/* status vis-a-vis laying down the file name */
    101 	unsigned	pos;		/* position in ffline */
    102 	int		flags;
    103 	char		quickchar;	/* '\0' or 'q' or 'Q' */
    104 	unsigned	quickpos;	/* value of pos (above) if 'q' or 'Q' */
    105 };
    106 
    107 struct	texmfrec {		/* list of texmf components */
    108 	struct texmfrec	*next;
    109 	const	char	*home;
    110 	const	char	*str;
    111 	int		len;
    112 	int		flags;
    113 };
    114 
    115 static	const char	default_texmf_path[]	= DEFAULT_TEXMF_PATH;
    116 
    117 /*	These are global variables. */
    118 
    119 const	char			*fF_values[MAX_N_OPTS];	/* not static */
    120 
    121 static	FILE			*file_found;
    122 static	jmp_buf			got_it;
    123 static	struct steprec		**bracepp;
    124 static	struct steprec		**steppp;
    125 static	struct findrec		*frecp;		/* used to distinguish ls-Rs */
    126 static	int			seqno;
    127 static	struct texmfrec		*texmfhead	= NULL;
    128 static	struct rootrec		**rootpp;
    129 static	const	struct atomrec	*treeatom;
    130 
    131 	/* dummy records for handling %s after // or wild cards */
    132 static	struct atomrec	pct_s_dummy = {NULL, NULL, NULL, F_PCT_S};
    133 static	struct atomrec	pct_s_dummy_slash
    134 			= {NULL, NULL, NULL, F_PCT_S | F_SLASH_SLASH};
    135 
    136 	/* flag value for directories not read yet */
    137 static	struct treerec	not_read_yet = {NULL, NULL, NULL};
    138 
    139 /*
    140  *	The following expands in 128-byte increments to be as long as is
    141  *	necessary.  We don't often use pointers to refer to elements of this
    142  *	array, since it may move when it is enlarged.
    143  */
    144 
    145 #if !EXTERN_FFLINE
    146 static	char	*ffline		= NULL;
    147 static	int	ffline_len	= 0;	/* current length of the array */
    148 #endif
    149 
    150 
    151 /*
    152  *	ls-R database hash table
    153  */
    154 
    155 struct	lsr {
    156 	struct lsr	*next;		/* next in hash chain */
    157 	struct findrec	*frecp;		/* which search type? */
    158 	short		seqno;		/* which ls-R invocation we are using */
    159 	short		keylen;		/* length of key */
    160 	const	char	*key;
    161 	const	char	*value;
    162 };
    163 
    164 static	struct lsr	*lsrtab[1024];	/* hash table */
    165 
    166 
    167 /*
    168  *	Forward references.
    169  */
    170 
    171 static	void		dobrace(const char *, unsigned, int, Boolean);
    172 static	struct steprec	*scan_pct_s(void);
    173 static	void		atomize_pct_s(void);
    174 
    175 
    176 #if !EXTERN_FFLINE
    177 
    178 /*
    179  *	Expand ffline[] to at least the given size.
    180  */
    181 
    182 static	void
    183 expandline(n)
    184 	int	n;
    185 {
    186 	int	newlen	= n + 128;
    187 
    188 	ffline = (ffline == NULL) ? xmalloc(newlen) : xrealloc(ffline, newlen);
    189 	ffline_len = newlen;
    190 }
    191 
    192 #endif	/* !EXTERN_FFLINE */
    193 
    194 
    195 /*
    196  *	prehash() - hash function (before modding).
    197  */
    198 
    199 static	unsigned int
    200 prehash(str, len)
    201 	const	char	*str;
    202 	size_t		len;
    203 {
    204 	const	char	*p;
    205 	unsigned int	hash;
    206 
    207 	hash = 0;
    208 	for (p = str + len; p > str; )
    209 	    hash = hash * 5 + *--p;
    210 	return hash;
    211 }
    212 
    213 
    214 #if !EXTERN_GETPW
    215 
    216 /*
    217  *	Look up the home directory.
    218  */
    219 
    220 static	const	struct passwd *
    221 ff_getpw(pp, p_end)
    222 	const	char	**pp;
    223 	const	char	*p_end;
    224 {
    225 	const	char		*p	= *pp;
    226 	const	char		*p1;
    227 	int			len;
    228 	const	struct passwd	*pw;
    229 
    230 	++p;	/* skip the tilde */
    231 	p1 = p;
    232 	while (p1 < p_end && *p1 != '/') ++p1;
    233 	len = p1 - p;
    234 	if (len == 0)	/* if no user name */
    235 	    pw = getpwuid(getuid());
    236 	else {
    237 	    if (len >= ffline_len)
    238 		expandline(len);
    239 	    bcopy(p, ffline, len);
    240 	    ffline[len] = '\0';
    241 	    pw = getpwnam(ffline);
    242 	}
    243 	if (pw != NULL)
    244 	    *pp = p1;
    245 	return pw;
    246 }
    247 
    248 #endif	/* !EXTERN_GETPW */
    249 
    250 
    251 static	void
    252 gethome(pp, p_end, homepp)
    253 	const	char	**pp;
    254 	const	char	*p_end;
    255 	const	char	**homepp;
    256 {
    257 	const	struct passwd	*pw;
    258 
    259 	pw = ff_getpw(pp, p_end);
    260 	if (pw != NULL)
    261 	    *homepp = xstrdup(pw->pw_dir);
    262 }
    263 
    264 /*
    265  *	Scan the path part from // or wild cards, to the end.
    266  *	This assumes that {} have already been taken care of.
    267  */
    268 
    269 static	void
    270 atomize(p0, sp)
    271 	const	char	*p0;
    272 	struct steprec	*sp;
    273 {
    274 	struct atomrec	*head;
    275 	struct atomrec	**linkpp;
    276 	int		flags;		/* F_SLASH_SLASH and F_WILD */
    277 	Boolean		percent_s;
    278 	const	char	*p	= p0;
    279 	const	char	*p1;
    280 	const	char	*p2;
    281 
    282 	/*
    283 	 * First check the string for braces.
    284 	 */
    285 
    286 	for (p1 = p; ;++p1) {
    287 	    if (*p1 == '\0' || IS_SEP(*p1))
    288 		break;
    289 	    if (*p1 == LBRACE) {
    290 		/* Take care of braces first.  This code should only be reached
    291 		 * if atomize() is called from ff_prescan(). */
    292 		unsigned n;
    293 
    294 		bracepp = &sp->nextstep;
    295 		n = p1 - p0;
    296 		if (n >= ffline_len)
    297 		    expandline(n);
    298 		bcopy(p0, ffline, n);
    299 		dobrace(p1, n, 1, False);
    300 		return;
    301 	    }
    302 	    if (*p1 == '%' && p1[1] != '\0')
    303 		++p1;
    304 	}
    305 
    306 	/*
    307 	 * No braces.  Split it up into atoms (delimited by / or //).
    308 	 */
    309 
    310 	linkpp = &head;
    311 	flags = 0;
    312 	percent_s = False;
    313 	for (;;) {
    314 	    if (*p == '/') {
    315 		++p;
    316 		flags |= F_SLASH_SLASH;
    317 		/* a %[qQ] may occur following the first //. */
    318 		if (*p == '%')
    319 		    if (p[1] == 'q' || p[1] == 'Q') {
    320 			if (p[1] == 'Q') flags |= F_QUICKONLY;
    321 			flags |= F_QUICKFIND;
    322 			p += 2;
    323 		    }
    324 	    }
    325 	    p1 = p;
    326 	    for (;; ++p) {
    327 		if (*p == '\0' || IS_SEP(*p)) {
    328 		    p2 = NULL;
    329 		    if (!(sp->flags & F_FILE_USED))
    330 			p2 = lrec.no_f_str + 1;
    331 		    break;
    332 		}
    333 		if (*p == '/') {
    334 		    p2 = p + 1;
    335 		    break;
    336 		}
    337 		if (*p == '%' && p[1] != '\0' && p[1] != '/') {
    338 		    ++p;
    339 		    if (*p == 'f') sp->flags |= F_FILE_USED;
    340 		    else if (*p == 's' && (p[1] == '\0' || IS_SEP(p[1]))) {
    341 			--p;
    342 			percent_s = True;
    343 			if (lrec.v.pct_s_atom == NULL)
    344 			    atomize_pct_s();
    345 			break;
    346 		    }
    347 		}
    348 		else if (*p == '*' || *p == '?' || *p == '[')
    349 		    flags |= F_WILD;
    350 	    }
    351 	    if (p != p1) {	/* add on an atomrec */
    352 		struct atomrec	*atomp;
    353 
    354 		atomp = xmalloc(sizeof(struct atomrec));
    355 		*linkpp = atomp;
    356 		linkpp = &atomp->next;
    357 		atomp->flags = flags;
    358 		flags = 0;
    359 		atomp->p = p1;
    360 		atomp->p_end = p;
    361 	    }
    362 	    if (percent_s) {
    363 		*linkpp = (flags & F_SLASH_SLASH) ? &pct_s_dummy_slash
    364 		    : &pct_s_dummy;
    365 		sp->flags |= F_PCT_S;
    366 		break;
    367 	    }
    368 	    p = p2;
    369 	    if (p == NULL) {
    370 		*linkpp = NULL;
    371 		break;
    372 	    }
    373 	}
    374 	sp->atom = head;
    375 }
    376 
    377 /*
    378  *	prescan2() sets up the steprec information for a brace alternative.
    379  *	It also does much of the work in ff_prescan().
    380  */
    381 
    382 static	const	char *
    383 prescan2(sp, p, skip)
    384 	struct steprec	*sp;
    385 	const	char	*p;
    386 	Boolean		skip;		/* true if initial / does not mean // */
    387 {
    388 	int		flags;
    389 
    390 	/*
    391 	 * Scan the string, looking for // or wild cards or braces.
    392 	 * It takes the input / by / so that we can back up to the previous
    393 	 * slash if there is a wild card (which may be hidden in braces).
    394 	 */
    395 
    396 	flags = sp->flags;
    397 	for (;;) {	/* loop over slash-separated substrings */
    398 	    sp->strend = p;
    399 	    sp->flags = flags;
    400 	    while (*p == '/') {	/* check for // */
    401 		if (skip) {
    402 		    ++p;
    403 		    skip = False;
    404 		}
    405 		else {
    406 		    atomize(p, sp);
    407 		    return p;
    408 		}
    409 	    }
    410 	    for (;;) {	/* loop over characters in this subpart */
    411 		if (*p == '\0' || IS_SEP(*p)) {
    412 		    sp->strend = p;
    413 		    sp->flags = flags;
    414 		    return p;
    415 		}
    416 		if (*p == '/')
    417 		    break;
    418 		if (*p == LBRACE) {
    419 		    unsigned n;
    420 
    421 		    bracepp = &sp->nextstep;
    422 		    n = p - sp->strend;
    423 		    if (n >= ffline_len)
    424 			expandline(n);
    425 		    bcopy(sp->strend, ffline, n);
    426 		    dobrace(p, n, 1, skip);
    427 		    return p;
    428 		}
    429 		skip = False;
    430 		if (*p == '%' && p[1] != '\0') {
    431 		    ++p;
    432 		    if (*p == 's' && (p[1] == '\0' || IS_SEP(p[1]))) {
    433 			sp->strend = p - 1;
    434 			sp->flags = flags;
    435 			sp->nextstep = scan_pct_s();
    436 			return p;
    437 		    }
    438 		    else if (*p == 'f')
    439 			flags |= F_FILE_USED | F_VARIABLE;
    440 		    else if (*p == 'F' || *p == lrec.x_var_char)
    441 			flags |= F_VARIABLE;
    442 		    /* %c and %C are sorted out later. */
    443 		}
    444 		else if (*p == '*' || *p == '?' || *p == '[') {
    445 		    atomize(sp->strend, sp);
    446 		    return p;
    447 		}
    448 		++p;
    449 	    }
    450 	    ++p;
    451 	}
    452 }
    453 
    454 /*
    455  *	dobrace() is the recursive routine for handling {} alternatives.
    456  *	bracepp (global) = address of where to put next brace item on the
    457  *		linked list.  It has to be global because dobrace() is called
    458  *		recursively and leaves its droppings at varying levels.
    459  *	p = input string pointer; it should point to the opening brace.
    460  *	pos = position within ffline[] for output string.
    461  *	level = nesting depth.
    462  *
    463  *	skip:	true if initial / does not mean //
    464  */
    465 
    466 static	void
    467 dobrace(const char *p, unsigned pos0, int level, Boolean skip)
    468 {
    469 	int		lev;
    470 	int		level1;
    471 	const	char	*p1;
    472 	unsigned	pos;
    473 
    474 	for (;;) {	/* loop over the alternatives */
    475 	    lev = 0;
    476 	    ++p;	/* skip left brace or comma */
    477 	    pos = pos0;
    478 	    for (;;) {	/* loop over characters */
    479 		if (*p == '\0' || IS_SEP(*p)) {
    480 			/* keep the braces matched:  { */
    481 		    warnx("Missing } in '%s' path.", lrec.type);
    482 		    return;
    483 		}
    484 		else if (*p == '%') {
    485 		    if (p[1] != '\0') {
    486 			if (pos >= ffline_len)
    487 			    expandline(pos);
    488 			ffline[pos++] = *p++;
    489 		    }
    490 		}
    491 		else if (*p == LBRACE) {
    492 		    dobrace(p, pos, level + 1, skip && pos == pos0);
    493 		    /* skip to the next matching comma or right brace */
    494 		    for (;;) {
    495 			if (*p == '\0' || IS_SEP(*p))
    496 			    return;	/* this has already been reported */
    497 			else if (*p == '%') {
    498 			    if (p[1] != '\0') ++p;
    499 			}
    500 			else if (*p == LBRACE)
    501 			    ++lev;
    502 			else if (*p == ',') {
    503 			    if (lev == 0)
    504 				break;
    505 			}
    506 			else if (*p == RBRACE) {
    507 			    if (lev == 0)
    508 				break;
    509 			    --lev;
    510 			}
    511 			++p;
    512 		    }
    513 		    goto out2;
    514 		}
    515 		else if (*p == ',' || *p == RBRACE)
    516 		    break;
    517 		if (pos >= ffline_len)
    518 		    expandline(pos);
    519 		ffline[pos++] = *p++;
    520 	    }
    521 	    p1 = p;
    522 	    level1 = level;
    523 	    for (;;) {
    524 		/* skip until matching right brace */
    525 		for (;;) {
    526 		    if (*p1 == '\0' || IS_SEP(*p1))
    527 			goto out2;	/* this has already been reported */
    528 		    if (*p1 == '%') {
    529 			if (p1[1] != '\0') ++p1;
    530 		    }
    531 		    else if (*p1 == LBRACE)
    532 			++lev;
    533 		    else if (*p1 == RBRACE) {
    534 			if (lev == 0) {
    535 			    ++p1;
    536 			    break;
    537 			}
    538 			--lev;
    539 		    }
    540 		    ++p1;
    541 		}
    542 		--level1;
    543 		/* now copy until a qualifying comma */
    544 		for (;;) {
    545 		    if (*p1 == '\0' || IS_SEP(*p1)) {
    546 			if (level1 == 0) {
    547 			    struct steprec *bp;
    548 
    549 			    if (pos >= ffline_len)
    550 				expandline(pos);
    551 			    ffline[pos++] = '\0';
    552 			    *bracepp = bp = xmalloc(sizeof(struct steprec));
    553 			    bp->next = NULL;
    554 			    bp->atom = NULL;
    555 			    bp->nextstep = NULL;
    556 			    bracepp = &bp->next;
    557 			    bp->str = xmemdup(ffline, pos);
    558 			    bp->flags = 0;
    559 			    (void) prescan2(bp, bp->str, skip);
    560 			}
    561 			/* else:  this has already been reported */
    562 			goto out2;
    563 		    }
    564 		    if (*p1 == '%') {
    565 			if (p1[1] != '\0') {
    566 			    if (pos >= ffline_len)
    567 				expandline(pos);
    568 			    ffline[pos++] = '%';
    569 			    ++p1;
    570 			}
    571 		    }
    572 		    else if (*p1 == LBRACE) {
    573 			dobrace(p1, pos, level1 + 1, False);
    574 			goto out2;
    575 		    }
    576 		    else if (*p1 == ',' && level1 > 0)
    577 			break;
    578 		    else if (*p1 == RBRACE && level1 > 0) {
    579 			--level1;
    580 			++p1;
    581 			continue;
    582 		    }
    583 		    if (pos >= ffline_len)
    584 			expandline(pos);
    585 		    ffline[pos++] = *p1++;
    586 		}
    587 	    }
    588 	    out2:
    589 	    if (*p != ',') break;
    590 	}
    591 }
    592 
    593 
    594 /*
    595  *	Prescan the '%s' string.  This is done only once.
    596  */
    597 
    598 static	struct steprec *
    599 scan_pct_s()
    600 {
    601 	struct steprec	**headpp;
    602 	const	char	*p;
    603 	struct steprec	*sp;
    604 
    605 	if (lrec.v.pct_s_head != NULL)	/* if we've already done this */
    606 	    return lrec.v.pct_s_head;
    607 
    608 	headpp = &lrec.v.pct_s_head;
    609 	p = lrec.pct_s_str;
    610 	for (p = lrec.pct_s_str; ; ++p) {
    611 	    ++lrec.v.pct_s_count;
    612 	    *headpp = sp = xmalloc(sizeof(struct steprec));
    613 	    sp->atom = NULL;
    614 	    sp->nextstep = NULL;
    615 	    sp->flags = 0;
    616 	    sp->str = p;
    617 	    headpp = &sp->next;
    618 	    (void) prescan2(sp, p, False);
    619 	    for (; *p != '\0' && !IS_SEP(*p); ++p)
    620 		if (*p == '%' && p[1] != '\0') ++p;
    621 	    if (*p == '\0') break;
    622 	}
    623 	*headpp = NULL;
    624 	return lrec.v.pct_s_head;
    625 }
    626 
    627 /*
    628  *	Pre-atomize the '%s' strings.  Again, done only once.
    629  */
    630 
    631 static	void
    632 atomize_pct_s()
    633 {
    634 	const	struct atomrec	**app;
    635 	struct steprec		*sp;
    636 
    637 	(void) scan_pct_s();	/* make sure the count is valid */
    638 
    639 	app = lrec.v.pct_s_atom =
    640 		xmalloc((unsigned) lrec.v.pct_s_count * sizeof(*app));
    641 
    642 	for (sp = lrec.v.pct_s_head; sp != NULL; sp = sp->next, ++app) {
    643 	    static struct steprec dummy;
    644 
    645 	    dummy.flags = 0;
    646 	    atomize(sp->str, &dummy);
    647 	    *app = dummy.atom;
    648 	}
    649 }
    650 
    651 
    652 /*
    653  *	Init_texmf - Initialize the linked list of options for %t.
    654  */
    655 
    656 static	void		/* here's a small subroutine */
    657 add_to_texmf_list(pp)
    658 	const	char	**pp;
    659 {
    660 	static	struct texmfrec	**tpp	= &texmfhead;
    661 	struct texmfrec		*tp;
    662 	const	char		*p;
    663 	const	char		*p_end;
    664 	const	char		*home	= NULL;
    665 	int			flags	= 0;
    666 
    667 	p = *pp;
    668 	p_end = index(p, ':');
    669 	if (p_end == NULL) p_end = p + strlen(p);
    670 	*pp = p_end;
    671 
    672 	if (*p == '!' && p[1] == '!') {
    673 	    p += 2;
    674 	    flags = F_QUICKONLY;
    675 	}
    676 
    677 	if (*p == '~')
    678 	    gethome(&p, p_end, &home);
    679 
    680 	tp = *tpp = xmalloc(sizeof(struct texmfrec));
    681 	tp->home = home;
    682 	tp->str = p;
    683 	tp->len = p_end - p;
    684 	tp->flags = flags;
    685 	tp->next = NULL;
    686 	tpp = &tp->next;
    687 }
    688 
    689 static	void
    690 init_texmf()
    691 {
    692 	const	char		*texmf;
    693 	const	char		*texmf2;
    694 	const	struct texmfrec	*tp;
    695 	unsigned		n;
    696 
    697 	if (texmfhead != NULL)
    698 	    return;
    699 
    700 	texmf = getenv("TEXMF");
    701 	texmf2 = default_texmf_path;
    702 	if (texmf == NULL) {
    703 	    texmf = default_texmf_path;
    704 	    texmf2 = NULL;
    705 	}
    706 
    707 	for (;;) {
    708 	    if (*texmf == '\0' || *texmf == ':') {
    709 		if (texmf2 != NULL)
    710 		    for (;;) {
    711 			while (*texmf2 == ':') ++texmf2;
    712 			if (*texmf2 == '\0') break;
    713 			add_to_texmf_list(&texmf2);
    714 		    }
    715 	    }
    716 	    else
    717 		add_to_texmf_list(&texmf);
    718 	    if (*texmf == '\0')
    719 		break;
    720 	    ++texmf;
    721 	}
    722 
    723 	/* Make sure ffline[] is long enough for these. */
    724 
    725 	n = 0;
    726 	for (tp = texmfhead; tp != NULL; tp = tp->next)
    727 	    if (tp->len > n)
    728 		n = tp->len;
    729 
    730 	if (n > ffline_len)
    731 	    expandline(n);
    732 }
    733 
    734 
    735 /*
    736  *	Scan this path part.  This is done at most once.
    737  *	We look for // or wild cards or {}.
    738  */
    739 
    740 static	const	char *
    741 ff_prescan(p)
    742 	const	char	*p;
    743 {
    744 	struct steprec	*sp;
    745 
    746 	*steppp = sp = xmalloc(sizeof(struct steprec));
    747 	sp->next = NULL;
    748 	sp->flags = 0;
    749 	sp->atom = NULL;
    750 	sp->nextstep = NULL;
    751 
    752 	/*
    753 	 * Scan the string, looking for // or wild cards or braces.
    754 	 */
    755 
    756 	sp->str = p;
    757 	return prescan2(sp, p, True);
    758 }
    759 
    760 /*
    761  *	This routine handles the translation of the '%' modifiers.
    762  *	It returns the new number string length (pos).
    763  *	It makes sure that at least one free byte remains in ffline[].
    764  */
    765 
    766 static	unsigned
    767 xlat(stat_in, stat_ret, pos0, p, lastp)
    768 	const	struct statrec	*stat_in;
    769 	struct statrec		*stat_ret;
    770 	unsigned		pos0;
    771 	const	char		*p;
    772 	const	char		*lastp;
    773 {
    774 	struct statrec		status;
    775 	const	char		*q;
    776 	int			l;
    777 
    778 	status.pos = pos0;
    779 	if (stat_ret != NULL)
    780 	    status = *stat_in;
    781 
    782 	while (p < lastp) {
    783 	    q = memchr(p, '%', lastp - p);
    784 	    l = (q != NULL ? q : lastp) - p;
    785 	    if (status.pos + l >= ffline_len)
    786 		expandline(status.pos + l);
    787 	    bcopy(p, ffline + status.pos, l);
    788 	    status.pos += l;
    789 	    p = q;
    790 	    if (p == NULL) break;
    791 	    do {
    792 		++p;
    793 		if (p >= lastp) break;
    794 		q = index(lrec.fF_etc, *p);
    795 		if (q != NULL) {
    796 		    const char *str = fF_values[q - lrec.fF_etc];
    797 
    798 		    if (str != NULL) {
    799 			l = strlen(str);
    800 			if (status.pos + l >= ffline_len)
    801 			    expandline(status.pos + l);
    802 			bcopy(str, ffline + status.pos, l);
    803 			status.pos += l;
    804 		    }
    805 		    else {	/* eliminate a possible double slash */
    806 			if (p[1] == '/' && (status.pos == 0
    807 				|| ffline[status.pos - 1] == '/'))
    808 			    --status.pos;
    809 		    }
    810 		}
    811 		else if (*p == 'q' || *p == 'Q') {
    812 		    status.quickchar = *p;
    813 		    status.quickpos = status.pos;
    814 		}
    815 		else {
    816 		    if (status.pos + 1 >= ffline_len)
    817 			expandline(ffline_len);
    818 		    ffline[status.pos++] = *p;
    819 		}
    820 		++p;
    821 	    }
    822 	    while (p < lastp && *p == '%');
    823 	}
    824 
    825 	if (stat_ret != NULL)
    826 	    *stat_ret = status;
    827 	return status.pos;
    828 }
    829 
    830 
    831 /*
    832  *	Try to open the file.  Exit via longjmp() if success.
    833  */
    834 static	void
    835 try_to_open(unsigned pos, int flags)
    836 {
    837 	ffline[pos] = '\0';
    838 
    839 	if (FFDEBUG)
    840 	    printf("Trying %s file %s\n", lrec.type, ffline);
    841 
    842 	file_found = xfopen(ffline, "r");
    843 	if (file_found != NULL)
    844 	    longjmp(got_it, 1);
    845 }
    846 
    847 
    848 /*
    849  *	lsrgetline - Read a line from the (ls-R) file and return:
    850  *		minus its length if it ends in a colon (including the colon);
    851  *		its length	 otherwise;
    852  *		zero		 for end of file.
    853  *	Blank lines are skipped.
    854  */
    855 
    856 static	int
    857 lsrgetline(f, pos0)
    858 	FILE		*f;	/* ls-R file */
    859 	unsigned	pos0;	/* relative position in line to start reading */
    860 {
    861 	unsigned	pos	= pos0;
    862 
    863 	for (;;) {
    864 	    if (pos + 4 >= ffline_len)
    865 		expandline(pos);
    866 	    if (fgets(ffline + pos, ffline_len - pos, f) == NULL)
    867 		break;
    868 	    pos += strlen(ffline + pos);
    869 	    if (pos > pos0 && ffline[pos - 1] == '\n') {
    870 		--pos;		/* trim newline */
    871 		if (pos > pos0) break;	/* if ffline still nonempty */
    872 	    }
    873 	}
    874 	if (pos > pos0 && ffline[pos - 1] == ':') {
    875 	    ffline[pos - 1] = '/';	/* change trailing colon to slash */
    876 	    return pos0 - pos;
    877 	}
    878 	else
    879 	    return pos - pos0;
    880 }
    881 
    882 
    883 /*
    884  *	lsrput - Put a key/value pair into the ls-R database.
    885  */
    886 
    887 static	void
    888 lsrput(key, keylen, value)
    889 	const	char	*key;
    890 	unsigned	keylen;
    891 	const	char	*value;
    892 {
    893 	struct lsr	**lpp;
    894 	struct lsr	*lp;
    895 
    896 #if 0
    897 	fputs("lsrput:  key = `", stdout);
    898 	fwrite(key, 1, keylen, stdout);
    899 	printf("'; value = `%s'.\n", value);
    900 #endif
    901 
    902 	    /* make the new record */
    903 	lp = xmalloc(sizeof(*lp));
    904 	lp->next = NULL;
    905 	lp->frecp = frecp;
    906 	lp->seqno = seqno;
    907 	lp->keylen = keylen;
    908 	lp->key = xmemdup(key, keylen);
    909 	lp->value = value;
    910 
    911 	    /* insert it */
    912 	lpp = lsrtab + prehash(key, keylen) % XtNumber(lsrtab);
    913 	while (*lpp != NULL)	/* skip to end of chain (performance reasons) */
    914 	    lpp = &(*lpp)->next;
    915 	*lpp = lp;
    916 }
    917 
    918 
    919 /*
    920  *	init_quick_find - Read ls-R database for this particular search.
    921  */
    922 
    923 static	void
    924 init_quick_find(root, pos, atom, pos1)
    925 	struct rootrec		*root;	/* pointer to the governing rootrec */
    926 	unsigned		pos;	/* position of %[qQ] in ffline[] */
    927 	const	struct atomrec	*atom;	/* the beginning of the atomlist */
    928 	unsigned		pos1;	/* next free byte in ffline[] */
    929 {
    930 #define	LS_R_LEN	5		/* length of the string "ls-R\0" */
    931 	char		tmpsav[LS_R_LEN];
    932 	int		keypos;		/* position of start of key pattern */
    933 	int		keyend;		/* last + 1 character of the key */
    934 	int		keypos_a;	/* position + 1 of last slash */
    935 	unsigned	pos2;		/* end + 1 of the decoded thing */
    936 	int		nslashes;	/* how many slashes in bbbcccddd */
    937 	FILE		*f;		/* ls-R file */
    938 	const	char	*fullpat;	/* fields in *root */
    939 	const	char	*keypat;	/* " */
    940 	const	char	*keypat_end;	/* " */
    941 	const	char	*fullpat_end;	/* " */
    942 	const	char	*keypat_a;	/* position of end+1 of restricted
    943 					 * initial fixed part */
    944 	int		retval;		/* return value of lsrgetline */
    945 
    946 	/*
    947 	 *	Reencode the string of atoms into a character string
    948 	 *	When we're done, ffline[] contains:
    949 	 *	/aa/aa/a...a/ bbb/bb...bb/bbb %f/cc/c...c/%d ddd/ddd/.../ddd
    950 	 *
    951 	 *	where the aaa part is what's already in ffline[], and
    952 	 *	the rest is the decoded atom chain.  The ccc part of this
    953 	 *	string contains all the variable specifiers (not replaced by
    954 	 *	anything), and the bbb and ddd parts are character strings that
    955 	 *	should occur verbatim in the file name.
    956 	 */
    957 
    958 	root->flags &= ~F_QUICKFIND;	/* clear it for now; set it later */
    959 
    960 	if (atom->flags & F_PCT_S)
    961 	    atom = treeatom;
    962 
    963 	if (atom->flags & F_WILD) return;	/* not allowed */
    964 
    965 	pos2 = pos1;
    966 	keypos = -1;
    967 	nslashes = 0;
    968 
    969 	for (;;) {
    970 	    const char *p;
    971 
    972 	    keypos_a = pos2;
    973 		/* translate the atom, leaving %f and maybe %d alone */
    974 	    for (p = atom->p; p < atom->p_end; ++p) {
    975 		if (p >= atom->p_end) break;
    976 		if (*p == '%') {
    977 		    const char *q;
    978 
    979 		    ++p;
    980 		    if (p >= atom->p_end) break;
    981 		    q = index(lrec.fF_etc, *p);
    982 		    if (q != NULL) {
    983 			if (q - lrec.fF_etc < lrec.n_var_opts) {
    984 			    /* it's a variable specifier (%f or ...) */
    985 			    --p;	/* copy it over */
    986 			    if (keypos < 0) keypos = pos2;
    987 			    keyend = pos2 + 2;
    988 			}
    989 			else {
    990 			    const char *str	= fF_values[q - lrec.fF_etc];
    991 
    992 			    if (str != NULL) {
    993 				int	l	= strlen(str);
    994 
    995 				if (pos2 + l >= ffline_len)
    996 				    expandline(pos2 + l);
    997 				bcopy(str, ffline + pos2, l);
    998 				pos2 += l;
    999 			    }
   1000 			    continue;
   1001 			}
   1002 		    }
   1003 		}
   1004 		if (pos2 + 1 >= ffline_len)
   1005 		    expandline(pos2);
   1006 		ffline[pos2++] = *p;
   1007 	    }
   1008 
   1009 	    atom = atom->next;
   1010 	    if (atom == NULL) break;
   1011 
   1012 	    if (atom->flags & F_PCT_S) atom = treeatom;
   1013 	    if (atom->flags & (F_SLASH_SLASH | F_WILD))
   1014 		return;		/* not supported */
   1015 
   1016 	    if (pos2 == keypos_a) continue;	/* if /%m/ with no mode */
   1017 	    ffline[pos2++] = '/';
   1018 	    ++nslashes;
   1019 	}
   1020 
   1021 	if (keypos_a > keypos) keypos_a = keypos;
   1022 
   1023 	root->flags |= F_QUICKFIND;	/* it's eligible */
   1024 
   1025 	if (FFDEBUG) {
   1026 #define	DDD(a,b,c)	\
   1027 		fputs(a, stdout); \
   1028 		fwrite(ffline + (b), 1, (c) - (b), stdout); \
   1029 		puts("'");
   1030 
   1031 	    DDD("\nReading ls-R.\nOriginal string = `", 0, pos1);
   1032 	    DDD("Restricted initial fixed part = `", pos1, keypos_a);
   1033 	    DDD("Initial fixed part = `", pos1, keypos);
   1034 	    DDD("Key pattern = `", keypos, keyend);
   1035 	    DDD("Final fixed part = `", keyend, pos2);
   1036 #undef	DDD
   1037 	}
   1038 
   1039 	/*
   1040 	 * Open ls-R and start reading it.
   1041 	 */
   1042 
   1043 	bcopy(ffline + pos, tmpsav, sizeof(tmpsav));
   1044 	bcopy("ls-R", ffline + pos, sizeof(tmpsav));
   1045 	if (FFDEBUG) printf("Opening ls-R file %s\n", ffline);
   1046 	f = xfopen(ffline, "r");
   1047 	bcopy(tmpsav, ffline + pos, sizeof(tmpsav));
   1048 	if (f == NULL) {
   1049 	    if (FFDEBUG) printf("xfopen: %s\n", strerror(errno));
   1050 	    return;
   1051 	}
   1052 
   1053 	fullpat = xmemdup(ffline + pos1, pos2 - pos1);
   1054 	keypat = fullpat + (keypos - pos1);
   1055 	keypat_a = fullpat + (keypos_a - pos1);
   1056 	keypat_end = fullpat + (keyend - pos1);
   1057 	fullpat_end = fullpat + (pos2 - pos1);
   1058 
   1059 	do {			/* skip initial comment lines */
   1060 	    retval = lsrgetline(f, pos1);
   1061 	    if (retval == 0) {
   1062 		fclose(f);
   1063 		return;		/* premature end of file */
   1064 	    }
   1065 	} while (ffline[pos1] == '%');
   1066 
   1067 	/* First take care of file names without a directory: these are
   1068 	 * those in the same directory as the ls-R file */
   1069 
   1070 	while (retval > 0) {
   1071 	    if (nslashes == 0	/* if there are no slashes in the pattern */
   1072 				/* and if ls-R is in the same directory
   1073 				 * as the //: */
   1074 		    && pos1 == pos
   1075 				/* and if the initial fixed part matches: */
   1076 		    && memcmp(ffline + pos1, fullpat, keypat - fullpat) == 0
   1077 				/* and if the final fixed part matches: */
   1078 		    && memcmp(ffline + pos1 + retval
   1079 				- (fullpat_end - keypat_end),
   1080 				keypat_end, fullpat_end - keypat_end) == 0)
   1081 				/* then create a directory entry */
   1082 		lsrput(ffline + pos1 + (keypat - fullpat),
   1083 		  retval - (keypat - fullpat) - (fullpat_end - keypat_end), "");
   1084 	    retval = lsrgetline(f, pos1);	/* read next line */
   1085 	}
   1086 
   1087 	/*
   1088 	 * Now do whole directories.
   1089 	 */
   1090 
   1091 	for (;;) {
   1092 	    int		i;
   1093 	    const char *p;
   1094 	    const char *p_begin;
   1095 	    const char *p_end;
   1096 	    unsigned	pos3;
   1097 	    unsigned	pos4;
   1098 	    unsigned	pos5;
   1099 	    unsigned	retpos;
   1100 	    char	*value;
   1101 
   1102 	    while (retval > 0)		/* skip to next directory */
   1103 		retval = lsrgetline(f, pos1);
   1104 	    if (retval == 0) break;	/* if end of file */
   1105 	    retval = -retval;		/* length of the string */
   1106 	    /* also, retval is now > 0, so a continue statement won't lead to
   1107 	     * an infinite loop. */
   1108 	    retpos = pos1 + retval;
   1109 	    if (ffline[pos1] == '/') {	/* if absolute path */
   1110 			/* these should be referring to the same directory */
   1111 		if (retval < pos1 || memcmp(ffline + pos1, ffline, pos1) != 0)
   1112 		    continue;
   1113 		pos2 = pos1 + pos1;
   1114 	    }
   1115 	    else {		/* relative path */
   1116 		pos2 = pos1;
   1117 		if (retval >= 2 && ffline[pos1] == '.'
   1118 		  && ffline[pos1 + 1] == '/')
   1119 		    pos2 += 2;	/* eliminate "./" */
   1120 			/* these should be referring to the same directory */
   1121 		if (retpos - pos2 < pos1 - pos
   1122 			|| memcmp(ffline + pos2, ffline + pos, pos1 - pos) != 0)
   1123 		    continue;
   1124 		pos2 += pos1 - pos;
   1125 	    }
   1126 	    /* now pos2 points to what should follow the // */
   1127 	    /* count backwards from the end of the string to just after the
   1128 	       (nslashes + 1)-st slash */
   1129 	    p_begin = ffline + pos2;
   1130 	    p_end = ffline + retpos;
   1131 	    i = nslashes;
   1132 	    for (p = p_end; p > p_begin; )
   1133 		if (*--p == '/') {
   1134 		    if (i == 0) {
   1135 			++p;
   1136 			break;
   1137 		    }
   1138 		    else --i;
   1139 		}
   1140 	    if (i > 0) continue;	/* if too few slashes */
   1141 	    pos3 = p - ffline;
   1142 	    /* the next part of the directory name should match the initial
   1143 	       fixed part of the pattern */
   1144 	    if (p_end - p < keypat_a - fullpat
   1145 	      || memcmp(p, fullpat, keypat_a - fullpat) != 0)
   1146 		continue;
   1147 	    pos4 = pos3 + (keypat - fullpat);	/* start of key */
   1148 	    value = NULL;
   1149 	    for (;;) {		/* read the files in this directory */
   1150 		retval = lsrgetline(f, retpos);
   1151 		if (retval <= 0) {	/* if done with list*/
   1152 		    bcopy(ffline + retpos, ffline + pos1, -retval);
   1153 		    break;
   1154 		}
   1155 		    /* check the remaining part of the initial fixed part */
   1156 		    /* (the first test is only for performance) */
   1157 		if (keypat_a != keypat
   1158 		  && memcmp(ffline + retpos, keypat_a, keypat - keypat_a) != 0)
   1159 		    continue;
   1160 		    /* end of key */
   1161 		pos5 = retpos + retval - (fullpat_end - keypat_end);
   1162 		    /* check the final fixed part */
   1163 		if (pos5 < pos4 || (fullpat_end != keypat_end
   1164 			&& memcmp(ffline + pos5, keypat_end,
   1165 			fullpat_end - keypat_end) != 0))
   1166 		    continue;
   1167 		if (value == NULL) {
   1168 		    value = xmemdup(ffline + pos2, pos3 - pos2 + 1);
   1169 		    value[pos3 - pos2] = '\0';
   1170 		}
   1171 		lsrput(ffline + pos4, pos5 - pos4, value);
   1172 	    }
   1173 	}
   1174 
   1175 	fclose(f);	/* close the file */
   1176 
   1177 	    /* Fill in the requisite entries in *root:  we have ls-R. */
   1178 	root->fullpat = fullpat;
   1179 	root->fullpat_end = fullpat_end;
   1180 	root->keypat = keypat;
   1181 	root->keypat_end = keypat_end;
   1182 }
   1183 
   1184 /*
   1185  *	wildmatch - Tell whether the string matches the pattern.
   1186  *
   1187  *		pat		pointer to first character of pattern
   1188  *		pat_end		pointer to last + 1 character of pattern
   1189  *		candidate	the string
   1190  *		allowvar	how many characters of fF_etc should be treated
   1191  *				as *
   1192  */
   1193 
   1194 static	Boolean
   1195 wildmatch(pat, pat_end, candidate, allowvar)
   1196 	const	char	*pat;
   1197 	const	char	*pat_end;
   1198 	const	char	*candidate;
   1199 	int		allowvar;	/* How many of %f, %F, %d, ... to */
   1200 					/* treat as '*' */
   1201 {
   1202 	const	char	*p;
   1203 	const	char	*q	= candidate;
   1204 
   1205 	for (p = pat; p < pat_end; ++p)
   1206 	    switch (*p) {
   1207 
   1208 		case '%': {
   1209 		    const char *q1;
   1210 
   1211 		    if (p + 1 >= pat_end) continue;
   1212 		    ++p;
   1213 		    q1 = index(lrec.fF_etc, *p);
   1214 		    if (q1 == NULL) {		/* %[ or something */
   1215 			if (*q != *p)
   1216 			    return False;
   1217 			++q;
   1218 			break;
   1219 		    }
   1220 
   1221 		    if (q1 - lrec.fF_etc < allowvar) {	/* treat it as '*' */
   1222 			for (q1 = q + strlen(q); q1 >= q; --q1)
   1223 			    if (wildmatch(p + 1, pat_end, q1, allowvar))
   1224 				return True;
   1225 			return False;
   1226 		    }
   1227 		    else {
   1228 			const char	*str	= fF_values[q1 - lrec.fF_etc];
   1229 			int		l;
   1230 
   1231 			if (str == NULL) str = "";
   1232 			l = strlen(str);
   1233 			if (bcmp(q, str, l) != 0)
   1234 			    return False;
   1235 			q += l;
   1236 		    }
   1237 		    break;
   1238 		}
   1239 
   1240 		case '*': {
   1241 		    const char *q1;
   1242 
   1243 		    for (q1 = q + strlen(q); q1 >= q; --q1)
   1244 			if (wildmatch(p + 1, pat_end, q1, allowvar))
   1245 			    return True;
   1246 		    return False;
   1247 		}
   1248 
   1249 		case '?':
   1250 		    if (*q == '\0')
   1251 			return False;
   1252 		    ++q;
   1253 		    break;
   1254 
   1255 		case '[': {
   1256 		    char c;
   1257 		    Boolean reverse = False;
   1258 
   1259 		    c = *q++;
   1260 		    if (c == '\0')
   1261 			return False;
   1262 
   1263 		    ++p;
   1264 		    if (*p == '^') {
   1265 			reverse = True;
   1266 			++p;
   1267 		    }
   1268 
   1269 		    for (;;) {
   1270 			char	c1;
   1271 			char	c2;
   1272 
   1273 			if (p >= pat_end)
   1274 			    return False;	/* syntax error */
   1275 			c1 = *p;
   1276 			if (c1 == ']') {
   1277 			    if (reverse)
   1278 				break;		/* success */
   1279 			    else
   1280 				return False;
   1281 			}
   1282 			c2 = c1;
   1283 			++p;
   1284 			if (*p == '-' && p[1] != ']') {
   1285 			    ++p;
   1286 			    c2 = *p++;
   1287 			    if (c2 == '%') c2 = *p++;
   1288 			}
   1289 			if (p >= pat_end)
   1290 			    return False;	/* syntax error */
   1291 			if (c >= c1 && c <= c2) {
   1292 			    if (reverse)
   1293 				return False;
   1294 			    else {		/* success */
   1295 				while (*p != ']') {
   1296 				    if (*p == '%') ++p;
   1297 				    ++p;
   1298 				    if (p >= pat_end)
   1299 					return False;	/* syntax error */
   1300 				}
   1301 				break;
   1302 			    }
   1303 			}
   1304 		    }
   1305 		    break;
   1306 		}
   1307 
   1308 		default:
   1309 		    if (*q != *p)
   1310 			return False;
   1311 		    ++q;
   1312 	    }
   1313 
   1314 	if (*q != '\0')
   1315 	    return False;
   1316 	return True;
   1317 }
   1318 
   1319 
   1320 /*
   1321  *	Read in the given directory.
   1322  */
   1323 
   1324 static	void
   1325 filltree(pos, treepp, atom, slashslash)
   1326 	unsigned	pos;
   1327 	struct treerec	**treepp;
   1328 	const struct atomrec	*atom;
   1329 	Boolean		slashslash;
   1330 {
   1331 	DIR		*f;
   1332 	struct dirent	*dp;
   1333 
   1334 	if (pos == 0) {
   1335 	    if (ffline_len == 0)
   1336 		expandline(2);
   1337 	    ffline[0] = '.';
   1338 	    ffline[1] = '\0';
   1339 	}
   1340 	else
   1341 	    ffline[pos - 1] = '\0';
   1342 
   1343 	if (FFDEBUG)
   1344 	    printf("Opening directory %s\n", ffline);
   1345 
   1346 	f = opendir(ffline);
   1347 	if (f == NULL) {
   1348 	    if (FFDEBUG) printf("%s: %s\n", ffline, strerror(errno));
   1349 	    *treepp = NULL;
   1350 	    return;
   1351 	}
   1352 
   1353 	if (pos != 0) ffline[pos - 1] = '/';
   1354 	for (;;) {
   1355 	    char		*line1;
   1356 	    struct stat		statbuf;
   1357 	    struct treerec	*leaf;
   1358 	    Boolean		isdir;
   1359 	    unsigned		len;
   1360 #if	ST_NLINK_TRICK
   1361 	    struct treerec	*child;
   1362 #endif
   1363 
   1364 	    dp = readdir(f);
   1365 	    if (dp == NULL) break;	/* done */
   1366 
   1367 	    len = strlen((dp)->d_name);
   1368 	    if (pos + len >= ffline_len)
   1369 		expandline(pos + (int) len);
   1370 	    line1 = ffline + pos;
   1371 	    bcopy(dp->d_name, line1, len);
   1372 	    line1[len] = '\0';
   1373 	    if (*line1 == '.' && (line1[1] == '\0'
   1374 		    || (line1[1] == '.' && line1[2] == '\0')))
   1375 		continue;	/* skip . and .. */
   1376 
   1377 	    if (lstat(ffline, &statbuf) != 0) {
   1378 		warn("lstat: filltree ffline");
   1379 		continue;
   1380 	    }
   1381 	    isdir = False;
   1382 #if	ST_NLINK_TRICK
   1383 	    child = &not_read_yet;
   1384 #endif
   1385 	    if (S_ISDIR(statbuf.st_mode)) {
   1386 		isdir = True;
   1387 		if (!slashslash
   1388 			&& (atom->next == NULL
   1389 			|| !wildmatch(atom->p, atom->p_end, line1,
   1390 				lrec.n_var_opts)))
   1391 		    continue;
   1392 #if	ST_NLINK_TRICK
   1393 		if (statbuf.st_nlink <= 2)
   1394 		    child = NULL;
   1395 #endif
   1396 	    }
   1397 	    else if (S_ISREG(statbuf.st_mode)) {
   1398 		if (atom->next != NULL
   1399 			|| (slashslash && !(atom->flags & F_WILD))
   1400 			|| !wildmatch(atom->p, atom->p_end, line1,
   1401 				lrec.n_var_opts))
   1402 		    continue;
   1403 	    }
   1404 	    else continue;	/* something else */
   1405 
   1406 	    leaf = xmalloc(sizeof(struct treerec));
   1407 	    {
   1408 		char	*str;
   1409 
   1410 		str = xmemdup(dp->d_name, (unsigned) len + 1);
   1411 		str[len] = '\0';
   1412 		leaf->dirname = str;
   1413 	    }
   1414 #if	ST_NLINK_TRICK
   1415 	    leaf->child = child;
   1416 #else
   1417 	    leaf->child = &not_read_yet;
   1418 #endif
   1419 	    leaf->isdir = isdir;
   1420 	    *treepp = leaf;
   1421 	    treepp = &leaf->next;
   1422 	}
   1423 	closedir(f);
   1424 
   1425 	*treepp = NULL;
   1426 }
   1427 
   1428 
   1429 /*
   1430  *	do_tree_search() - recursive routine for // and wild card searches.
   1431  */
   1432 
   1433 static	void
   1434 do_tree_search(treepp, atom, goback, pos, flags)
   1435 	struct treerec		**treepp;
   1436 	const	struct atomrec	*atom;
   1437 	const	struct atomrec	*goback;
   1438 	unsigned		pos;
   1439 	int			flags;
   1440 {
   1441 	int		aflags;
   1442 	struct treerec	*tp;
   1443 
   1444 	/*
   1445 	 * If we're at the end of the chain of atoms, try to open the file.
   1446 	 */
   1447 
   1448 	if (atom == NULL) {
   1449 	    try_to_open(pos - 1, flags);
   1450 	    return;
   1451 	}
   1452 
   1453 	/*
   1454 	 * Otherwise, we're still forming the path.
   1455 	 */
   1456 
   1457 	aflags = atom->flags;
   1458 	if (aflags & F_PCT_S) {		/* if it's a dummy record */
   1459 	    atom = treeatom;
   1460 	    aflags |= atom->flags;
   1461 	}
   1462 
   1463 	if (aflags & F_SLASH_SLASH)
   1464 	    goback = atom;
   1465 
   1466 	if (goback == NULL) {		/* wild card search, no // yet */
   1467 	    /*
   1468 	     * If the next atom is neither wild nor variable, then we don't
   1469 	     * need to do a readdir() on the directory.  Just add the
   1470 	     * appropriate characters to the path and recurse.
   1471 	     */
   1472 	    if (!(atom->flags & (F_WILD | F_VARIABLE))) {
   1473 		pos = xlat(NULL, NULL, pos, atom->p, atom->p_end);
   1474 		ffline[pos++] = '/';
   1475 		do_tree_search(treepp, atom->next, goback, pos,
   1476 		    flags | atom->flags);
   1477 		return;
   1478 	    }
   1479 	    /*
   1480 	     * Otherwise, go down one level in the tree.
   1481 	     */
   1482 	    if (*treepp == &not_read_yet)	/* if readdir() not done yet */
   1483 		filltree(pos, treepp, atom, False);	/* do it */
   1484 
   1485 	    for (tp = *treepp; tp != NULL; tp = tp->next)
   1486 		if (wildmatch(atom->p, atom->p_end, tp->dirname, 0)) {
   1487 		    int len = strlen(tp->dirname);
   1488 		    bcopy(tp->dirname, ffline + pos, len);
   1489 		    ffline[pos + len++] = '/';
   1490 		    do_tree_search(&tp->child, atom->next, goback, pos + len,
   1491 			flags | atom->flags);
   1492 		}
   1493 
   1494 	    return;
   1495 	}
   1496 
   1497 	/*
   1498 	 * If we got here, then we're past a //
   1499 	 */
   1500 
   1501 	if (atom->next == NULL && !(atom->flags & F_WILD))
   1502 		/* if we can try a file */
   1503 	    try_to_open(xlat(NULL, NULL, pos, atom->p, atom->p_end),
   1504 		flags | atom->flags);
   1505 
   1506 	if (*treepp == &not_read_yet)		/* if readdir() not done yet */
   1507 	    filltree(pos, treepp, atom, True);	/* do it */
   1508 
   1509 	for (tp = *treepp; tp != NULL; tp = tp->next) {
   1510 	    tp->tried_already = False;
   1511 	    if ((atom->next != NULL || (atom->flags & F_WILD))
   1512 		    && wildmatch(atom->p, atom->p_end, tp->dirname, 0)) {
   1513 		int len = strlen(tp->dirname);
   1514 		bcopy(tp->dirname, ffline + pos, len);
   1515 		ffline[pos + len++] = '/';
   1516 		do_tree_search(&tp->child, atom->next, goback, pos + len,
   1517 		    flags | atom->flags);
   1518 		tp->tried_already = True;
   1519 	    }
   1520 	}
   1521 
   1522 	for (tp = *treepp; tp != NULL; tp = tp->next)
   1523 	    if (!tp->tried_already && tp->isdir) {
   1524 		int len = strlen(tp->dirname);
   1525 		bcopy(tp->dirname, ffline + pos, len);
   1526 		ffline[pos + len++] = '/';
   1527 		do_tree_search(&tp->child, goback, goback, pos + len, flags);
   1528 	    }
   1529 }
   1530 
   1531 
   1532 /*
   1533  *	begin_tree_search() - start a wild card or // search.
   1534  */
   1535 
   1536 static	void
   1537 begin_tree_search(status, oneatom, twoatom)
   1538 	const	struct statrec	*status;
   1539 	const	struct atomrec	*oneatom;	/* initial atom */
   1540 	const	struct atomrec	*twoatom;	/* if F_PCT_S set */
   1541 {
   1542 	struct treerec		**tpp;
   1543 
   1544 #if 0
   1545 	{
   1546 	    const struct atomrec	*atom;
   1547 
   1548 	    puts("begin_tree_search():");
   1549 	    fwrite(ffline, 1, status->pos, stdout);
   1550 	    putchar('\n');
   1551 	    atom = oneatom;
   1552 	    for (;;) {
   1553 		fputs("+ ", stdout);
   1554 		if (atom->flags & F_SLASH_SLASH) fputs("(//) ", stdout);
   1555 		if (atom->flags & F_PCT_S) atom = twoatom;
   1556 		fwrite(atom->p, 1, atom->p_end - atom->p, stdout);
   1557 		if (atom->flags & F_WILD) fputs(" (wild)", stdout);
   1558 		putchar('\n');
   1559 		atom = atom->next;
   1560 		if (atom == NULL) break;
   1561 	    }
   1562 	    putchar('\n');
   1563 	}
   1564 #endif
   1565 
   1566 	++seqno;
   1567 	treeatom = twoatom;		/* save this globally */
   1568 
   1569 	/*
   1570 	 * Find the record controlling this mess.  Create one, if necessary.
   1571 	 */
   1572 
   1573 	if (status->flags & F_VARIABLE) {
   1574 	    struct vrootrec {
   1575 		struct rootrec	*next;	/* link to next in hash chain */
   1576 		struct treerec	*tree;	/* the tree */
   1577 		const char	*path;
   1578 		int		seqno;
   1579 	    };
   1580 	    struct rtab {
   1581 		struct rtab	*next;
   1582 		int		seqno;
   1583 		int		len;
   1584 		const char	*tag;
   1585 		struct vrootrec	*r;
   1586 	    };
   1587 	    static struct rtab	*varroot[32];
   1588 	    struct rtab		**tpp2;
   1589 	    struct rtab		*t;
   1590 	    struct vrootrec	*r;
   1591 
   1592 	    tpp2 = varroot + prehash(ffline, status->pos) % XtNumber(varroot);
   1593 	    for (;;) {
   1594 		t = *tpp2;
   1595 		if (t == NULL) {
   1596 		    t = xmalloc(sizeof(*t));
   1597 		    t->seqno = seqno;
   1598 		    t->len = status->pos;
   1599 		    t->tag = xmemdup(ffline, status->pos);
   1600 		    r = xmalloc(sizeof(*r));
   1601 		    r->next = NULL;
   1602 		    r->tree = &not_read_yet;	/* readdir() not done yet */
   1603 		    t->r = r;
   1604 		    t->next = NULL;
   1605 		    *tpp2 = t;
   1606 		    break;
   1607 		}
   1608 		if (t->seqno == seqno && t->len == status->pos
   1609 			&& memcmp(t->tag, ffline, t->len) == 0) {
   1610 		    r = t->r;
   1611 		    break;
   1612 		}
   1613 		tpp2 = &t->next;
   1614 	    }
   1615 	    tpp = &r->tree;
   1616 	}
   1617 	else {
   1618 	    struct rootrec *r;
   1619 
   1620 	    r = *rootpp;
   1621 	    if (r == NULL) {
   1622 		r = xmalloc(sizeof(*r));
   1623 		r->next = NULL;
   1624 		r->tree = &not_read_yet;	/* readdir() not done yet */
   1625 		r->fullpat = NULL;		/* no ls-R yet */
   1626 		*rootpp = r;
   1627 		r->flags = status->flags |
   1628 		    ((oneatom->flags & F_PCT_S) ? twoatom : oneatom) ->flags;
   1629 		if (r->flags & F_QUICKFIND)
   1630 		    init_quick_find(r, status->pos, oneatom, status->pos);
   1631 		else if (status->quickchar != '\0') {
   1632 		    r->flags |= (status->quickchar == 'Q' ? F_QUICKONLY : 0);
   1633 		    /* (init_quick_find() will set F_QUICKFIND in r->flags) */
   1634 		    init_quick_find(r, status->quickpos, oneatom, status->pos);
   1635 		}
   1636 	    }
   1637 	    rootpp = &r->next;
   1638 	    tpp = &r->tree;
   1639 		/* do ls-R search, if appropriate */
   1640 	    if ((r->flags & F_QUICKFIND) && r->fullpat != NULL) {
   1641 		unsigned	pos;
   1642 		struct lsr	*lp;
   1643 
   1644 		pos = xlat(NULL, NULL, status->pos, r->keypat, r->keypat_end);
   1645 		lp = lsrtab[prehash(ffline + status->pos, pos - status->pos)
   1646 		    % XtNumber(lsrtab)];
   1647 		for (;;) {
   1648 		    if (lp == NULL) break;	/* no match */
   1649 		    if (lp->frecp == frecp && lp->seqno == seqno
   1650 		      && lp->keylen == pos - status->pos
   1651 		      && memcmp(lp->key, ffline + status->pos, lp->keylen) == 0)
   1652 		      {		/* if match */
   1653 			struct statrec	stat1;
   1654 			int		len = strlen(lp->value);
   1655 
   1656 			stat1 = *status;
   1657 			if (stat1.pos + len > ffline_len)
   1658 			    expandline(stat1.pos + len);
   1659 			bcopy(lp->value, ffline + stat1.pos, len);
   1660 			stat1.pos += len;
   1661 			(void) xlat(&stat1, &stat1, 0,
   1662 			  r->fullpat, r->fullpat_end);
   1663 			try_to_open(stat1.pos, stat1.flags);
   1664 			return;
   1665 		    }
   1666 		    lp = lp->next;
   1667 		}
   1668 		    /* if there's an ls-R database, and our file is not there,
   1669 		     * then we don't look recursively this time. */
   1670 		return;
   1671 	    }
   1672 	    if ((r->flags & (F_QUICKFIND | F_QUICKONLY))
   1673 		    == (F_QUICKFIND | F_QUICKONLY)) {
   1674 		return;
   1675 	    }
   1676 	}
   1677 
   1678 	do_tree_search(tpp, oneatom, NULL, status->pos, status->flags);
   1679 }
   1680 
   1681 
   1682 /*
   1683  *	dostep() - Process a steprec record.
   1684  */
   1685 
   1686 static	void
   1687 dostep(sp, stat0)
   1688 	const	struct steprec	*sp;
   1689 	const	struct statrec	*stat0;
   1690 {
   1691 	struct statrec		status;
   1692 
   1693 	(void) xlat(stat0, &status, 0, sp->str, sp->strend);
   1694 
   1695 	status.flags |= sp->flags;
   1696 
   1697 	if (sp->atom != NULL) {		/* if wild cards or // */
   1698 	    if (sp->flags & F_PCT_S) {
   1699 		int	i;
   1700 
   1701 		for (i = 0; i < lrec.v.pct_s_count; ++i)
   1702 		    begin_tree_search(&status, sp->atom, lrec.v.pct_s_atom[i]);
   1703 	    }
   1704 	    else
   1705 		begin_tree_search(&status, sp->atom, NULL);
   1706 	}
   1707 	else if (sp->nextstep != NULL) {	/* if {} list */
   1708 	    struct steprec *sp1;
   1709 
   1710 	    for (sp1 = sp->nextstep; sp1 != NULL; sp1 = sp1->next)
   1711 		dostep(sp1, &status);
   1712 	}
   1713 	else {	/* end of string */
   1714 	    if (!(status.flags & F_FILE_USED)) {
   1715 		(void) xlat(&status, &status, 0,
   1716 		  lrec.no_f_str, lrec.no_f_str_end);
   1717 	    }
   1718 	    try_to_open(status.pos, status.flags);
   1719 	}
   1720 }
   1721 
   1722 
   1723 /*
   1724  *	fixbegin() - Handle !!, %t, %S, and ~ at the beginning of a specifier.
   1725  */
   1726 
   1727 static	void
   1728 fixbegin(sp)
   1729 	struct steprec	*sp;
   1730 {
   1731 	const	char	*p	= sp->str;
   1732 
   1733 	/*
   1734 	 * Initial !!.
   1735 	 */
   1736 
   1737 	if (*p == '!' && p[1] == '!' && sp->strend >= p + 2) {
   1738 	    sp->flags |= F_QUICKONLY;
   1739 	    p += 2;
   1740 	}
   1741 
   1742 	/*
   1743 	 * Take care of %S, %t, and ~.
   1744 	 */
   1745 
   1746 	sp->home = NULL;
   1747 
   1748 	if (*p == '%') {
   1749 	    if (p[1] == 'S' && sp->strend == p + 2 && sp->atom == NULL
   1750 	      && sp->nextstep == NULL) {	/* %S */
   1751 		init_texmf();
   1752 		sp->flags |= F_PCT_T;
   1753 		p = "/";
   1754 		sp->strend = p + 1;
   1755 		sp->nextstep = scan_pct_s();
   1756 	    }
   1757 	    if (p[1] == 't' && sp->strend >= p + 2) {	/* %t */
   1758 		init_texmf();
   1759 		sp->flags |= F_PCT_T;
   1760 		p += 2;
   1761 	    }
   1762 	}
   1763 	else if (*p == '~')
   1764 	    gethome(&p, sp->strend, &sp->home);
   1765 
   1766 	sp->str = p;
   1767 }
   1768 
   1769 
   1770 /*
   1771  *	pathpart() - Handle one (colon-separated) part of the search path.
   1772  */
   1773 
   1774 static	const	char *
   1775 pathpart(p)
   1776 	const	char	*p;
   1777 {
   1778 	struct steprec			*sp;
   1779 	static	const	struct statrec	stat_ini	= {0, 0, '\0', 0};
   1780 	struct statrec			status;
   1781 
   1782 	sp = *steppp;
   1783 	if (sp == NULL) {	/* do prescanning (lazy evaluation) */
   1784 	    const char *p_end;
   1785 	    p_end = ff_prescan(p);
   1786 		/* skip to next colon or end of string */
   1787 	    for (; *p_end != '\0' && *p_end != ':'; ++p_end)
   1788 		if (*p_end == '%' && p_end[1] != '\0')
   1789 		    ++p_end;
   1790 
   1791 	    sp = *steppp;
   1792 	    if (sp->str == sp->strend && sp->nextstep != NULL) {
   1793 		/* If it's braces right off the bat, then pretend these are */
   1794 		/* all different specifiers (so %t, %S, !!, and ~ can work). */
   1795 		/* There's a memory leak here, but it's bounded. */
   1796 		struct steprec	*sp1	= sp->nextstep;
   1797 
   1798 		for (;;) {
   1799 		    fixbegin(sp1);
   1800 		    if (sp1->next == NULL) {
   1801 			sp1->nextpart = p_end;
   1802 			break;
   1803 		    }
   1804 		    sp1->nextpart = p;
   1805 		    sp1 = sp1->next;
   1806 		}
   1807 		*steppp = sp = sp->nextstep;	/* relink the chain */
   1808 	    }
   1809 	    else {
   1810 		fixbegin(sp);
   1811 		sp->nextpart = p_end;
   1812 	    }
   1813 	}
   1814 	steppp = &sp->next;
   1815 
   1816 	if (sp->flags & F_PCT_T) {
   1817 	    struct texmfrec	*tp;
   1818 
   1819 	    for (tp = texmfhead; tp != NULL; tp = tp->next) {
   1820 		status = stat_ini;
   1821 		status.flags = tp->flags;
   1822 		if (tp->home != NULL) {
   1823 		    status.pos = strlen(tp->home);
   1824 		    if (status.pos + tp->len >= ffline_len)
   1825 			expandline(status.pos + tp->len);
   1826 		    bcopy(tp->home, ffline, status.pos);
   1827 		}
   1828 		bcopy(tp->str, ffline + status.pos, tp->len);
   1829 		status.pos += tp->len;
   1830 		dostep(sp, &status);
   1831 	    }
   1832 	}
   1833 	else {
   1834 	    status = stat_ini;
   1835 	    if (sp->home != NULL) {
   1836 		unsigned len = strlen(sp->home);
   1837 
   1838 		if (len >= ffline_len)
   1839 		    expandline(len);
   1840 		bcopy(sp->home, ffline, len);
   1841 		status.pos = len;
   1842 	    }
   1843 	    dostep(sp, &status);
   1844 	}
   1845 
   1846 	return sp->nextpart;
   1847 }
   1848 
   1849 #if FFDUMP
   1850 
   1851 /*
   1852  *	Debugging routine.  Dump those crazy step records.
   1853  */
   1854 
   1855 static	void
   1856 dumpsteps(sp, indent)
   1857 	const struct steprec	*sp;
   1858 	int			indent;
   1859 {
   1860 	const struct steprec	*sp1;
   1861 	int			i;
   1862 
   1863 	while (sp != NULL) {
   1864 	    for (i = 0; i < indent; ++i) putchar(' ');
   1865 	    printf("%04o `", sp->flags);
   1866 	    fwrite(sp->str, 1, sp->strend - sp->str, stdout);
   1867 	    puts("'");
   1868 
   1869 	    if (sp->home != NULL) {
   1870 		for (i = 0; i < indent; ++i) putchar(' ');
   1871 		printf("      Home = %s\n", sp->home);
   1872 	    }
   1873 
   1874 	    /* there's an atomrec dumper in begin_tree_search() */
   1875 
   1876 	    dumpsteps(sp->nextstep, indent + 2);
   1877 
   1878 	    sp = sp->next;
   1879 	}
   1880 }
   1881 
   1882 #endif
   1883 
   1884 
   1885 /*
   1886  *	This is the main search routine.  It calls pathpath() for each
   1887  *	component of the path.  Substitution for the default path is done here.
   1888  */
   1889 
   1890 FILE *
   1891 filefind(name, srchtype, path_ret)
   1892 	const	char	*name;		/* name of the font or file */
   1893 	struct findrec	*srchtype;	/* what type of search to perform */
   1894 	const	char	**path_ret;	/* put the name of the file here */
   1895 {
   1896 	lrec = *srchtype;
   1897 	fF_values[0] = fF_values[1] = name;
   1898 
   1899 	if (setjmp(got_it) == 0) {
   1900 	    if (*name == '/' || *name == '~') {		/* if absolute path */
   1901 		unsigned		pos;
   1902 		unsigned		pos0	= 0;
   1903 		const struct passwd	*pw	= NULL;
   1904 
   1905 		if (*name == '~')
   1906 		    pw = ff_getpw(&name, name + strlen(name));
   1907 		if (pw != NULL) {
   1908 		    pos0 = strlen(pw->pw_dir);
   1909 		    if (pos0 >= ffline_len)
   1910 			expandline(pos0);
   1911 		    bcopy(pw->pw_dir, ffline, pos0);
   1912 		    fF_values[0] = fF_values[1] = name;
   1913 		}
   1914 		pos = xlat(NULL, NULL, pos0, lrec.abs_str,
   1915 			lrec.abs_str + strlen(lrec.abs_str));
   1916 		try_to_open(pos, 0);
   1917 	    }
   1918 	    else {
   1919 		const char	*p;
   1920 
   1921 		steppp = &lrec.v.stephead;
   1922 		frecp = srchtype;
   1923 		seqno = 0;
   1924 		rootpp = &lrec.v.rootp;
   1925 
   1926 		for (p = lrec.path1;;) {
   1927 		    if (*p == '\0' || *p == ':') {
   1928 			    /* bring in the default path */
   1929 			if (lrec.path2 != NULL) {
   1930 			    const char	*p1	= lrec.path2;
   1931 
   1932 			    for (;;) {
   1933 				if (*p1 == '\0') break;
   1934 				if (*p1 == ':') continue;
   1935 				p1 = pathpart(p1);
   1936 				if (*p1 == '\0') break;
   1937 				++p1;	/* skip the colon */
   1938 			    }
   1939 			}
   1940 		    }
   1941 		    else
   1942 			p = pathpart(p);
   1943 		    if (*p == '\0') break;
   1944 		    ++p;	/* skip the colon */
   1945 		}
   1946 
   1947 		file_found = NULL;
   1948 	    }
   1949 	}
   1950 	else {
   1951 	    /* it longjmp()s here when it finds the file */
   1952 	    if (FFDEBUG) puts("--Success--\n");
   1953 	    if (path_ret != NULL)
   1954 		*path_ret = xstrdup(ffline);
   1955 	}
   1956 
   1957 	srchtype->v = lrec.v;	/* restore volatile parameters of *srchtype */
   1958 
   1959 #if FFDUMP
   1960 	dumpsteps(srchtype->v.stephead, 0);
   1961 	putchar('\n');
   1962 #endif
   1963 
   1964 	return file_found;
   1965 }