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=d7e4318defa903c52864f0e62540035eaa5db969;hpb=999463d14994808ff15fc4dd66c540d8a29f8121;p=elogind.git diff --git a/src/shared/util.c b/src/shared/util.c index d7e4318de..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; @@ -1486,13 +1466,12 @@ static int cunescape_one(const char *p, size_t length, char *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. */ @@ -1500,7 +1479,7 @@ 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); @@ -1512,31 +1491,17 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi 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; - } - - return -EINVAL; - } - 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; - } - - return k; + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + continue; } f += k; @@ -1544,17 +1509,17 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi } *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) { @@ -3023,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) @@ -3039,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); @@ -6766,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; @@ -6868,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