From ca4a0e7d41f0b2a1fe2f99dbc3763187c16cf7ab Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 26 Jan 2021 16:30:06 +0100
Subject: [PATCH 1/6] rm-rf: refactor rm_rf_children(), split out body of
 directory iteration loop

This splits out rm_rf_children_inner() as body of the loop. We can use
that to implement rm_rf_child() for deleting one specific entry in a
directory.

(cherry picked from commit 1f0fb7d544711248cba34615e43c5a76bc902d74)
---
 src/shared/rm-rf.c | 223 ++++++++++++++++++++++++++-------------------
 src/shared/rm-rf.h |   3 +-
 2 files changed, 131 insertions(+), 95 deletions(-)

diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 900a7fb5ff..dffb9cf6ee 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -19,6 +19,9 @@
 #include "stat-util.h"
 #include "string-util.h"
 
+/* We treat tmpfs/ramfs + cgroupfs as non-physical file sytems. cgroupfs is similar to tmpfs in a way after
+ * all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it to remove
+ * those again. */
 static bool is_physical_fs(const struct statfs *sfs) {
         return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
 }
@@ -113,133 +116,145 @@ int fstatat_harder(int dfd,
         return 0;
 }
 
-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
-        _cleanup_closedir_ DIR *d = NULL;
-        struct dirent *de;
-        int ret = 0, r;
-        struct statfs sfs;
+static int rm_rf_children_inner(
+                int fd,
+                const char *fname,
+                int is_dir,
+                RemoveFlags flags,
+                const struct stat *root_dev) {
 
-        assert(fd >= 0);
+        struct stat st;
+        int r;
 
-        /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
-         * fd, in all cases, including on failure.. */
+        assert(fd >= 0);
+        assert(fname);
 
-        if (!(flags & REMOVE_PHYSICAL)) {
+        if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
 
-                r = fstatfs(fd, &sfs);
-                if (r < 0) {
-                        safe_close(fd);
-                        return -errno;
-                }
+                r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags);
+                if (r < 0)
+                        return r;
 
-                if (is_physical_fs(&sfs)) {
-                        /* 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. */
-                        _cleanup_free_ char *path = NULL;
+                is_dir = S_ISDIR(st.st_mode);
+        }
 
-                        (void) fd_get_path(fd, &path);
-                        log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
-                                  strna(path));
+        if (is_dir) {
+                _cleanup_close_ int subdir_fd = -1;
+                int q;
 
-                        safe_close(fd);
-                        return -EPERM;
-                }
-        }
+                /* if root_dev is set, remove subdirectories only if device is same */
+                if (root_dev && st.st_dev != root_dev->st_dev)
+                        return 0;
 
-        d = fdopendir(fd);
-        if (!d) {
-                safe_close(fd);
-                return errno == ENOENT ? 0 : -errno;
-        }
+                /* Stop at mount points */
+                r = fd_is_mount_point(fd, fname, 0);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return 0;
 
-        FOREACH_DIRENT_ALL(de, d, return -errno) {
-                bool is_dir;
-                struct stat st;
+                if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
 
-                if (dot_or_dot_dot(de->d_name))
-                        continue;
+                        /* This could be a subvolume, try to remove it */
 
-                if (de->d_type == DT_UNKNOWN ||
-                    (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
-                        r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags);
+                        r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
                         if (r < 0) {
-                                if (ret == 0 && r != -ENOENT)
-                                        ret = r;
-                                continue;
-                        }
+                                if (!IN_SET(r, -ENOTTY, -EINVAL))
+                                        return r;
 
-                        is_dir = S_ISDIR(st.st_mode);
-                } else
-                        is_dir = de->d_type == DT_DIR;
+                                /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
+                        } else
+                                /* It was a subvolume, done. */
+                                return 1;
+                }
 
-                if (is_dir) {
-                        _cleanup_close_ int subdir_fd = -1;
+                subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+                if (subdir_fd < 0)
+                        return -errno;
 
-                        /* if root_dev is set, remove subdirectories only if device is same */
-                        if (root_dev && st.st_dev != root_dev->st_dev)
-                                continue;
+                /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
+                 * again for each directory */
+                q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
 
-                        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 = unlinkat_harder(fd, fname, AT_REMOVEDIR, flags);
+                if (r < 0)
+                        return r;
+                if (q < 0)
+                        return q;
 
-                        /* Stop at mount points */
-                        r = fd_is_mount_point(fd, de->d_name, 0);
-                        if (r < 0) {
-                                if (ret == 0 && r != -ENOENT)
-                                        ret = r;
+                return 1;
 
-                                continue;
-                        }
-                        if (r > 0)
-                                continue;
+        } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+                r = unlinkat_harder(fd, fname, 0, flags);
+                if (r < 0)
+                        return r;
 
