X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=src%2Fregex%2Fglob.c;h=9de080ed9ccd79276657d8a3333f869882fc9a96;hb=616a8bf660d6c616aedf1e17adfb3de64f1ab9de;hp=bbe78f7d9a345372a0822d4a89d4fbdb5b6d6d16;hpb=787c2648a9bdc9431237abc0fdd61971cbf289cd;p=musl diff --git a/src/regex/glob.c b/src/regex/glob.c index bbe78f7d..9de080ed 100644 --- a/src/regex/glob.c +++ b/src/regex/glob.c @@ -1,3 +1,4 @@ +#define _BSD_SOURCE #include #include #include @@ -8,132 +9,166 @@ #include #include #include -#include -#include "libc.h" +#include struct match { struct match *next; - char name[1]; + char name[]; }; -static int is_literal(const char *p, int useesc) -{ - int bracket = 0; - for (; *p; p++) { - switch (*p) { - case '\\': - if (!useesc) break; - case '?': - case '*': - return 0; - case '[': - bracket = 1; - break; - case ']': - if (bracket) return 0; - break; - } - } - return 1; -} - static int append(struct match **tail, const char *name, size_t len, int mark) { - struct match *new = malloc(sizeof(struct match) + len + 1); + struct match *new = malloc(sizeof(struct match) + len + 2); if (!new) return -1; (*tail)->next = new; new->next = NULL; - strcpy(new->name, name); - if (mark) strcat(new->name, "/"); + memcpy(new->name, name, len+1); + if (mark && len && name[len-1]!='/') { + new->name[len] = '/'; + new->name[len+1] = 0; + } *tail = new; return 0; } -static int match_in_dir(const char *d, const char *p, int flags, int (*errfunc)(const char *path, int err), struct match **tail) +static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, int (*errfunc)(const char *path, int err), struct match **tail) { - DIR *dir; - struct dirent de_buf, *de; - char pat[strlen(p)+1]; - char *p2; - size_t l = strlen(d); - int literal; - int fnm_flags= ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) - | ((!(flags & GLOB_PERIOD)) ? FNM_PERIOD : 0); - int error; - - if ((p2 = strchr(p, '/'))) { - strcpy(pat, p); - pat[p2-p] = 0; - for (; *p2 == '/'; p2++); - p = pat; - } - literal = is_literal(p, !(flags & GLOB_NOESCAPE)); - if (*d == '/' && !*(d+1)) l = 0; + /* If GLOB_MARK is unused, we don't care about type. */ + if (!type && !(flags & GLOB_MARK)) type = DT_REG; - /* rely on opendir failing for nondirectory objects */ - dir = opendir(*d ? d : "."); - error = errno; - if (!dir) { - /* this is not an error -- we let opendir call stat for us */ - if (error == ENOTDIR) return 0; - if (error == EACCES && !*p) { - struct stat st; - if (!stat(d, &st) && S_ISDIR(st.st_mode)) { - if (append(tail, d, l, l)) - return GLOB_NOSPACE; - return 0; - } + /* Special-case the remaining pattern being all slashes, in + * which case we can use caller-passed type if it's a dir. */ + if (*pat && type!=DT_DIR) type = 0; + while (pos+1 < PATH_MAX && *pat=='/') buf[pos++] = *pat++; + + /* Consume maximal [escaped-]literal prefix of pattern, copying + * and un-escaping it to the running buffer as we go. */ + ptrdiff_t i=0, j=0; + int in_bracket = 0, overflow = 0; + for (; pat[i]!='*' && pat[i]!='?' && (!in_bracket || pat[i]!=']'); i++) { + if (!pat[i]) { + if (overflow) return 0; + pat += i; + pos += j; + i = j = 0; + break; + } else if (pat[i] == '[') { + in_bracket = 1; + } else if (pat[i] == '\\' && !(flags & GLOB_NOESCAPE)) { + /* Backslashes inside a bracket are (at least by + * our interpretation) non-special, so if next + * char is ']' we have a complete expression. */ + if (in_bracket && pat[i+1]==']') break; + /* Unpaired final backslash never matches. */ + if (!pat[i+1]) return 0; + i++; } - if (errfunc(d, error) || (flags & GLOB_ERR)) - return GLOB_ABORTED; + if (pat[i] == '/') { + if (overflow) return 0; + in_bracket = 0; + pat += i+1; + i = -1; + pos += j+1; + j = -1; + } + /* Only store a character if it fits in the buffer, but if + * a potential bracket expression is open, the overflow + * must be remembered and handled later only if the bracket + * is unterminated (and thereby a literal), so as not to + * disallow long bracket expressions with short matches. */ + if (pos+(j+1) < PATH_MAX) { + buf[pos+j++] = pat[i]; + } else if (in_bracket) { + overflow = 1; + } else { + return 0; + } + /* If we consume any new components, the caller-passed type + * or dummy type from above is no longer valid. */ + type = 0; + } + buf[pos] = 0; + if (!*pat) { + /* If we consumed any components above, or if GLOB_MARK is + * requested and we don't yet know if the match is a dir, + * we must confirm the file exists and/or determine its type. + * + * If marking dirs, symlink type is inconclusive; we need the + * type for the symlink target, and therefore must try stat + * first unless type is known not to be a symlink. Otherwise, + * or if that fails, use lstat for determining existence to + * avoid false negatives in the case of broken symlinks. */ + struct stat st; + if ((flags & GLOB_MARK) && (!type||type==DT_LNK) && !stat(buf, &st)) { + if (S_ISDIR(st.st_mode)) type = DT_DIR; + else type = DT_REG; + } + if (!type && lstat(buf, &st)) { + if (errno!=ENOENT && (errfunc(buf, errno) || (flags & GLOB_ERR))) + return GLOB_ABORTED; + return 0; + } + if (append(tail, buf, pos, (flags & GLOB_MARK) && type==DT_DIR)) + return GLOB_NOSPACE; return 0; } - if (!*p) { - error = append(tail, d, l, l) ? GLOB_NOSPACE : 0; - closedir(dir); - return error; + char *p2 = strchr(pat, '/'), saved_sep = '/'; + /* Check if the '/' was escaped and, if so, remove the escape char + * so that it will not be unpaired when passed to fnmatch. */ + if (p2 && !(flags & GLOB_NOESCAPE)) { + char *p; + for (p=p2; p>pat && p[-1]=='\\'; p--); + if ((p2-p)%2) { + p2--; + saved_sep = '\\'; + } + } + DIR *dir = opendir(pos ? buf : "."); + if (!dir) { + if (errfunc(buf, errno) || (flags & GLOB_ERR)) + return GLOB_ABORTED; + return 0; } - while (!(error = readdir_r(dir, &de_buf, &de)) && de) { - char namebuf[l+de->d_reclen+2], *name = namebuf; - if (!literal && fnmatch(p, de->d_name, fnm_flags)) + int old_errno = errno; + struct dirent *de; + while (errno=0, de=readdir(dir)) { + /* Quickly skip non-directories when there's pattern left. */ + if (p2 && de->d_type && de->d_type!=DT_DIR && de->d_type!=DT_LNK) continue; - if (literal && strcmp(p, de->d_name)) + + size_t l = strlen(de->d_name); + if (l >= PATH_MAX-pos) continue; + + if (p2) *p2 = 0; + + int fnm_flags= ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) + | ((!(flags & GLOB_PERIOD)) ? FNM_PERIOD : 0); + + if (fnmatch(pat, de->d_name, fnm_flags)) continue; - if (p2 && de->d_type && !S_ISDIR(de->d_type<<12) && !S_ISLNK(de->d_type<<12)) + + /* With GLOB_PERIOD, don't allow matching . or .. unless + * fnmatch would match them with FNM_PERIOD rules in effect. */ + if (p2 && (flags & GLOB_PERIOD) && de->d_name[0]=='.' + && (!de->d_name[1] || de->d_name[1]=='.' && !de->d_name[2]) + && fnmatch(pat, de->d_name, fnm_flags | FNM_PERIOD)) continue; - if (*d) { - memcpy(name, d, l); - name[l] = '/'; - strcpy(name+l+1, de->d_name); - } else { - name = de->d_name; - } - if (p2) { - if ((error = match_in_dir(name, p2, flags, errfunc, tail))) { - closedir(dir); - return error; - } - } else { - int mark = 0; - if (flags & GLOB_MARK) { - if (de->d_type) - mark = S_ISDIR(de->d_type<<12); - else { - struct stat st; - stat(name, &st); - mark = S_ISDIR(st.st_mode); - } - } - if (append(tail, name, l+de->d_reclen+1, mark)) { - closedir(dir); - return GLOB_NOSPACE; - } + + memcpy(buf+pos, de->d_name, l+1); + if (p2) *p2 = saved_sep; + int r = do_glob(buf, pos+l, de->d_type, p2 ? p2 : "", flags, errfunc, tail); + if (r) { + closedir(dir); + return r; } } + int readerr = errno; + if (p2) *p2 = saved_sep; closedir(dir); - if (error && (errfunc(d, error) || (flags & GLOB_ERR))) + if (readerr && (errfunc(buf, errno) || (flags & GLOB_ERR))) return GLOB_ABORTED; + errno = old_errno; return 0; } @@ -156,23 +191,47 @@ static int sort(const void *a, const void *b) return strcmp(*(const char **)a, *(const char **)b); } -int glob(const char *pat, int flags, int (*errfunc)(const char *path, int err), glob_t *g) +static int expand_tilde(char **pat, char *buf, size_t *pos) +{ + char *p = *pat + 1; + size_t i = 0; + + char delim, *name_end = __strchrnul(p, '/'); + if ((delim = *name_end)) *name_end++ = 0; + *pat = name_end; + + char *home = *p ? NULL : getenv("HOME"); + if (!home) { + struct passwd pw, *res; + switch (*p ? getpwnam_r(p, &pw, buf, PATH_MAX, &res) + : getpwuid_r(getuid(), &pw, buf, PATH_MAX, &res)) { + case ENOMEM: + return GLOB_NOSPACE; + case 0: + if (!res) + default: + return GLOB_NOMATCH; + } + home = pw.pw_dir; + } + while (i < PATH_MAX - 2 && *home) + buf[i++] = *home++; + if (*home) + return GLOB_NOMATCH; + if ((buf[i] = delim)) + buf[++i] = 0; + *pos = i; + return 0; +} + +int glob(const char *restrict pat, int flags, int (*errfunc)(const char *path, int err), glob_t *restrict g) { - const char *p=pat, *d; struct match head = { .next = NULL }, *tail = &head; size_t cnt, i; size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0; int error = 0; + char buf[PATH_MAX]; - if (*p == '/') { - for (; *p == '/'; p++); - d = "/"; - } else { - d = ""; - } - - if (strlen(p) > PATH_MAX) return GLOB_NOSPACE; - if (!errfunc) errfunc = ignore_err; if (!(flags & GLOB_APPEND)) { @@ -181,7 +240,19 @@ int glob(const char *pat, int flags, int (*errfunc)(const char *path, int err), g->gl_pathv = NULL; } - if (*p) error = match_in_dir(d, p, flags, errfunc, &tail); + if (*pat) { + char *p = strdup(pat); + if (!p) return GLOB_NOSPACE; + buf[0] = 0; + size_t pos = 0; + char *s = p; + if ((flags & (GLOB_TILDE | GLOB_TILDE_CHECK)) && *p == '~') + error = expand_tilde(&s, buf, &pos); + if (!error) + error = do_glob(buf, pos, 0, s, flags, errfunc, &tail); + free(p); + } + if (error == GLOB_NOSPACE) { freelist(&head); return error; @@ -236,5 +307,5 @@ void globfree(glob_t *g) g->gl_pathv = NULL; } -LFS64(glob); -LFS64(globfree); +weak_alias(glob, glob64); +weak_alias(globfree, globfree64);