chase: Add glnx_chase_and_mkdirat

We found that there is a common use case where we need to get a
subdirectory (potentially multiple levels) which might not exist yet.
Adding another flag for this to GlnxChaseFlags is what systemd has done,
but creating a directory takes a mode, so the flag creates directories
with a fixed mode. This approach instead takes the mode as argument.
This commit is contained in:
Sebastian Wick
2026-06-12 13:45:22 +02:00
parent 4f8674709d
commit ae7355612a
3 changed files with 162 additions and 0 deletions

View File

@@ -17,6 +17,7 @@
#include <unistd.h>
#include <glnx-backports.h>
#include <glnx-dirfd.h>
#include <glnx-errors.h>
#include <glnx-fdio.h>
#include <glnx-local-alloc.h>
@@ -823,3 +824,89 @@ glnx_chase_and_statxat (int dirfd,
return g_steal_fd (&fd);
}
static int
chase_and_mkdir (int next_fd,
int current_fd,
const char *segment,
G_GNUC_UNUSED int open_tree_flags,
gpointer user_data,
GError **error)
{
mode_t mode = GPOINTER_TO_INT (user_data);
glnx_autofd int new_fd = -1;
/* if chase managed to get the next segment, we already got our answer */
if (next_fd >= 0)
return next_fd;
/* if the problem isn't that the file doesn't exist, we propagate the error */
g_assert (*error != NULL);
if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
return -1;
g_clear_error (error);
/* create the directory with the specified mode */
if (!glnx_ensure_dir (current_fd, segment, mode, error))
return -1;
/* Get a fd to the created dir.
* This is racy, meaning another process can modify the filesystem and we
* might open a directory that we did not create and might have a different
* mode. There isn't a kernel API which returns a fd or an inode which makes
* this race unavoidable. This is fine though because the semantics of
* `glnx_chase_and_mkdirat` accept arbitrary directories (even with different
* modes) when chasing the path.
*/
new_fd = openat (current_fd, segment,
O_CLOEXEC | O_PATH | O_DIRECTORY | O_NOFOLLOW);
if (new_fd < 0)
return glnx_fd_throw_errno_prefix (error, "opening created dir failed");
return g_steal_fd (&new_fd);
}
/**
* glnx_chase_and_mkdirat:
* @dirfd: a directory file descriptor
* @path: a path
* @flags: restricted combination of GlnxChaseFlags flags
* @mode: the mode for new directories
* @error: a #GError
*
* Same as glnx_chase with `GLNX_CHASE_MUST_BE_DIRECTORY`, but when a path
* segment does not exist, a directory is created for the segment with the mode
* @mode.
* This essentially implement a fd-relative equivalent of g_mkdir_with_parents()
* or `mkdir -p`.
* Note, that directories in the path which already exist can have arbitrary
* modes.
*
* See glnx_chaseat for the meaning of @dirfd and @path.
*
* The @flags argument is the same as in glnx_chaseat, but setting
* `GLNX_CHASE_MUST_BE_REGULAR`, `GLNX_CHASE_MUST_BE_SOCKET`, or
* `GLNX_CHASE_MUST_BE_DIRECTORY` is an error.
*
* Returns: the chased file, or -1 with @error set on error
*/
int
glnx_chase_and_mkdirat (int dirfd,
const char *path,
GlnxChaseFlags flags,
mode_t mode,
GError **error)
{
g_return_val_if_fail ((flags & ~(GLNX_CHASE_ALL_FLAGS)) == 0, -1);
g_return_val_if_fail ((flags & (GLNX_CHASE_MUST_BE_REGULAR |
GLNX_CHASE_MUST_BE_SOCKET |
GLNX_CHASE_MUST_BE_DIRECTORY)) == 0, -1);
g_return_val_if_fail ((mode & ~((mode_t) 07777)) == 0, -1);
/* This function always implies GLNX_CHASE_MUST_BE_DIRECTORY */
flags |= GLNX_CHASE_MUST_BE_DIRECTORY;
return glnx_chaseat_full (dirfd, path, flags,
chase_and_mkdir, GINT_TO_POINTER (mode),
error);
}

View File

@@ -50,4 +50,10 @@ int glnx_chase_and_statxat (int dirfd,
struct glnx_statx *statbuf,
GError **error);
int glnx_chase_and_mkdirat (int dirfd,
const char *path,
GlnxChaseFlags flags,
mode_t mode,
GError **error);
G_END_DECLS

View File

@@ -50,6 +50,18 @@ path_get_ino (const char *path)
return st.st_ino;
}
static mode_t
get_mode (int fd)
{
int r;
struct stat st;
r = fstatat (fd, "", &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
g_assert_cmpint (r, >=, 0);
return st.st_mode & 0777;
}
static char *
get_abspath (int dfd,
const char *path)
@@ -586,6 +598,62 @@ test_chase_and_statxat_permissions (void)
g_clear_fd (&chase_fd, NULL);
}
static void
test_chase_and_mkdir (void)
{
g_autoptr(GError) error = NULL;
glnx_autofd int dfd = -1;
glnx_autofd int file_fd = -1;
glnx_autofd int chase_fd = -1;
ino_t expected_ino;
g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "d1", 0755,
&dfd,
NULL, &error));
g_assert_no_error (error);
chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d2/d3",
GLNX_CHASE_DEFAULT,
0755,
&error);
g_assert_cmpint (chase_fd, >=, 0);
g_assert_no_error (error);
file_fd = openat (AT_FDCWD, "d1/d2/d3", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
expected_ino = get_ino (file_fd);
g_assert_cmpint (get_ino (chase_fd), ==, expected_ino);
g_assert_cmpint (get_mode (dfd), ==, 0755);
g_assert_cmpint (get_mode (chase_fd), ==, 0755);
g_clear_fd (&chase_fd, NULL);
g_clear_fd (&file_fd, NULL);
chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d4/d5",
GLNX_CHASE_DEFAULT,
0700,
&error);
g_assert_cmpint (chase_fd, >=, 0);
g_assert_no_error (error);
file_fd = openat (AT_FDCWD, "d1", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0755);
g_clear_fd (&file_fd, NULL);
file_fd = openat (AT_FDCWD, "d1/d4", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0700);
g_clear_fd (&file_fd, NULL);
file_fd = openat (AT_FDCWD, "d1/d4/d5", O_PATH | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint (file_fd, >=, 0);
g_assert_cmpint (get_mode (file_fd), ==, 0700);
g_clear_fd (&file_fd, NULL);
}
int main (int argc, char **argv)
{
_GLNX_TEST_SCOPED_TEMP_DIR;
@@ -602,6 +670,7 @@ int main (int argc, char **argv)
g_test_add_func ("/chase-and-statxat-basic", test_chase_and_statxat_basic);
g_test_add_func ("/chase-and-statxat-symlink", test_chase_and_statxat_symlink);
g_test_add_func ("/chase-and-statxat-permissions", test_chase_and_statxat_permissions);
g_test_add_func ("/chase-and-mkdir", test_chase_and_mkdir);
ret = g_test_run();