8220793: (fs) No support for changing modification time of symlink

Reviewed-by: alanb, rriggs
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 6d43c0f..b7df6cc 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, 2015, 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
@@ -73,15 +73,23 @@
 
             boolean haveFd = false;
             boolean useFutimes = false;
+            boolean useLutimes = false;
             int fd = -1;
             try {
-                fd = file.openForAttributeAccess(followLinks);
-                if (fd != -1) {
-                    haveFd = true;
-                    useFutimes = futimesSupported();
+                if (!followLinks) {
+                    useLutimes = lutimesSupported() &&
+                        UnixFileAttributes.get(file, false).isSymbolicLink();
+                }
+                if (!useLutimes) {
+                    fd = file.openForAttributeAccess(followLinks);
+                    if (fd != -1) {
+                        haveFd = true;
+                        useFutimes = futimesSupported();
+                    }
                 }
             } catch (UnixException x) {
-                if (x.errno() != UnixConstants.ENXIO) {
+                if (!(x.errno() == UnixConstants.ENXIO ||
+                     (x.errno() == UnixConstants.ELOOP && useLutimes))) {
                     x.rethrowAsIOException(file);
                 }
             }
@@ -112,6 +120,8 @@
                 try {
                     if (useFutimes) {
                         futimes(fd, accessValue, modValue);
+                    } else if (useLutimes) {
+                        lutimes(file, accessValue, modValue);
                     } else {
                         utimes(file, accessValue, modValue);
                     }
@@ -131,6 +141,8 @@
                     try {
                         if (useFutimes) {
                             futimes(fd, accessValue, modValue);
+                        } else if (useLutimes) {
+                            lutimes(file, accessValue, modValue);
                         } 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 e6a62fa..f100821 100644
--- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java
+++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2015, 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
@@ -401,7 +401,7 @@
     static native void fchmod(int fd, int mode) throws UnixException;
 
     /**
-     * utimes(conar char* path, const struct timeval times[2])
+     * utimes(const char* path, const struct timeval times[2])
      */
     static void utimes(UnixPath path, long times0, long times1)
         throws UnixException
@@ -417,11 +417,27 @@
         throws UnixException;
 
     /**
-     * futimes(int fildes,, const struct timeval times[2])
+     * futimes(int fildes, const struct timeval times[2])
      */
     static native void futimes(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)
+        throws UnixException
+    {
+        NativeBuffer buffer = copyToNativeBuffer(path);
+        try {
+            lutimes0(buffer.address(), times0, times1);
+        } finally {
+            buffer.release();
+        }
+    }
+    private static native void lutimes0(long pathAddress, long times0, long times1)
+        throws UnixException;
+
+    /**
      * DIR *opendir(const char* dirname)
      */
     static long opendir(UnixPath path) throws UnixException {
@@ -578,9 +594,10 @@
     /**
      * Capabilities
      */
-    private static final int SUPPORTS_OPENAT        = 1 << 1;    // syscalls
+    private static final int SUPPORTS_OPENAT        = 1 << 1;  // syscalls
     private static final int SUPPORTS_FUTIMES       = 1 << 2;
-    private static final int SUPPORTS_BIRTHTIME     = 1 << 16;   // other features
+    private static final int SUPPORTS_LUTIMES       = 1 << 4;
+    private static final int SUPPORTS_BIRTHTIME     = 1 << 16; // other features
     private static final int capabilities;
 
     /**
@@ -598,6 +615,13 @@
     }
 
     /**
+     * Supports lutimes
+     */
+    static boolean lutimesSupported() {
+        return (capabilities & SUPPORTS_LUTIMES) != 0;
+    }
+
+    /**
      * Supports file birth (creation) time attribute
      */
     static boolean birthtimeSupported() {
diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c
index 1d92bcb..51ec40a 100644
--- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c
+++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2018, 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
@@ -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 lutimes_func(const char *, const struct timeval *);
 typedef DIR* fdopendir_func(int);
 
 static openat64_func* my_openat64_func = NULL;
@@ -150,6 +151,7 @@
 static unlinkat_func* my_unlinkat_func = NULL;
 static renameat_func* my_renameat_func = NULL;
 static futimesat_func* my_futimesat_func = NULL;
+static lutimes_func* my_lutimes_func = NULL;
 static fdopendir_func* my_fdopendir_func = NULL;
 
 /**
@@ -269,7 +271,10 @@
 #endif
     my_unlinkat_func = (unlinkat_func*) dlsym(RTLD_DEFAULT, "unlinkat");
     my_renameat_func = (renameat_func*) dlsym(RTLD_DEFAULT, "renameat");
+#ifndef _ALLBSD_SOURCE
     my_futimesat_func = (futimesat_func*) dlsym(RTLD_DEFAULT, "futimesat");
+    my_lutimes_func = (lutimes_func*) dlsym(RTLD_DEFAULT, "lutimes");
+#endif
 #if defined(_AIX)
     my_fdopendir_func = (fdopendir_func*) dlsym(RTLD_DEFAULT, "fdopendir64");
 #else
@@ -282,13 +287,16 @@
         my_fstatat64_func = (fstatat64_func*)&fstatat64_wrapper;
 #endif
 
-    /* supports futimes or futimesat */
+    /* supports futimes or futimesat and/or lutimes */
 
 #ifdef _ALLBSD_SOURCE
     capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FUTIMES;
+    capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_LUTIMES;
 #else
     if (my_futimesat_func != NULL)
         capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FUTIMES;
+    if (my_lutimes_func != NULL)
+        capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_LUTIMES;
 #endif
 
     /* supports openat, etc. */
@@ -675,7 +683,7 @@
     RESTARTABLE(futimes(filedes, &times[0]), err);
 #else
     if (my_futimesat_func == NULL) {
-        JNU_ThrowInternalError(env, "my_ftimesat_func is NULL");
+        JNU_ThrowInternalError(env, "my_futimesat_func is NULL");
         return;
     }
     RESTARTABLE((*my_futimesat_func)(filedes, NULL, &times[0]), err);
@@ -685,6 +693,34 @@
     }
 }
 
+JNIEXPORT void JNICALL
+Java_sun_nio_fs_UnixNativeDispatcher_lutimes0(JNIEnv* env, jclass this,
+    jlong pathAddress, jlong accessTime, jlong modificationTime)
+{
+    int err;
+    struct timeval times[2];
+    const char* path = (const char*)jlong_to_ptr(pathAddress);
+
+    times[0].tv_sec = accessTime / 1000000;
+    times[0].tv_usec = accessTime % 1000000;
+
+    times[1].tv_sec = modificationTime / 1000000;
+    times[1].tv_usec = modificationTime % 1000000;
+
+#ifdef _ALLBSD_SOURCE
+    RESTARTABLE(lutimes(path, &times[0]), err);
+#else
+    if (my_lutimes_func == NULL) {
+        JNU_ThrowInternalError(env, "my_lutimes_func is NULL");
+        return;
+    }
+    RESTARTABLE((*my_lutimes_func)(path, &times[0]), err);
+#endif
+    if (err == -1) {
+        throwUnixException(env, errno);
+    }
+}
+
 JNIEXPORT jlong JNICALL
 Java_sun_nio_fs_UnixNativeDispatcher_opendir0(JNIEnv* env, jclass this,
     jlong pathAddress)
diff --git a/test/jdk/java/nio/file/Files/SymlinkTime.java b/test/jdk/java/nio/file/Files/SymlinkTime.java
new file mode 100644
index 0000000..89590cf
--- /dev/null
+++ b/test/jdk/java/nio/file/Files/SymlinkTime.java
@@ -0,0 +1,121 @@
+/*
+ * 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 8220793
+ * @summary Unit test for updating access and modification times of symlinks
+ * @requires (os.family == "linux" | os.family == "mac" | os.family == "windows")
+ * @library ..
+ * @build SymlinkTime
+ * @run main/othervm SymlinkTime
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileTime;
+
+public class SymlinkTime {
+    public static void main(String[] args) throws IOException {
+        Path dir = TestUtil.createTemporaryDirectory();
+        if (!TestUtil.supportsLinks(dir)) {
+            System.out.println("Links not supported: skipping test");
+            return;
+        }
+
+        try {
+            // Create file and symbolic link to it
+            final Path file = dir.resolve("file");
+            final Path link = dir.resolve("link");
+            Files.createFile(file);
+            try {
+                // Delay creating the link to get different time attributes
+                Thread.currentThread().sleep(5000);
+            } catch (InterruptedException ignored) {
+            }
+            Files.createSymbolicLink(link, file);
+
+            // Save file modification and access times
+            BasicFileAttributeView view = Files.getFileAttributeView(link,
+                BasicFileAttributeView.class);
+            BasicFileAttributes attr = view.readAttributes();
+            printTimes("Original file times", attr);
+            FileTime fileModTime = attr.lastModifiedTime();
+            FileTime fileAccTime = attr.lastAccessTime();
+
+            // Read link modification and access times
+            view = Files.getFileAttributeView(link,
+                BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
+            attr = view.readAttributes();
+            printTimes("Original link times", attr);
+
+            // Set new base time and offset increment
+            long base = 1000000000000L; // 2001-09-09T01:46:40Z
+            long delta = 1000*60L;
+
+            // Set new link modification and access times
+            FileTime linkModTime = FileTime.fromMillis(base + delta);
+            FileTime linkAccTime = FileTime.fromMillis(base + 2L*delta);
+            view.setTimes(linkModTime, linkAccTime, null);
+
+            // Verify link modification and access times updated correctly
+            view = Files.getFileAttributeView(link,
+                BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
+            attr = view.readAttributes();
+            printTimes("Updated link times", attr);
+            check("Link", attr, linkModTime, linkAccTime);
+
+            // Verify file modification and access times unchanged
+            view = Files.getFileAttributeView(file,
+                BasicFileAttributeView.class);
+            attr = view.readAttributes();
+            printTimes("File times", attr);
+            check("File", attr, fileModTime, fileAccTime);
+        } finally {
+            TestUtil.removeAll(dir);
+        }
+    }
+
+    private static void check(String pathType, BasicFileAttributes attr,
+        FileTime modTimeExpected, FileTime accTimeExpected) {
+        if (!attr.lastModifiedTime().equals(modTimeExpected) ||
+            !attr.lastAccessTime().equals(accTimeExpected)) {
+            String message = String.format(
+                "%s - modification time: expected %s, actual %s;%n" +
+                "access time: expected %s, actual %s.%n", pathType,
+                modTimeExpected, attr.lastModifiedTime(),
+                accTimeExpected, attr.lastAccessTime());
+            throw new RuntimeException(message);
+        }
+    }
+
+    private static void printTimes(String label, BasicFileAttributes attr) {
+        System.out.format
+            ("%s%ncreation:     %s%nmodification: %s%naccess:       %s%n%n",
+            label, attr.creationTime(), attr.lastModifiedTime(),
+            attr.lastAccessTime());
+    }
+}