From e777ddfd71f3b2d3217ffe74f287388832915d0b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 4 Apr 2015 11:52:57 +0200 Subject: [PATCH] util: rework rm_rf() logic - Move to its own file rm-rf.c - Change parameters into a single flags parameter - Remove "honour sticky" logic, it's unused these days --- src/login/logind-user.c | 5 +- src/shared/machine-image.c | 3 +- src/shared/rm-rf.c | 173 ++++++++++++++++++++++++++++++++ src/shared/rm-rf.h | 33 ++++++ src/shared/switch-root.c | 5 +- src/shared/util.c | 199 +------------------------------------ src/shared/util.h | 9 +- 7 files changed, 219 insertions(+), 208 deletions(-) create mode 100644 src/shared/rm-rf.c create mode 100644 src/shared/rm-rf.h diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 0aac3bcf5..1f6fffff6 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -26,6 +26,7 @@ #include "util.h" #include "mkdir.h" +#include "rm-rf.h" #include "hashmap.h" #include "fileio.h" #include "path-util.h" @@ -374,7 +375,7 @@ static int user_remove_runtime_path(User *u) { if (!u->runtime_path) return 0; - r = rm_rf(u->runtime_path, false, false, false); + r = rm_rf(u->runtime_path, 0); if (r < 0) log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path); @@ -385,7 +386,7 @@ static int user_remove_runtime_path(User *u) { if (r < 0 && errno != EINVAL && errno != ENOENT) log_error_errno(errno, "Failed to unmount user runtime directory %s: %m", u->runtime_path); - r = rm_rf(u->runtime_path, false, true, false); + r = rm_rf(u->runtime_path, REMOVE_ROOT); if (r < 0) log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index edf986d4d..c5808af81 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -28,6 +28,7 @@ #include "path-util.h" #include "copy.h" #include "mkdir.h" +#include "rm-rf.h" #include "machine-image.h" static const char image_search_path[] = @@ -366,7 +367,7 @@ int image_remove(Image *i) { /* fall through */ case IMAGE_RAW: - return rm_rf_dangerous(i->path, false, true, false); + return rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL); default: return -EOPNOTSUPP; diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c new file mode 100644 index 000000000..99d12b11c --- /dev/null +++ b/src/shared/rm-rf.c @@ -0,0 +1,173 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "path-util.h" +#include "rm-rf.h" + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + int ret = 0, r; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + if (!(flags & REMOVE_PHYSICAL)) { + + r = fd_is_temporary_fs(fd); + if (r < 0) { + safe_close(fd); + return r; + } + + if (!r) { + /* We refuse to clean physical file systems + * with this call, unless explicitly + * requested. This is extra paranoia just to + * be sure we never ever remove non-state + * data */ + + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + } + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + bool is_dir; + struct stat st; + + 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 || (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); + } else + is_dir = de->d_type == DT_DIR; + + 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; + } + + /* We pass REMOVE_PHYSICAL here, to avoid + * doing the fstatfs() to check the file + * system type again for each directory */ + r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } +} + +int rm_rf(const char *path, RemoveFlags flags) { + 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 (!(flags & REMOVE_PHYSICAL)) { + 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 ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, flags, NULL); + + if (flags & REMOVE_ROOT) { + + if (rmdir(path) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + } + } + + return r; +} diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h new file mode 100644 index 000000000..769bbc853 --- /dev/null +++ b/src/shared/rm-rf.h @@ -0,0 +1,33 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +typedef enum RemoveFlags { + REMOVE_ONLY_DIRECTORIES = 1, + REMOVE_ROOT = 2, + REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ +} RemoveFlags; + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +int rm_rf(const char *path, RemoveFlags flags); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 813641ad4..ae3839de1 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -29,10 +29,11 @@ #include "util.h" #include "path-util.h" -#include "switch-root.h" #include "mkdir.h" +#include "rm-rf.h" #include "base-filesystem.h" #include "missing.h" +#include "switch-root.h" int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { @@ -142,7 +143,7 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, if (fstat(old_root_fd, &rb) < 0) log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); else { - rm_rf_children(old_root_fd, false, false, &rb); + (void) rm_rf_children(old_root_fd, 0, &rb); old_root_fd = -1; } } diff --git a/src/shared/util.c b/src/shared/util.c index 392c42ba2..973f1070b 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -3009,101 +3009,14 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return 0; } -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) { +bool is_temporary_fs(const struct statfs *s) { assert(s); return F_TYPE_EQUAL(s->f_type, TMPFS_MAGIC) || F_TYPE_EQUAL(s->f_type, RAMFS_MAGIC); } -int is_fd_on_temporary_fs(int fd) { +int fd_is_temporary_fs(int fd) { struct statfs s; if (fstatfs(fd, &s) < 0) @@ -3112,114 +3025,6 @@ int is_fd_on_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); diff --git a/src/shared/util.h b/src/shared/util.h index 3b3a05643..9f0149e1c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -41,6 +41,7 @@ #include #include #include +#include #if SIZEOF_PID_T == 4 # define PID_PRI PRIi32 @@ -462,12 +463,8 @@ int get_ctty(pid_t, dev_t *_devnr, char **r); int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); -int is_fd_on_temporary_fs(int fd); - -int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); -int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev); -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); -int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky); +bool is_temporary_fs(const struct statfs *s) _pure_; +int fd_is_temporary_fs(int fd); int pipe_eof(int fd); -- 2.30.2