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:
Calvin Cheung 2017-11-21 09:49:52 -08:00
parent 28b3c9f104
commit e7ff0665e4
4 changed files with 283 additions and 47 deletions
src/hotspot
os/windows
share/classfile
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;

@ -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);
}
}
}

@ -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");
}
}