From: Daan De Meyer Date: Tue, 14 Mar 2023 08:55:04 +0000 (+0100) Subject: fs-util: Add xopenat() X-Git-Tag: v254-rc1~1014^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7486f9c3421a5cd011c72a8c8d9c8b1cf62410ca;p=thirdparty%2Fsystemd.git fs-util: Add xopenat() xopenat() will create directories if O_DIRECTORY and O_CREAT are specified. Note that this is not an atomic operation. --- diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a895f4f2df2..5007a829ea9 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -993,7 +993,6 @@ int parse_cifs_service( int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF; _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 @@ -1033,33 +1032,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { 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); - + fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, mode); + if (IN_SET(fd, -ELOOP, -ENOTDIR)) + return -EEXIST; + if (fd < 0) return fd; - } return TAKE_FD(fd); } @@ -1110,3 +1087,45 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b return -EEXIST; } } + +int xopenat(int dir_fd, const char *path, int flags, mode_t mode) { + _cleanup_close_ int fd = -EBADF; + bool made = false; + int r; + + if (FLAGS_SET(flags, O_DIRECTORY|O_CREAT)) { + r = RET_NERRNO(mkdirat(dir_fd, path, mode)); + if (r == -EEXIST) { + if (FLAGS_SET(flags, O_EXCL)) + return -EEXIST; + + made = false; + } else if (r < 0) + return r; + else + made = true; + + flags &= ~(O_EXCL|O_CREAT); + } + + fd = RET_NERRNO(openat(dir_fd, path, flags, mode)); + if (fd < 0) { + if (IN_SET(fd, + /* 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. */ + -ENOENT, + /* is a symlink? exists already → created by someone else, don't unlink */ + -ELOOP, + /* not a directory? exists already → created by someone else, don't unlink */ + -ENOTDIR)) + return fd; + + if (made) + (void) unlinkat(dir_fd, path, AT_REMOVEDIR); + + return fd; + } + + return TAKE_FD(fd); +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 932d003f199..3db9df22243 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -129,3 +129,5 @@ int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); + +int xopenat(int dir_fd, const char *path, int flags, mode_t mode); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index c009424a2bc..065ad3c6e7f 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -1155,6 +1155,31 @@ TEST(openat_report_new) { assert_se(b); } +TEST(xopenat) { + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Test that xopenat() creates directories if O_DIRECTORY is specified. */ + + assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0755)) >= 0); + assert_se((fd_verify_directory(fd) >= 0)); + fd = safe_close(fd); + + assert_se(xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0755) == -EEXIST); + + assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_CLOEXEC, 0755)) >= 0); + assert_se((fd_verify_directory(fd) >= 0)); + fd = safe_close(fd); + + /* Test that xopenat() creates regular files if O_DIRECTORY is not specified. */ + + assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0644)) >= 0); + assert_se(fd_verify_regular(fd) >= 0); + fd = safe_close(fd); +} + static int intro(void) { arg_test_dir = saved_argv[1]; return EXIT_SUCCESS;