8188122: Path length limits on Windows leads to obscure class loading failures
Summary: Used the unicode version of windows API's to handled long paths and avoid using the stat() function.
Reviewed-by: stuefe, iklam
--- a/src/hotspot/os/windows/os_windows.cpp Tue Nov 21 09:20:56 2017 -0800
+++ b/src/hotspot/os/windows/os_windows.cpp Tue Nov 21 09:49:52 2017 -0800
@@ -4061,41 +4061,116 @@
}
}
+// combine the high and low DWORD into a ULONGLONG
+static ULONGLONG make_double_word(DWORD high_word, DWORD low_word) {
+ ULONGLONG value = high_word;
+ value <<= sizeof(high_word) * 8;
+ value |= low_word;
+ return value;
+}
+
+// Transfers data from WIN32_FILE_ATTRIBUTE_DATA structure to struct stat
+static void file_attribute_data_to_stat(struct stat* sbuf, WIN32_FILE_ATTRIBUTE_DATA file_data) {
+ ::memset((void*)sbuf, 0, sizeof(struct stat));
+ sbuf->st_size = (_off_t)make_double_word(file_data.nFileSizeHigh, file_data.nFileSizeLow);
+ sbuf->st_mtime = make_double_word(file_data.ftLastWriteTime.dwHighDateTime,
+ file_data.ftLastWriteTime.dwLowDateTime);
+ sbuf->st_ctime = make_double_word(file_data.ftCreationTime.dwHighDateTime,
+ file_data.ftCreationTime.dwLowDateTime);
+ sbuf->st_atime = make_double_word(file_data.ftLastAccessTime.dwHighDateTime,
+ file_data.ftLastAccessTime.dwLowDateTime);
+ if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ sbuf->st_mode |= S_IFDIR;
+ } else {
+ sbuf->st_mode |= S_IFREG;
+ }
+}
+
+// The following function is adapted from java.base/windows/native/libjava/canonicalize_md.c
+// Creates an UNC path from a single byte path. Return buffer is
+// allocated in C heap and needs to be freed by the caller.
+// Returns NULL on error.
+static wchar_t* create_unc_path(const char* path, errno_t &err) {
+ wchar_t* wpath = NULL;
+ size_t converted_chars = 0;
+ size_t path_len = strlen(path) + 1; // includes the terminating NULL
+ if (path[0] == '\\' && path[1] == '\\') {
+ if (path[2] == '?' && path[3] == '\\'){
+ // if it already has a \\?\ don't do the prefix
+ wpath = (wchar_t*)os::malloc(path_len * sizeof(wchar_t), mtInternal);
+ if (wpath != NULL) {
+ err = ::mbstowcs_s(&converted_chars, wpath, path_len, path, path_len);
+ } else {
+ err = ENOMEM;
+ }
+ } else {
+ // only UNC pathname includes double slashes here
+ wpath = (wchar_t*)os::malloc((path_len + 7) * sizeof(wchar_t), mtInternal);
+ if (wpath != NULL) {
+ ::wcscpy(wpath, L"\\\\?\\UNC\0");
+ err = ::mbstowcs_s(&converted_chars, &wpath[7], path_len, path, path_len);
+ } else {
+ err = ENOMEM;
+ }
+ }
+ } else {
+ wpath = (wchar_t*)os::malloc((path_len + 4) * sizeof(wchar_t), mtInternal);
+ if (wpath != NULL) {
+ ::wcscpy(wpath, L"\\\\?\\\0");
+ err = ::mbstowcs_s(&converted_chars, &wpath[4], path_len, path, path_len);
+ } else {
+ err = ENOMEM;
+ }
+ }
+ return wpath;
+}
+
+static void destroy_unc_path(wchar_t* wpath) {
+ os::free(wpath);
+}
int os::stat(const char *path, struct stat *sbuf) {
- char pathbuf[MAX_PATH];
- if (strlen(path) > MAX_PATH - 1) {
- errno = ENAMETOOLONG;
+ char* pathbuf = (char*)os::strdup(path, mtInternal);
+ if (pathbuf == NULL) {
+ errno = ENOMEM;
return -1;
}
- os::native_path(strcpy(pathbuf, path));
- int ret = ::stat(pathbuf, sbuf);
- if (sbuf != NULL && UseUTCFileTimestamp) {
- // Fix for 6539723. st_mtime returned from stat() is dependent on
- // the system timezone and so can return different values for the
- // same file if/when daylight savings time changes. This adjustment
- // makes sure the same timestamp is returned regardless of the TZ.
- //
- // See:
- // http://msdn.microsoft.com/library/
- // default.asp?url=/library/en-us/sysinfo/base/
- // time_zone_information_str.asp
- // and
- // http://msdn.microsoft.com/library/default.asp?url=
- // /library/en-us/sysinfo/base/settimezoneinformation.asp
- //
- // NOTE: there is a insidious bug here: If the timezone is changed
- // after the call to stat() but before 'GetTimeZoneInformation()', then
- // the adjustment we do here will be wrong and we'll return the wrong
- // value (which will likely end up creating an invalid class data
- // archive). Absent a better API for this, or some time zone locking
- // mechanism, we'll have to live with this risk.
- TIME_ZONE_INFORMATION tz;
- DWORD tzid = GetTimeZoneInformation(&tz);
- int daylightBias =
- (tzid == TIME_ZONE_ID_DAYLIGHT) ? tz.DaylightBias : tz.StandardBias;
- sbuf->st_mtime += (tz.Bias + daylightBias) * 60;
- }
+ os::native_path(pathbuf);
+ int ret;
+ WIN32_FILE_ATTRIBUTE_DATA file_data;
+ // Not using stat() to avoid the problem described in JDK-6539723
+ if (strlen(path) < MAX_PATH) {
+ BOOL bret = ::GetFileAttributesExA(pathbuf, GetFileExInfoStandard, &file_data);
+ if (!bret) {
+ errno = ::GetLastError();
+ ret = -1;
+ }
+ else {
+ file_attribute_data_to_stat(sbuf, file_data);
+ ret = 0;
+ }
+ } else {
+ errno_t err = ERROR_SUCCESS;
+ wchar_t* wpath = create_unc_path(pathbuf, err);
+ if (err != ERROR_SUCCESS) {
+ if (wpath != NULL) {
+ destroy_unc_path(wpath);
+ }
+ os::free(pathbuf);
+ errno = err;
+ return -1;
+ }
+ BOOL bret = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &file_data);
+ if (!bret) {
+ errno = ::GetLastError();
+ ret = -1;
+ } else {
+ file_attribute_data_to_stat(sbuf, file_data);
+ ret = 0;
+ }
+ destroy_unc_path(wpath);
+ }
+ os::free(pathbuf);
return ret;
}
@@ -4208,14 +4283,34 @@
// from src/windows/hpi/src/sys_api_md.c
int os::open(const char *path, int oflag, int mode) {
- char pathbuf[MAX_PATH];
-
- if (strlen(path) > MAX_PATH - 1) {
- errno = ENAMETOOLONG;
+ char* pathbuf = (char*)os::strdup(path, mtInternal);
+ if (pathbuf == NULL) {
+ errno = ENOMEM;
return -1;
}
- os::native_path(strcpy(pathbuf, path));
- return ::open(pathbuf, oflag | O_BINARY | O_NOINHERIT, mode);
+ os::native_path(pathbuf);
+ int ret;
+ if (strlen(path) < MAX_PATH) {
+ ret = ::open(pathbuf, oflag | O_BINARY | O_NOINHERIT, mode);
+ } else {
+ errno_t err = ERROR_SUCCESS;
+ wchar_t* wpath = create_unc_path(pathbuf, err);
+ if (err != ERROR_SUCCESS) {
+ if (wpath != NULL) {
+ destroy_unc_path(wpath);
+ }
+ os::free(pathbuf);
+ errno = err;
+ return -1;
+ }
+ ret = ::_wopen(wpath, oflag | O_BINARY | O_NOINHERIT, mode);
+ if (ret == -1) {
+ errno = ::GetLastError();
+ }
+ destroy_unc_path(wpath);
+ }
+ os::free(pathbuf);
+ return ret;
}
FILE* os::open(int fd, const char* mode) {
--- a/src/hotspot/share/classfile/classLoader.cpp Tue Nov 21 09:20:56 2017 -0800
+++ b/src/hotspot/share/classfile/classLoader.cpp Tue Nov 21 09:49:52 2017 -0800
@@ -262,11 +262,11 @@
ClassFileStream* ClassPathDirEntry::open_stream(const char* name, TRAPS) {
// construct full path name
- char* path = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, JVM_MAXPATHLEN);
- if (jio_snprintf(path, JVM_MAXPATHLEN, "%s%s%s", _dir, os::file_separator(), name) == -1) {
- FREE_RESOURCE_ARRAY(char, path, JVM_MAXPATHLEN);
- return NULL;
- }
+ assert((_dir != NULL) && (name != NULL), "sanity");
+ size_t path_len = strlen(_dir) + strlen(name) + strlen(os::file_separator()) + 1;
+ char* path = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, path_len);
+ int len = jio_snprintf(path, path_len, "%s%s%s", _dir, os::file_separator(), name);
+ assert(len == (int)(path_len - 1), "sanity");
// check if file exists
struct stat st;
if (os::stat(path, &st) == 0) {
@@ -291,7 +291,7 @@
if (UsePerfData) {
ClassLoader::perf_sys_classfile_bytes_read()->inc(num_read);
}
- FREE_RESOURCE_ARRAY(char, path, JVM_MAXPATHLEN);
+ FREE_RESOURCE_ARRAY(char, path, path_len);
// Resource allocated
return new ClassFileStream(buffer,
st.st_size,
@@ -300,7 +300,7 @@
}
}
}
- FREE_RESOURCE_ARRAY(char, path, JVM_MAXPATHLEN);
+ FREE_RESOURCE_ARRAY(char, path, path_len);
return NULL;
}
@@ -381,9 +381,13 @@
if (is_multi_ver) {
int n;
- char* entry_name = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, JVM_MAXPATHLEN);
+ const char* version_entry = "META-INF/versions/";
+ // 10 is the max length of a decimal 32-bit non-negative number
+ // 2 includes the '/' and trailing zero
+ size_t entry_name_len = strlen(version_entry) + 10 + strlen(name) + 2;
+ char* entry_name = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, char, entry_name_len);
if (version > 0) {
- n = jio_snprintf(entry_name, JVM_MAXPATHLEN, "META-INF/versions/%d/%s", version, name);
+ n = jio_snprintf(entry_name, entry_name_len, "%s%d/%s", version_entry, version, name);
entry_name[n] = '\0';
buffer = open_entry((const char*)entry_name, filesize, false, CHECK_NULL);
if (buffer == NULL) {
@@ -392,7 +396,7 @@
}
if (buffer == NULL) {
for (int i = cur_ver; i >= base_version; i--) {
- n = jio_snprintf(entry_name, JVM_MAXPATHLEN, "META-INF/versions/%d/%s", i, name);
+ n = jio_snprintf(entry_name, entry_name_len, "%s%d/%s", version_entry, i, name);
entry_name[n] = '\0';
buffer = open_entry((const char*)entry_name, filesize, false, CHECK_NULL);
if (buffer != NULL) {
@@ -400,7 +404,7 @@
}
}
}
- FREE_RESOURCE_ARRAY(char, entry_name, JVM_MAXPATHLEN);
+ FREE_RESOURCE_ARRAY(char, entry_name, entry_name_len);
}
}
return buffer;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/LoadClass/LongBCP.java Tue Nov 21 09:49:52 2017 -0800
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017, 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
+ * @summary JVM should be able to handle full path (directory path plus
+ * class name) or directory path longer than MAX_PATH specified
+ * in -Xbootclasspath/a on windows.
+ * @library /test/lib
+ * @modules java.base/jdk.internal.misc
+ * java.management
+ * @run main LongBCP
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import jdk.test.lib.Platform;
+import jdk.test.lib.compiler.CompilerUtils;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class LongBCP {
+
+ private static final int MAX_PATH = 260;
+
+ public static void main(String args[]) throws Exception {
+ Path sourceDir = Paths.get(System.getProperty("test.src"), "test-classes");
+ Path classDir = Paths.get(System.getProperty("test.classes"));
+ Path destDir = classDir;
+
+ // create a sub-path so that the destDir length is almost MAX_PATH
+ // so that the full path (with the class name) will exceed MAX_PATH
+ int subDirLen = MAX_PATH - classDir.toString().length() - 2;
+ if (subDirLen > 0) {
+ char[] chars = new char[subDirLen];
+ Arrays.fill(chars, 'x');
+ String subPath = new String(chars);
+ destDir = Paths.get(System.getProperty("test.classes"), subPath);
+ }
+
+ CompilerUtils.compile(sourceDir, destDir);
+
+ String bootCP = "-Xbootclasspath/a:" + destDir.toString();
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
+ bootCP, "Hello");
+
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.shouldContain("Hello World")
+ .shouldHaveExitValue(0);
+
+ // increase the length of destDir to slightly over MAX_PATH
+ destDir = Paths.get(destDir.toString(), "xxxxx");
+ CompilerUtils.compile(sourceDir, destDir);
+
+ bootCP = "-Xbootclasspath/a:" + destDir.toString();
+ pb = ProcessTools.createJavaProcessBuilder(
+ bootCP, "Hello");
+
+ output = new OutputAnalyzer(pb.start());
+ output.shouldContain("Hello World")
+ .shouldHaveExitValue(0);
+
+ // relative path tests
+ // We currently cannot handle relative path specified in the
+ // -Xbootclasspath/a on windows.
+ //
+ // relative path length within the 256 limit
+ char[] chars = new char[255];
+ Arrays.fill(chars, 'y');
+ String subPath = new String(chars);
+ destDir = Paths.get(".", subPath);
+
+ CompilerUtils.compile(sourceDir, destDir);
+
+ bootCP = "-Xbootclasspath/a:" + destDir.toString();
+ pb = ProcessTools.createJavaProcessBuilder(
+ bootCP, "Hello");
+
+ output = new OutputAnalyzer(pb.start());
+ if (!Platform.isWindows()) {
+ output.shouldContain("Hello World")
+ .shouldHaveExitValue(0);
+ } else {
+ output.shouldContain("Could not find or load main class Hello")
+ .shouldHaveExitValue(1);
+ }
+
+ // total relative path length exceeds MAX_PATH
+ destDir = Paths.get(destDir.toString(), "yyyyyyyy");
+
+ CompilerUtils.compile(sourceDir, destDir);
+
+ bootCP = "-Xbootclasspath/a:" + destDir.toString();
+ pb = ProcessTools.createJavaProcessBuilder(
+ bootCP, "Hello");
+
+ output = new OutputAnalyzer(pb.start());
+ if (!Platform.isWindows()) {
+ output.shouldContain("Hello World")
+ .shouldHaveExitValue(0);
+ } else {
+ output.shouldContain("Could not find or load main class Hello")
+ .shouldHaveExitValue(1);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/LoadClass/test-classes/Hello.java Tue Nov 21 09:49:52 2017 -0800
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+public class Hello {
+ public static void main(String args[]) {
+ System.out.println("Hello World");
+ }
+}