8316304: (fs) Add support for BasicFileAttributes.creationTime() for Linux

Reviewed-by: stuefe, alanb, bpb, mli
This commit is contained in:
Severin Gehwolf 2023-10-16 07:40:29 +00:00
parent 77d40ce166
commit 0275efac88
3 changed files with 242 additions and 13 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,10 +25,15 @@
package sun.nio.fs;
import java.nio.file.attribute.*;
import java.util.concurrent.TimeUnit;
import java.util.Set;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Unix implementation of PosixFileAttributes.
@ -52,6 +57,7 @@ class UnixFileAttributes
private long st_ctime_sec;
private long st_ctime_nsec;
private long st_birthtime_sec;
private long st_birthtime_nsec;
// created lazily
private volatile UserPrincipal owner;
@ -158,7 +164,7 @@ class UnixFileAttributes
@Override
public FileTime creationTime() {
if (UnixNativeDispatcher.birthtimeSupported()) {
return FileTime.from(st_birthtime_sec, TimeUnit.SECONDS);
return toFileTime(st_birthtime_sec, st_birthtime_nsec);
} else {
// return last modified when birth time not supported
return lastModifiedTime();

View File

@ -51,6 +51,7 @@
#ifdef __linux__
#include <sys/syscall.h>
#include <sys/sysmacros.h> // makedev macros
#endif
#if defined(__linux__) || defined(_AIX)
@ -71,6 +72,98 @@
#define readdir64 readdir
#endif
#if defined(__linux__)
// Account for the case where we compile on a system without statx
// support. We still want to ensure we can call statx at runtime
// if the runtime glibc version supports it (>= 2.28). We do this
// by defining binary compatible statx structs in this file and
// not relying on included headers.
#ifndef __GLIBC__
// Alpine doesn't know these types, define them
typedef unsigned int __uint32_t;
typedef unsigned short __uint16_t;
typedef unsigned long int __uint64_t;
#endif
/*
* Timestamp structure for the timestamps in struct statx.
*/
struct my_statx_timestamp {
int64_t tv_sec;
__uint32_t tv_nsec;
int32_t __reserved;
};
/*
* struct statx used by statx system call on >= glibc 2.28
* systems
*/
struct my_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 __statx_pad1[1];
__uint64_t stx_ino;
__uint64_t stx_size;
__uint64_t stx_blocks;
__uint64_t stx_attributes_mask;
struct my_statx_timestamp stx_atime;
struct my_statx_timestamp stx_btime;
struct my_statx_timestamp stx_ctime;
struct my_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 __statx_pad2[14];
};
// statx masks, flags, constants
#ifndef AT_SYMLINK_NOFOLLOW
#define AT_SYMLINK_NOFOLLOW 0x100
#endif
#ifndef AT_STATX_SYNC_AS_STAT
#define AT_STATX_SYNC_AS_STAT 0x0000
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#ifndef STATX_BASIC_STATS
#define STATX_BASIC_STATS 0x000007ffU
#endif
#ifndef STATX_BTIME
#define STATX_BTIME 0x00000800U
#endif
#ifndef STATX_ALL
#define STATX_ALL (STATX_BTIME | STATX_BASIC_STATS)
#endif
#ifndef AT_FDCWD
#define AT_FDCWD -100
#endif
#ifndef RTLD_DEFAULT
#define RTLD_DEFAULT RTLD_LOCAL
#endif
#define NO_FOLLOW_SYMLINK 1
#define FOLLOW_SYMLINK 0
#endif // __linux__
#include "jni.h"
#include "jni_util.h"
#include "jlong.h"
@ -117,9 +210,12 @@ static jfieldID attrs_st_mtime_nsec;
static jfieldID attrs_st_ctime_sec;
static jfieldID attrs_st_ctime_nsec;
#ifdef _DARWIN_FEATURE_64_BIT_INODE
#if defined(_DARWIN_FEATURE_64_BIT_INODE) || defined(__linux__)
static jfieldID attrs_st_birthtime_sec;
#endif
#if defined(__linux__) // Linux has nsec granularity if supported
static jfieldID attrs_st_birthtime_nsec;
#endif
static jfieldID attrs_f_frsize;
static jfieldID attrs_f_blocks;
@ -143,6 +239,10 @@ typedef int futimesat_func(int, const char *, const struct timeval *);
typedef int futimens_func(int, const struct timespec *);
typedef int lutimes_func(const char *, const struct timeval *);
typedef DIR* fdopendir_func(int);
#if defined(__linux__)
typedef int statx_func(int dirfd, const char *restrict pathname, int flags,
unsigned int mask, struct my_statx *restrict statxbuf);
#endif
static openat64_func* my_openat64_func = NULL;
static fstatat64_func* my_fstatat64_func = NULL;
@ -152,6 +252,9 @@ static futimesat_func* my_futimesat_func = NULL;
static futimens_func* my_futimens_func = NULL;
static lutimes_func* my_lutimes_func = NULL;
static fdopendir_func* my_fdopendir_func = NULL;
#if defined(__linux__)
static statx_func* my_statx_func = NULL;
#endif
/**
* fstatat missing from glibc on Linux.
@ -177,6 +280,13 @@ static int fstatat64_wrapper(int dfd, const char *path,
}
#endif
#if defined(__linux__)
static int statx_wrapper(int dirfd, const char *restrict pathname, int flags,
unsigned int mask, struct my_statx *restrict statxbuf) {
return (*my_statx_func)(dirfd, pathname, flags, mask, statxbuf);
}
#endif
/**
* Call this to throw an internal UnixException when a system/library
* call fails
@ -229,10 +339,14 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this)
attrs_st_ctime_nsec = (*env)->GetFieldID(env, clazz, "st_ctime_nsec", "J");
CHECK_NULL_RETURN(attrs_st_ctime_nsec, 0);
#ifdef _DARWIN_FEATURE_64_BIT_INODE
#if defined(_DARWIN_FEATURE_64_BIT_INODE) || defined(__linux__)
attrs_st_birthtime_sec = (*env)->GetFieldID(env, clazz, "st_birthtime_sec", "J");
CHECK_NULL_RETURN(attrs_st_birthtime_sec, 0);
#endif
#if defined (__linux__) // Linux has nsec granularity
attrs_st_birthtime_nsec = (*env)->GetFieldID(env, clazz, "st_birthtime_nsec", "J");
CHECK_NULL_RETURN(attrs_st_birthtime_nsec, 0);
#endif
clazz = (*env)->FindClass(env, "sun/nio/fs/UnixFileStoreAttributes");
CHECK_NULL_RETURN(clazz, 0);
@ -314,6 +428,12 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this)
#ifdef _DARWIN_FEATURE_64_BIT_INODE
capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_BIRTHTIME;
#endif
#if defined(__linux__)
my_statx_func = (statx_func*) dlsym(RTLD_DEFAULT, "statx");
if (my_statx_func != NULL) {
capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_BIRTHTIME;
}
#endif
/* supports extended attributes */
@ -490,10 +610,37 @@ Java_sun_nio_fs_UnixNativeDispatcher_write0(JNIEnv* env, jclass this, jint fd,
return (jint)n;
}
#if defined(__linux__)
/**
* Copy statx members into sun.nio.fs.UnixFileAttributes
*/
static void copy_statx_attributes(JNIEnv* env, struct my_statx* buf, jobject attrs) {
(*env)->SetIntField(env, attrs, attrs_st_mode, (jint)buf->stx_mode);
(*env)->SetLongField(env, attrs, attrs_st_ino, (jlong)buf->stx_ino);
(*env)->SetIntField(env, attrs, attrs_st_nlink, (jint)buf->stx_nlink);
(*env)->SetIntField(env, attrs, attrs_st_uid, (jint)buf->stx_uid);
(*env)->SetIntField(env, attrs, attrs_st_gid, (jint)buf->stx_gid);
(*env)->SetLongField(env, attrs, attrs_st_size, (jlong)buf->stx_size);
(*env)->SetLongField(env, attrs, attrs_st_atime_sec, (jlong)buf->stx_atime.tv_sec);
(*env)->SetLongField(env, attrs, attrs_st_mtime_sec, (jlong)buf->stx_mtime.tv_sec);
(*env)->SetLongField(env, attrs, attrs_st_ctime_sec, (jlong)buf->stx_ctime.tv_sec);
(*env)->SetLongField(env, attrs, attrs_st_birthtime_sec, (jlong)buf->stx_btime.tv_sec);
(*env)->SetLongField(env, attrs, attrs_st_birthtime_nsec, (jlong)buf->stx_btime.tv_nsec);
(*env)->SetLongField(env, attrs, attrs_st_atime_nsec, (jlong)buf->stx_atime.tv_nsec);
(*env)->SetLongField(env, attrs, attrs_st_mtime_nsec, (jlong)buf->stx_mtime.tv_nsec);
(*env)->SetLongField(env, attrs, attrs_st_ctime_nsec, (jlong)buf->stx_ctime.tv_nsec);
// convert statx major:minor to dev_t using makedev
dev_t dev = makedev(buf->stx_dev_major, buf->stx_dev_minor);
dev_t rdev = makedev(buf->stx_rdev_major, buf->stx_rdev_minor);
(*env)->SetLongField(env, attrs, attrs_st_dev, (jlong)dev);
(*env)->SetLongField(env, attrs, attrs_st_rdev, (jlong)rdev);
}
#endif
/**
* Copy stat64 members into sun.nio.fs.UnixFileAttributes
*/
static void prepAttributes(JNIEnv* env, struct stat64* buf, jobject attrs) {
static void copy_stat64_attributes(JNIEnv* env, struct stat64* buf, jobject attrs) {
(*env)->SetIntField(env, attrs, attrs_st_mode, (jint)buf->st_mode);
(*env)->SetLongField(env, attrs, attrs_st_ino, (jlong)buf->st_ino);
(*env)->SetLongField(env, attrs, attrs_st_dev, (jlong)buf->st_dev);
@ -508,6 +655,7 @@ static void prepAttributes(JNIEnv* env, struct stat64* buf, jobject attrs) {
#ifdef _DARWIN_FEATURE_64_BIT_INODE
(*env)->SetLongField(env, attrs, attrs_st_birthtime_sec, (jlong)buf->st_birthtime);
// rely on default value of 0 for st_birthtime_nsec field on Darwin
#endif
#ifndef MACOSX
@ -528,10 +676,25 @@ Java_sun_nio_fs_UnixNativeDispatcher_stat0(JNIEnv* env, jclass this,
int err;
struct stat64 buf;
const char* path = (const char*)jlong_to_ptr(pathAddress);
#if defined(__linux__)
struct my_statx statx_buf;
int flags = AT_STATX_SYNC_AS_STAT;
unsigned int mask = STATX_ALL;
if (my_statx_func != NULL) {
// Prefer statx over stat64 on Linux if it's available
RESTARTABLE(statx_wrapper(AT_FDCWD, path, flags, mask, &statx_buf), err);
if (err == 0) {
copy_statx_attributes(env, &statx_buf, attrs);
return 0;
} else {
return errno;
}
}
#endif
RESTARTABLE(stat64(path, &buf), err);
if (err == 0) {
prepAttributes(env, &buf, attrs);
copy_stat64_attributes(env, &buf, attrs);
return 0;
} else {
return errno;
@ -545,12 +708,28 @@ Java_sun_nio_fs_UnixNativeDispatcher_lstat0(JNIEnv* env, jclass this,
int err;
struct stat64 buf;
const char* path = (const char*)jlong_to_ptr(pathAddress);
#if defined(__linux__)
struct my_statx statx_buf;
int flags = AT_STATX_SYNC_AS_STAT | AT_SYMLINK_NOFOLLOW;
unsigned int mask = STATX_ALL;
if (my_statx_func != NULL) {
// Prefer statx over stat64 on Linux if it's available
RESTARTABLE(statx_wrapper(AT_FDCWD, path, flags, mask, &statx_buf), err);
if (err == 0) {
copy_statx_attributes(env, &statx_buf, attrs);
} else {
throwUnixException(env, errno);
}
// statx was available, so return now
return;
}
#endif
RESTARTABLE(lstat64(path, &buf), err);
if (err == -1) {
throwUnixException(env, errno);
} else {
prepAttributes(env, &buf, attrs);
copy_stat64_attributes(env, &buf, attrs);
}
}
@ -560,12 +739,29 @@ Java_sun_nio_fs_UnixNativeDispatcher_fstat0(JNIEnv* env, jclass this, jint fd,
{
int err;
struct stat64 buf;
#if defined(__linux__)
struct my_statx statx_buf;
int flags = AT_EMPTY_PATH | AT_STATX_SYNC_AS_STAT;
unsigned int mask = STATX_ALL;
if (my_statx_func != NULL) {
// statx supports FD use via dirfd iff pathname is an empty string and the
// AT_EMPTY_PATH flag is specified in flags
RESTARTABLE(statx_wrapper((int)fd, "", flags, mask, &statx_buf), err);
if (err == 0) {
copy_statx_attributes(env, &statx_buf, attrs);
} else {
throwUnixException(env, errno);
}
// statx was available, so return now
return;
}
#endif
RESTARTABLE(fstat64((int)fd, &buf), err);
if (err == -1) {
throwUnixException(env, errno);
} else {
prepAttributes(env, &buf, attrs);
copy_stat64_attributes(env, &buf, attrs);
}
}
@ -576,6 +772,26 @@ Java_sun_nio_fs_UnixNativeDispatcher_fstatat0(JNIEnv* env, jclass this, jint dfd
int err;
struct stat64 buf;
const char* path = (const char*)jlong_to_ptr(pathAddress);
#if defined(__linux__)
struct my_statx statx_buf;
int flags = AT_STATX_SYNC_AS_STAT;
unsigned int mask = STATX_ALL;
if (my_statx_func != NULL) {
// Prefer statx over stat64 on Linux if it's available
if (((int)flag & AT_SYMLINK_NOFOLLOW) > 0) { // flag set in java code
flags |= AT_SYMLINK_NOFOLLOW;
}
RESTARTABLE(statx_wrapper((int)dfd, path, flags, mask, &statx_buf), err);
if (err == 0) {
copy_statx_attributes(env, &statx_buf, attrs);
} else {
throwUnixException(env, errno);
}
// statx was available, so return now
return;
}
#endif
if (my_fstatat64_func == NULL) {
JNU_ThrowInternalError(env, "should not reach here");
@ -585,7 +801,7 @@ Java_sun_nio_fs_UnixNativeDispatcher_fstatat0(JNIEnv* env, jclass this, jint dfd
if (err == -1) {
throwUnixException(env, errno);
} else {
prepAttributes(env, &buf, attrs);
copy_stat64_attributes(env, &buf, attrs);
}
}

View File

@ -22,7 +22,7 @@
*/
/* @test
* @bug 8011536 8151430
* @bug 8011536 8151430 8316304
* @summary Basic test for creationTime attribute on platforms/file systems
* that support it.
* @library ../.. /test/lib
@ -30,6 +30,7 @@
* @run main CreationTime
*/
import java.lang.foreign.Linker;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.attribute.*;
@ -88,7 +89,13 @@ public class CreationTime {
supportsCreationTimeRead = true;
supportsCreationTimeWrite = true;
}
} else if (Platform.isLinux()) {
// Creation time read depends on statx system call support
supportsCreationTimeRead = Linker.nativeLinker().defaultLookup().find("statx").isPresent();
// Creation time updates are not supported on Linux
supportsCreationTimeWrite = false;
}
System.out.println("supportsCreationTimeRead == " + supportsCreationTimeRead);
/**
* If the creation-time attribute is supported then change the file's