diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template b/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template index 82807148224..7d2e73ea915 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template +++ b/src/java.base/unix/classes/sun/nio/fs/UnixConstants.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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 @@ -139,11 +139,13 @@ class UnixConstants { #endif // flags used with openat/unlinkat/etc. -#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_REMOVEDIR) +#if defined(AT_FDCWD) && defined(AT_SYMLINK_NOFOLLOW) && defined(AT_REMOVEDIR) + static final int PREFIX_AT_FDCWD = AT_FDCWD; static final int PREFIX_AT_SYMLINK_NOFOLLOW = AT_SYMLINK_NOFOLLOW; static final int PREFIX_AT_REMOVEDIR = AT_REMOVEDIR; #else // not supported (dummy values will not be used at runtime). + static final int PREFIX_AT_FDCWD = 00; static final int PREFIX_AT_SYMLINK_NOFOLLOW = 00; static final int PREFIX_AT_REMOVEDIR = 00; #endif diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java index 94e0f91e812..033deee2f4f 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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 @@ -76,13 +76,16 @@ class UnixFileAttributeViews { boolean useFutimes = false; boolean useFutimens = false; boolean useLutimes = false; + boolean useUtimensat = false; int fd = -1; try { if (!followLinks) { - useLutimes = lutimesSupported() && - UnixFileAttributes.get(file, false).isSymbolicLink(); + // these path-based syscalls also work if following links + if (!(useUtimensat = utimensatSupported())) { + useLutimes = lutimesSupported(); + } } - if (!useLutimes) { + if (!useUtimensat && !useLutimes) { fd = file.openForAttributeAccess(followLinks); if (fd != -1) { haveFd = true; @@ -92,8 +95,8 @@ class UnixFileAttributeViews { } } } catch (UnixException x) { - if (!(x.errno() == UnixConstants.ENXIO || - (x.errno() == UnixConstants.ELOOP && useLutimes))) { + if (!(x.errno() == ENXIO || + (x.errno() == ELOOP && (useUtimensat || useLutimes)))) { x.rethrowAsIOException(file); } } @@ -117,7 +120,7 @@ class UnixFileAttributeViews { } // update times - TimeUnit timeUnit = useFutimens ? + TimeUnit timeUnit = (useFutimens || useUtimensat) ? TimeUnit.NANOSECONDS : TimeUnit.MICROSECONDS; long modValue = lastModifiedTime.to(timeUnit); long accessValue= lastAccessTime.to(timeUnit); @@ -130,13 +133,16 @@ class UnixFileAttributeViews { futimes(fd, accessValue, modValue); } else if (useLutimes) { lutimes(file, accessValue, modValue); + } else if (useUtimensat) { + utimensat(AT_FDCWD, file, accessValue, modValue, + followLinks ? 0 : AT_SYMLINK_NOFOLLOW); } else { utimes(file, accessValue, modValue); } } catch (UnixException x) { // if futimes/utimes fails with EINVAL and one/both of the times is // negative then we adjust the value to the epoch and retry. - if (x.errno() == UnixConstants.EINVAL && + if (x.errno() == EINVAL && (modValue < 0L || accessValue < 0L)) { retry = true; } else { @@ -153,6 +159,9 @@ class UnixFileAttributeViews { futimes(fd, accessValue, modValue); } else if (useLutimes) { lutimes(file, accessValue, modValue); + } else if (useUtimensat) { + utimensat(AT_FDCWD, file, accessValue, modValue, + followLinks ? 0 : AT_SYMLINK_NOFOLLOW); } else { utimes(file, accessValue, modValue); } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java index ab8975c6d12..d6d2b9fdcd7 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java @@ -391,6 +391,20 @@ class UnixNativeDispatcher { private static native void lutimes0(long pathAddress, long times0, long times1) throws UnixException; + /** + * utimensat(int fd, const char* path, + * const struct timeval times[2], int flags) + */ + static void utimensat(int fd, UnixPath path, long times0, long times1, int flags) + throws UnixException + { + try (NativeBuffer buffer = copyToNativeBuffer(path)) { + utimensat0(fd, buffer.address(), times0, times1, flags); + } + } + private static native void utimensat0(int fd, long pathAddress, long times0, long times1, int flags) + throws UnixException; + /** * DIR *opendir(const char* dirname) */ @@ -557,7 +571,8 @@ class UnixNativeDispatcher { private static final int SUPPORTS_FUTIMES = 1 << 2; private static final int SUPPORTS_FUTIMENS = 1 << 3; private static final int SUPPORTS_LUTIMES = 1 << 4; - private static final int SUPPORTS_XATTR = 1 << 5; + private static final int SUPPORTS_UTIMENSAT = 1 << 5; + private static final int SUPPORTS_XATTR = 1 << 6; private static final int SUPPORTS_BIRTHTIME = 1 << 16; // other features private static final int capabilities; @@ -589,6 +604,13 @@ class UnixNativeDispatcher { return (capabilities & SUPPORTS_LUTIMES) != 0; } + /** + * Supports utimensat + */ + static boolean utimensatSupported() { + return (capabilities & SUPPORTS_UTIMENSAT) != 0; + } + /** * Supports file birth (creation) time attribute */ diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index 9a68a12c219..aa46a2470a1 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -211,6 +211,8 @@ 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); +typedef int utimensat_func(int dirfd, const char *pathname, + const struct timespec[2], int flags); #endif static openat_func* my_openat_func = NULL; @@ -223,6 +225,7 @@ static lutimes_func* my_lutimes_func = NULL; static fdopendir_func* my_fdopendir_func = NULL; #if defined(__linux__) static statx_func* my_statx_func = NULL; +static utimensat_func* my_utimensat_func = NULL; #endif /** @@ -432,6 +435,10 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this) if (my_statx_func != NULL) { capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_BIRTHTIME; } + my_utimensat_func = (utimensat_func*) dlsym(RTLD_DEFAULT, "utimensat"); + if (my_utimensat_func != NULL) { + capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_UTIMENSAT; + } #endif /* supports extended attributes */ @@ -976,6 +983,34 @@ Java_sun_nio_fs_UnixNativeDispatcher_lutimes0(JNIEnv* env, jclass this, } } +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_utimensat0(JNIEnv* env, jclass this, + jint fd, jlong pathAddress, jlong accessTime, jlong modificationTime, jint flags) { +#if defined(__linux__) + int err; + struct timespec times[2]; + const char* path = (const char*)jlong_to_ptr(pathAddress); + + times[0].tv_sec = accessTime / 1000000000; + times[0].tv_nsec = accessTime % 1000000000; + + times[1].tv_sec = modificationTime / 1000000000; + times[1].tv_nsec = modificationTime % 1000000000; + + if (my_utimensat_func == NULL) { + JNU_ThrowInternalError(env, "my_utimensat_func is NULL"); + return; + } + RESTARTABLE((*my_utimensat_func)(fd, path, ×[0], flags), err); + + if (err == -1) { + throwUnixException(env, errno); + } +#else + JNU_ThrowInternalError(env, "should not reach here"); +#endif +} + JNIEXPORT jlong JNICALL Java_sun_nio_fs_UnixNativeDispatcher_opendir0(JNIEnv* env, jclass this, jlong pathAddress) diff --git a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java index 0c07c9ab9db..d71b9407bbc 100644 --- a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, 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 @@ -22,10 +22,13 @@ */ /* @test - * @bug 8181493 8231174 + * @bug 8181493 8231174 8343417 * @summary Verify that nanosecond precision is maintained for file timestamps * @requires (os.family == "linux") | (os.family == "mac") | (os.family == "windows") + * @library ../.. /test/lib + * @build jdk.test.lib.Platform * @modules java.base/sun.nio.fs:+open + * @run main SetTimesNanos */ import java.io.IOException; @@ -36,15 +39,19 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileTime; +import java.util.List; import java.util.Set; -import java.util.concurrent.TimeUnit; + +import static java.nio.file.LinkOption.*; +import static java.util.concurrent.TimeUnit.*; + +import jdk.test.lib.Platform; +import jtreg.SkippedException; public class SetTimesNanos { - private static final boolean IS_WINDOWS = - System.getProperty("os.name").startsWith("Windows"); public static void main(String[] args) throws Exception { - if (!IS_WINDOWS) { + if (!Platform.isWindows()) { // Check whether futimens() system call is supported Class unixNativeDispatcherClass = Class.forName("sun.nio.fs.UnixNativeDispatcher"); @@ -52,8 +59,7 @@ public class SetTimesNanos { unixNativeDispatcherClass.getDeclaredMethod("futimensSupported"); futimensSupported.setAccessible(true); if (!(boolean)futimensSupported.invoke(null)) { - System.err.println("futimens() not supported; skipping test"); - return; + throw new SkippedException("futimens() not supported"); } } @@ -63,30 +69,34 @@ public class SetTimesNanos { System.out.format("FileStore: \"%s\" on %s (%s)%n", dir, store.name(), store.type()); - Set testedTypes = IS_WINDOWS ? + Set testedTypes = Platform.isWindows() ? Set.of("NTFS") : Set.of("apfs", "ext4", "xfs", "zfs"); if (!testedTypes.contains(store.type())) { - System.err.format("%s not in %s; skipping test", store.type(), testedTypes); - return; + throw new SkippedException(store.type() + " not in " + testedTypes); } testNanos(dir); Path file = Files.createFile(dir.resolve("test.dat")); testNanos(file); + + if (Platform.isLinux()) { + testNanosLink(false); + testNanosLink(true); + } } private static void testNanos(Path path) throws IOException { // Set modification and access times // Time stamp = "2017-01-01 01:01:01.123456789"; long timeNanos = 1_483_261_261L*1_000_000_000L + 123_456_789L; - FileTime pathTime = FileTime.from(timeNanos, TimeUnit.NANOSECONDS); + FileTime pathTime = FileTime.from(timeNanos, NANOSECONDS); BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class); view.setTimes(pathTime, pathTime, null); // Windows file time resolution is 100ns so truncate - if (IS_WINDOWS) { + if (Platform.isWindows()) { timeNanos = 100L*(timeNanos/100L); } @@ -99,7 +109,7 @@ public class SetTimesNanos { FileTime[] times = new FileTime[] {attrs.lastModifiedTime(), attrs.lastAccessTime()}; for (int i = 0; i < timeNames.length; i++) { - long nanos = times[i].to(TimeUnit.NANOSECONDS); + long nanos = times[i].to(NANOSECONDS); if (nanos != timeNanos) { throw new RuntimeException("Expected " + timeNames[i] + " timestamp to be '" + timeNanos + "', but was '" + @@ -107,4 +117,42 @@ public class SetTimesNanos { } } } + + private static void testNanosLink(boolean absolute) throws IOException { + System.out.println("absolute: " + absolute); + + var target = Path.of("target"); + var symlink = Path.of("symlink"); + if (absolute) + symlink = symlink.toAbsolutePath(); + + try { + Files.createFile(target); + Files.createSymbolicLink(symlink, target); + + var newTime = FileTime.from(1730417633157646106L, NANOSECONDS); + System.out.println("newTime: " + newTime.to(NANOSECONDS)); + + for (Path p : List.of(target, symlink)) { + System.out.println("p: " + p); + + var view = Files.getFileAttributeView(p, + BasicFileAttributeView.class, NOFOLLOW_LINKS); + view.setTimes(newTime, newTime, null); + var attrs = view.readAttributes(); + + if (!attrs.lastAccessTime().equals(newTime)) + throw new RuntimeException("Last access time " + + attrs.lastAccessTime() + + " != " + newTime); + if (!attrs.lastAccessTime().equals(newTime)) + throw new RuntimeException("Last modified time " + + attrs.lastModifiedTime() + + " != " + newTime); + } + } finally { + Files.deleteIfExists(target); + Files.deleteIfExists(symlink); + } + } }