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=8071bb231438ee9addf863e6aa8f727eaa122d89;hpb=322e94abd97bbd3b8b0d37719449c0be5b9d99d8;p=elogind.git diff --git a/src/shared/util.c b/src/shared/util.c index 8071bb231..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) @@ -1487,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. */ @@ -1501,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); @@ -1513,33 +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; - } - - free(r); - 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; - } - - free(r); - return k; + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + continue; } f += k; @@ -1547,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) { @@ -1566,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) @@ -3026,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) @@ -3042,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); @@ -6769,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; @@ -6871,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 @@ -7637,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); @@ -7648,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); @@ -7670,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) { @@ -7994,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; -}