140087Sbostic /* 240087Sbostic * Copyright (c) 1989 The Regents of the University of California. 340087Sbostic * All rights reserved. 440087Sbostic * 540087Sbostic * This code is derived from software contributed to Berkeley by 640087Sbostic * Guido van Rossum. 740087Sbostic * 842625Sbostic * %sccs.include.redist.c% 940087Sbostic */ 1040087Sbostic 1140087Sbostic #if defined(LIBC_SCCS) && !defined(lint) 12*50049Sbostic static char sccsid[] = "@(#)glob.c 5.9 (Berkeley) 06/16/91"; 1340087Sbostic #endif /* LIBC_SCCS and not lint */ 1440087Sbostic 1540087Sbostic /* 1640087Sbostic * Glob: the interface is a superset of the one defined in POSIX 1003.2, 1740087Sbostic * draft 9. 1840087Sbostic * 1940087Sbostic * The [!...] convention to negate a range is supported (SysV, Posix, ksh). 2040087Sbostic * 2140087Sbostic * Optional extra services, controlled by flags not defined by POSIX: 2247590Sbostic * 2347590Sbostic * GLOB_QUOTE: 2447590Sbostic * Escaping convention: \ inhibits any special meaning the following 2547590Sbostic * character might have (except \ at end of string is retained). 2647590Sbostic * GLOB_MAGCHAR: 2748540Sbostic * Set in gl_flags if pattern contained a globbing character. 2847590Sbostic * gl_matchc: 2947590Sbostic * Number of matches in the current invocation of glob. 3040087Sbostic */ 3140087Sbostic 3248540Sbostic #include <sys/cdefs.h> 3340087Sbostic #include <sys/param.h> 3440087Sbostic #include <sys/stat.h> 3540087Sbostic #include <dirent.h> 3640087Sbostic #include <glob.h> 3740087Sbostic #include <ctype.h> 3840087Sbostic #include <errno.h> 3940087Sbostic #include <string.h> 4040087Sbostic #include <stdio.h> 4146597Sdonn #include <stdlib.h> 4240087Sbostic 4340087Sbostic #define DOLLAR '$' 4440087Sbostic #define DOT '.' 4540087Sbostic #define EOS '\0' 4640087Sbostic #define LBRACKET '[' 4740087Sbostic #define NOT '!' 4840087Sbostic #define QUESTION '?' 4940087Sbostic #define QUOTE '\\' 5040087Sbostic #define RANGE '-' 5140087Sbostic #define RBRACKET ']' 5240087Sbostic #define SEP '/' 5340087Sbostic #define STAR '*' 5440087Sbostic #define TILDE '~' 5540087Sbostic #define UNDERSCORE '_' 5640087Sbostic 57*50049Sbostic #define M_QUOTE 0x8000 58*50049Sbostic #define M_PROTECT 0x4000 5948540Sbostic #define M_MASK 0xffff 6048540Sbostic 61*50049Sbostic #define META(c) ((c)|M_QUOTE) 6240087Sbostic #define M_ALL META('*') 6340087Sbostic #define M_END META(']') 6440087Sbostic #define M_NOT META('!') 6540087Sbostic #define M_ONE META('?') 6640087Sbostic #define M_RNG META('-') 6740087Sbostic #define M_SET META('[') 68*50049Sbostic #define ismeta(c) (((c)&M_QUOTE) != 0) 6940087Sbostic 70*50049Sbostic typedef u_short shortchar_t; 71*50049Sbostic 72*50049Sbostic static int glob1 __P((shortchar_t *, glob_t *)); 73*50049Sbostic static int glob2 __P((shortchar_t *, shortchar_t *, shortchar_t *, glob_t *)); 74*50049Sbostic static int glob3 __P((shortchar_t *, 75*50049Sbostic shortchar_t *, shortchar_t *, shortchar_t *, glob_t *)); 76*50049Sbostic static int globextend __P((shortchar_t *, glob_t *)); 77*50049Sbostic static int match __P((shortchar_t *, shortchar_t *, shortchar_t *)); 78*50049Sbostic static DIR *Opendir __P((shortchar_t *)); 79*50049Sbostic static int Lstat __P((shortchar_t *, struct stat *)); 80*50049Sbostic static int Stat __P((shortchar_t *, struct stat *)); 81*50049Sbostic static shortchar_t *Strchr __P((shortchar_t *, int)); 82*50049Sbostic #ifdef DEBUG 83*50049Sbostic static void qprintf __P((shortchar_t *)); 84*50049Sbostic #endif 85*50049Sbostic 8648540Sbostic static DIR * 8748540Sbostic Opendir(str) 88*50049Sbostic register shortchar_t *str; 8948540Sbostic { 9048540Sbostic register char *dc; 9148540Sbostic char buf[MAXPATHLEN]; 9248540Sbostic 9348540Sbostic if (!*str) 9448540Sbostic return(opendir(".")); 9548540Sbostic for (dc = buf; *dc++ = *str++;); 9648540Sbostic return(opendir(buf)); 9748540Sbostic } 9848540Sbostic 9948540Sbostic static int 10048540Sbostic Lstat(fn, sb) 101*50049Sbostic register shortchar_t *fn; 102*50049Sbostic struct stat *sb; 10348540Sbostic { 10448540Sbostic register char *dc; 10548540Sbostic char buf[MAXPATHLEN]; 10648540Sbostic 10748540Sbostic for (dc = buf; *dc++ = *fn++;); 10848540Sbostic return(lstat(buf, sb)); 10948540Sbostic } 11048540Sbostic 11148540Sbostic static int 11248540Sbostic Stat(fn, sb) 113*50049Sbostic register shortchar_t *fn; 114*50049Sbostic struct stat *sb; 11548540Sbostic { 11648540Sbostic register char *dc; 11748540Sbostic char buf[MAXPATHLEN]; 11848540Sbostic 11948540Sbostic for (dc = buf; *dc++ = *fn++;); 12048540Sbostic return(stat(buf, sb)); 12148540Sbostic } 122*50049Sbostic 123*50049Sbostic static shortchar_t * 124*50049Sbostic Strchr(str, ch) 125*50049Sbostic shortchar_t *str; 126*50049Sbostic int ch; 127*50049Sbostic { 128*50049Sbostic do { 129*50049Sbostic if (*str == ch) 130*50049Sbostic return (str); 131*50049Sbostic } while (*str++); 132*50049Sbostic return (NULL); 133*50049Sbostic } 134*50049Sbostic 135*50049Sbostic #ifdef DEBUG 136*50049Sbostic static void 137*50049Sbostic qprintf(s) 138*50049Sbostic shortchar_t *s; 139*50049Sbostic { 140*50049Sbostic shortchar_t *p; 141*50049Sbostic 142*50049Sbostic for (p = s; *p; p++) 143*50049Sbostic printf("%c", *p & 0xff); 144*50049Sbostic printf("\n"); 145*50049Sbostic for (p = s; *p; p++) 146*50049Sbostic printf("%c", *p & M_PROTECT ? '"' : ' '); 147*50049Sbostic printf("\n"); 148*50049Sbostic for (p = s; *p; p++) 149*50049Sbostic printf("%c", *p & M_META ? '_' : ' '); 150*50049Sbostic printf("\n"); 151*50049Sbostic } 15248540Sbostic #endif 15348540Sbostic 15448540Sbostic static int 15540087Sbostic compare(p, q) 15643531Sbostic void **p, **q; 15740087Sbostic { 15843531Sbostic return(strcmp(*(char **)p, *(char **)q)); 15940087Sbostic } 16040087Sbostic 16140087Sbostic /* 16240087Sbostic * The main glob() routine: compiles the pattern (optionally processing 16340087Sbostic * quotes), calls glob1() to do the real pattern matching, and finally 16440087Sbostic * sorts the list (unless unsorted operation is requested). Returns 0 16540087Sbostic * if things went well, nonzero if errors occurred. It is not an error 16640087Sbostic * to find no matches. 16740087Sbostic */ 16840087Sbostic glob(pattern, flags, errfunc, pglob) 16946597Sdonn const char *pattern; 17046597Sdonn int flags; 17146597Sdonn int (*errfunc) __P((char *, int)); 17240087Sbostic glob_t *pglob; 17340087Sbostic { 17440087Sbostic int err, oldpathc; 17548540Sbostic shortchar_t *bufnext, *bufend, *compilebuf; 17646597Sdonn const char *compilepat, *patnext; 17748540Sbostic char c; 178*50049Sbostic shortchar_t patbuf[MAXPATHLEN+1], *qpatnext; 17940087Sbostic 18040087Sbostic patnext = pattern; 18140087Sbostic if (!(flags & GLOB_APPEND)) { 18240087Sbostic pglob->gl_pathc = 0; 18340087Sbostic pglob->gl_pathv = NULL; 18440087Sbostic if (!(flags & GLOB_DOOFFS)) 18540087Sbostic pglob->gl_offs = 0; 18640087Sbostic } 18747590Sbostic pglob->gl_flags = flags & ~GLOB_MAGCHAR; 18840087Sbostic pglob->gl_errfunc = errfunc; 18940087Sbostic oldpathc = pglob->gl_pathc; 19047590Sbostic pglob->gl_matchc = 0; 19140087Sbostic 19240087Sbostic bufnext = patbuf; 193*50049Sbostic bufend = bufnext + MAXPATHLEN; 194*50049Sbostic if (flags & GLOB_QUOTE) { 195*50049Sbostic /* Protect the quoted characters */ 196*50049Sbostic while (bufnext < bufend && (c = *patnext++) != EOS) 197*50049Sbostic if (c == QUOTE) { 198*50049Sbostic if ((c = *patnext++) == EOS) { 199*50049Sbostic c = QUOTE; 200*50049Sbostic --patnext; 201*50049Sbostic } 202*50049Sbostic *bufnext++ = c | M_PROTECT; 203*50049Sbostic } 204*50049Sbostic else 205*50049Sbostic *bufnext++ = c; 206*50049Sbostic } 207*50049Sbostic else 208*50049Sbostic while (bufnext < bufend && (c = *patnext++) != EOS) 209*50049Sbostic *bufnext++ = c; 210*50049Sbostic *bufnext = EOS; 21140087Sbostic 212*50049Sbostic bufnext = patbuf; 213*50049Sbostic qpatnext = patbuf; 21440087Sbostic compilebuf = bufnext; 21540087Sbostic compilepat = patnext; 216*50049Sbostic /* we don't need to check for buffer overflow any more */ 217*50049Sbostic while ((c = *qpatnext++) != EOS) { 21840087Sbostic switch (c) { 21940087Sbostic case LBRACKET: 22047590Sbostic pglob->gl_flags |= GLOB_MAGCHAR; 221*50049Sbostic c = *qpatnext; 22240087Sbostic if (c == NOT) 223*50049Sbostic ++qpatnext; 224*50049Sbostic if (*qpatnext == EOS || 225*50049Sbostic Strchr(qpatnext+1, RBRACKET) == NULL) { 22640087Sbostic *bufnext++ = LBRACKET; 22740087Sbostic if (c == NOT) 228*50049Sbostic --qpatnext; 22940087Sbostic break; 23040087Sbostic } 23140087Sbostic *bufnext++ = M_SET; 23240087Sbostic if (c == NOT) 23340087Sbostic *bufnext++ = M_NOT; 234*50049Sbostic c = *qpatnext++; 23540087Sbostic do { 23640087Sbostic *bufnext++ = c; 237*50049Sbostic if (*qpatnext == RANGE && 238*50049Sbostic (c = qpatnext[1]) != RBRACKET) { 23940087Sbostic *bufnext++ = M_RNG; 24040087Sbostic *bufnext++ = c; 241*50049Sbostic qpatnext += 2; 24240087Sbostic } 243*50049Sbostic } while ((c = *qpatnext++) != RBRACKET); 24440087Sbostic *bufnext++ = M_END; 24540087Sbostic break; 24640087Sbostic case QUESTION: 24747590Sbostic pglob->gl_flags |= GLOB_MAGCHAR; 24840087Sbostic *bufnext++ = M_ONE; 24940087Sbostic break; 25040087Sbostic case STAR: 25147590Sbostic pglob->gl_flags |= GLOB_MAGCHAR; 25240087Sbostic *bufnext++ = M_ALL; 25340087Sbostic break; 25440087Sbostic default: 25540087Sbostic *bufnext++ = c; 25640087Sbostic break; 25740087Sbostic } 25840087Sbostic } 25940087Sbostic *bufnext = EOS; 260*50049Sbostic #ifdef DEBUG 261*50049Sbostic qprintf(patbuf); 262*50049Sbostic #endif 26340087Sbostic 26440087Sbostic if ((err = glob1(patbuf, pglob)) != 0) 26540087Sbostic return(err); 26640087Sbostic 26740087Sbostic if (pglob->gl_pathc == oldpathc && flags & GLOB_NOCHECK) { 26848540Sbostic if (!(flags & GLOB_QUOTE)) { 26948540Sbostic shortchar_t *dp = compilebuf; 27048540Sbostic const char *sp = compilepat; 27148540Sbostic while (*dp++ = (u_char)*sp++); 27248540Sbostic } 27340087Sbostic else { 27440087Sbostic /* 27540087Sbostic * copy pattern, interpreting quotes; this is slightly 27640087Sbostic * different than the interpretation of quotes above 27740087Sbostic * -- which should prevail? 27840087Sbostic */ 27940087Sbostic while (*compilepat != EOS) { 28040087Sbostic if (*compilepat == QUOTE) { 28140087Sbostic if (*++compilepat == EOS) 28240087Sbostic --compilepat; 28340087Sbostic } 28448540Sbostic *compilebuf++ = (u_char)*compilepat++; 28540087Sbostic } 28640087Sbostic *compilebuf = EOS; 28740087Sbostic } 28840087Sbostic return(globextend(patbuf, pglob)); 28948540Sbostic } else if (!(flags & GLOB_NOSORT)) 29048540Sbostic qsort((char*)(pglob->gl_pathv + pglob->gl_offs + oldpathc), 29140087Sbostic pglob->gl_pathc - oldpathc, sizeof(char*), compare); 29240087Sbostic return(0); 29340087Sbostic } 29440087Sbostic 29540087Sbostic static 29640087Sbostic glob1(pattern, pglob) 29748540Sbostic shortchar_t *pattern; 29840087Sbostic glob_t *pglob; 29940087Sbostic { 30048540Sbostic shortchar_t pathbuf[MAXPATHLEN+1]; 30140087Sbostic 30240087Sbostic /* 303*50049Sbostic * A null pathname is invalid -- POSIX 1003.1 sect. 2.4. 30448540Sbostic */ 30540087Sbostic if (*pattern == EOS) 30640087Sbostic return(0); 30740087Sbostic return(glob2(pathbuf, pathbuf, pattern, pglob)); 30840087Sbostic } 30940087Sbostic 31040087Sbostic /* 31140087Sbostic * functions glob2 and glob3 are mutually recursive; there is one level 31240087Sbostic * of recursion for each segment in the pattern that contains one or 31340087Sbostic * more meta characters. 31440087Sbostic */ 31540087Sbostic static 31640087Sbostic glob2(pathbuf, pathend, pattern, pglob) 31748540Sbostic shortchar_t *pathbuf, *pathend, *pattern; 31840087Sbostic glob_t *pglob; 31940087Sbostic { 32048540Sbostic struct stat sbuf; 32148540Sbostic shortchar_t *p, *q; 32248540Sbostic int anymeta; 32340087Sbostic 32440087Sbostic /* 32540087Sbostic * loop over pattern segments until end of pattern or until 32640087Sbostic * segment with meta character found. 32740087Sbostic */ 32848357Sbostic for (anymeta = 0;;) { 32940087Sbostic if (*pattern == EOS) { /* end of pattern? */ 33040087Sbostic *pathend = EOS; 33148540Sbostic if (Lstat(pathbuf, &sbuf)) 33248357Sbostic return(0); 33348540Sbostic 33448540Sbostic if (((pglob->gl_flags & GLOB_MARK) && 33548540Sbostic pathend[-1] != SEP) && (S_ISDIR(sbuf.st_mode) 33648540Sbostic || (S_ISLNK(sbuf.st_mode) && 33748540Sbostic (Stat(pathbuf, &sbuf) == 0) && 33848540Sbostic S_ISDIR(sbuf.st_mode)))) { 33940087Sbostic *pathend++ = SEP; 34040087Sbostic *pathend = EOS; 34140087Sbostic } 34247590Sbostic ++pglob->gl_matchc; 34340087Sbostic return(globextend(pathbuf, pglob)); 34440087Sbostic } 34540087Sbostic 34640087Sbostic /* find end of next segment, copy tentatively to pathend */ 34740087Sbostic q = pathend; 34840087Sbostic p = pattern; 34940087Sbostic while (*p != EOS && *p != SEP) { 35040087Sbostic if (ismeta(*p)) 35140087Sbostic anymeta = 1; 35240087Sbostic *q++ = *p++; 35340087Sbostic } 35440087Sbostic 35540087Sbostic if (!anymeta) { /* no expansion, do next segment */ 35640087Sbostic pathend = q; 35740087Sbostic pattern = p; 35840087Sbostic while (*pattern == SEP) 35940087Sbostic *pathend++ = *pattern++; 36040087Sbostic } else /* need expansion, recurse */ 36140087Sbostic return(glob3(pathbuf, pathend, pattern, p, pglob)); 36240087Sbostic } 36340087Sbostic /* NOTREACHED */ 36440087Sbostic } 36540087Sbostic 36640087Sbostic static 36740087Sbostic glob3(pathbuf, pathend, pattern, restpattern, pglob) 36848540Sbostic shortchar_t *pathbuf, *pathend, *pattern, *restpattern; 36940087Sbostic glob_t *pglob; 37040087Sbostic { 37140087Sbostic extern int errno; 37240087Sbostic DIR *dirp; 37340087Sbostic struct dirent *dp; 37440087Sbostic int len, err; 37540087Sbostic 37640087Sbostic *pathend = EOS; 37740087Sbostic errno = 0; 37848540Sbostic 37948540Sbostic if (!(dirp = Opendir(pathbuf))) 38040087Sbostic /* todo: don't call for ENOENT or ENOTDIR? */ 38140087Sbostic if (pglob->gl_errfunc && 38240087Sbostic (*pglob->gl_errfunc)(pathbuf, errno) || 38340087Sbostic (pglob->gl_flags & GLOB_ERR)) 38440087Sbostic return(GLOB_ABEND); 38540087Sbostic else 38640087Sbostic return(0); 38740087Sbostic 38840087Sbostic err = 0; 38940087Sbostic 39040087Sbostic /* search directory for matching names */ 39140087Sbostic while ((dp = readdir(dirp))) { 39248540Sbostic register char *sc; 39348540Sbostic register shortchar_t *dc; 39440087Sbostic /* initial DOT must be matched literally */ 39540087Sbostic if (dp->d_name[0] == DOT && *pattern != DOT) 39640087Sbostic continue; 39748540Sbostic for (sc = dp->d_name, dc = pathend; 39848540Sbostic *dc++ = (u_char)*sc++;); 39948540Sbostic if (!match(pathend, pattern, restpattern)) { 40048540Sbostic *pathend = EOS; 40140087Sbostic continue; 40248540Sbostic } 40348540Sbostic err = glob2(pathbuf, --dc, restpattern, pglob); 40440087Sbostic if (err) 40540087Sbostic break; 40640087Sbostic } 40740087Sbostic /* todo: check error from readdir? */ 40840087Sbostic (void)closedir(dirp); 40940087Sbostic return(err); 41040087Sbostic } 41140087Sbostic 41240087Sbostic 41340087Sbostic /* 41440087Sbostic * Extend the gl_pathv member of a glob_t structure to accomodate a new item, 41540087Sbostic * add the new item, and update gl_pathc. 41640087Sbostic * 41740087Sbostic * This assumes the BSD realloc, which only copies the block when its size 41840087Sbostic * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic 41940087Sbostic * behavior. 42040087Sbostic * 42140087Sbostic * Return 0 if new item added, error code if memory couldn't be allocated. 42240087Sbostic * 42340087Sbostic * Invariant of the glob_t structure: 42440087Sbostic * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and 42540087Sbostic * gl_pathv points to (gl_offs + gl_pathc + 1) items. 42640087Sbostic */ 42748540Sbostic static int 42840087Sbostic globextend(path, pglob) 42948540Sbostic shortchar_t *path; 43040087Sbostic glob_t *pglob; 43140087Sbostic { 43240087Sbostic register char **pathv; 43340087Sbostic register int i; 43448540Sbostic u_int newsize; 43540087Sbostic char *copy; 43648540Sbostic shortchar_t *p; 43740087Sbostic 43840087Sbostic newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); 43948540Sbostic pathv = (char **)realloc((char *)pglob->gl_pathv, newsize); 44040087Sbostic if (pathv == NULL) 44140087Sbostic return(GLOB_NOSPACE); 44240087Sbostic 44340087Sbostic if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { 44440087Sbostic /* first time around -- clear initial gl_offs items */ 44540087Sbostic pathv += pglob->gl_offs; 44640087Sbostic for (i = pglob->gl_offs; --i >= 0; ) 44740087Sbostic *--pathv = NULL; 44840087Sbostic } 44940087Sbostic pglob->gl_pathv = pathv; 45040087Sbostic 45148540Sbostic for (p = path; *p++;); 45248540Sbostic if ((copy = malloc(p - path)) != NULL) { 453*50049Sbostic register char *dc; 454*50049Sbostic register shortchar_t *sc; 455*50049Sbostic 456*50049Sbostic for (dc = copy, sc = path; *dc++ = *sc++;); 45740087Sbostic pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; 45840087Sbostic } 45940087Sbostic pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; 46040087Sbostic return((copy == NULL) ? GLOB_NOSPACE : 0); 46140087Sbostic } 46240087Sbostic 46340087Sbostic 46440087Sbostic /* 46540087Sbostic * pattern matching function for filenames. Each occurrence of the * 46640087Sbostic * pattern causes a recursion level. 46740087Sbostic */ 46848540Sbostic static 46940087Sbostic match(name, pat, patend) 47048540Sbostic register shortchar_t *name, *pat, *patend; 47140087Sbostic { 47248540Sbostic int ok, negate_range; 47348540Sbostic shortchar_t c, k; 47440087Sbostic 47540087Sbostic while (pat < patend) { 47640087Sbostic c = *pat++; 47748540Sbostic switch (c & M_MASK) { 47840087Sbostic case M_ALL: 47940087Sbostic if (pat == patend) 48040087Sbostic return(1); 48140087Sbostic for (; *name != EOS; ++name) { 48240087Sbostic if (match(name, pat, patend)) 48340087Sbostic return(1); 48440087Sbostic } 48540087Sbostic return(0); 48640087Sbostic case M_ONE: 48740087Sbostic if (*name++ == EOS) 48840087Sbostic return(0); 48940087Sbostic break; 49040087Sbostic case M_SET: 49140087Sbostic ok = 0; 49240087Sbostic k = *name++; 49348540Sbostic if (negate_range = ((*pat & M_MASK) == M_NOT)) 49440087Sbostic ++pat; 49548540Sbostic while (((c = *pat++) & M_MASK) != M_END) { 49648540Sbostic if ((*pat & M_MASK) == M_RNG) { 49740087Sbostic if (c <= k && k <= pat[1]) 49840087Sbostic ok = 1; 49940087Sbostic pat += 2; 50040087Sbostic } 50140087Sbostic else if (c == k) 50240087Sbostic ok = 1; 50340087Sbostic } 50440087Sbostic if (ok == negate_range) 50540087Sbostic return(0); 50640087Sbostic break; 50740087Sbostic default: 50840087Sbostic if (*name++ != c) 50940087Sbostic return(0); 51040087Sbostic break; 51140087Sbostic } 51240087Sbostic } 51340087Sbostic return(*name == EOS); 51440087Sbostic } 51540087Sbostic 51640087Sbostic /* free allocated data belonging to a glob_t structure */ 51740087Sbostic void 51840087Sbostic globfree(pglob) 51940087Sbostic glob_t *pglob; 52040087Sbostic { 52140087Sbostic register int i; 52240087Sbostic register char **pp; 52340087Sbostic 52440087Sbostic if (pglob->gl_pathv != NULL) { 52540087Sbostic pp = pglob->gl_pathv + pglob->gl_offs; 52640087Sbostic for (i = pglob->gl_pathc; i--; ++pp) 52740087Sbostic if (*pp) 528*50049Sbostic free(pp); 529*50049Sbostic free(pglob->gl_pathv); 53040087Sbostic } 53140087Sbostic } 532