X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=72984735ced828e33c2eca18df7828fdf1db58c9;hb=30ab6a0fc1bb950c4dcd90dcd3dfe00a810c7fc1;hp=aea5364b585633f9eb08ffb78f0eb8541dd08532;hpb=03635567fbc1d916c44e13527454a80e8ee9db6b;p=elogind.git diff --git a/src/shared/util.c b/src/shared/util.c index aea5364b5..72984735c 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -150,27 +151,6 @@ char* endswith(const char *s, const char *postfix) { return (char*) s + sl - pl; } -char* endswith_no_case(const char *s, const char *postfix) { - size_t sl, pl; - - assert(s); - assert(postfix); - - sl = strlen(s); - pl = strlen(postfix); - - if (pl == 0) - return (char*) s + sl; - - if (sl < pl) - return NULL; - - if (strcasecmp(s + sl - pl, postfix) != 0) - return NULL; - - return (char*) s + sl - pl; -} - char* first_word(const char *s, const char *word) { size_t sl, wl; const char *p; @@ -1353,8 +1333,7 @@ char *cescape(const char *s) { assert(s); - /* Does C style string escaping. May be reversed with - * cunescape(). */ + /* Does C style string escaping. */ r = new(char, strlen(s)*4 + 1); if (!r) @@ -1368,17 +1347,13 @@ char *cescape(const char *s) { return r; } -static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) { +static int cunescape_one(const char *p, size_t length, char *ret) { int r = 1; assert(p); assert(*p); assert(ret); - /* Unescapes C style. Returns the unescaped character in ret, - * unless we encountered a \u sequence in which case the full - * unicode character is returned in ret_unicode, instead. */ - if (length != (size_t) -1 && length < 1) return -EINVAL; @@ -1435,92 +1410,15 @@ static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_ if (b < 0) return -EINVAL; - /* Don't allow NUL bytes */ + /* don't allow NUL bytes */ if (a == 0 && b == 0) return -EINVAL; - *ret = (char) ((a << 4U) | b); + *ret = (char) ((a << 4) | b); r = 3; break; } - case 'u': { - /* C++11 style 16bit unicode */ - - int a[4]; - unsigned i; - uint32_t c; - - if (length != (size_t) -1 && length < 5) - return -EINVAL; - - for (i = 0; i < 4; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - if (c < 128) - *ret = c; - else { - if (!ret_unicode) - return -EINVAL; - - *ret = 0; - *ret_unicode = c; - } - - r = 5; - break; - } - - case 'U': { - /* C++11 style 32bit unicode */ - - int a[8]; - unsigned i; - uint32_t c; - - if (length != (size_t) -1 && length < 9) - return -EINVAL; - - for (i = 0; i < 8; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | - ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - /* Don't allow invalid code points */ - if (!unichar_is_valid(c)) - return -EINVAL; - - if (c < 128) - *ret = c; - else { - if (!ret_unicode) - return -EINVAL; - - *ret = 0; - *ret_unicode = c; - } - - r = 9; - break; - } - case '0': case '1': case '2': @@ -1530,8 +1428,7 @@ static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_ case '6': case '7': { /* octal encoding */ - int a, b, c; - uint32_t m; + int a, b, c, m; if (length != (size_t) -1 && length < 4) return -EINVAL; @@ -1553,11 +1450,11 @@ static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_ return -EINVAL; /* Don't allow bytes above 255 */ - m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; + m = (a << 6) | (b << 3) | c; if (m > 255) return -EINVAL; - *ret = m; + *ret = (char) m; r = 3; break; } @@ -1569,13 +1466,12 @@ static int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_ return r; } -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { +char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) { char *r, *t; const char *f; size_t pl; assert(s); - assert(ret); /* Undoes C style string escaping, and optionally prefixes it. */ @@ -1583,71 +1479,47 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi r = new(char, pl+length+1); if (!r) - return -ENOMEM; + return NULL; if (prefix) memcpy(r, prefix, pl); for (f = s, t = r + pl; f < s + length; f++) { size_t remaining; - uint32_t u; - char c; int k; remaining = s + length - f; assert(remaining > 0); - if (*f != '\\') { - /* A literal literal, copy verbatim */ + if (*f != '\\' || remaining == 1) { + /* a literal literal, or a trailing backslash, copy verbatim */ *(t++) = *f; continue; } - if (remaining == 1) { - if (flags & UNESCAPE_RELAX) { - /* A trailing backslash, copy verbatim */ - *(t++) = *f; - continue; - } - - free(r); - return -EINVAL; - } - - k = cunescape_one(f + 1, remaining - 1, &c, &u); + k = cunescape_one(f + 1, remaining - 1, t); if (k < 0) { - if (flags & UNESCAPE_RELAX) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - continue; - } - - free(r); - return k; + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + continue; } - if (c != 0) - /* Non-Unicode? Let's encode this directly */ - *(t++) = c; - else - /* Unicode? Then let's encode this in UTF-8 */ - t += utf8_encode_unichar(t, u); - f += k; + t++; } *t = 0; - - *ret = r; - return t - r; + return r; } -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { - return cunescape_length_with_prefix(s, length, NULL, flags, ret); +char *cunescape_length(const char *s, size_t length) { + return cunescape_length_with_prefix(s, length, NULL); } -int cunescape(const char *s, UnescapeFlags flags, char **ret) { - return cunescape_length(s, strlen(s), flags, ret); +char *cunescape(const char *s) { + assert(s); + + return cunescape_length(s, strlen(s)); } char *xescape(const char *s, const char *bad) { @@ -1656,7 +1528,7 @@ char *xescape(const char *s, const char *bad) { /* Escapes all chars in bad, in addition to \ and all special * chars, in \xFF style escaping. May be reversed with - * cunescape(). */ + * cunescape. */ r = new(char, strlen(s) * 4 + 1); if (!r) @@ -3116,14 +2988,101 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return 0; } -bool is_temporary_fs(const struct statfs *s) { +int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + int ret = 0; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + bool is_dir, keep_around; + struct stat st; + int r; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno != 0 && ret == 0) + ret = -errno; + return ret; + } + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN || + honour_sticky || + (de->d_type == DT_DIR && root_dev)) { + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + keep_around = + honour_sticky && + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); + } else { + is_dir = de->d_type == DT_DIR; + keep_around = false; + } + + if (is_dir) { + int subdir_fd; + + /* if root_dev is set, remove subdirectories only, if device is same as dir */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, + O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + r = rm_rf_children_dangerous(subdir_fd, only_dirs, honour_sticky, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (!keep_around) + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!only_dirs && !keep_around) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } +} + +_pure_ static int is_temporary_fs(struct statfs *s) { assert(s); return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } -int fd_is_temporary_fs(int fd) { +int is_fd_on_temporary_fs(int fd) { struct statfs s; if (fstatfs(fd, &s) < 0) @@ -3132,6 +3091,114 @@ int fd_is_temporary_fs(int fd) { return is_temporary_fs(&s); } +int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + struct statfs s; + + assert(fd >= 0); + + if (fstatfs(fd, &s) < 0) { + safe_close(fd); + return -errno; + } + + /* We refuse to clean disk file systems with this call. This + * is extra paranoia just to be sure we never ever remove + * non-state data */ + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + + return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); +} + +static int file_is_priv_sticky(const char *p) { + struct stat st; + + assert(p); + + if (lstat(p, &st) < 0) + return -errno; + + return + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); +} + +static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { + int fd, r; + struct statfs s; + + assert(path); + + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { + + if (errno != ENOTDIR && errno != ELOOP) + return -errno; + + if (!dangerous) { + if (statfs(path, &s) < 0) + return -errno; + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + + if (delete_root && !only_dirs) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + if (!dangerous) { + if (fstatfs(fd, &s) < 0) { + safe_close(fd); + return -errno; + } + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + } + + r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); + if (delete_root) { + + if (honour_sticky && file_is_priv_sticky(path) > 0) + return r; + + if (rmdir(path) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + return r; +} + +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); +} + +int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); +} + int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { assert(path); @@ -3676,15 +3743,14 @@ int touch(const char *path) { return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, 0); } -static char *unquote(const char *s, const char* quotes) { +char *unquote(const char *s, const char* quotes) { size_t l; assert(s); /* This is rather stupid, simply removes the heading and * trailing quotes if there is one. Doesn't care about - * escaping or anything. - * - * DON'T USE THIS FOR NEW CODE ANYMORE!*/ + * escaping or anything. We should make this smarter one + * day... */ l = strlen(s); if (l < 2) @@ -3696,6 +3762,36 @@ static char *unquote(const char *s, const char* quotes) { return strdup(s); } +char *normalize_env_assignment(const char *s) { + _cleanup_free_ char *value = NULL; + const char *eq; + char *p, *name; + + eq = strchr(s, '='); + if (!eq) { + char *r, *t; + + r = strdup(s); + if (!r) + return NULL; + + t = strstrip(r); + if (t != r) + memmove(r, t, strlen(t) + 1); + + return r; + } + + name = strndupa(s, eq - s); + p = strdupa(eq + 1); + + value = unquote(strstrip(p), QUOTES); + if (!value) + return NULL; + + return strjoin(strstrip(name), "=", value, NULL); +} + int wait_for_terminate(pid_t pid, siginfo_t *status) { siginfo_t dummy; @@ -3843,7 +3939,7 @@ static char *tag_to_udev_node(const char *tagvalue, const char *by) { _cleanup_free_ char *t = NULL, *u = NULL; size_t enc_len; - u = unquote(tagvalue, QUOTES); + u = unquote(tagvalue, "\"\'"); if (!u) return NULL; @@ -6830,9 +6926,9 @@ int umount_recursive(const char *prefix, int flags) { continue; } - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; + p = cunescape(path); + if (!p) + return -ENOMEM; if (!path_startswith(p, prefix)) continue; @@ -6932,9 +7028,9 @@ int bind_remount_recursive(const char *prefix, bool ro) { continue; } - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; + p = cunescape(path); + if (!p) + return -ENOMEM; /* Let's ignore autofs mounts. If they aren't * triggered yet, we want to avoid triggering @@ -7276,26 +7372,20 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { return -EINVAL; } - if (!GREEDY_REALLOC(s, allocated, sz+7)) + if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; if (flags & UNQUOTE_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); + r = cunescape_one(*p, (size_t) -1, &c); if (r < 0) return -EINVAL; (*p) += r - 1; + } - if (c != 0) - s[sz++] = c; /* normal explicit char */ - else - sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ - } else - s[sz++] = c; - + s[sz++] = c; state = VALUE; + break; case SINGLE_QUOTE: @@ -7323,25 +7413,18 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { return -EINVAL; } - if (!GREEDY_REALLOC(s, allocated, sz+7)) + if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; if (flags & UNQUOTE_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); + r = cunescape_one(*p, (size_t) -1, &c); if (r < 0) return -EINVAL; (*p) += r - 1; + } - if (c != 0) - s[sz++] = c; - else - sz += utf8_encode_unichar(s + sz, u); - } else - s[sz++] = c; - + s[sz++] = c; state = SINGLE_QUOTE; break; @@ -7368,25 +7451,18 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { return -EINVAL; } - if (!GREEDY_REALLOC(s, allocated, sz+7)) + if (!GREEDY_REALLOC(s, allocated, sz+2)) return -ENOMEM; if (flags & UNQUOTE_CUNESCAPE) { - uint32_t u; - - r = cunescape_one(*p, (size_t) -1, &c, &u); + r = cunescape_one(*p, (size_t) -1, &c); if (r < 0) return -EINVAL; (*p) += r - 1; + } - if (c != 0) - s[sz++] = c; - else - sz += utf8_encode_unichar(s + sz, u); - } else - s[sz++] = c; - + s[sz++] = c; state = DOUBLE_QUOTE; break; @@ -7718,7 +7794,7 @@ int fd_setcrtime(int fd, usec_t usec) { return 0; } -int chattr_fd(int fd, unsigned value, unsigned mask) { +int chattr_fd(int fd, bool b, unsigned mask) { unsigned old_attr, new_attr; assert(fd >= 0); @@ -7729,17 +7805,21 @@ int chattr_fd(int fd, unsigned value, unsigned mask) { if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) return -errno; - new_attr = (old_attr & ~mask) | (value & mask); + if (b) + new_attr = old_attr | mask; + else + new_attr = old_attr & ~mask; + if (new_attr == old_attr) return 0; if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) return -errno; - return 1; + return 0; } -int chattr_path(const char *p, unsigned value, unsigned mask) { +int chattr_path(const char *p, bool b, unsigned mask) { _cleanup_close_ int fd = -1; assert(p); @@ -7751,7 +7831,29 @@ int chattr_path(const char *p, unsigned value, unsigned mask) { if (fd < 0) return -errno; - return chattr_fd(fd, value, mask); + return chattr_fd(fd, b, mask); +} + +int change_attr_fd(int fd, unsigned value, unsigned mask) { + unsigned old_attr, new_attr; + + assert(fd >= 0); + + if (mask == 0) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) + return -errno; + + new_attr = (old_attr & ~mask) |(value & mask); + + if (new_attr == old_attr) + return 0; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) + return -errno; + + return 0; } int read_attr_fd(int fd, unsigned *ret) { @@ -8075,43 +8177,3 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char return 0; } - -char *shell_maybe_quote(const char *s) { - const char *p; - char *r, *t; - - assert(s); - - /* Encloses a string in double quotes if necessary to make it - * OK as shell string. */ - - for (p = s; *p; p++) - if (*p <= ' ' || - *p >= 127 || - strchr(SHELL_NEED_QUOTES, *p)) - break; - - if (!*p) - return strdup(s); - - r = new(char, 1+strlen(s)*2+1+1); - if (!r) - return NULL; - - t = r; - *(t++) = '"'; - t = mempcpy(t, s, p - s); - - for (; *p; p++) { - - if (strchr(SHELL_NEED_ESCAPE, *p)) - *(t++) = '\\'; - - *(t++) = *p; - } - - *(t++)= '"'; - *t = 0; - - return r; -}