rm-rf: fstatat() might fail if containing dir has limited access mode, patch that too
This commit is contained in:
committed by
Yu Watanabe
parent
1071c195f7
commit
1b55621dab
+67
-17
@@ -23,23 +23,14 @@ static bool is_physical_fs(const struct statfs *sfs) {
|
||||
return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
|
||||
}
|
||||
|
||||
static int unlinkat_harder(
|
||||
static int patch_dirfd_mode(
|
||||
int dfd,
|
||||
const char *filename,
|
||||
int unlink_flags,
|
||||
RemoveFlags remove_flags) {
|
||||
mode_t *ret_old_mode) {
|
||||
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
/* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
|
||||
* directory. This is useful if we run unprivileged and have some files where the w bit is
|
||||
* missing. */
|
||||
|
||||
if (unlinkat(dfd, filename, unlink_flags) >= 0)
|
||||
return 0;
|
||||
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
|
||||
return -errno;
|
||||
assert(dfd >= 0);
|
||||
assert(ret_old_mode);
|
||||
|
||||
if (fstat(dfd, &st) < 0)
|
||||
return -errno;
|
||||
@@ -53,10 +44,68 @@ static int unlinkat_harder(
|
||||
if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
|
||||
return -errno;
|
||||
|
||||
*ret_old_mode = st.st_mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unlinkat_harder(
|
||||
int dfd,
|
||||
const char *filename,
|
||||
int unlink_flags,
|
||||
RemoveFlags remove_flags) {
|
||||
|
||||
mode_t old_mode;
|
||||
int r;
|
||||
|
||||
/* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
|
||||
* directory. This is useful if we run unprivileged and have some files where the w bit is
|
||||
* missing. */
|
||||
|
||||
if (unlinkat(dfd, filename, unlink_flags) >= 0)
|
||||
return 0;
|
||||
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
|
||||
return -errno;
|
||||
|
||||
r = patch_dirfd_mode(dfd, &old_mode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (unlinkat(dfd, filename, unlink_flags) < 0) {
|
||||
r = -errno;
|
||||
/* Try to restore the original access mode if this didn't work */
|
||||
(void) fchmod(dfd, st.st_mode & 07777);
|
||||
(void) fchmod(dfd, old_mode);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* If this worked, we won't reset the old mode, since we'll need it for other entries too, and we
|
||||
* should destroy the whole thing */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fstatat_harder(
|
||||
int dfd,
|
||||
const char *filename,
|
||||
struct stat *ret,
|
||||
int fstatat_flags,
|
||||
RemoveFlags remove_flags) {
|
||||
|
||||
mode_t old_mode;
|
||||
int r;
|
||||
|
||||
/* Like unlink_harder() but does the same for fstatat() */
|
||||
|
||||
if (fstatat(dfd, filename, ret, fstatat_flags) >= 0)
|
||||
return 0;
|
||||
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
|
||||
return -errno;
|
||||
|
||||
r = patch_dirfd_mode(dfd, &old_mode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
|
||||
r = -errno;
|
||||
(void) fchmod(dfd, old_mode);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -112,9 +161,10 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
|
||||
|
||||
if (de->d_type == DT_UNKNOWN ||
|
||||
(de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
|
||||
if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
|
||||
if (ret == 0 && errno != ENOENT)
|
||||
ret = -errno;
|
||||
r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags);
|
||||
if (r < 0) {
|
||||
if (ret == 0 && r != -ENOENT)
|
||||
ret = r;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user