chiark / gitweb /
util: rework rm_rf() logic
authorLennart Poettering <lennart@poettering.net>
Sat, 4 Apr 2015 09:52:57 +0000 (11:52 +0200)
committerSven Eden <yamakuzure@gmx.net>
Tue, 14 Mar 2017 06:49:53 +0000 (07:49 +0100)
- 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
src/shared/machine-image.c
src/shared/rm-rf.c [new file with mode: 0644]
src/shared/rm-rf.h [new file with mode: 0644]
src/shared/switch-root.c
src/shared/util.c
src/shared/util.h

index 0aac3bcf52dc68d2d5890f4900c1d7aaab687661..1f6fffff6bc7bcbd5b63fe95048c47ca3be49270 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "util.h"
 #include "mkdir.h"
 
 #include "util.h"
 #include "mkdir.h"
+#include "rm-rf.h"
 #include "hashmap.h"
 #include "fileio.h"
 #include "path-util.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;
 
         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);
 
         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);
 
         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);
 
         if (r < 0)
                 log_error_errno(r, "Failed to remove runtime directory %s: %m", u->runtime_path);
 
index edf986d4db11801abb0ca47c79b8003793189f3a..c5808af81efe27dfb13cdf6805f9b3b3318a3d25 100644 (file)
@@ -28,6 +28,7 @@
 #include "path-util.h"
 #include "copy.h"
 #include "mkdir.h"
 #include "path-util.h"
 #include "copy.h"
 #include "mkdir.h"
+#include "rm-rf.h"
 #include "machine-image.h"
 
 static const char image_search_path[] =
 #include "machine-image.h"
 
 static const char image_search_path[] =
@@ -366,7 +367,7 @@ int image_remove(Image *i) {
                 /* fall through */
 
         case IMAGE_RAW:
                 /* 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;
 
         default:
                 return -EOPNOTSUPP;
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
new file mode 100644 (file)
index 0000000..99d12b1
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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 (file)
index 0000000..769bbc8
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/stat.h>
+
+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);
index 813641ad44da59b7c0969c89adc192a40623aceb..ae3839de162c9f4d254c04ce5fe9d274442460ed 100644 (file)
 
 #include "util.h"
 #include "path-util.h"
 
 #include "util.h"
 #include "path-util.h"
-#include "switch-root.h"
 #include "mkdir.h"
 #include "mkdir.h"
+#include "rm-rf.h"
 #include "base-filesystem.h"
 #include "missing.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) {
 
 
 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 {
                 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;
                 }
         }
                         old_root_fd = -1;
                 }
         }
index 392c42ba2be4280ba0284f0ccbe6449a75ad1c1c..973f1070b8a1178ee4796a03ecdca2872f3cf5af 100644 (file)
@@ -3009,101 +3009,14 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) {
         return 0;
 }
 
         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);
 }
 
         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)
         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);
 }
 
         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);
 
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
         assert(path);
 
index 3b3a0564379d67ba80f5b858f5ce850fcfcd1828..9f0149e1c5a39bf63e43b582e2af394b43be8995 100644 (file)
@@ -41,6 +41,7 @@
 #include <locale.h>
 #include <mntent.h>
 #include <sys/inotify.h>
 #include <locale.h>
 #include <mntent.h>
 #include <sys/inotify.h>
+#include <sys/statfs.h>
 
 #if SIZEOF_PID_T == 4
 #  define PID_PRI PRIi32
 
 #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 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);
 
 
 int pipe_eof(int fd);