8148023: File.createTempFile is not adhering to the contract regarding file name lengths
authorbpb
Tue, 20 Dec 2016 11:46:09 -0800
changeset 42777 a94fc33e9866
parent 42776 bd88c3c48915
child 42778 3c8baf9075f1
8148023: File.createTempFile is not adhering to the contract regarding file name lengths Summary: Truncate the prefix, suffix, random characters per the specification Reviewed-by: rriggs
jdk/make/mapfiles/libjava/mapfile-vers
jdk/src/java.base/share/classes/java/io/File.java
jdk/src/java.base/share/classes/java/io/FileSystem.java
jdk/src/java.base/unix/classes/java/io/UnixFileSystem.java
jdk/src/java.base/unix/native/libjava/UnixFileSystem_md.c
jdk/src/java.base/windows/classes/java/io/WinNTFileSystem.java
jdk/src/java.base/windows/native/libjava/WinNTFileSystem_md.c
jdk/test/java/io/File/createTempFile/NameTooLong.java
--- a/jdk/make/mapfiles/libjava/mapfile-vers	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/make/mapfiles/libjava/mapfile-vers	Tue Dec 20 11:46:09 2016 -0800
@@ -114,6 +114,7 @@
 		Java_java_io_UnixFileSystem_getBooleanAttributes0;
 		Java_java_io_UnixFileSystem_getLastModifiedTime;
 		Java_java_io_UnixFileSystem_getLength;
+		Java_java_io_UnixFileSystem_getNameMax0;
 		Java_java_io_UnixFileSystem_getSpace;
 		Java_java_io_UnixFileSystem_initIDs;
 		Java_java_io_UnixFileSystem_list;
--- a/jdk/src/java.base/share/classes/java/io/File.java	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/share/classes/java/io/File.java	Tue Dec 20 11:46:09 2016 -0800
@@ -1903,20 +1903,72 @@
 
         // file name generation
         private static final SecureRandom random = new SecureRandom();
+        private static int shortenSubName(int subNameLength, int excess,
+            int nameMin) {
+            int newLength = Math.max(nameMin, subNameLength - excess);
+            if (newLength < subNameLength) {
+                return newLength;
+            }
+            return subNameLength;
+        }
         static File generateFile(String prefix, String suffix, File dir)
             throws IOException
         {
             long n = random.nextLong();
+            String nus = Long.toUnsignedString(n);
 
             // Use only the file name from the supplied prefix
             prefix = (new File(prefix)).getName();
-            String name = prefix + Long.toUnsignedString(n) + suffix;
+
+            int prefixLength = prefix.length();
+            int nusLength = nus.length();
+            int suffixLength = suffix.length();;
+
+            String name;
+            int nameMax = fs.getNameMax(dir.getPath());
+            int excess = prefixLength + nusLength + suffixLength - nameMax;
+            if (excess <= 0) {
+                name = prefix + nus + suffix;
+            } else {
+                // Name exceeds the maximum path component length: shorten it
+
+                // Attempt to shorten the prefix length to no less then 3
+                prefixLength = shortenSubName(prefixLength, excess, 3);
+                excess = prefixLength + nusLength + suffixLength - nameMax;
+
+                if (excess > 0) {
+                    // Attempt to shorten the suffix length to no less than
+                    // 0 or 4 depending on whether it begins with a dot ('.')
+                    suffixLength = shortenSubName(suffixLength, excess,
+                        suffix.indexOf(".") == 0 ? 4 : 0);
+                    suffixLength = shortenSubName(suffixLength, excess, 3);
+                    excess = prefixLength + nusLength + suffixLength - nameMax;
+                }
+
+                if (excess > 0 && excess <= nusLength - 5) {
+                    // Attempt to shorten the random character string length
+                    // to no less than 5
+                    nusLength = shortenSubName(nusLength, excess, 5);
+                }
+
+                StringBuilder sb =
+                    new StringBuilder(prefixLength + nusLength + suffixLength);
+                sb.append(prefixLength < prefix.length() ?
+                    prefix.substring(0, prefixLength) : prefix);
+                sb.append(nusLength < nus.length() ?
+                    nus.substring(0, nusLength) : nus);
+                sb.append(suffixLength < suffix.length() ?
+                    suffix.substring(0, suffixLength) : suffix);
+                name = sb.toString();
+            }
+
             File f = new File(dir, name);
             if (!name.equals(f.getName()) || f.isInvalid()) {
                 if (System.getSecurityManager() != null)
                     throw new IOException("Unable to create temporary file");
                 else
-                    throw new IOException("Unable to create temporary file, " + f);
+                    throw new IOException("Unable to create temporary file, "
+                        + name);
             }
             return f;
         }
--- a/jdk/src/java.base/share/classes/java/io/FileSystem.java	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/share/classes/java/io/FileSystem.java	Tue Dec 20 11:46:09 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016, 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
@@ -213,6 +213,13 @@
     /* -- Basic infrastructure -- */
 
     /**
+     * Retrieve the maximum length of a component of a file path.
+     *
+     * @return The maximum length of a file path component.
+     */
+    public abstract int getNameMax(String path);
+
+    /**
      * Compare two abstract pathnames lexicographically.
      */
     public abstract int compare(File f1, File f2);
--- a/jdk/src/java.base/unix/classes/java/io/UnixFileSystem.java	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/unix/classes/java/io/UnixFileSystem.java	Tue Dec 20 11:46:09 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016, 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
@@ -299,6 +299,16 @@
 
     /* -- Basic infrastructure -- */
 
+    private native long getNameMax0(String path);
+
+    public int getNameMax(String path) {
+        long nameMax = getNameMax0(path);
+        if (nameMax > Integer.MAX_VALUE) {
+            nameMax = Integer.MAX_VALUE;
+        }
+        return (int)nameMax;
+    }
+
     public int compare(File f1, File f2) {
         return f1.getPath().compareTo(f2.getPath());
     }
