linux: use statx() to obtain file birth time

Kernels > 4.11 support the statx() system call that lets one retrieve
the birth time of a file. Teach libuv about it.

Fixes: https://github.com/libuv/libuv/issues/2152
PR-URL: https://github.com/libuv/libuv/pull/2184
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
Ben Noordhuis 2019-02-09 23:17:05 +01:00
parent 4fc5a0ee61
commit 19d8eb0c92
4 changed files with 153 additions and 0 deletions

View File

@ -1053,10 +1053,82 @@ static void uv__to_stat(struct stat* src, uv_stat_t* dst) {
} }
static int uv__fs_statx(int fd,
const char* path,
int is_fstat,
int is_lstat,
uv_stat_t* buf) {
STATIC_ASSERT(UV_ENOSYS != -1);
#ifdef __linux__
static int no_statx;
struct uv__statx statxbuf;
int dirfd;
int flags;
int mode;
int rc;
if (no_statx)
return UV_ENOSYS;
dirfd = AT_FDCWD;
flags = 0; /* AT_STATX_SYNC_AS_STAT */
mode = 0xFFF; /* STATX_BASIC_STATS + STATX_BTIME */
if (is_fstat) {
dirfd = fd;
flags |= 0x1000; /* AT_EMPTY_PATH */
}
if (is_lstat)
flags |= AT_SYMLINK_NOFOLLOW;
rc = uv__statx(dirfd, path, flags, mode, &statxbuf);
if (rc == -1) {
/* EPERM happens when a seccomp filter rejects the system call.
* Has been observed with libseccomp < 2.3.3 and docker < 18.04.
*/
if (errno != EINVAL && errno != EPERM && errno != ENOSYS)
return -1;
no_statx = 1;
return UV_ENOSYS;
}
buf->st_dev = 256 * statxbuf.stx_dev_major + statxbuf.stx_dev_minor;
buf->st_mode = statxbuf.stx_mode;
buf->st_nlink = statxbuf.stx_nlink;
buf->st_uid = statxbuf.stx_uid;
buf->st_gid = statxbuf.stx_gid;
buf->st_rdev = statxbuf.stx_rdev_major;
buf->st_ino = statxbuf.stx_ino;
buf->st_size = statxbuf.stx_size;
buf->st_blksize = statxbuf.stx_blksize;
buf->st_blocks = statxbuf.stx_blocks;
buf->st_atim.tv_sec = statxbuf.stx_atime.tv_sec;
buf->st_atim.tv_nsec = statxbuf.stx_atime.tv_nsec;
buf->st_mtim.tv_sec = statxbuf.stx_mtime.tv_sec;
buf->st_mtim.tv_nsec = statxbuf.stx_mtime.tv_nsec;
buf->st_ctim.tv_sec = statxbuf.stx_ctime.tv_sec;
buf->st_ctim.tv_nsec = statxbuf.stx_ctime.tv_nsec;
buf->st_birthtim.tv_sec = statxbuf.stx_btime.tv_sec;
buf->st_birthtim.tv_nsec = statxbuf.stx_btime.tv_nsec;
return 0;
#else
return UV_ENOSYS;
#endif /* __linux__ */
}
static int uv__fs_stat(const char *path, uv_stat_t *buf) { static int uv__fs_stat(const char *path, uv_stat_t *buf) {
struct stat pbuf; struct stat pbuf;
int ret; int ret;
ret = uv__fs_statx(-1, path, /* is_fstat */ 0, /* is_lstat */ 0, buf);
if (ret != UV_ENOSYS)
return ret;
ret = stat(path, &pbuf); ret = stat(path, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);
@ -1069,6 +1141,10 @@ static int uv__fs_lstat(const char *path, uv_stat_t *buf) {
struct stat pbuf; struct stat pbuf;
int ret; int ret;
ret = uv__fs_statx(-1, path, /* is_fstat */ 0, /* is_lstat */ 1, buf);
if (ret != UV_ENOSYS)
return ret;
ret = lstat(path, &pbuf); ret = lstat(path, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);
@ -1081,6 +1157,10 @@ static int uv__fs_fstat(int fd, uv_stat_t *buf) {
struct stat pbuf; struct stat pbuf;
int ret; int ret;
ret = uv__fs_statx(fd, "", /* is_fstat */ 1, /* is_lstat */ 0, buf);
if (ret != UV_ENOSYS)
return ret;
ret = fstat(fd, &pbuf); ret = fstat(fd, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);

View File

@ -187,6 +187,21 @@
# endif # endif
#endif /* __NR_pwritev */ #endif /* __NR_pwritev */
#ifndef __NR_statx
# if defined(__x86_64__)
# define __NR_statx 332
# elif defined(__i386__)
# define __NR_statx 383
# elif defined(__aarch64__)
# define __NR_statx 397
# elif defined(__arm__)
# define __NR_statx (UV_SYSCALL_BASE + 397)
# elif defined(__ppc__)
# define __NR_statx 383
# elif defined(__s390__)
# define __NR_statx 379
# endif
#endif /* __NR_statx */
int uv__accept4(int fd, struct sockaddr* addr, socklen_t* addrlen, int flags) { int uv__accept4(int fd, struct sockaddr* addr, socklen_t* addrlen, int flags) {
#if defined(__i386__) #if defined(__i386__)
@ -336,3 +351,16 @@ int uv__dup3(int oldfd, int newfd, int flags) {
return errno = ENOSYS, -1; return errno = ENOSYS, -1;
#endif #endif
} }
int uv__statx(int dirfd,
const char* path,
int flags,
unsigned int mask,
struct uv__statx* statxbuf) {
#if defined(__NR_statx)
return syscall(__NR_statx, dirfd, path, flags, mask, statxbuf);
#else
return errno = ENOSYS, -1;
#endif
}

View File

@ -80,6 +80,36 @@
#define UV__IN_DELETE_SELF 0x400 #define UV__IN_DELETE_SELF 0x400
#define UV__IN_MOVE_SELF 0x800 #define UV__IN_MOVE_SELF 0x800
struct uv__statx_timestamp {
int64_t tv_sec;
uint32_t tv_nsec;
int32_t unused0;
};
struct uv__statx {
uint32_t stx_mask;
uint32_t stx_blksize;
uint64_t stx_attributes;
uint32_t stx_nlink;
uint32_t stx_uid;
uint32_t stx_gid;
uint16_t stx_mode;
uint16_t unused0;
uint64_t stx_ino;
uint64_t stx_size;
uint64_t stx_blocks;
uint64_t stx_attributes_mask;
struct uv__statx_timestamp stx_atime;
struct uv__statx_timestamp stx_btime;
struct uv__statx_timestamp stx_ctime;
struct uv__statx_timestamp stx_mtime;
uint32_t stx_rdev_major;
uint32_t stx_rdev_minor;
uint32_t stx_dev_major;
uint32_t stx_dev_minor;
uint64_t unused1[14];
};
struct uv__inotify_event { struct uv__inotify_event {
int32_t wd; int32_t wd;
uint32_t mask; uint32_t mask;
@ -113,5 +143,10 @@ int uv__sendmmsg(int fd,
ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, int64_t offset); ssize_t uv__preadv(int fd, const struct iovec *iov, int iovcnt, int64_t offset);
ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, int64_t offset); ssize_t uv__pwritev(int fd, const struct iovec *iov, int iovcnt, int64_t offset);
int uv__dup3(int oldfd, int newfd, int flags); int uv__dup3(int oldfd, int newfd, int flags);
int uv__statx(int dirfd,
const char* path,
int flags,
unsigned int mask,
struct uv__statx* statxbuf);
#endif /* UV_LINUX_SYSCALL_H_ */ #endif /* UV_LINUX_SYSCALL_H_ */

View File

@ -1248,6 +1248,16 @@ TEST_IMPL(fs_fstat) {
#endif #endif
#endif #endif
#if defined(__linux__)
/* If statx() is supported, the birth time should be equal to the change time
* because we just created the file. On older kernels, it's set to zero.
*/
ASSERT(s->st_birthtim.tv_sec == 0 ||
s->st_birthtim.tv_sec == t.st_ctim.tv_sec);
ASSERT(s->st_birthtim.tv_nsec == 0 ||
s->st_birthtim.tv_nsec == t.st_ctim.tv_nsec);
#endif
uv_fs_req_cleanup(&req); uv_fs_req_cleanup(&req);
/* Now do the uv_fs_fstat call asynchronously */ /* Now do the uv_fs_fstat call asynchronously */