# HG changeset patch # User bpb # Date 1564415332 25200 # Node ID 460ac76019f402d46c49caa1869b5439f0c9886b # Parent b000362a89a0c0fca2afe2c0590ef1220938596e 8181493: (fs) Files.readAttributes(path, BasicFileAttributes.class) should preserve nano second time stamps Reviewed-by: alanb, lancea diff -r b000362a89a0 -r 460ac76019f4 src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java Mon Jul 29 10:34:20 2019 -0400 +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java Mon Jul 29 08:48:52 2019 -0700 @@ -73,6 +73,7 @@ boolean haveFd = false; boolean useFutimes = false; + boolean useFutimens = false; boolean useLutimes = false; int fd = -1; try { @@ -84,7 +85,9 @@ fd = file.openForAttributeAccess(followLinks); if (fd != -1) { haveFd = true; - useFutimes = futimesSupported(); + if (!(useFutimens = futimensSupported())) { + useFutimes = futimesSupported(); + } } } } catch (UnixException x) { @@ -112,13 +115,17 @@ } } - // uptime times - long modValue = lastModifiedTime.to(TimeUnit.MICROSECONDS); - long accessValue= lastAccessTime.to(TimeUnit.MICROSECONDS); + // update times + TimeUnit timeUnit = useFutimens ? + TimeUnit.NANOSECONDS : TimeUnit.MICROSECONDS; + long modValue = lastModifiedTime.to(timeUnit); + long accessValue= lastAccessTime.to(timeUnit); boolean retry = false; try { - if (useFutimes) { + if (useFutimens) { + futimens(fd, accessValue, modValue); + } else if (useFutimes) { futimes(fd, accessValue, modValue); } else if (useLutimes) { lutimes(file, accessValue, modValue); @@ -139,7 +146,9 @@ if (modValue < 0L) modValue = 0L; if (accessValue < 0L) accessValue= 0L; try { - if (useFutimes) { + if (useFutimens) { + futimens(fd, accessValue, modValue); + } else if (useFutimes) { futimes(fd, accessValue, modValue); } else if (useLutimes) { lutimes(file, accessValue, modValue); diff -r b000362a89a0 -r 460ac76019f4 src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java Mon Jul 29 10:34:20 2019 -0400 +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java Mon Jul 29 08:48:52 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2019, 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 @@ -109,11 +109,15 @@ if (nsec == 0) { return FileTime.from(sec, TimeUnit.SECONDS); } else { - // truncate to microseconds to avoid overflow with timestamps - // way out into the future. We can re-visit this if FileTime - // is updated to define a from(secs,nsecs) method. - long micro = sec*1000000L + nsec/1000L; - return FileTime.from(micro, TimeUnit.MICROSECONDS); + try { + long nanos = Math.addExact(nsec, + Math.multiplyExact(sec, 1_000_000_000L)); + return FileTime.from(nanos, TimeUnit.NANOSECONDS); + } catch (ArithmeticException ignore) { + // truncate to microseconds if nanoseconds overflow + long micro = sec*1_000_000L + nsec/1_000L; + return FileTime.from(micro, TimeUnit.MICROSECONDS); + } } } diff -r b000362a89a0 -r 460ac76019f4 src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java --- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java Mon Jul 29 10:34:20 2019 -0400 +++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java Mon Jul 29 08:48:52 2019 -0700 @@ -419,6 +419,11 @@ static native void futimes(int fd, long times0, long times1) throws UnixException; /** + * futimens(int fildes, const struct timespec times[2]) + */ + static native void futimens(int fd, long times0, long times1) throws UnixException; + + /** * lutimes(const char* path, const struct timeval times[2]) */ static void lutimes(UnixPath path, long times0, long times1) @@ -593,7 +598,8 @@ */ private static final int SUPPORTS_OPENAT = 1 << 1; // syscalls private static final int SUPPORTS_FUTIMES = 1 << 2; - private static final int SUPPORTS_LUTIMES = 1 << 4; + private static final int SUPPORTS_FUTIMENS = 1 << 4; + private static final int SUPPORTS_LUTIMES = 1 << 8; private static final int SUPPORTS_BIRTHTIME = 1 << 16; // other features private static final int capabilities; @@ -612,6 +618,13 @@ } /** + * Supports futimens + */ + static boolean futimensSupported() { + return (capabilities & SUPPORTS_FUTIMENS) != 0; + } + + /** * Supports lutimes */ static boolean lutimesSupported() { diff -r b000362a89a0 -r 460ac76019f4 src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c Mon Jul 29 10:34:20 2019 -0400 +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c Mon Jul 29 08:48:52 2019 -0700 @@ -143,6 +143,7 @@ typedef int unlinkat_func(int, const char*, int); typedef int renameat_func(int, const char*, int, const char*); 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); @@ -151,6 +152,7 @@ static unlinkat_func* my_unlinkat_func = NULL; static renameat_func* my_renameat_func = NULL; 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; @@ -275,6 +277,7 @@ my_futimesat_func = (futimesat_func*) dlsym(RTLD_DEFAULT, "futimesat"); my_lutimes_func = (lutimes_func*) dlsym(RTLD_DEFAULT, "lutimes"); #endif + my_futimens_func = (futimens_func*) dlsym(RTLD_DEFAULT, "futimens"); #if defined(_AIX) my_fdopendir_func = (fdopendir_func*) dlsym(RTLD_DEFAULT, "fdopendir64"); #else @@ -287,7 +290,7 @@ my_fstatat64_func = (fstatat64_func*)&fstatat64_wrapper; #endif - /* supports futimes or futimesat and/or lutimes */ + /* supports futimes or futimesat, futimens, and/or lutimes */ #ifdef _ALLBSD_SOURCE capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FUTIMES; @@ -298,6 +301,8 @@ if (my_lutimes_func != NULL) capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_LUTIMES; #endif + if (my_futimens_func != NULL) + capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FUTIMENS; /* supports openat, etc. */ @@ -694,6 +699,29 @@ } JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_futimens(JNIEnv* env, jclass this, jint filedes, + jlong accessTime, jlong modificationTime) +{ + struct timespec times[2]; + int err = 0; + + 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_futimens_func == NULL) { + JNU_ThrowInternalError(env, "my_futimens_func is NULL"); + return; + } + RESTARTABLE((*my_futimens_func)(filedes, ×[0]), err); + if (err == -1) { + throwUnixException(env, errno); + } +} + +JNIEXPORT void JNICALL Java_sun_nio_fs_UnixNativeDispatcher_lutimes0(JNIEnv* env, jclass this, jlong pathAddress, jlong accessTime, jlong modificationTime) { diff -r b000362a89a0 -r 460ac76019f4 test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java Mon Jul 29 08:48:52 2019 -0700 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8181493 + * @summary Verify that nanosecond precision is maintained for file timestamps + * @requires (os.family == "linux") | (os.family == "mac") | (os.family == "solaris") + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.FileStore; +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.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +public class SetTimesNanos { + public static void main(String[] args) throws IOException, + InterruptedException { + + Path dirPath = Path.of("test"); + Path dir = Files.createDirectory(dirPath); + FileStore store = Files.getFileStore(dir); + System.out.format("FileStore: %s on %s (%s)%n", dir, store.name(), + store.type()); + if (System.getProperty("os.name").toLowerCase().startsWith("mac") && + store.type().equalsIgnoreCase("hfs")) { + System.err.println + ("HFS on macOS does not have nsec timestamps: skipping test"); + return; + } + testNanos(dir); + + Path file = Files.createFile(dir.resolve("test.dat")); + testNanos(file); + } + + 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); + BasicFileAttributeView view = + Files.getFileAttributeView(path, BasicFileAttributeView.class); + view.setTimes(pathTime, pathTime, null); + + // Read attributes + BasicFileAttributes attrs = + Files.readAttributes(path, BasicFileAttributes.class); + + // Check timestamps + String[] timeNames = new String[] {"modification", "access"}; + FileTime[] times = new FileTime[] {attrs.lastModifiedTime(), + attrs.lastAccessTime()}; + for (int i = 0; i < timeNames.length; i++) { + long nanos = times[i].to(TimeUnit.NANOSECONDS); + if (nanos != timeNanos) { + throw new RuntimeException("Expected " + timeNames[i] + + " timestamp to be '" + timeNanos + "', but was '" + + nanos + "'"); + } + } + } +}