]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
chase: allow using chase() as mkdir_p() replacement
authorLennart Poettering <lennart@poettering.net>
Mon, 13 Jan 2025 11:53:54 +0000 (12:53 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 20 Jan 2025 10:31:55 +0000 (11:31 +0100)
This allows using CHASE_MKDIR_0755 without CHASE_NONEXISTENT or
CHASE_PARENT, so that it will create the final component of the path
too should it be missing.

This is really useful as a mkdir_p() replacement that returns an fd to
the final component, and knows how to operate relative to a root fs.

Kinda reverts 4ea0bcb9229fe12e0c428659d76934351b821872 (which only
refused the flags combination which didn't work, instead of making it
work, which is what this commit does.)

This also corrects behaviour if CHASE_MKDIR_0755 is used in one more
way: we'll now always open the dir as O_PATH. This is generally the
better idea, but matters in particular once with allow using
CHASE_MKDIR_0755 to create the final component: we should uniformly
return an O_PATH dir that must be converted to a proper fd first before
using it.

src/basic/chase.c
src/basic/chase.h
src/test/test-chase.c

index 43fad0d93ff5a1c389265ff6ed59cad6d71e5ff4..b1e27b48c131753cf0c5245e94d59370d54616dc 100644 (file)
@@ -91,7 +91,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
         assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
         assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME));
         assert(!FLAGS_SET(flags, CHASE_TRAIL_SLASH|CHASE_EXTRACT_FILENAME));
-        assert(!FLAGS_SET(flags, CHASE_MKDIR_0755) || (flags & (CHASE_NONEXISTENT | CHASE_PARENT)) != 0);
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
         /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
@@ -370,13 +369,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
                         if (r != -ENOENT)
                                 return r;
 
-                        if (!isempty(todo) && !path_is_safe(todo))
+                        if (!isempty(todo) && !path_is_safe(todo)) /* Refuse parent/mkdir handling if suffix contains ".." or something weird */
                                 return r;
 
-                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
+                        if (FLAGS_SET(flags, CHASE_MKDIR_0755) && (!isempty(todo) || !(flags & (CHASE_PARENT|CHASE_NONEXISTENT)))) {
                                 child = xopenat_full(fd,
                                                      first,
-                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,
+                                                     O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_PATH|O_CLOEXEC,
                                                      /* xopen_flags = */ 0,
                                                      0755);
                                 if (child < 0)
index cfc714b9f774cc570cf0c5d8323c6eb811ba13e9..cedd7097fb893ae66a7b79502fb2a6bedf6d0e2b 100644 (file)
@@ -27,11 +27,7 @@ typedef enum ChaseFlags {
                                              * also points to the result path even if this flag is set.
                                              * When this specified, chase() will succeed with 1 even if the
                                              * file points to the last path component does not exist. */
-        CHASE_MKDIR_0755         = 1 << 11, /* Create any missing parent directories in the given path. This
-                                             * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT.
-                                             * Note, chase_and_open() or friends always add CHASE_PARENT flag
-                                             * when internally call chase(), hence CHASE_MKDIR_0755 can be
-                                             * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */
+        CHASE_MKDIR_0755         = 1 << 11, /* Create any missing directories in the given path. */
         CHASE_EXTRACT_FILENAME   = 1 << 12, /* Only return the last component of the resolved path */
 } ChaseFlags;
 
index c7ca3fd05170f412d5d320697475bf24dd88f81f..510264c547e461517b86ffad18acdb208c06b01c 100644 (file)
@@ -10,6 +10,7 @@
 #include "id128-util.h"
 #include "mkdir.h"
 #include "path-util.h"
+#include "random-util.h"
 #include "rm-rf.h"
 #include "string-util.h"
 #include "tests.h"
@@ -754,6 +755,34 @@ TEST(trailing_dot_dot) {
         assert_se(path_equal(fdpath, expected2));
 }
 
+TEST(use_chase_as_mkdir_p) {
+        _cleanup_free_ char *p = NULL;
+        ASSERT_OK_ERRNO(asprintf(&p, "/tmp/chasemkdir%" PRIu64 "/a/b/c", random_u64()));
+
+        _cleanup_close_ int fd = -EBADF;
+        ASSERT_OK(chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755, NULL, &fd));
+
+        ASSERT_OK_EQ(inode_same_at(AT_FDCWD, p, fd, NULL, AT_EMPTY_PATH), 1);
+
+        _cleanup_close_ int fd2 = -EBADF;
+        ASSERT_OK(chase(p, p, CHASE_PREFIX_ROOT|CHASE_MKDIR_0755, NULL, &fd2));
+
+        _cleanup_free_ char *pp = ASSERT_PTR(path_join(p, p));
+
+        ASSERT_OK_EQ(inode_same_at(AT_FDCWD, pp, fd2, NULL, AT_EMPTY_PATH), 1);
+
+        _cleanup_free_ char *f = NULL;
+        ASSERT_OK(path_extract_directory(p, &f));
+
+        _cleanup_free_ char *ff = NULL;
+        ASSERT_OK(path_extract_directory(f, &ff));
+
+        _cleanup_free_ char *fff = NULL;
+        ASSERT_OK(path_extract_directory(ff, &fff));
+
+        ASSERT_OK(rm_rf(fff, REMOVE_PHYSICAL));
+}
+
 static int intro(void) {
         arg_test_dir = saved_argv[1];
         return EXIT_SUCCESS;