diff --git a/include/glob.h b/include/glob.h index 76f6c1c6..477ddf2b 100644 --- a/include/glob.h +++ b/include/glob.h @@ -30,6 +30,7 @@ void globfree(glob_t *); #define GLOB_APPEND 0x20 #define GLOB_NOESCAPE 0x40 #define GLOB_PERIOD 0x80 +#define GLOB_TILDE 0x1000 #define GLOB_NOSPACE 1 #define GLOB_ABORTED 2 diff --git a/src/regex/glob.c b/src/regex/glob.c index 5b6ff124..37bbfafd 100644 --- a/src/regex/glob.c +++ b/src/regex/glob.c @@ -8,6 +8,8 @@ #include #include #include "libc.h" +#include +#include struct match { @@ -154,13 +156,125 @@ static int sort(const void *a, const void *b) return strcmp(*(const char **)a, *(const char **)b); } +#include +#define GLOB_STRLCPY_ALIGN (sizeof(size_t)-1) +#define GLOB_STRLCPY_ONES ((size_t)-1/UCHAR_MAX) +#define GLOB_STRLCPY_HIGHS (GLOB_STRLCPY_ONES * (UCHAR_MAX/2+1)) +#define GLOB_STRLCPY_HASZERO(x) ((x)-GLOB_STRLCPY_ONES & ~(x) & GLOB_STRLCPY_HIGHS) + +size_t glob_strlcpy(char *d, const char *s, size_t n) +{ + char *d0 = d; + size_t *wd; + const size_t *ws; + + if (!n--) goto finish; + if (((uintptr_t)s & GLOB_STRLCPY_ALIGN) == ((uintptr_t)d & GLOB_STRLCPY_ALIGN)) { + for (; ((uintptr_t)s & GLOB_STRLCPY_ALIGN) && n && (*d=*s); n--, s++, d++); + if (n && *s) { + wd=(void *)d; ws=(const void *)s; + for (; n>=sizeof(size_t) && !GLOB_STRLCPY_HASZERO(*ws); + n-=sizeof(size_t), ws++, wd++) *wd = *ws; + d=(void *)wd; s=(const void *)ws; + } + } + for (; n && (*d=*s); n--, s++, d++); + *d = 0; +finish: + return d-d0 + strlen(s); +} + +static size_t glob_strlcat(char *d, const char *s, size_t n) +{ + size_t l = strnlen(d, n); + if (l == n) return l + strlen(s); + return l + glob_strlcpy(d+l, s, n-l); +} + + + +/*"~" or "~/(...)" case*/ +static int expand_tilde_cur_user(const char *pat_after_tilde, char *new_pat, size_t new_pat_size) +{ + char *home; + struct passwd pw_store, *pw_result; + char pw_buf[LOGIN_NAME_MAX]; + + /*FIXME: add check for issetugid as in libc of openbsd?*/ + home = getenv("HOME"); + if(home == NULL) { + getpwuid_r(getuid(), &pw_store, pw_buf, sizeof(pw_buf), &pw_result); + if(pw_result == NULL) { + return 0; + } + home = pw_store.pw_dir; + } + + return glob_strlcpy(new_pat, home, new_pat_size) < new_pat_size + && glob_strlcat(new_pat, pat_after_tilde, new_pat_size) < new_pat_size; +} + +/* "~user/(...) case*/ +static int expand_tilde_named_user(const char *pat_after_tilde, char *new_pat, size_t new_pat_size) +{ + struct passwd pw_store, *pw_result; + char pw_buf[1024], username[LOGIN_NAME_MAX]; + const char *slash_pos = strchr(pat_after_tilde, '/'); + if(slash_pos == NULL) { + return 0; + } + + ptrdiff_t pat_username_size = slash_pos - pat_after_tilde; + if(pat_username_size <= 0 || pat_username_size >= sizeof(username)) { + return 0; + } + strncpy(username, pat_after_tilde, pat_username_size); + username[pat_username_size] = '\0'; + + getpwnam_r(username, &pw_store, pw_buf, sizeof(pw_buf), &pw_result); + if (pw_result == NULL) + return 0; + + return glob_strlcpy(new_pat, pw_store.pw_dir, new_pat_size) < new_pat_size + && glob_strlcat(new_pat, slash_pos, new_pat_size) < new_pat_size; +} + +/* expands: + * ~ into /home/user/ + * ~/asd into /home/user/asd + * ~user1/asd into /home/user1/asd + * the values for the home directory are taken from passwd + * + * returning true means successful expansion and that expanded_pat is valid + */ +static int expand_tilde(const char *pat, char *new_pat, int new_pat_size) +{ + const char *pat_after_tilde = pat + 1; + if(*pat_after_tilde == '\0' || *pat_after_tilde == '/') { + return expand_tilde_cur_user(pat_after_tilde, new_pat, new_pat_size); + } else { + return expand_tilde_named_user(pat_after_tilde, new_pat, new_pat_size); + } +} + int glob(const char *restrict pat, int flags, int (*errfunc)(const char *path, int err), glob_t *restrict g) { - const char *p=pat, *d; + const char *p, *d; + const int tilde_flag = flags & GLOB_TILDE; + char new_pat[tilde_flag ? PATH_MAX : 1]; struct match head = { .next = NULL }, *tail = &head; size_t cnt, i; size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0; int error = 0; + + /*even if expanding fails(e.g. expansion make pat too big) + * we should try to match the ~ or ~user literally*/ + int should_expand_tilde = tilde_flag && (pat[0] == '~'); + if(should_expand_tilde && expand_tilde(pat, new_pat, sizeof(new_pat))) { + p = new_pat; + } else { + p = pat; + } if (*p == '/') { for (; *p == '/'; p++);