diff options
author | Lennart Poettering <lennart@poettering.net> | 2021-11-18 10:13:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-18 10:13:26 +0100 |
commit | 3510cef8fa0211f74ebe15fbab1be55db11432e0 (patch) | |
tree | ebde1a4d4da20d6c24bee56b047ee9a03f65ed42 /src | |
parent | meson: add check:true/false to all run_command() invocations (diff) | |
parent | tree-wide: port various places over to open_mkdir_at() (diff) | |
download | systemd-3510cef8fa0211f74ebe15fbab1be55db11432e0.tar.xz systemd-3510cef8fa0211f74ebe15fbab1be55db11432e0.zip |
Merge pull request #21401 from poettering/open-mkdir-at
add open_mkdir_at() helper and use it
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/fs-util.c | 74 | ||||
-rw-r--r-- | src/basic/fs-util.h | 2 | ||||
-rw-r--r-- | src/core/namespace.c | 17 | ||||
-rw-r--r-- | src/home/homework-cifs.c | 14 | ||||
-rw-r--r-- | src/shared/copy.c | 14 | ||||
-rw-r--r-- | src/shared/creds-util.c | 6 | ||||
-rw-r--r-- | src/test/test-fs-util.c | 44 |
7 files changed, 144 insertions, 27 deletions
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index b9ea654e7a..a135632cee 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1011,3 +1011,77 @@ int parse_cifs_service( return 0; } + +int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { + _cleanup_close_ int fd = -1, parent_fd = -1; + _cleanup_free_ char *fname = NULL; + bool made; + int r; + + /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can + * do. Guarantees that the returned fd refers to a directory. If O_EXCL is specified will fail if the + * dir already exists. Otherwise will open an existing dir, but only if it is one. */ + + if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH)) + return -EINVAL; + if ((flags & O_ACCMODE) != O_RDONLY) + return -EINVAL; + + /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following + * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */ + + if (isempty(path)) + return -EINVAL; + + if (!filename_is_valid(path)) { + _cleanup_free_ char *parent = NULL; + + /* If this is not a valid filename, it's a path. Let's open the parent directory then, so + * that we can pin it, and operate below it. */ + + r = path_extract_directory(path, &parent); + if (r < 0) + return r; + + r = path_extract_filename(path, &fname); + if (r < 0) + return r; + + parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (parent_fd < 0) + return -errno; + + dirfd = parent_fd; + path = fname; + } + + r = RET_NERRNO(mkdirat(dirfd, path, mode)); + if (r == -EEXIST) { + if (FLAGS_SET(flags, O_EXCL)) + return -EEXIST; + + made = false; + } else if (r < 0) + return r; + else + made = true; + + fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW)); + if (fd < 0) { + if (fd == -ENOENT) /* We got ENOENT? then someone else immediately removed it after we + * created it. In that case let's return immediately without unlinking + * anything, because there simply isn't anything to unlink anymore. */ + return -ENOENT; + if (fd == -ELOOP) /* is a symlink? exists already → created by someone else, don't unlink */ + return -EEXIST; + if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */ + return -EEXIST; + + if (made) + (void) unlinkat(dirfd, path, AT_REMOVEDIR); + + return fd; + } + + return TAKE_FD(fd); +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 4cf4cabdd0..0bbb3f6298 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -108,3 +108,5 @@ static inline int conservative_rename(const char *oldpath, const char *newpath) int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); + +int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); diff --git a/src/core/namespace.c b/src/core/namespace.c index a84060c682..c8e7e65e27 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2499,6 +2499,7 @@ int temporary_filesystem_add( static int make_tmp_prefix(const char *prefix) { _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -1; int r; /* Don't do anything unless we know the dir is actually missing */ @@ -2517,18 +2518,20 @@ static int make_tmp_prefix(const char *prefix) { if (r < 0) return r; - if (mkdir(t, 0777) < 0) /* umask will corrupt this access mode, but that doesn't matter, we need to - * call chmod() anyway for the suid bit, below. */ - return -errno; + /* umask will corrupt this access mode, but that doesn't matter, we need to call chmod() anyway for + * the suid bit, below. */ + fd = open_mkdir_at(AT_FDCWD, t, O_EXCL|O_CLOEXEC, 0777); + if (fd < 0) + return fd; - if (chmod(t, 01777) < 0) { - r = -errno; + r = RET_NERRNO(fchmod(fd, 01777)); + if (r < 0) { (void) rmdir(t); return r; } - if (rename(t, prefix) < 0) { - r = -errno; + r = RET_NERRNO(rename(t, prefix)); + if (r < 0) { (void) rmdir(t); return r == -EEXIST ? 0 : r; /* it's fine if someone else created the dir by now */ } diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index c76d6a6b13..b49b7b3dcd 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -127,15 +127,17 @@ int home_setup_cifs( return log_oom(); if (FLAGS_SET(flags, HOME_SETUP_CIFS_MKDIR)) { - r = mkdir_p(j, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create CIFS subdirectory: %m"); + setup->root_fd = open_mkdir_at(AT_FDCWD, j, O_CLOEXEC, 0700); + if (setup->root_fd < 0) + return log_error_errno(setup->root_fd, "Failed to create CIFS subdirectory: %m"); } } - setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); - if (setup->root_fd < 0) - return log_error_errno(errno, "Failed to open home directory: %m"); + if (setup->root_fd < 0) { + setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + if (setup->root_fd < 0) + return log_error_errno(errno, "Failed to open home directory: %m"); + } setup->mount_suffix = TAKE_PTR(cdir); return 0; diff --git a/src/shared/copy.c b/src/shared/copy.c index fd83d74265..bb3ac8a3f8 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -485,8 +485,6 @@ static int hardlink_context_setup( } static int hardlink_context_realize(HardlinkContext *c) { - int r; - if (!c) return 0; @@ -498,15 +496,9 @@ static int hardlink_context_realize(HardlinkContext *c) { assert(c->subdir); - if (mkdirat(c->parent_fd, c->subdir, 0700) < 0) - return -errno; - - c->dir_fd = openat(c->parent_fd, c->subdir, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (c->dir_fd < 0) { - r = -errno; - (void) unlinkat(c->parent_fd, c->subdir, AT_REMOVEDIR); - return r; - } + c->dir_fd = open_mkdir_at(c->parent_fd, c->subdir, O_EXCL|O_CLOEXEC, 0700); + if (c->dir_fd < 0) + return c->dir_fd; return 1; } diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index b764198b76..0c8181bce2 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -215,10 +215,10 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * fn = "credential.secret"; } - (void) mkdir_p(p, 0755); - dfd = open(p, O_CLOEXEC|O_DIRECTORY|O_RDONLY); + mkdir_parents(p, 0755); + dfd = open_mkdir_at(AT_FDCWD, p, O_CLOEXEC, 0755); if (dfd < 0) - return -errno; + return dfd; if (FLAGS_SET(flags, CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS)) { r = fd_is_temporary_fs(dfd); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index d8273bc846..0a36d676af 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -952,6 +952,49 @@ static void test_parse_cifs_service(void) { test_parse_cifs_service_one("//./a", NULL, NULL, NULL, -EINVAL); } +static void test_open_mkdir_at(void) { + _cleanup_close_ int fd = -1, subdir_fd = -1, subsubdir_fd = -1; + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + log_info("/* %s */", __func__); + + assert_se(open_mkdir_at(AT_FDCWD, "/proc", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + fd = open_mkdir_at(AT_FDCWD, "/proc", O_CLOEXEC, 0); + assert_se(fd >= 0); + fd = safe_close(fd); + + assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_CLOEXEC, 0) == -EEXIST); + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + assert_se(open_mkdir_at(AT_FDCWD, t, O_EXCL|O_CLOEXEC, 0) == -EEXIST); + assert_se(open_mkdir_at(AT_FDCWD, t, O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + fd = open_mkdir_at(AT_FDCWD, t, O_CLOEXEC, 0000); + assert_se(fd >= 0); + fd = safe_close(fd); + + fd = open_mkdir_at(AT_FDCWD, t, O_PATH|O_CLOEXEC, 0000); + assert_se(fd >= 0); + + subdir_fd = open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0700); + assert_se(subdir_fd >= 0); + + assert_se(open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + subsubdir_fd = open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0700); + assert_se(subsubdir_fd >= 0); + subsubdir_fd = safe_close(subsubdir_fd); + + assert_se(open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + assert_se(open_mkdir_at(fd, "xxx/yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + subsubdir_fd = open_mkdir_at(fd, "xxx/yyy", O_CLOEXEC, 0700); + assert_se(subsubdir_fd >= 0); +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_INFO); @@ -972,6 +1015,7 @@ int main(int argc, char *argv[]) { test_conservative_rename(); test_rmdir_parents(); test_parse_cifs_service(); + test_open_mkdir_at(); return 0; } |