--- a/jdk/src/java.base/unix/native/libjava/UnixFileSystem_md.c	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/unix/native/libjava/UnixFileSystem_md.c	Tue Dec 20 11:46:09 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016, 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
@@ -23,6 +23,7 @@
  * questions.
  */
 
+#include <unistd.h>
 #include <assert.h>
 #include <sys/types.h>
 #include <sys/time.h>
@@ -38,6 +39,10 @@
 #include <dlfcn.h>
 #include <limits.h>
 
+#if defined(__solaris__) && !defined(NAME_MAX)
+#define NAME_MAX MAXNAMLEN
+#endif
+
 #include "jni.h"
 #include "jni_util.h"
 #include "jlong.h"
@@ -487,3 +492,14 @@
     } END_PLATFORM_STRING(env, path);
     return rv;
 }
+
+JNIEXPORT jlong JNICALL
+Java_java_io_UnixFileSystem_getNameMax0(JNIEnv *env, jobject this,
+                                        jstring pathname)
+{
+    jlong length = -1;
+    WITH_PLATFORM_STRING(env, pathname, path) {
+        length = (jlong)pathconf(path, _PC_NAME_MAX);
+    } END_PLATFORM_STRING(env, path);
+    return length != -1 ? length : (jlong)NAME_MAX;
+}
--- a/jdk/src/java.base/windows/classes/java/io/WinNTFileSystem.java	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/windows/classes/java/io/WinNTFileSystem.java	Tue Dec 20 11:46:09 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2016, 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,6 +25,8 @@
 
 package java.io;
 
+import java.io.File;
+import java.nio.file.Path;
 import java.util.Locale;
 import java.util.Properties;
 import sun.security.action.GetPropertyAction;
@@ -627,6 +629,27 @@
 
     /* -- Basic infrastructure -- */
 
+    // Obtain maximum file component length from GetVolumeInformation which
+    // expects the path to be null or a root component ending in a backslash
+    private native int getNameMax0(String path);
+
+    public int getNameMax(String path) {
+        String s = null;
+        if (path != null) {
+            File f = new File(path);
+            if (f.isAbsolute()) {
+                Path root = f.toPath().getRoot();
+                if (root != null) {
+                    s = root.toString();
+                    if (!s.endsWith("\\")) {
+                        s = s + "\\";
+                    }
+                }
+            }
+        }
+        return getNameMax0(s);
+    }
+
     @Override
     public int compare(File f1, File f2) {
         return f1.getPath().compareToIgnoreCase(f2.getPath());
--- a/jdk/src/java.base/windows/native/libjava/WinNTFileSystem_md.c	Tue Dec 20 10:49:50 2016 +0100
+++ b/jdk/src/java.base/windows/native/libjava/WinNTFileSystem_md.c	Tue Dec 20 11:46:09 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2016, 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
@@ -887,3 +887,42 @@
     free(pathbuf);
     return rv;
 }
+
+// pathname is expected to be either null or to contain the root
+// of the path terminated by a backslash
+JNIEXPORT jint JNICALL
+Java_java_io_WinNTFileSystem_getNameMax0(JNIEnv *env, jobject this,
+                                         jstring pathname)
+{
+    BOOL res = 0;
+    DWORD maxComponentLength;
+
+    if (pathname == NULL) {
+            res = GetVolumeInformationW(NULL,
+                                        NULL,
+                                        0,
+                                        NULL,
+                                        &maxComponentLength,
+                                        NULL,
+                                        NULL,
+                                        0);
+    } else {
+        WITH_UNICODE_STRING(env, pathname, path) {
+            res = GetVolumeInformationW(path,
+                                        NULL,
+                                        0,
+                                        NULL,
+                                        &maxComponentLength,
+                                        NULL,
+                                        NULL,
+                                        0);
+        } END_UNICODE_STRING(env, path);
+    }
+
+    if (res == 0) {
+        JNU_ThrowIOExceptionWithLastError(env,
+            "Could not get maximum component length");
+    }
+
+    return (jint)maxComponentLength;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/File/createTempFile/NameTooLong.java	Tue Dec 20 11:46:09 2016 -0800
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016, 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 8148023
+ * @summary Verify that createTempFile() will not fail for long component names.
+ */
+
+import java.io.File;
+import java.io.IOException;
+
+public class NameTooLong {
+    public static void main(String[] args) {
+        String[][] prefixSuffix = new String[][] {
+            new String[] {"1234567890123456789012345678901234567xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx89012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890","txt"},
+            new String[] {"prefix","1234567890123456789012345678901234567xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx89012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890.txt"},
+            new String[] {"prefix",".txt1234567890123456789012345678901234567xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx89012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"}
+        };
+
+        int failures = 0;
+        int index = 0;
+        for (String[] ps : prefixSuffix) {
+            File f;
+            try {
+                f = File.createTempFile(ps[0], ps[1]);
+                String s = f.toPath().getFileName().toString();
+                if (!s.startsWith(ps[0].substring(0, 3))) {
+                    System.err.printf("%s did not start with %s%n", s,
+                        ps[0].substring(0, 3));
+                    failures++;
+                }
+                if (ps[1].startsWith(".")
+                    && !s.contains(ps[1].substring(0, 4))) {
+                    System.err.printf("%s did not contain %s%n", s,
+                        ps[1].substring(0, 4));;
+                    failures++;
+                }
+            } catch (IOException e) {
+                failures++;
+                System.err.println();
+                e.printStackTrace();
+                System.err.println();
+            }
+            index++;
+        }
+
+        if (failures != 0) {
+            throw new RuntimeException("Test failed!");
+        }
+    }
+}