8181493: (fs) Files.readAttributes(path, BasicFileAttributes.class) should preserve nano second time stamps
authorbpb
Mon, 29 Jul 2019 08:48:52 -0700
changeset 57568 460ac76019f4
parent 57567 b000362a89a0
child 57569 be47f3ccdf12
8181493: (fs) Files.readAttributes(path, BasicFileAttributes.class) should preserve nano second time stamps Reviewed-by: alanb, lancea
src/java.base/unix/classes/sun/nio/fs/UnixFileAttributeViews.java
src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java
src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java
src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c
test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.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);
--- 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);
+            }
         }
     }
 
--- 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() {
--- 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, &times[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)
 {
--- /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 + "'");
+            }
+        }
+    }
+}