From: Lennart Poettering Date: Fri, 28 Nov 2025 15:12:22 +0000 (+0100) Subject: recursedir: introduce new RECURSE_DIR_UNLINK callback return value X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b6aa78f707a7957237d9a479fca615cad0545cbd;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 9eef4e1d8c4..f7f8e4a7ab6 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #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; - - 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; + 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 = 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; } diff --git a/src/basic/recurse-dir.h b/src/basic/recurse-dir.h index 68abb279f40..af33c239f70 100644 --- a/src/basic/recurse-dir.h +++ b/src/basic/recurse-dir.h @@ -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);