Systemd/src/basic/xattr-util.c
Lennart Poettering 4c2e1b399f xattr-util: use crtime/btime if statx() is available for implementation of fd_setcrtime() and friends
The Linux kernel exposes the birth time now for files through statx()
hence make use of it where available. We keep the xattr logic in place
for this however, since only a subset of file systems on Linux currently
expose the birth time. NFS and tmpfs for example do not support it. OTOH
there are other file systems that do support the birth time but might
not support xattrs (smb…), hence make the best of the two, in particular
in order to deal with journal files copied between file system types and
to maintain compatibility with older file systems that are updated to
newer version of the file system.
2018-02-20 15:41:49 +01:00

229 lines
6.7 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <fcntl.h>
#include <linux/stat.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/xattr.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "macro.h"
#include "missing.h"
#include "sparse-endian.h"
#include "stdio-util.h"
#include "string-util.h"
#include "time-util.h"
#include "xattr-util.h"
int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink) {
char *v;
size_t l;
ssize_t n;
assert(path);
assert(name);
assert(value);
for (l = 100; ; l = (size_t) n + 1) {
v = new0(char, l);
if (!v)
return -ENOMEM;
if (allow_symlink)
n = lgetxattr(path, name, v, l);
else
n = getxattr(path, name, v, l);
if (n >= 0 && (size_t) n < l) {
*value = v;
return n;
}
free(v);
if (n < 0 && errno != ERANGE)
return -errno;
if (allow_symlink)
n = lgetxattr(path, name, NULL, 0);
else
n = getxattr(path, name, NULL, 0);
if (n < 0)
return -errno;
}
}
int fgetxattr_malloc(int fd, const char *name, char **value) {
char *v;
size_t l;
ssize_t n;
assert(fd >= 0);
assert(name);
assert(value);
for (l = 100; ; l = (size_t) n + 1) {
v = new0(char, l);
if (!v)
return -ENOMEM;
n = fgetxattr(fd, name, v, l);
if (n >= 0 && (size_t) n < l) {
*value = v;
return n;
}
free(v);
if (n < 0 && errno != ERANGE)
return -errno;
n = fgetxattr(fd, name, NULL, 0);
if (n < 0)
return -errno;
}
}
ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) {
char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
_cleanup_close_ int fd = -1;
ssize_t l;
/* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */
if (flags & ~(AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH))
return -EINVAL;
if (isempty(filename)) {
if (!(flags & AT_EMPTY_PATH))
return -EINVAL;
xsprintf(fn, "/proc/self/fd/%i", dirfd);
} else {
fd = openat(dirfd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0));
if (fd < 0)
return -errno;
xsprintf(fn, "/proc/self/fd/%i", fd);
}
l = getxattr(fn, attribute, value, size);
if (l < 0)
return -errno;
return l;
}
static int parse_crtime(le64_t le, usec_t *usec) {
uint64_t u;
assert(usec);
u = le64toh(le);
if (IN_SET(u, 0, (uint64_t) -1))
return -EIO;
*usec = (usec_t) u;
return 0;
}
int fd_getcrtime_at(int dirfd, const char *name, usec_t *ret, int flags) {
struct statx sx;
usec_t a, b;
le64_t le;
ssize_t n;
int r;
assert(ret);
if (flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW))
return -EINVAL;
/* 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(dirfd, strempty(name), flags|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;
n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags);
if (n < 0)
r = -errno;
else if (n != 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_getcrtime(int fd, usec_t *ret) {
return fd_getcrtime_at(fd, NULL, ret, AT_EMPTY_PATH);
}
int path_getcrtime(const char *p, usec_t *ret) {
return fd_getcrtime_at(AT_FDCWD, p, ret, 0);
}
int fd_setcrtime(int fd, usec_t usec) {
le64_t le;
assert(fd >= 0);
if (IN_SET(usec, 0, USEC_INFINITY))
usec = now(CLOCK_REALTIME);
le = htole64((uint64_t) usec);
if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
return -errno;
return 0;
}