summaryrefslogtreecommitdiffstats
path: root/src/shared/rm-rf.c
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2023-03-18 18:34:22 +0100
committerYu Watanabe <watanabe.yu+github@gmail.com>2023-03-31 04:52:03 +0200
commit7be9657706073fb91e969c4cbb2905dbbce054bb (patch)
treeea681c13e7c96bed6a79d13d30338436063c911c /src/shared/rm-rf.c
parentcore: always calculate the next restart interval (diff)
downloadsystemd-7be9657706073fb91e969c4cbb2905dbbce054bb.tar.xz
systemd-7be9657706073fb91e969c4cbb2905dbbce054bb.zip
rm-rf: also chmod() directory if it cannot be opened
Otherwise, directory with zero access mode cannot be removed. This is a revised version of 808c8b25eece33c503430151641f5f77676af38c, - dropped O_NOFOLLOW from fd_reopen() - fixed error handling on opening path in rm_rf().
Diffstat (limited to 'src/shared/rm-rf.c')
-rw-r--r--src/shared/rm-rf.c156
1 files changed, 135 insertions, 21 deletions
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 55858c7ed1..e99c321418 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -11,6 +11,7 @@
#include "cgroup-util.h"
#include "dirent-util.h"
#include "fd-util.h"
+#include "fs-util.h"
#include "log.h"
#include "macro.h"
#include "mountpoint-util.h"
@@ -28,9 +29,11 @@ static bool is_physical_fs(const struct statfs *sfs) {
static int patch_dirfd_mode(
int dfd,
+ bool refuse_already_set,
mode_t *ret_old_mode) {
struct stat st;
+ int r;
assert(dfd >= 0);
assert(ret_old_mode);
@@ -39,16 +42,24 @@ static int patch_dirfd_mode(
return -errno;
if (!S_ISDIR(st.st_mode))
return -ENOTDIR;
- if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
- return -EACCES; /* original error */
+
+ if (FLAGS_SET(st.st_mode, 0700)) { /* Already set? */
+ if (refuse_already_set)
+ return -EACCES; /* original error */
+
+ *ret_old_mode = st.st_mode;
+ return 0;
+ }
+
if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
return -EACCES;
- if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
- return -errno;
+ r = fchmod_opath(dfd, (st.st_mode | 0700) & 07777);
+ if (r < 0)
+ return r;
*ret_old_mode = st.st_mode;
- return 0;
+ return 1;
}
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
@@ -64,7 +75,7 @@ int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
return -errno;
- r = patch_dirfd_mode(dfd, &old_mode);
+ r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
if (r < 0)
return r;
@@ -99,7 +110,7 @@ int fstatat_harder(int dfd,
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
return -errno;
- r = patch_dirfd_mode(dfd, &old_mode);
+ r = patch_dirfd_mode(dfd, /* refuse_already_set = */ true, &old_mode);
if (r < 0)
return r;
@@ -115,6 +126,68 @@ int fstatat_harder(int dfd,
return 0;
}
+static int openat_harder(int dfd, const char *path, int open_flags, RemoveFlags remove_flags, mode_t *ret_old_mode) {
+ _cleanup_close_ int pfd = -EBADF, fd = -EBADF;
+ bool chmod_done = false;
+ mode_t old_mode;
+ int r;
+
+ assert(dfd >= 0 || dfd == AT_FDCWD);
+ assert(path);
+
+ /* Unlike unlink_harder() and fstatat_harder(), this chmod the specified path. */
+
+ if (FLAGS_SET(open_flags, O_PATH) ||
+ !FLAGS_SET(open_flags, O_DIRECTORY) ||
+ !FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+
+ fd = RET_NERRNO(openat(dfd, path, open_flags));
+ if (fd < 0)
+ return fd;
+
+ if (ret_old_mode) {
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ *ret_old_mode = st.st_mode;
+ }
+
+ return TAKE_FD(fd);
+ }
+
+ pfd = RET_NERRNO(openat(dfd, path, (open_flags & (O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW)) | O_PATH));
+ if (pfd < 0)
+ return pfd;
+
+ if (FLAGS_SET(remove_flags, REMOVE_CHMOD)) {
+ r = patch_dirfd_mode(pfd, /* refuse_already_set = */ false, &old_mode);
+ if (r < 0)
+ return r;
+
+ chmod_done = r;
+ }
+
+ fd = fd_reopen(pfd, open_flags & ~O_NOFOLLOW);
+ if (fd < 0) {
+ if (chmod_done)
+ (void) fchmod_opath(pfd, old_mode & 07777);
+ return fd;
+ }
+
+ if (ret_old_mode)
+ *ret_old_mode = old_mode;
+
+ return TAKE_FD(fd);
+}
+
+static int rm_rf_children_impl(
+ int fd,
+ RemoveFlags flags,
+ const struct stat *root_dev,
+ mode_t old_mode);
+
static int rm_rf_inner_child(
int fd,
const char *fname,
@@ -169,13 +242,16 @@ static int rm_rf_inner_child(
if (!allow_recursion)
return -EISDIR;
- int subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ mode_t old_mode;
+ int subdir_fd = openat_harder(fd, fname,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+ flags, &old_mode);
if (subdir_fd < 0)
- return -errno;
+ return subdir_fd;
/* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
* again for each directory */
- q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
+ q = rm_rf_children_impl(subdir_fd, flags | REMOVE_PHYSICAL, root_dev, old_mode);
} else if (flags & REMOVE_ONLY_DIRECTORIES)
return 0;
@@ -191,6 +267,7 @@ static int rm_rf_inner_child(
typedef struct TodoEntry {
DIR *dir; /* A directory that we were operating on. */
char *dirname; /* The filename of that directory itself. */
+ mode_t old_mode; /* The original file mode. */
} TodoEntry;
static void free_todo_entries(TodoEntry **todos) {
@@ -207,6 +284,22 @@ int rm_rf_children(
RemoveFlags flags,
const struct stat *root_dev) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ return rm_rf_children_impl(fd, flags, root_dev, st.st_mode);
+}
+
+static int rm_rf_children_impl(
+ int fd,
+ RemoveFlags flags,
+ const struct stat *root_dev,
+ mode_t old_mode) {
+
_cleanup_(free_todo_entries) TodoEntry *todos = NULL;
size_t n_todo = 0;
_cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */
@@ -223,14 +316,20 @@ int rm_rf_children(
* We need to remove the inner directory we were operating on. */
assert(dirname);
r = unlinkat_harder(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR, flags);
- if (r < 0 && r != -ENOENT && ret == 0)
- ret = r;
+ if (r < 0 && r != -ENOENT) {
+ if (ret == 0)
+ ret = r;
+
+ if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE))
+ (void) fchmodat(dirfd(todos[n_todo-1].dir), dirname, old_mode & 07777, 0);
+ }
dirname = mfree(dirname);
/* And now let's back out one level up */
n_todo --;
d = TAKE_PTR(todos[n_todo].dir);
dirname = TAKE_PTR(todos[n_todo].dirname);
+ old_mode = todos[n_todo].old_mode;
assert(d);
fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */
@@ -287,12 +386,20 @@ int rm_rf_children(
if (!newdirname)
return log_oom();
- int newfd = RET_NERRNO(openat(fd, de->d_name,
- O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME));
+ mode_t mode;
+ int newfd = openat_harder(fd, de->d_name,
+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME,
+ flags, &mode);
if (newfd >= 0) {
- todos[n_todo++] = (TodoEntry) { TAKE_PTR(d), TAKE_PTR(dirname) };
+ todos[n_todo++] = (TodoEntry) {
+ .dir = TAKE_PTR(d),
+ .dirname = TAKE_PTR(dirname),
+ .old_mode = old_mode
+ };
+
fd = newfd;
dirname = TAKE_PTR(newdirname);
+ old_mode = mode;
goto next_fd;
@@ -306,14 +413,20 @@ int rm_rf_children(
if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
ret = -errno;
- if (n_todo == 0)
+ if (n_todo == 0) {
+ if (FLAGS_SET(flags, REMOVE_CHMOD_RESTORE) &&
+ fchmod(fd, old_mode & 07777) < 0 && ret >= 0)
+ ret = -errno;
+
break;
+ }
}
return ret;
}
int rm_rf(const char *path, RemoveFlags flags) {
+ mode_t old_mode;
int fd, r, q = 0;
assert(path);
@@ -345,19 +458,20 @@ int rm_rf(const char *path, RemoveFlags flags) {
/* Not btrfs or not a subvolume */
}
- fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+ fd = openat_harder(AT_FDCWD, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME, flags, &old_mode);
if (fd >= 0) {
/* We have a dir */
- r = rm_rf_children(fd, flags, NULL);
+ r = rm_rf_children_impl(fd, flags, NULL, old_mode);
if (FLAGS_SET(flags, REMOVE_ROOT))
q = RET_NERRNO(rmdir(path));
} else {
- if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
+ r = fd;
+ if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
return 0;
- if (!IN_SET(errno, ENOTDIR, ELOOP))
- return -errno;
+ if (!IN_SET(r, -ENOTDIR, -ELOOP))
+ return r;
if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT))
return 0;