diff --git a/README b/README index 975f5e5a5e5..1f3d1df2754 100644 --- a/README +++ b/README @@ -67,6 +67,7 @@ REQUIREMENTS: and MOVE_MOUNT_BENEATH ≥ 6.6 for quota support on tmpfs ≥ 6.9 for pidfs + ≥ 6.13 for PIDFD_GET_INFO and {set,remove}xattrat() ✅ systemd utilizes several new kernel APIs, but will fall back gracefully when unavailable. diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index a68cff79860..411103b83e9 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -12,6 +12,7 @@ #include "fd-util.h" #include "macro.h" #include "missing_syscall.h" +#include "missing_threads.h" #include "parse-util.h" #include "sparse-endian.h" #include "stat-util.h" @@ -20,162 +21,131 @@ #include "time-util.h" #include "xattr-util.h" +/* Use a single cache for all of *xattrat syscalls (added in kernel 6.13) */ +static thread_local bool have_xattrat = true; + +static int normalize_and_maybe_pin_inode( + int *fd, + const char **path, + int *at_flags, + int *ret_tfd, + bool *ret_opath) { + + int r; + + assert(fd); + assert(*fd >= 0 || *fd == AT_FDCWD); + assert(path); + assert(at_flags); + assert(ret_tfd); + assert(ret_opath); + + if (isempty(*path)) + *path = NULL; /* Normalize "" to NULL */ + + if (*fd == AT_FDCWD) { + if (!*path) /* Both unspecified? Then operate on current working directory */ + *path = "."; + + *ret_tfd = -EBADF; + *ret_opath = false; + return 0; + } + + *at_flags |= AT_EMPTY_PATH; + + if (!*path) { + r = fd_is_opath(*fd); + if (r < 0) + return r; + *ret_opath = r; + + *ret_tfd = -EBADF; + return 0; + } + + /* If both have been specified, then we go via O_PATH */ + + int tfd = openat(*fd, *path, O_PATH|O_CLOEXEC|(FLAGS_SET(*at_flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); + if (tfd < 0) + return -errno; + + *fd = *ret_tfd = tfd; + *path = NULL; + *ret_opath = true; + + return 0; +} + +static int getxattr_pinned_internal( + int fd, + const char *path, + int at_flags, + bool by_procfs, + const char *name, + char *buf, + size_t size) { + + ssize_t n; + + assert(!path || !isempty(path)); + assert((fd >= 0) == !path); + assert((at_flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH)) == 0); + assert(path || FLAGS_SET(at_flags, AT_EMPTY_PATH)); + assert(name); + assert(buf || size == 0); + + if (path) + n = FLAGS_SET(at_flags, AT_SYMLINK_NOFOLLOW) ? lgetxattr(path, name, buf, size) + : getxattr(path, name, buf, size); + else + n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, buf, size) + : fgetxattr(fd, name, buf, size); + if (n < 0) + return -errno; + + assert((size_t) n <= size); + + if (n > INT_MAX) /* We couldn't return this as 'int' anymore */ + return -E2BIG; + + return (int) n; +} + int getxattr_at_malloc( int fd, const char *path, const char *name, - int flags, + int at_flags, char **ret) { _cleanup_close_ int opened_fd = -EBADF; - unsigned n_attempts = 7; - bool by_procfs = false; - size_t l = 100; + bool by_procfs; + int r; assert(fd >= 0 || fd == AT_FDCWD); assert(name); - assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); assert(ret); /* So, this is single function that does what getxattr()/lgetxattr()/fgetxattr() does, but in one go, * and with additional bells and whistles. Specifically: * - * 1. This works on O_PATH fds (which fgetxattr() does not) - * 2. Provides full openat()-style semantics, i.e. by-fd, by-path and combination thereof - * 3. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is NULL. - * 4. Does a malloc() loop, automatically sizing the allocation - * 5. NUL-terminates the returned buffer (for safety) + * 1. This works on O_PATH fds (via /proc/self/fd/, since getxattrat() syscall refuses them...) + * 2. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is empty + * 3. Does a malloc() loop, automatically sizing the allocation + * 4. NUL-terminates the returned buffer (for safety) */ - if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't — for safety reasons. */ - flags |= AT_EMPTY_PATH; - - if (isempty(path)) { - if (!FLAGS_SET(flags, AT_EMPTY_PATH)) - return -EINVAL; - - if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */ - path = "."; - else - path = NULL; - - } else if (fd != AT_FDCWD) { - - /* If both have been specified, then we go via O_PATH */ - opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); - if (opened_fd < 0) - return -errno; - - fd = opened_fd; - path = NULL; - by_procfs = true; /* fgetxattr() is not going to work, go via /proc/ link right-away */ - } - - for (;;) { - _cleanup_free_ char *v = NULL; - ssize_t n; - - if (n_attempts == 0) /* If someone is racing against us, give up eventually */ - return -EBUSY; - n_attempts--; - - v = new0(char, l+1); - if (!v) - return -ENOMEM; - - l = MALLOC_ELEMENTSOF(v) - 1; - - if (path) - n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? getxattr(path, name, v, l) : lgetxattr(path, name, v, l); - else - n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, v, l) : fgetxattr(fd, name, v, l); - if (n < 0) { - if (errno == EBADF) { - if (by_procfs || path) - return -EBADF; - - by_procfs = true; /* Might be an O_PATH fd, try again via /proc/ link */ - continue; - } - - if (errno != ERANGE) - return -errno; - } else { - v[n] = 0; /* NUL terminate */ - *ret = TAKE_PTR(v); - return (int) n; - } - - if (path) - n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? getxattr(path, name, NULL, 0) : lgetxattr(path, name, NULL, 0); - else - n = by_procfs ? getxattr(FORMAT_PROC_FD_PATH(fd), name, NULL, 0) : fgetxattr(fd, name, NULL, 0); - if (n < 0) - return -errno; - if (n > INT_MAX) /* We couldn't return this as 'int' anymore */ - return -E2BIG; - - l = (size_t) n; - } -} - -int getxattr_at_bool(int fd, const char *path, const char *name, int flags) { - _cleanup_free_ char *v = NULL; - int r; - - r = getxattr_at_malloc(fd, path, name, flags, &v); + r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); if (r < 0) return r; - if (memchr(v, 0, r)) /* Refuse embedded NUL byte */ - return -EINVAL; + at_flags = at_flags_normalize_nofollow(at_flags); - return parse_boolean(v); -} - -int listxattr_at_malloc( - int fd, - const char *path, - int flags, - char **ret) { - - _cleanup_close_ int opened_fd = -EBADF; - bool by_procfs = false; - unsigned n_attempts = 7; size_t l = 100; - - assert(fd >= 0 || fd == AT_FDCWD); - assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); - assert(ret); - - /* This is to listxattr()/llistattr()/flistattr() what getxattr_at_malloc() is to getxattr()/… */ - - if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't. */ - flags |= AT_EMPTY_PATH; - - if (isempty(path)) { - if (!FLAGS_SET(flags, AT_EMPTY_PATH)) - return -EINVAL; - - if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */ - path = "."; - else - path = NULL; - - } else if (fd != AT_FDCWD) { - /* If both have been specified, then we go via O_PATH */ - opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); - if (opened_fd < 0) - return -errno; - - fd = opened_fd; - path = NULL; - by_procfs = true; - } - - for (;;) { + for (unsigned n_attempts = 7;;) { _cleanup_free_ char *v = NULL; - ssize_t n; if (n_attempts == 0) /* If someone is racing against us, give up eventually */ return -EBUSY; @@ -187,103 +157,205 @@ int listxattr_at_malloc( l = MALLOC_ELEMENTSOF(v) - 1; - if (path) - n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? listxattr(path, v, l) : llistxattr(path, v, l); - else - n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), v, l) : flistxattr(fd, v, l); - if (n < 0) { - if (errno == EBADF) { - if (by_procfs || path) - return -EBADF; - - by_procfs = true; /* Might be an O_PATH fd, try again via /proc/ link */ - continue; - } - - if (errno != ERANGE) - return -errno; - } else { - v[n] = 0; /* NUL terminate */ + r = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, v, l); + if (r >= 0) { + v[r] = 0; /* NUL terminate */ *ret = TAKE_PTR(v); - return (int) n; + return r; } + if (r != -ERANGE) + return r; - if (path) - n = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? listxattr(path, NULL, 0) : llistxattr(path, NULL, 0); - else - n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), NULL, 0) : flistxattr(fd, NULL, 0); - if (n < 0) - return -errno; - if (n > INT_MAX) /* We couldn't return this as 'int' anymore */ - return -E2BIG; + r = getxattr_pinned_internal(fd, path, at_flags, by_procfs, name, NULL, 0); + if (r < 0) + return r; - l = (size_t) n; + l = (size_t) r; } } -int xsetxattr(int fd, - const char *path, - const char *name, - const char *value, - size_t size, - int flags) { +int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags) { + _cleanup_free_ char *v = NULL; + int r; + r = getxattr_at_malloc(fd, path, name, at_flags, &v); + if (r < 0) + return r; + + if (memchr(v, 0, r)) /* Refuse embedded NUL byte */ + return -EINVAL; + + return parse_boolean(v); +} + +static int listxattr_pinned_internal( + int fd, + const char *path, + int at_flags, + bool by_procfs, + char *buf, + size_t size) { + + ssize_t n; + + assert(!path || !isempty(path)); + assert((fd >= 0) == !path); + assert((at_flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH)) == 0); + assert(path || FLAGS_SET(at_flags, AT_EMPTY_PATH)); + assert(buf || size == 0); + + if (path) + n = FLAGS_SET(at_flags, AT_SYMLINK_NOFOLLOW) ? llistxattr(path, buf, size) + : listxattr(path, buf, size); + else + n = by_procfs ? listxattr(FORMAT_PROC_FD_PATH(fd), buf, size) + : flistxattr(fd, buf, size); + if (n < 0) + return -errno; + + assert((size_t) n <= size); + + if (n > INT_MAX) /* We couldn't return this as 'int' anymore */ + return -E2BIG; + + return (int) n; +} + +int listxattr_at_malloc(int fd, const char *path, int at_flags, char **ret) { _cleanup_close_ int opened_fd = -EBADF; - bool by_procfs = false; + bool by_procfs; int r; assert(fd >= 0 || fd == AT_FDCWD); + assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert(ret); + + /* This is to listxattr()/llistattr()/flistattr() what getxattr_at_malloc() is to getxattr()/… */ + + r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); + if (r < 0) + return r; + + at_flags = at_flags_normalize_nofollow(at_flags); + + size_t l = 100; + for (unsigned n_attempts = 7;;) { + _cleanup_free_ char *v = NULL; + + if (n_attempts == 0) /* If someone is racing against us, give up eventually */ + return -EBUSY; + n_attempts--; + + v = new(char, l+1); + if (!v) + return -ENOMEM; + + l = MALLOC_ELEMENTSOF(v) - 1; + + r = listxattr_pinned_internal(fd, path, at_flags, by_procfs, v, l); + if (r >= 0) { + v[r] = 0; /* NUL terminate */ + *ret = TAKE_PTR(v); + return r; + } + if (r != -ERANGE) + return r; + + r = listxattr_pinned_internal(fd, path, at_flags, by_procfs, NULL, 0); + if (r < 0) + return r; + + l = (size_t) r; + } +} + +int xsetxattr_full( + int fd, + const char *path, + int at_flags, + const char *name, + const char *value, + size_t size, + int xattr_flags) { + + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); assert(name); assert(value); - assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); - - /* So, this is a single function that does what setxattr()/lsetxattr()/fsetxattr() do, but in one go, - * and with additional bells and whistles. Specifically: - * - * 1. This works on O_PATH fds (which fsetxattr() does not) - * 2. Provides full openat()-style semantics, i.e. by-fd, by-path and combination thereof - * 3. As extension to openat()-style semantics implies AT_EMPTY_PATH if path is NULL. - */ - - if (!path) /* If path is NULL, imply AT_EMPTY_PATH. – But if it's "", don't — for safety reasons. */ - flags |= AT_EMPTY_PATH; if (size == SIZE_MAX) size = strlen(value); - if (isempty(path)) { - if (!FLAGS_SET(flags, AT_EMPTY_PATH)) - return -EINVAL; + if (have_xattrat && !isempty(path)) { + struct xattr_args args = { + .value = PTR_TO_UINT64(value), + .size = size, + .flags = xattr_flags, + }; - if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */ - path = "."; - else { - r = fd_is_opath(fd); - if (r < 0) - return r; + r = RET_NERRNO(setxattrat(fd, path, + at_flags_normalize_nofollow(at_flags), + name, + &args, sizeof(args))); + if (r != -ENOSYS) /* No ERRNO_IS_NOT_SUPPORTED here, as EOPNOTSUPP denotes the fs doesn't + support xattr */ + return r; - by_procfs = r; - path = NULL; - } - - } else if (fd != AT_FDCWD) { - - /* If both have been specified, then we go via O_PATH */ - opened_fd = openat(fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : O_NOFOLLOW)); - if (opened_fd < 0) - return -errno; - - fd = opened_fd; - path = NULL; - by_procfs = true; /* fsetxattr() is not going to work, go via /proc/ link right-away */ + have_xattrat = false; } + _cleanup_close_ int opened_fd = -EBADF; + bool by_procfs; + + r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs); + if (r < 0) + return r; + if (path) - r = FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? setxattr(path, name, value, size, 0) - : lsetxattr(path, name, value, size, 0); + r = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? setxattr(path, name, value, size, xattr_flags) + : lsetxattr(path, name, value, size, xattr_flags); else - r = by_procfs ? setxattr(FORMAT_PROC_FD_PATH(fd), name, value, size, 0) - : fsetxattr(fd, name, value, size, 0); + r = by_procfs ? setxattr(FORMAT_PROC_FD_PATH(fd), name, value, size, xattr_flags) + : fsetxattr(fd, name, value, size, xattr_flags); + if (r < 0) + return -errno; + + return 0; +} + +int xremovexattr(int fd, const char *path, int at_flags, const char *name) { + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + assert((at_flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert(name); + + if (have_xattrat && !isempty(path)) { + r = RET_NERRNO(removexattrat(fd, path, + at_flags_normalize_nofollow(at_flags), + name)); + if (r != -ENOSYS) /* No ERRNO_IS_NOT_SUPPORTED here, as EOPNOTSUPP denotes the fs doesn't + support xattr */ + return r; + + have_xattrat = false; + } + + _cleanup_close_ int tfd = -EBADF; + bool by_procfs; + + r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &tfd, &by_procfs); + if (r < 0) + return r; + + if (path) + r = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? removexattr(path, name) + : lremovexattr(path, name); + else + r = by_procfs ? removexattr(FORMAT_PROC_FD_PATH(fd), name) + : fremovexattr(fd, name); if (r < 0) return -errno; @@ -373,5 +445,7 @@ int fd_setcrtime(int fd, usec_t usec) { usec = now(CLOCK_REALTIME); le = htole64((uint64_t) usec); - return xsetxattr(fd, /* path = */ NULL, "user.crtime_usec", (const char*) &le, sizeof(le), AT_EMPTY_PATH); + return xsetxattr_full(fd, /* path = */ NULL, AT_EMPTY_PATH, + "user.crtime_usec", (const char*) &le, sizeof(le), + /* xattr_flags = */ 0); } diff --git a/src/basic/xattr-util.h b/src/basic/xattr-util.h index 7d5aea30171..03e6a46cdbc 100644 --- a/src/basic/xattr-util.h +++ b/src/basic/xattr-util.h @@ -7,7 +7,7 @@ #include "time-util.h" -int getxattr_at_malloc(int fd, const char *path, const char *name, int flags, char **ret); +int getxattr_at_malloc(int fd, const char *path, const char *name, int at_flags, char **ret); static inline int getxattr_malloc(const char *path, const char *name, char **ret) { return getxattr_at_malloc(AT_FDCWD, path, name, AT_SYMLINK_FOLLOW, ret); } @@ -18,9 +18,9 @@ static inline int fgetxattr_malloc(int fd, const char *name, char **ret) { return getxattr_at_malloc(fd, NULL, name, AT_EMPTY_PATH, ret); } -int getxattr_at_bool(int fd, const char *path, const char *name, int flags); +int getxattr_at_bool(int fd, const char *path, const char *name, int at_flags); -int listxattr_at_malloc(int fd, const char *path, int flags, char **ret); +int listxattr_at_malloc(int fd, const char *path, int at_flags, char **ret); static inline int listxattr_malloc(const char *path, char **ret) { return listxattr_at_malloc(AT_FDCWD, path, AT_SYMLINK_FOLLOW, ret); } @@ -31,7 +31,24 @@ static inline int flistxattr_malloc(int fd, char **ret) { return listxattr_at_malloc(fd, NULL, AT_EMPTY_PATH, ret); } -int xsetxattr(int fd, const char *path, const char *name, const char *value, size_t size, int flags); +int xsetxattr_full( + int fd, + const char *path, + int at_flags, + const char *name, + const char *value, + size_t size, + int xattr_flags); +static inline int xsetxattr( + int fd, + const char *path, + int at_flags, + const char *name, + const char *value) { + return xsetxattr_full(fd, path, at_flags, name, value, SIZE_MAX, 0); +} + +int xremovexattr(int fd, const char *path, int at_flags, const char *name); int fd_setcrtime(int fd, usec_t usec); int getcrtime_at(int fd, const char *path, int at_flags, usec_t *ret); diff --git a/src/shared/copy.c b/src/shared/copy.c index cb3944b279c..17c027a9e89 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -1679,8 +1679,7 @@ int copy_xattr(int df, const char *from, int dt, const char *to, CopyFlags copy_ if (r < 0) return r; - if (xsetxattr(dt, to, p, value, r, 0) < 0) - ret = -errno; + RET_GATHER(ret, xsetxattr_full(dt, to, /* at_flags = */ 0, p, value, r, /* xattr_flags = */ 0)); } return ret; diff --git a/src/test/test-xattr-util.c b/src/test/test-xattr-util.c index c754ac526e8..57b5939d939 100644 --- a/src/test/test-xattr-util.c +++ b/src/test/test-xattr-util.c @@ -82,7 +82,7 @@ TEST(getcrtime) { static void verify_xattr(int dfd, const char *expected) { _cleanup_free_ char *value = NULL; - assert_se(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value) == (int) strlen(expected)); + ASSERT_OK_EQ(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value), (int) strlen(expected)); ASSERT_STREQ(value, expected); } @@ -98,31 +98,38 @@ TEST(xsetxattr) { assert_se(touch(x) >= 0); /* by full path */ - r = xsetxattr(AT_FDCWD, x, "user.foo", "fullpath", SIZE_MAX, 0); + r = xsetxattr(AT_FDCWD, x, 0, "user.foo", "fullpath"); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return (void) log_tests_skipped_errno(r, "no xattrs supported on /var/tmp"); - assert_se(r >= 0); + ASSERT_OK(r); verify_xattr(dfd, "fullpath"); /* by dirfd */ - assert_se(xsetxattr(dfd, "test", "user.foo", "dirfd", SIZE_MAX, 0) >= 0); + ASSERT_ERROR(xsetxattr_full(dfd, "test", 0, "user.foo", "dirfd", SIZE_MAX, XATTR_CREATE), EEXIST); + verify_xattr(dfd, "fullpath"); + + ASSERT_OK(xsetxattr_full(dfd, "test", 0, "user.foo", "dirfd", SIZE_MAX, XATTR_REPLACE)); verify_xattr(dfd, "dirfd"); /* by fd (O_PATH) */ - fd = openat(dfd, "test", O_PATH|O_CLOEXEC); - assert_se(fd >= 0); - assert_se(xsetxattr(fd, NULL, "user.foo", "fd_opath", SIZE_MAX, 0) >= 0); + ASSERT_OK_ERRNO(fd = openat(dfd, "test", O_PATH|O_CLOEXEC)); + + ASSERT_OK(xremovexattr(fd, "", 0, "user.foo")); + + ASSERT_OK(xsetxattr_full(fd, NULL, AT_EMPTY_PATH, "user.foo", "fd_opath", SIZE_MAX, XATTR_CREATE)); verify_xattr(dfd, "fd_opath"); - assert_se(xsetxattr(fd, "", "user.foo", "fd_opath", SIZE_MAX, 0) == -EINVAL); - assert_se(xsetxattr(fd, "", "user.foo", "fd_opath_empty", SIZE_MAX, AT_EMPTY_PATH) >= 0); + + ASSERT_OK(xsetxattr(fd, "", 0, "user.foo", "fd_opath_empty")); verify_xattr(dfd, "fd_opath_empty"); + fd = safe_close(fd); fd = openat(dfd, "test", O_RDONLY|O_CLOEXEC); - assert_se(xsetxattr(fd, NULL, "user.foo", "fd_regular", SIZE_MAX, 0) >= 0); + + ASSERT_OK(xsetxattr_full(fd, NULL, 0, "user.foo", "fd_regular", SIZE_MAX, XATTR_REPLACE)); verify_xattr(dfd, "fd_regular"); - assert_se(xsetxattr(fd, "", "user.foo", "fd_regular_empty", SIZE_MAX, 0) == -EINVAL); - assert_se(xsetxattr(fd, "", "user.foo", "fd_regular_empty", SIZE_MAX, AT_EMPTY_PATH) >= 0); + + ASSERT_OK(xsetxattr(fd, "", 0, "user.foo", "fd_regular_empty")); verify_xattr(dfd, "fd_regular_empty"); }