diff options
Diffstat (limited to 'src/basic/xattr-util.c')
-rw-r--r-- | src/basic/xattr-util.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c new file mode 100644 index 0000000..0b661d9 --- /dev/null +++ b/src/basic/xattr-util.c @@ -0,0 +1,295 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/xattr.h> + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "macro.h" +#include "missing_syscall.h" +#include "sparse-endian.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "time-util.h" +#include "xattr-util.h" + +int getxattr_at_malloc( + int fd, + const char *path, + const char *name, + int flags, + char **ret) { + + _cleanup_close_ int opened_fd = -1; + unsigned n_attempts = 7; + bool by_procfs = false; + size_t l = 100; + + assert(fd >= 0 || fd == AT_FDCWD); + assert(name); + assert((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) + */ + + 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; + } +} + +static int parse_crtime(le64_t le, usec_t *usec) { + uint64_t u; + + assert(usec); + + u = le64toh(le); + if (IN_SET(u, 0, UINT64_MAX)) + return -EIO; + + *usec = (usec_t) u; + return 0; +} + +int fd_getcrtime_at( + int fd, + const char *path, + int flags, + usec_t *ret) { + + _cleanup_free_ le64_t *le = NULL; + STRUCT_STATX_DEFINE(sx); + usec_t a, b; + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert(ret); + + if (!path) + flags |= AT_EMPTY_PATH; + + /* So here's the deal: the creation/birth time (crtime/btime) of a file is a relatively newly supported concept + * on Linux (or more strictly speaking: a concept that only recently got supported in the API, it was + * implemented on various file systems on the lower level since a while, but never was accessible). However, we + * needed a concept like that for vaccuuming algorithms and such, hence we emulated it via a user xattr for a + * long time. Starting with Linux 4.11 there's statx() which exposes the timestamp to userspace for the first + * time, where it is available. Thius function will read it, but it tries to keep some compatibility with older + * systems: we try to read both the crtime/btime and the xattr, and then use whatever is older. After all the + * concept is useful for determining how "old" a file really is, and hence using the older of the two makes + * most sense. */ + + if (statx(fd, strempty(path), + (flags & ~AT_SYMLINK_FOLLOW)|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : AT_SYMLINK_NOFOLLOW)|AT_STATX_DONT_SYNC, + STATX_BTIME, + &sx) >= 0 && + (sx.stx_mask & STATX_BTIME) && + sx.stx_btime.tv_sec != 0) + a = (usec_t) sx.stx_btime.tv_sec * USEC_PER_SEC + + (usec_t) sx.stx_btime.tv_nsec / NSEC_PER_USEC; + else + a = USEC_INFINITY; + + r = getxattr_at_malloc(fd, path, "user.crtime_usec", flags, (char**) &le); + if (r >= 0) { + if (r != sizeof(*le)) + r = -EIO; + else + r = parse_crtime(*le, &b); + } + if (r < 0) { + if (a != USEC_INFINITY) { + *ret = a; + return 0; + } + + return r; + } + + if (a != USEC_INFINITY) + *ret = MIN(a, b); + else + *ret = b; + + return 0; +} + +int fd_setcrtime(int fd, usec_t usec) { + le64_t le; + + assert(fd >= 0); + + if (!timestamp_is_set(usec)) + usec = now(CLOCK_REALTIME); + + le = htole64((uint64_t) usec); + return RET_NERRNO(fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0)); +} + +int listxattr_at_malloc( + int fd, + const char *path, + int flags, + char **ret) { + + _cleanup_close_ int opened_fd = -1; + 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 (;;) { + _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 = new(char, l+1); + if (!v) + return -ENOMEM; + + 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 */ + *ret = TAKE_PTR(v); + return (int) n; + } + + 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; + + l = (size_t) n; + } +} |