summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2023-03-15 15:51:47 +0100
committerGitHub <noreply@github.com>2023-03-15 15:51:47 +0100
commitf5abe8f95721213edd76c16ed829cc3e37d7bdc6 (patch)
treeb4af5bf9ca547f211ad9d841a37bd0a4d5c88430 /src
parent39eb3ffaaa3cdaf47c39cee11c80500c2fee338f (diff)
parentea8282b6fc90a80f48ed66fead1e662ddb9050b9 (diff)
downloadsystemd-f5abe8f95721213edd76c16ed829cc3e37d7bdc6.tar.gz
Merge pull request #26784 from DaanDeMeyer/chase-fix
Allow creating files and directories with chase_symlinks_open() and further improvements
Diffstat (limited to 'src')
-rw-r--r--src/basic/chase-symlinks.c153
-rw-r--r--src/basic/chase-symlinks.h10
-rw-r--r--src/basic/fs-util.c73
-rw-r--r--src/basic/fs-util.h2
-rw-r--r--src/basic/stat-util.c2
-rw-r--r--src/boot/bootctl-install.c2
-rw-r--r--src/boot/bootctl-status.c2
-rw-r--r--src/core/dbus-manager.c2
-rw-r--r--src/coredump/coredumpctl.c2
-rw-r--r--src/nspawn/nspawn-mount.c2
-rw-r--r--src/partition/repart.c2
-rw-r--r--src/shared/bootspec.c2
-rw-r--r--src/systemctl/systemctl-switch-root.c4
-rw-r--r--src/test/test-fs-util.c89
14 files changed, 209 insertions, 138 deletions
diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c
index f134d36925..5c2b56ea20 100644
--- a/src/basic/chase-symlinks.c
+++ b/src/basic/chase-symlinks.c
@@ -92,26 +92,15 @@ int chase_symlinks_at(
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 */
- if ((flags & CHASE_NONEXISTENT) && ret_fd)
- return -EINVAL;
-
- if ((flags & CHASE_STEP) && ret_fd)
- return -EINVAL;
+ if ((flags & CHASE_NONEXISTENT))
+ assert(!ret_fd);
- if (FLAGS_SET(flags, CHASE_MKDIR_0755|CHASE_NONEXISTENT))
- return -EINVAL;
+ if ((flags & CHASE_STEP))
+ assert(!ret_fd);
if (isempty(path))
path = ".";
- if (flags & CHASE_PARENT) {
- r = path_extract_directory(path, &buffer);
- if (r == -EDESTADDRREQ)
- path = "."; /* If we don't have a parent directory, fall back to the dir_fd directory. */
- else if (r < 0)
- return r;
- }
-
/* This function resolves symlinks of the path relative to the given directory file descriptor. If
* CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks
* are resolved relative to the given directory file descriptor. Otherwise, they are resolved
@@ -292,6 +281,9 @@ int chase_symlinks_at(
previous_stat = st;
}
+ if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
+ break;
+
close_and_replace(fd, fd_parent);
continue;
@@ -306,20 +298,23 @@ int chase_symlinks_at(
if (!isempty(todo) && !path_is_safe(todo))
return r;
- if (flags & CHASE_NONEXISTENT) {
+ if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) {
+ child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755);
+ if (child < 0)
+ return child;
+ } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
+ if (!path_extend(&done, first))
+ return -ENOMEM;
+
+ break;
+ } else if (flags & CHASE_NONEXISTENT) {
if (!path_extend(&done, first, todo))
return -ENOMEM;
exists = false;
break;
- }
-
- if (!(flags & CHASE_MKDIR_0755))
+ } else
return r;
-
- child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755);
- if (child < 0)
- return child;
}
if (fstat(child, &st) < 0)
@@ -394,11 +389,14 @@ int chase_symlinks_at(
if (!path_extend(&done, first))
return -ENOMEM;
+ if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
+ break;
+
/* And iterate again, but go one directory further down. */
close_and_replace(fd, child);
}
- if (flags & (CHASE_PARENT|CHASE_MKDIR_0755)) {
+ if (flags & CHASE_PARENT) {
r = fd_verify_directory(fd);
if (r < 0)
return r;
@@ -522,7 +520,9 @@ int chase_symlinks(
return r;
if (ret_path) {
- char *q = path_join(empty_to_root(root), p);
+ _cleanup_free_ char *q = NULL;
+
+ q = path_join(empty_to_root(root), p);
if (!q)
return -ENOMEM;
@@ -549,28 +549,37 @@ int chase_symlinks_and_open(
char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
- _cleanup_free_ char *p = NULL;
+ _cleanup_free_ char *p = NULL, *fname = NULL;
+ mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
+ const char *q;
int r;
- if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
- return -EINVAL;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
- r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
- if (r < 0)
- return -errno;
-
- return r;
- }
+ return RET_NERRNO(xopenat(AT_FDCWD, path,
+ open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
+ mode));
- r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase_symlinks(path, root, CHASE_PARENT|chase_flags, &p, &path_fd);
if (r < 0)
return r;
assert(path_fd >= 0);
- r = fd_reopen(path_fd, open_flags);
+ assert_se(q = path_startswith(p, empty_to_root(root)));
+ if (isempty(q))
+ q = ".";
+
+ r = path_extract_filename(q, &fname);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ return r;
+
+ if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EADDRNOTAVAIL)
+ r = fd_reopen(path_fd, open_flags);
+ else
+ r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
@@ -592,10 +601,8 @@ int chase_symlinks_and_opendir(
DIR *d;
int r;
- if (!ret_dir)
- return -EINVAL;
- if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
- return -EINVAL;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
+ assert(ret_dir);
if (empty_or_root(root) && !ret_path &&
(chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
@@ -629,21 +636,18 @@ int chase_symlinks_and_stat(
const char *root,
ChaseSymlinksFlags chase_flags,
char **ret_path,
- struct stat *ret_stat,
- int *ret_fd) {
+ struct stat *ret_stat) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
assert(ret_stat);
- if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
- return -EINVAL;
-
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0 && !ret_fd) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
/* Shortcut this call if none of the special features of this call are requested */
if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
@@ -662,8 +666,6 @@ int chase_symlinks_and_stat(
if (ret_path)
*ret_path = TAKE_PTR(p);
- if (ret_fd)
- *ret_fd = TAKE_FD(path_fd);
return 1;
}
@@ -673,20 +675,17 @@ int chase_symlinks_and_access(
const char *root,
ChaseSymlinksFlags chase_flags,
int access_mode,
- char **ret_path,
- int *ret_fd) {
+ char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
_cleanup_free_ char *p = NULL;
int r;
assert(path);
-
- if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
- return -EINVAL;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
if (empty_or_root(root) && !ret_path &&
- (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0 && !ret_fd) {
+ (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) {
/* Shortcut this call if none of the special features of this call are requested */
if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
@@ -706,8 +705,6 @@ int chase_symlinks_and_access(
if (ret_path)
*ret_path = TAKE_PTR(p);
- if (ret_fd)
- *ret_fd = TAKE_FD(path_fd);
return 1;
}
@@ -725,6 +722,7 @@ int chase_symlinks_and_fopen_unlocked(
int mode_flags, r;
assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
assert(open_flags);
assert(ret_file);
@@ -753,31 +751,26 @@ int chase_symlinks_and_unlink(
int unlink_flags,
char **ret_path) {
- _cleanup_free_ char *p = NULL, *rp = NULL, *fname = NULL;
+ _cleanup_free_ char *p = NULL, *fname = NULL;
_cleanup_close_ int fd = -EBADF;
int r;
assert(path);
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT)));
- r = path_extract_filename(path, &fname);
- if (r < 0)
- return r;
-
- fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL);
+ fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p);
if (fd < 0)
return fd;
- if (p) {
- rp = path_join(p, fname);
- if (!rp)
- return -ENOMEM;
- }
+ r = path_extract_filename(p, &fname);
+ if (r < 0)
+ return r;
if (unlinkat(fd, fname, unlink_flags) < 0)
return -errno;
if (ret_path)
- *ret_path = TAKE_PTR(rp);
+ *ret_path = TAKE_PTR(p);
return 0;
}
@@ -790,23 +783,31 @@ int chase_symlinks_at_and_open(
char **ret_path) {
_cleanup_close_ int path_fd = -EBADF;
- _cleanup_free_ char *p = NULL;
+ _cleanup_free_ char *p = NULL, *fname = NULL;
+ mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644;
int r;
- if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
- return -EINVAL;
+ assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)));
if (dir_fd == AT_FDCWD && !ret_path &&
(chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0)
/* Shortcut this call if none of the special features of this call are requested */
- return RET_NERRNO(openat(dir_fd, path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)));
+ return RET_NERRNO(xopenat(dir_fd, path,
+ open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0),
+ mode));
- r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd);
+ r = chase_symlinks_at(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd);
if (r < 0)
return r;
- assert(path_fd >= 0);
- r = fd_reopen(path_fd, open_flags);
+ r = path_extract_filename(p, &fname);
+ if (r < 0 && r != -EDESTADDRREQ)
+ return r;
+
+ if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EDESTADDRREQ)
+ r = fd_reopen(path_fd, open_flags);
+ else
+ r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode);
if (r < 0)
return r;
diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h
index 19acf9e4b5..448471fa6d 100644
--- a/src/basic/chase-symlinks.h
+++ b/src/basic/chase-symlinks.h
@@ -20,8 +20,10 @@ typedef enum ChaseSymlinksFlags {
CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved
* relative to the given directory fd instead of root. */
CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */
- CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. */
- CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */
+ CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the
+ * full path is still stored in ret_path and only the returned
+ * file descriptor will point to the parent directory. */
+ CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */
} ChaseSymlinksFlags;
bool unsafe_transition(const struct stat *a, const struct stat *b);
@@ -33,8 +35,8 @@ int chase_symlinks(const char *path_with_prefix, const char *root, ChaseSymlinks
int chase_symlinks_and_open(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path);
int chase_symlinks_and_opendir(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, DIR **ret_dir);
-int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat, int *ret_fd);
-int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path, int *ret_fd);
+int chase_symlinks_and_stat(const char *path, const char *root, ChaseSymlinksFlags chase_flags, char **ret_path, struct stat *ret_stat);
+int chase_symlinks_and_access(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int access_mode, char **ret_path);
int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseSymlinksFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path);
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index deab6e525d..7f1f90f4e9 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -988,7 +988,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
@@ -1028,33 +1027,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);
}
@@ -1105,3 +1082,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 dad8f35239..b66dda01b4 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -127,3 +127,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/basic/stat-util.c b/src/basic/stat-util.c
index 700e28f2c2..88b8346a0f 100644
--- a/src/basic/stat-util.c
+++ b/src/basic/stat-util.c
@@ -145,7 +145,7 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
return true;
- r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st, NULL);
+ r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
if (r < 0)
return r;
diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c
index 9fee309328..f520fc7ffa 100644
--- a/src/boot/bootctl-install.c
+++ b/src/boot/bootctl-install.c
@@ -664,7 +664,7 @@ static int install_variables(
return 0;
}
- r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL, NULL);
+ r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
if (r == -ENOENT)
return 0;
if (r < 0)
diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c
index e25cd963ae..ce97b013cf 100644
--- a/src/boot/bootctl-status.c
+++ b/src/boot/bootctl-status.c
@@ -560,7 +560,7 @@ static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *
return;
if (arg_dry_run) {
- r = chase_symlinks_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path, NULL);
+ r = chase_symlinks_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
if (r < 0)
log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn);
else
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 6d2ed62f94..ca65bbf106 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -1768,7 +1768,7 @@ static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_er
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Path to init binary '%s' not absolute.", init);
- r = chase_symlinks_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL, NULL);
+ r = chase_symlinks_and_access(init, root, CHASE_PREFIX_ROOT, X_OK, NULL);
if (r == -EACCES)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Init binary %s is not executable.", init);
diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c
index 4c312d0fbb..909ffc8d8b 100644
--- a/src/coredump/coredumpctl.c
+++ b/src/coredump/coredumpctl.c
@@ -981,7 +981,7 @@ static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp)
return r;
assert(r > 0);
- r = chase_symlinks_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved, NULL);
+ r = chase_symlinks_and_access(filename, arg_root, CHASE_PREFIX_ROOT, F_OK, &resolved);
if (r < 0)
return log_error_errno(r, "Cannot access \"%s%s\": %m", strempty(arg_root), filename);
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index ea54b23e08..f190239cad 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -926,7 +926,7 @@ static int mount_inaccessible(const char *dest, CustomMount *m) {
assert(dest);
assert(m);
- r = chase_symlinks_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st, NULL);
+ r = chase_symlinks_and_stat(m->destination, dest, CHASE_PREFIX_ROOT, &where, &st);
if (r < 0) {
log_full_errno(m->graceful ? LOG_DEBUG : LOG_ERR, r, "Failed to resolve %s/%s: %m", dest, m->destination);
return m->graceful ? 0 : r;
diff --git a/src/partition/repart.c b/src/partition/repart.c
index e8f3cb7fe7..2b1fbbdae2 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -4021,7 +4021,7 @@ static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type)
if (!st)
return log_oom();
- r = chase_symlinks_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st, NULL);
+ r = chase_symlinks_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st);
if (r == -ENOENT)
return 0;
if (r < 0)
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
index 49fb34b4d6..9dddb0e289 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -1269,7 +1269,7 @@ static void boot_entry_file_list(
assert(p);
assert(ret_status);
- int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL, NULL);
+ int status = chase_symlinks_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
/* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
* the absence of color support) to the user that the boot loader is only interested in the second
diff --git a/src/systemctl/systemctl-switch-root.c b/src/systemctl/systemctl-switch-root.c
index 7fa8b26f59..5c71d3d97f 100644
--- a/src/systemctl/systemctl-switch-root.c
+++ b/src/systemctl/systemctl-switch-root.c
@@ -21,11 +21,11 @@ static int same_file_in_root(
struct stat sta, stb;
int r;
- r = chase_symlinks_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta, NULL);
+ r = chase_symlinks_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
if (r < 0)
return r;
- r = chase_symlinks_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb, NULL);
+ r = chase_symlinks_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
if (r < 0)
return r;
diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c
index 98417c9d32..3d73e8e5b1 100644
--- a/src/test/test-fs-util.c
+++ b/src/test/test-fs-util.c
@@ -331,10 +331,10 @@ TEST(chase_symlinks) {
}
assert_se(lstat(p, &st) >= 0);
- r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result);
+ r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result);
+ assert_se(r == 0);
assert_se(path_equal(result, p));
result = mfree(result);
- assert_se(r == 0);
assert_se(lstat(p, &st) == -1 && errno == ENOENT);
/* Test CHASE_NOFOLLOW */
@@ -426,18 +426,6 @@ TEST(chase_symlinks) {
assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG);
assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG);
- /* Test CHASE_PARENT */
-
- assert_se(chase_symlinks("/chase/parent", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0);
- p = strjoina(temp, "/chase");
- assert_se(streq(p, result));
- result = mfree(result);
- assert_se(chase_symlinks("/chase", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0);
- assert_se(streq(temp, result));
- result = mfree(result);
- assert_se(chase_symlinks("/", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL);
- assert_se(chase_symlinks(".", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL);
-
cleanup:
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
@@ -479,23 +467,51 @@ TEST(chase_symlinks_at) {
/* Test CHASE_PARENT */
- assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0);
+ assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0);
+ assert_se(symlinkat("/def", fd, "parent") >= 0);
+ fd = safe_close(fd);
+
+ /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the
+ * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent
+ * directory of the symlink itself. */
+
+ assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "def", F_OK, 0) >= 0);
+ assert_se(streq(result, "def"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0);
+ assert_se(streq(result, "chase/parent"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chase_symlinks_at(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0);
+ assert_se(faccessat(fd, "chase", F_OK, 0) >= 0);
assert_se(streq(result, "chase"));
+ fd = safe_close(fd);
+ result = mfree(result);
+
+ assert_se(chase_symlinks_at(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
+ assert_se(streq(result, "."));
result = mfree(result);
- assert_se(chase_symlinks_at(tfd, "chase", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0);
+ assert_se(chase_symlinks_at(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0);
assert_se(streq(result, "."));
result = mfree(result);
/* Test CHASE_MKDIR_0755 */
- assert_se(chase_symlinks_at(tfd, "m/k/d/i/r", CHASE_MKDIR_0755, &result, NULL) >= 0);
- assert_se(faccessat(tfd, "m/k/d/i/r", F_OK, 0) >= 0);
+ assert_se(chase_symlinks_at(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+ assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0);
+ assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT);
assert_se(streq(result, "m/k/d/i/r"));
result = mfree(result);
- assert_se(chase_symlinks_at(tfd, "m/../q", CHASE_MKDIR_0755, &result, NULL) >= 0);
- assert_se(faccessat(tfd, "q", F_OK, 0) >= 0);
+ assert_se(chase_symlinks_at(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0);
+ assert_se(faccessat(tfd, "m", F_OK, 0) >= 0);
+ assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT);
assert_se(streq(result, "q"));
result = mfree(result);
@@ -503,8 +519,14 @@ TEST(chase_symlinks_at) {
/* Test chase_symlinks_at_and_open() */
- fd = chase_symlinks_at_and_open(tfd, "o/p/e/n", CHASE_MKDIR_0755, O_CLOEXEC, NULL);
+ fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL);
+ assert_se(fd >= 0);
+ assert_se(fd_verify_regular(fd) >= 0);
+ fd = safe_close(fd);
+
+ fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL);
assert_se(fd >= 0);
+ assert_se(fd_verify_directory(fd) >= 0);
fd = safe_close(fd);
}
@@ -1136,6 +1158,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;