#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"
#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;
+ r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs);
+ if (r < 0)
+ return r;
- fd = opened_fd;
- path = NULL;
- by_procfs = true; /* fgetxattr() is not going to work, go via /proc/ link right-away */
- }
+ at_flags = at_flags_normalize_nofollow(at_flags);
- for (;;) {
+ size_t l = 100;
+ 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;
n_attempts--;
- v = new0(char, l+1);
+ v = new(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 */
+ 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) ? 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;
+ 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 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) {
_cleanup_free_ char *v = NULL;
int r;
- r = getxattr_at_malloc(fd, path, name, flags, &v);
+ r = getxattr_at_malloc(fd, path, name, at_flags, &v);
if (r < 0)
return r;
return parse_boolean(v);
}
-int listxattr_at_malloc(
+static int listxattr_pinned_internal(
int fd,
const char *path,
- int flags,
- char **ret) {
+ 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;
- unsigned n_attempts = 7;
- size_t l = 100;
+ bool by_procfs;
+ int r;
assert(fd >= 0 || fd == AT_FDCWD);
- assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
+ 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()/… */
- 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;
+ r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs);
+ if (r < 0)
+ return r;
- } 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;
+ at_flags = at_flags_normalize_nofollow(at_flags);
- fd = opened_fd;
- path = NULL;
- by_procfs = true;
- }
-
- for (;;) {
+ size_t l = 100;
+ 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;
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 = listxattr_pinned_internal(fd, path, at_flags, by_procfs, 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 = listxattr_pinned_internal(fd, path, at_flags, by_procfs, 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 xsetxattr_full(
+ int fd,
+ const char *path,
+ int at_flags,
+ const char *name,
+ const char *value,
+ size_t size,
+ int xattr_flags) {
- _cleanup_close_ int opened_fd = -EBADF;
- bool by_procfs = false;
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,
+ };
+
+ 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;
+
+ have_xattrat = false;
+ }
- if (fd == AT_FDCWD) /* Both unspecified? Then operate on current working directory */
- path = ".";
- else {
- r = fd_is_opath(fd);
- if (r < 0)
- return r;
+ _cleanup_close_ int opened_fd = -EBADF;
+ bool by_procfs;
- by_procfs = r;
- path = NULL;
- }
+ r = normalize_and_maybe_pin_inode(&fd, &path, &at_flags, &opened_fd, &by_procfs);
+ if (r < 0)
+ return r;
- } else if (fd != AT_FDCWD) {
+ if (path)
+ 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, xattr_flags)
+ : fsetxattr(fd, name, value, size, xattr_flags);
+ if (r < 0)
+ return -errno;
- /* 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;
+ return 0;
+}
+
+int xremovexattr(int fd, const char *path, int at_flags, const char *name) {
+ int r;
- fd = opened_fd;
- path = NULL;
- by_procfs = true; /* fsetxattr() is not going to work, go via /proc/ link right-away */
+ 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(flags, AT_SYMLINK_FOLLOW) ? setxattr(path, name, value, size, 0)
- : lsetxattr(path, name, value, size, 0);
+ r = FLAGS_SET(at_flags, AT_SYMLINK_FOLLOW) ? removexattr(path, name)
+ : lremovexattr(path, name);
else
- r = by_procfs ? setxattr(FORMAT_PROC_FD_PATH(fd), name, value, size, 0)
- : fsetxattr(fd, name, value, size, 0);
+ r = by_procfs ? removexattr(FORMAT_PROC_FD_PATH(fd), name)
+ : fremovexattr(fd, name);
if (r < 0)
return -errno;
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);
}
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);
}
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");
}