8188122: Path length limits on Windows leads to obscure class loading failures
Used the unicode version of windows API's to handled long paths and avoid using the stat() function. Reviewed-by: stuefe, iklam
This commit is contained in:
parent
28b3c9f104
commit
e7ff0665e4
src/hotspot
test/hotspot/jtreg/runtime/LoadClass
@ -4061,41 +4061,116 @@ void os::make_polling_page_readable(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 @@ bool os::dont_yield() {
|
||||
// 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) {
|
||||
|
@ -262,11 +262,11 @@ ClassPathDirEntry::ClassPathDirEntry(const char* dir) : ClassPathEntry() {
|
||||
|
||||
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 @@ ClassFileStream* ClassPathDirEntry::open_stream(const char* name, TRAPS) {
|
||||
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 @@ ClassFileStream* ClassPathDirEntry::open_stream(const char* name, TRAPS) {
|
||||
}
|
||||
}
|
||||
}
|
||||
FREE_RESOURCE_ARRAY(char, path, JVM_MAXPATHLEN);
|
||||
FREE_RESOURCE_ARRAY(char, path, path_len);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -381,9 +381,13 @@ u1* ClassPathZipEntry::open_versioned_entry(const char* name, jint* filesize, TR
|
||||
|
||||
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 @@ u1* ClassPathZipEntry::open_versioned_entry(const char* name, jint* filesize, TR
|
||||
}
|
||||
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 @@ u1* ClassPathZipEntry::open_versioned_entry(const char* name, jint* filesize, TR
|
||||
}
|
||||
}
|
||||
}
|
||||
FREE_RESOURCE_ARRAY(char, entry_name, JVM_MAXPATHLEN);
|
||||
FREE_RESOURCE_ARRAY(char, entry_name, entry_name_len);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
|
127
test/hotspot/jtreg/runtime/LoadClass/LongBCP.java
Normal file
127
test/hotspot/jtreg/runtime/LoadClass/LongBCP.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
10
test/hotspot/jtreg/runtime/LoadClass/test-classes/Hello.java
Normal file
10
test/hotspot/jtreg/runtime/LoadClass/test-classes/Hello.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user