recursedir: introduce new RECURSE_DIR_UNLINK callback return value

This introduces RECURSE_DIR_UNLINK + RECURSE_DIR_UNLINK_GRACEFUL as new
return values for recurse_dir() callbacks. If either is returned the
inode currently processed will be removed and iteration continues with
the next item.

This is useful to have in the recurse_dir() mechanism itself (rather
than implement in the callback itself), due to ordering: we want to pin
the inodes via an fd while calling the callbacks, but we have to close
it before removal of the inodes. By moving this into the recurse_dir()
infra, we can implement this easily.
This commit is contained in:
Lennart Poettering
2025-12-21 07:04:42 +01:00
parent 65d173a02d
commit b6aa78f707
2 changed files with 36 additions and 24 deletions
+34 -24
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "dirent-util.h"
@@ -433,30 +434,25 @@ int recurse_dir(
i,
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
break;
if (r == RECURSE_DIR_SKIP_ENTRY)
continue;
if (r != RECURSE_DIR_CONTINUE)
return r;
if (r == RECURSE_DIR_CONTINUE) {
r = recurse_dir(subdir_fd,
p,
statx_mask,
n_depth_max - 1,
flags & ~RECURSE_DIR_TOPLEVEL, /* we already called the callback for this entry */
func,
userdata);
if (r != 0)
return r;
r = recurse_dir(subdir_fd,
p,
statx_mask,
n_depth_max - 1,
flags & ~RECURSE_DIR_TOPLEVEL, /* we already called the callback for this entry */
func,
userdata);
if (r != 0)
return r;
r = func(RECURSE_DIR_LEAVE,
p,
dir_fd,
subdir_fd,
i,
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
r = func(RECURSE_DIR_LEAVE,
p,
dir_fd,
subdir_fd,
i,
statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
userdata);
}
} else
/* Non-directory inode */
r = func(RECURSE_DIR_ENTRY,
@@ -469,7 +465,21 @@ int recurse_dir(
if (r == RECURSE_DIR_LEAVE_DIRECTORY)
break;
if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
if (IN_SET(r, RECURSE_DIR_UNLINK, RECURSE_DIR_UNLINK_GRACEFUL)) {
int f = subdir_fd >= 0 ? AT_REMOVEDIR : 0;
/* Close inodes before we try to delete them */
subdir_fd = safe_close(subdir_fd);
inode_fd = safe_close(inode_fd);
if (unlinkat(dir_fd, i->d_name, f) < 0) {
if (r != RECURSE_DIR_UNLINK_GRACEFUL)
return -errno;
log_debug_errno(errno, "Unable to remove '%s', ignoring: %m", p);
}
} else if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
return r;
}
+2
View File
@@ -53,6 +53,8 @@ typedef enum RecurseDirEvent {
#define RECURSE_DIR_CONTINUE 0
#define RECURSE_DIR_LEAVE_DIRECTORY INT_MIN
#define RECURSE_DIR_SKIP_ENTRY (INT_MIN+1)
#define RECURSE_DIR_UNLINK (INT_MIN+2)
#define RECURSE_DIR_UNLINK_GRACEFUL (INT_MIN+3)
/* Make sure that the negative errno range and these two special returns don't overlap */
assert_cc(RECURSE_DIR_LEAVE_DIRECTORY < -ERRNO_MAX);