-                        if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
+                return 1;
+        }
 
-                                /* This could be a subvolume, try to remove it */
+        return 0;
+}
 
-                                r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
-                                if (r < 0) {
-                                        if (!IN_SET(r, -ENOTTY, -EINVAL)) {
-                                                if (ret == 0)
-                                                        ret = r;
+int rm_rf_children(
+                int fd,
+                RemoveFlags flags,
+                const struct stat *root_dev) {
 
-                                                continue;
-                                        }
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        int ret = 0, r;
 
-                                        /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
-                                } else
-                                        /* It was a subvolume, continue. */
-                                        continue;
-                        }
+        assert(fd >= 0);
+
+        /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
+         * fd, in all cases, including on failure. */
+
+        d = fdopendir(fd);
+        if (!d) {
+                safe_close(fd);
+                return -errno;
+        }
 
-                        /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
-                         * system type again for each directory */
-                        r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
-                        if (r < 0 && ret == 0)
-                                ret = r;
+        if (!(flags & REMOVE_PHYSICAL)) {
+                struct statfs sfs;
 
-                        r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags);
-                        if (r < 0 && r != -ENOENT && ret == 0)
-                                ret = r;
+                if (fstatfs(dirfd(d), &sfs) < 0)
+                        return -errno;
+
+                if (is_physical_fs(&sfs)) {
+                        /* 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. */
 
-                } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+                        _cleanup_free_ char *path = NULL;
 
-                        r = unlinkat_harder(fd, de->d_name, 0, flags);
-                        if (r < 0 && r != -ENOENT && ret == 0)
-                                ret = r;
+                        (void) fd_get_path(fd, &path);
+                        return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+                                               "Attempted to remove disk file system under \"%s\", and we can't allow that.",
+                                               strna(path));
                 }
         }
+
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
+                int is_dir;
+
+                if (dot_or_dot_dot(de->d_name))
+                        continue;
+
+                is_dir =
+                        de->d_type == DT_UNKNOWN ? -1 :
+                        de->d_type == DT_DIR;
+
+                r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev);
+                if (r < 0 && r != -ENOENT && ret == 0)
+                        ret = r;
+        }
+
         return ret;
 }
 
 int rm_rf(const char *path, RemoveFlags flags) {
         int fd, r;
-        struct statfs s;
 
         assert(path);
 
@@ -284,9 +299,10 @@ int rm_rf(const char *path, RemoveFlags flags) {
                 if (FLAGS_SET(flags, REMOVE_ROOT)) {
 
                         if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
+                                struct statfs s;
+
                                 if (statfs(path, &s) < 0)
                                         return -errno;
-
                                 if (is_physical_fs(&s))
                                         return log_error_errno(SYNTHETIC_ERRNO(EPERM),
                                                                "Attempted to remove files from a disk file system under \"%s\", refusing.",
@@ -314,3 +330,22 @@ int rm_rf(const char *path, RemoveFlags flags) {
 
         return r;
 }
+
+int rm_rf_child(int fd, const char *name, RemoveFlags flags) {
+
+        /* Removes one specific child of the specified directory */
+
+        if (fd < 0)
+                return -EBADF;
+
+        if (!filename_is_valid(name))
+                return -EINVAL;
+
+        if ((flags & (REMOVE_ROOT|REMOVE_MISSING_OK)) != 0) /* Doesn't really make sense here, we are not supposed to remove 'fd' anyway */
+                return -EINVAL;
+
+        if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
+                return -EINVAL;
+
+        return rm_rf_children_inner(fd, name, -1, flags, NULL);
+}
diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h
index 40f0894c96..577a2795e0 100644
--- a/src/shared/rm-rf.h
+++ b/src/shared/rm-rf.h
@@ -23,7 +23,8 @@ int fstatat_harder(int dfd,
                 int fstatat_flags,
                 RemoveFlags remove_flags);
 
-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
+int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev);
+int rm_rf_child(int fd, const char *name, RemoveFlags flags);
 int rm_rf(const char *path, RemoveFlags flags);
 
 /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */
-- 
2.27.0

