# HG changeset patch # User rschmelter # Date 1562235236 25200 # Thu Jul 04 03:13:56 2019 -0700 # Node ID fb7df24173b9ed1b18a25b72a0584ba77834f46c # Parent 51ad5ced897aeb2c0ff72d37d84dd3e713747198 8191521: handle long relative path specified in -Xbootclasspath/a on windows Reviewed-by: diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -4279,128 +4279,135 @@ } } -// 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); +// Returns the given path as an absolute wide path in unc format.The returned path is NULL +// on error (with er being set accordingly) and should be freed via os::free() otherwise. +// additional_space is the number of additionally allocated wchars after the terminating L'\0'. +static wchar_t* wide_abs_unc_path(char const* path, errno_t & err, int additional_space = 0) { + if (path == NULL) { + err = ENOENT; + return NULL; + } + + size_t path_len = strlen(path); + // Need to allocate at least room for 3 characters, since os::native_path transforms C: to C:. + char* buf = (char*) os::malloc(1 + MAX2((size_t) 3, path_len), mtInternal); + wchar_t* result = NULL; + + if (buf == NULL) { + err = ENOMEM; + } else { + memcpy(buf, path, path_len + 1); + os::native_path(buf); + + wchar_t* prefix; + int prefix_off = 0; + bool is_abs = true; + bool needs_fullpath = true; + + if (::isalpha(buf[0]) && !::IsDBCSLeadByte(buf[0]) && buf[1] == ':' && buf[2] == '\\') { + prefix = L"\\\\?\\"; + } else if (buf[0] == '\\' && buf[1] == '\\') { + assert(buf[2] != '\\'); + + if (buf[2] == '?' && buf[3] == '\\') { + prefix = L""; + needs_fullpath = false; } else { - err = ENOMEM; + prefix = L"\\\\?\\UNC"; + prefix_off = 1; // Overwrite the first char with the prefix. } } 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; + is_abs = false; + prefix = L"\\\\?\\"; + } + + size_t buf_len = strlen(buf); + size_t prefix_len = wcslen(prefix); + size_t full_path_size = is_abs ? 1 + buf_len : JVM_MAXPATHLEN; + size_t result_size = prefix_len + full_path_size - prefix_off; + result = (wchar_t*) os::malloc(sizeof(wchar_t) * (additional_space + result_size), mtInternal); + + if (result == NULL) { + err = ENOMEM; + } else { + size_t converted_chars; + wchar_t* path_start = result + prefix_len - prefix_off; + err = ::mbstowcs_s(&converted_chars, path_start, buf_len + 1, buf, buf_len); + + if ((err == ERROR_SUCCESS) && needs_fullpath) { + wchar_t* tmp = (wchar_t*) os::malloc(sizeof(wchar_t) * full_path_size, mtInternal); + + if (tmp == NULL) { + err = ENOMEM; + } else { + if (!_wfullpath(tmp, path_start, full_path_size)) { + err = ENOENT; + } else { + ::memcpy(path_start, tmp, (1 + wcslen(tmp)) * sizeof(wchar_t)); + } + + os::free(tmp); + } + } + + memcpy(result, prefix, sizeof(wchar_t) * prefix_len); + + // Remove trailing pathsep (not for \\?\:\, since it would make it relative) + size_t result_len = wcslen(result); + + if (result[result_len - 1] == L'\\') { + if (!(::iswalpha(result[4]) && result[5] == L':' && result_len == 7)) { + result[result_len - 1] = L'\0'; + } } } - } 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); + } + + os::free(buf); + + if (err != ERROR_SUCCESS) { + os::free(result); + result = NULL; + } + + return result; } int os::stat(const char *path, struct stat *sbuf) { - char* pathbuf = (char*)os::strdup(path, mtInternal); - if (pathbuf == NULL) { - errno = ENOMEM; + errno_t err; + wchar_t* wide_path = wide_abs_unc_path(path, err); + + if (wide_path == NULL) { + errno = err; return -1; } - 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; + + WIN32_FILE_ATTRIBUTE_DATA file_data;; + BOOL bret = ::GetFileAttributesExW(wide_path, GetFileExInfoStandard, &file_data); + os::free(wide_path); + + if (!bret) { + errno = ::GetLastError(); + return -1; + } + + file_attribute_data_to_stat(sbuf, file_data); + return 0; } static HANDLE create_read_only_file_handle(const char* file) { - if (file == NULL) { - return INVALID_HANDLE_VALUE; - } - - char* nativepath = (char*)os::strdup(file, mtInternal); - if (nativepath == NULL) { - errno = ENOMEM; + errno_t err; + wchar_t* wide_path = wide_abs_unc_path(file, err); + + if (wide_path == NULL) { + errno = err; return INVALID_HANDLE_VALUE; } - os::native_path(nativepath); - - size_t len = strlen(nativepath); - HANDLE handle = INVALID_HANDLE_VALUE; - - if (len < MAX_PATH) { - handle = ::CreateFile(nativepath, 0, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - } else { - errno_t err = ERROR_SUCCESS; - wchar_t* wfile = create_unc_path(nativepath, err); - if (err != ERROR_SUCCESS) { - if (wfile != NULL) { - destroy_unc_path(wfile); - } - os::free(nativepath); - return INVALID_HANDLE_VALUE; - } - handle = ::CreateFileW(wfile, 0, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - destroy_unc_path(wfile); - } - - os::free(nativepath); + + HANDLE handle = ::CreateFileW(wide_path, 0, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + os::free(wide_path); + return handle; } @@ -4449,7 +4456,6 @@ return result; } - #define FT2INT64(ft) \ ((jlong)((jlong)(ft).dwHighDateTime << 32 | (julong)(ft).dwLowDateTime)) @@ -4554,38 +4560,22 @@ return DontYieldALot; } -// This method is a slightly reworked copy of JDK's sysOpen -// from src/windows/hpi/src/sys_api_md.c - int os::open(const char *path, int oflag, int mode) { - char* pathbuf = (char*)os::strdup(path, mtInternal); - if (pathbuf == NULL) { - errno = ENOMEM; + errno_t err; + wchar_t* wide_path = wide_abs_unc_path(path, err); + + if (wide_path == NULL) { + errno = err; return -1; } - 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; + int fd = ::_wopen(wide_path, oflag | O_BINARY | O_NOINHERIT, mode); + os::free(wide_path); + + if (fd == -1) { + errno = ::GetLastError(); + } + + return fd; } FILE* os::open(int fd, const char* mode) { @@ -4594,37 +4584,26 @@ // Is a (classpath) directory empty? bool os::dir_is_empty(const char* path) { - char* search_path = (char*)os::malloc(strlen(path) + 3, mtInternal); - if (search_path == NULL) { - errno = ENOMEM; - return false; - } - strcpy(search_path, path); - os::native_path(search_path); - // Append "*", or possibly "\\*", to path - if (search_path[1] == ':' && - (search_path[2] == '\0' || - (search_path[2] == '\\' && search_path[3] == '\0'))) { - // No '\\' needed for cases like "Z:" or "Z:\" - strcat(search_path, "*"); - } - else { - strcat(search_path, "\\*"); - } - errno_t err = ERROR_SUCCESS; - wchar_t* wpath = create_unc_path(search_path, err); - if (err != ERROR_SUCCESS) { - if (wpath != NULL) { - destroy_unc_path(wpath); - } - os::free(search_path); + errno_t err; + wchar_t* wide_path = wide_abs_unc_path(path, err, 2); + + if (wide_path == NULL) { errno = err; return false; } + + // Make sure we end with "\\*" + if (wide_path[wcslen(wide_path) - 1] == L'\\') { + wcscat(wide_path, L"*"); + } else { + wcscat(wide_path, L"\\*"); + } + WIN32_FIND_DATAW fd; - HANDLE f = ::FindFirstFileW(wpath, &fd); - destroy_unc_path(wpath); + HANDLE f = ::FindFirstFileW(wide_path, &fd); + os::free(wide_path); bool is_empty = true; + if (f != INVALID_HANDLE_VALUE) { while (is_empty && ::FindNextFileW(f, &fd)) { // An empty directory contains only the current directory file @@ -4635,8 +4614,10 @@ } } FindClose(f); - } - os::free(search_path); + } else { + errno = ::GetLastError(); + } + return is_empty; } diff --git a/test/hotspot/gtest/runtime/test_os_windows.cpp b/test/hotspot/gtest/runtime/test_os_windows.cpp --- a/test/hotspot/gtest/runtime/test_os_windows.cpp +++ b/test/hotspot/gtest/runtime/test_os_windows.cpp @@ -85,4 +85,554 @@ << "Failed to allocate memory at requested location " << expected_location << " of size " << expected_allocation_size; } + +// Test which tries to find out if the os::stat, os::open and os::dir_is_empty methods +// can handle long path names correctly. +enum ModsFilter { + Allow_None = 0, + Allow_Sep_Mods = 1, + Allow_Dot_Path = 2, + Allow_Dot_Dot_Path = 4, + Allow_All = Allow_Sep_Mods | Allow_Dot_Path | Allow_Dot_Dot_Path +}; + +enum Mode { + TEST, + EXAMPLES, + BENCH +}; + +static ModsFilter mods_filter = Allow_All; +static int mods_per_path = 50; +static Mode mode = TEST; + +static void get_current_dir_w(wchar_t* path, size_t size) { + DWORD count = GetCurrentDirectoryW((DWORD) size, path); + EXPECT_GT((int) count, 0) << "Failed to get current directory: " << GetLastError(); + EXPECT_LT((size_t) count, size) << "Buffer too small for current directory: " << size; +} + +#define WITH_ABS_PATH(path) \ + wchar_t abs_path[JVM_MAXPATHLEN]; \ + wchar_t cwd[JVM_MAXPATHLEN]; \ + get_current_dir_w(cwd, JVM_MAXPATHLEN); \ + wsprintfW(abs_path, L"\\\\?\\%ls\\%ls", cwd, (path)) + +static bool file_exists_w(const wchar_t* path) { + WIN32_FILE_ATTRIBUTE_DATA file_data; + return ::GetFileAttributesExW(path, GetFileExInfoStandard, &file_data); +} + +static void create_rel_directory_w(const wchar_t* path) { + WITH_ABS_PATH(path); + EXPECT_FALSE(file_exists_w(abs_path)) << "Can't create directory: \"" << path << "\" already exists"; + BOOL result = CreateDirectoryW(abs_path, NULL); + EXPECT_TRUE(result) << "Failed to create directory \"" << path << "\" " << GetLastError(); +} + +static void delete_empty_rel_directory_w(const wchar_t* path) { + WITH_ABS_PATH(path); + EXPECT_TRUE(file_exists_w(abs_path)) << "Can't delete directory: \"" << path << "\" does not exists"; + BOOL result = RemoveDirectoryW(abs_path); + EXPECT_TRUE(result) << "Failed to delete directory \"" << path << "\": " << GetLastError(); +} + +static void create_rel_file_w(const wchar_t* path) { + WITH_ABS_PATH(path); + EXPECT_FALSE(file_exists_w(abs_path)) << "Can't create file: \"" << path << "\" already exists"; + HANDLE h = CreateFileW(abs_path, 0, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + EXPECT_NE(h, INVALID_HANDLE_VALUE) << "Failed to create file \"" << path << "\": " << GetLastError(); + CloseHandle(h); +} + +static void delete_rel_file_w(const wchar_t* path) { + WITH_ABS_PATH(path); + EXPECT_TRUE(file_exists_w(abs_path)) << "Can't delete file: \"" << path << "\" does not exists"; + BOOL result = DeleteFileW(abs_path); + EXPECT_TRUE(result) << "Failed to delete file \"" << path << "\": " << GetLastError(); +} + +static bool convert_to_cstring(char* c_str, size_t size, wchar_t* w_str) { + size_t converted; + errno_t err = wcstombs_s(&converted, c_str, size, w_str, size - 1); + EXPECT_EQ(err, ERROR_SUCCESS) << "Could not convert \"" << w_str << "\" to c-string"; + + return err == ERROR_SUCCESS; +} + +static const wchar_t* sep_replacements[] = { + L"\\", L"\\/", L"/", L"//", L"\\\\/\\", L"//\\/" +}; + +static wchar_t* my_wcscpy_s(wchar_t* dest, size_t size, wchar_t* start, const wchar_t* to_copy) { + size_t already_used = dest - start; + size_t len = wcslen(to_copy); + + if (already_used + len < size) { + wcscpy_s(dest, size - already_used, to_copy); + } + + return dest + wcslen(to_copy); +} + +static bool unnormalize_path(wchar_t* result, size_t size, bool is_dir, const wchar_t* path) { + wchar_t* dest = result; + const wchar_t* src = path; + const wchar_t* path_start; + + if (wcsncmp(src, L"\\\\?\\UNC\\", 8) == 0) { + path_start = src + 8; + } else if (wcsncmp(src, L"\\\\?\\", 4) == 0) { + if (src[5] == L':') { + path_start = src + 6; + } else { + path_start = wcschr(src + 4, L'\\'); + } + } else if (wcsncmp(src, L"\\\\", 2) == 0) { + path_start = wcschr(src + 2, L'?'); + + if (path_start == NULL) { + path_start = wcschr(src + 2, L'\\'); + } else { + path_start = wcschr(path_start, L'\\'); + } + } else { + path_start = wcschr(src + 1, L'\\'); + } + + bool allow_sep_change = (mods_filter & Allow_Sep_Mods) && (os::random() & 1) == 0; + bool allow_dot_change = (mods_filter & Allow_Dot_Path) && (os::random() & 1) == 0; + bool allow_dotdot_change = (mods_filter & Allow_Dot_Dot_Path) && (os::random() & 1) == 0; + + while ((*src != L'\0') && (result + size > dest)) { + wchar_t c = *src; + *dest = c; + ++src; + ++dest; + + if (c == L'\\') { + if (allow_sep_change && (os::random() & 3) == 3) { + int i = os::random() % (sizeof(sep_replacements) / sizeof(sep_replacements[0])); + + if (i >= 0) { + const wchar_t* replacement = sep_replacements[i]; + dest = my_wcscpy_s(dest - 1, size, result, replacement); + } + } else if (path_start != NULL) { + if (allow_dotdot_change && (src > path_start + 1) && ((os::random() & 7) == 7)) { + wchar_t const* last_sep = src - 2; + + while (last_sep[0] != L'\\') { + --last_sep; + } + + if (last_sep > path_start) { + dest = my_wcscpy_s(dest, size, result, L"../"); + src = last_sep + 1; + } + } else if (allow_dot_change && (src > path_start + 1) && ((os::random() & 7) == 7)) { + dest = my_wcscpy_s(dest, size, result, L"./"); + } + } + } + } + + while (is_dir && ((os::random() & 15) == 1)) { + dest = my_wcscpy_s(dest, size, result, L"/"); + } + + if (result + size > dest) { + *dest = L'\0'; + } + + // Use this modification only if not too close to the max size. + return result + size - 10 > dest; +} + +static void check_dir_impl(wchar_t* path, bool should_be_empty) { + char buf[JVM_MAXPATHLEN]; + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + struct stat st; + EXPECT_EQ(os::stat(buf, &st), 0) << "os::stat failed for \"" << path << "\""; + EXPECT_EQ(st.st_mode & S_IFMT, S_IFDIR) << "\"" << path << "\" is not a directory according to os::stat"; + errno = ERROR_SUCCESS; + bool is_empty = os::dir_is_empty(buf); + errno_t err = errno; + EXPECT_EQ(is_empty, should_be_empty) << "os::dir_is_empty assumed \"" << path << "\" is " + << (should_be_empty ? "not ": "") << "empty"; + EXPECT_EQ(err, ERROR_SUCCESS) << "os::dir_is_empty failed for \"" << path << "\"with errno " << err; + } +} + +static void check_file_impl(wchar_t* path) { + char buf[JVM_MAXPATHLEN]; + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + struct stat st; + EXPECT_EQ(os::stat(buf, &st), 0) << "os::stat failed for \"" << path << "\""; + EXPECT_EQ(st.st_mode & S_IFMT, S_IFREG) << "\"" << path << "\" is not a regular file according to os::stat"; + int fd = os::open(buf, O_RDONLY, 0); + EXPECT_NE(fd, -1) << "os::open failed for \"" << path << "\" with errno " << errno; + if (fd >= 0) { + ::close(fd); + } + } +} + +static void check_file_not_present_impl(wchar_t* path) { + char buf[JVM_MAXPATHLEN]; + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + struct stat st; + int stat_ret; + EXPECT_EQ(stat_ret = os::stat(buf, &st), -1) << "os::stat did not fail for \"" << path << "\""; + if (stat_ret != -1) { + // Only check open if stat not already failed. + int fd = os::open(buf, O_RDONLY, 0); + EXPECT_EQ(fd, -1) << "os::open did not fail for \"" << path << "\""; + if (fd >= 0) { + ::close(fd); + } + } + } +} + +static void check_dir(wchar_t* path, bool should_be_empty) { + check_dir_impl(path, should_be_empty); + + for (int i = 0; mods_filter != Allow_None && i < mods_per_path; ++i) { + wchar_t tmp[JVM_MAXPATHLEN]; + if (unnormalize_path(tmp, JVM_MAXPATHLEN, true, path)) { + check_dir_impl(tmp, should_be_empty); + } + } +} + +static void check_file(wchar_t* path) { + check_file_impl(path); + + for (int i = 0; mods_filter != Allow_None && i < mods_per_path; ++i) { + wchar_t tmp[JVM_MAXPATHLEN]; + if (unnormalize_path(tmp, JVM_MAXPATHLEN, false, path)) { + check_file_impl(tmp); + } + } +} + +static void check_file_not_present(wchar_t* path) { + check_file_not_present_impl(path); + + for (int i = 0; mods_filter != Allow_None && i < mods_per_path; ++i) { + wchar_t tmp[JVM_MAXPATHLEN]; + if (unnormalize_path(tmp, JVM_MAXPATHLEN, false, path)) { + check_file_not_present_impl(tmp); + } + } +} + +static void record_path(char const* name, char const* len_name, wchar_t* path) { + char buf[JVM_MAXPATHLEN]; + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + ::testing::Test::RecordProperty(name, buf); + snprintf(buf, JVM_MAXPATHLEN, "%d", (int) wcslen(path)); + ::testing::Test::RecordProperty(len_name, buf); + } +} + +static void bench_path(wchar_t* path) { + char buf[JVM_MAXPATHLEN]; + int reps = 100000; + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + jlong wtime[2]; + + for (int t = 0; t < 2; ++t) { + wtime[t] = os::javaTimeNanos(); + + for (int i = 0; i < reps; ++i) { + bool succ = false; + size_t buf_len = strlen(buf); + wchar_t* w_path = (wchar_t*) os::malloc(sizeof(wchar_t) * (buf_len + 1), mtInternal); + + if (w_path != NULL) { + size_t converted_chars; + if (::mbstowcs_s(&converted_chars, w_path, buf_len + 1, buf, buf_len) == ERROR_SUCCESS) { + if (t == 1) { + wchar_t* tmp = (wchar_t*) os::malloc(sizeof(wchar_t) * JVM_MAXPATHLEN, mtInternal); + + if (tmp) { + if (_wfullpath(tmp, w_path, JVM_MAXPATHLEN)) { + succ = true; + } + + os::free(tmp); + } + if (!succ) { + printf("Failed fullpathing \"%s\"\n", buf); + return; + } + succ = false; + } + HANDLE h = ::CreateFileW(w_path, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + succ = true; + } + } + } + + os::free(w_path); + if (!succ) { + printf("Failed getting W*attr. \"%s\"\n", buf); + return; + } + } + + wtime[t] = os::javaTimeNanos() - wtime[t]; + } + + jlong ctime = os::javaTimeNanos(); + + for (int i = 0; i < reps; ++i) { + HANDLE h = ::CreateFileA(buf, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (h == INVALID_HANDLE_VALUE) { + return; + } + + ::CloseHandle(h); + } + + ctime = os::javaTimeNanos() - ctime; + + printf("\"%s\" %f us for *A, %f us for *W, %f us for *W with fullpath\n", buf, + 0.001 * ctime / reps, 0.001 * wtime[0] / reps, 0.001 * wtime[1] / reps); + } +} + +static void print_attr_result_for_path(wchar_t* path) { + WIN32_FILE_ATTRIBUTE_DATA file_data; + struct stat st; + char buf[JVM_MAXPATHLEN]; + + printf("Checking \"%ls\" (%d chars):\n", path, (int) wcslen(path)); + BOOL bret = ::GetFileAttributesExW(path, GetFileExInfoStandard, &file_data); + printf("GetFileAttributesExW() %s\n", bret ? "success" : "failed"); + + if (convert_to_cstring(buf, JVM_MAXPATHLEN, path)) { + bret = ::GetFileAttributesExA(buf, GetFileExInfoStandard, &file_data); + printf("GetFileAttributesExA() %s\n", bret ? "success" : "failed"); + + bool succ = os::stat(buf, &st) != -1; + printf("os::stat() %s\n", succ ? "success" : "failed"); + } +} + +static void print_attr_result(wchar_t* format, ...) { + va_list argptr; + wchar_t buf[JVM_MAXPATHLEN]; + + va_start(argptr, format); + wvsprintfW(buf, format, argptr); + print_attr_result_for_path(buf); + va_end(argptr); +} + +#define RECORD_PATH(name) record_path(#name, #name "Len", name) +#define NAME_PART_50 L"01234567890123456789012345678901234567890123456789" +#define NAME_PART_250 NAME_PART_50 NAME_PART_50 NAME_PART_50 NAME_PART_50 NAME_PART_50 + +TEST_VM(os_windows, handle_long_paths) { + static wchar_t cwd[JVM_MAXPATHLEN]; + static wchar_t nearly_long_rel_path[JVM_MAXPATHLEN]; + static wchar_t long_rel_path[JVM_MAXPATHLEN]; + static wchar_t empty_dir_rel_path[JVM_MAXPATHLEN]; + static wchar_t not_empty_dir_rel_path[JVM_MAXPATHLEN]; + static wchar_t file_rel_path[JVM_MAXPATHLEN]; + static wchar_t nearly_long_file_rel_path[JVM_MAXPATHLEN]; + static wchar_t nearly_long_path[JVM_MAXPATHLEN]; + static wchar_t empty_dir_path[JVM_MAXPATHLEN]; + static wchar_t not_empty_dir_path[JVM_MAXPATHLEN]; + static wchar_t nearly_long_file_path[JVM_MAXPATHLEN]; + static wchar_t file_path[JVM_MAXPATHLEN]; + static wchar_t nearly_long_unc_path[JVM_MAXPATHLEN]; + static wchar_t empty_dir_unc_path[JVM_MAXPATHLEN]; + static wchar_t not_empty_dir_unc_path[JVM_MAXPATHLEN]; + static wchar_t nearly_long_file_unc_path[JVM_MAXPATHLEN]; + static wchar_t file_unc_path[JVM_MAXPATHLEN]; + static wchar_t root_dir_path[JVM_MAXPATHLEN]; + static wchar_t root_rel_dir_path[JVM_MAXPATHLEN]; + + wchar_t* dir_prefix = L"os_windows_long_paths_dir_"; + wchar_t* empty_dir_name = L"empty_directory_with_long_path"; + wchar_t* not_empty_dir_name = L"not_empty_directory_with_long_path"; + wchar_t* file_name = L"file"; + wchar_t dir_letter; + + get_current_dir_w(cwd, sizeof(cwd) / sizeof(wchar_t)); + dir_letter = (cwd[1] == L':' ? cwd[0] : L'\0'); + int cwd_len = (int) wcslen(cwd); + int dir_prefix_len = (int) wcslen(dir_prefix); + int rel_path_len = MAX2(dir_prefix_len, 235 - cwd_len); + + memcpy(nearly_long_rel_path, dir_prefix, sizeof(wchar_t) * dir_prefix_len); + + for (int i = dir_prefix_len; i < rel_path_len; ++i) { + nearly_long_rel_path[i] = L'L'; + } + + nearly_long_rel_path[rel_path_len] = L'\0'; + + wsprintfW(long_rel_path, L"%ls\\%ls", nearly_long_rel_path, NAME_PART_250); + wsprintfW(empty_dir_rel_path, L"%ls\\%ls", nearly_long_rel_path, empty_dir_name); + wsprintfW(not_empty_dir_rel_path, L"%ls\\%ls", nearly_long_rel_path, not_empty_dir_name); + wsprintfW(nearly_long_file_rel_path, L"%ls\\%ls", nearly_long_rel_path, file_name); + wsprintfW(file_rel_path, L"%ls\\%ls\\%ls", nearly_long_rel_path, not_empty_dir_name, file_name); + wsprintfW(nearly_long_path, L"\\\\?\\%ls\\%ls", cwd, nearly_long_rel_path); + wsprintfW(empty_dir_path, L"%ls\\%ls", nearly_long_path, empty_dir_name); + wsprintfW(not_empty_dir_path, L"%ls\\%ls", nearly_long_path, not_empty_dir_name); + wsprintfW(nearly_long_file_path, L"%ls\\%ls", nearly_long_path, file_name); + wsprintfW(file_path, L"%ls\\%ls\\%ls", nearly_long_path, not_empty_dir_name, file_name); + wsprintfW(nearly_long_unc_path, L"\\\\localhost\\%lc$\\%s", dir_letter, nearly_long_path + 7); + wsprintfW(empty_dir_unc_path, L"%s\\%s", nearly_long_unc_path, empty_dir_name); + wsprintfW(not_empty_dir_unc_path, L"%s\\%s", nearly_long_unc_path, not_empty_dir_name); + wsprintfW(nearly_long_file_unc_path, L"%ls\\%ls", nearly_long_unc_path, file_name); + wsprintfW(file_unc_path, L"%s\\%s\\%s", nearly_long_unc_path, not_empty_dir_name, file_name); + wsprintfW(root_dir_path, L"%lc:\\", dir_letter); + wsprintfW(root_rel_dir_path, L"%lc:", dir_letter); + + RECORD_PATH(long_rel_path); + RECORD_PATH(nearly_long_rel_path); + RECORD_PATH(nearly_long_path); + RECORD_PATH(nearly_long_unc_path); + RECORD_PATH(empty_dir_rel_path); + RECORD_PATH(empty_dir_path); + RECORD_PATH(empty_dir_unc_path); + RECORD_PATH(not_empty_dir_rel_path); + RECORD_PATH(not_empty_dir_path); + RECORD_PATH(not_empty_dir_unc_path); + RECORD_PATH(nearly_long_file_rel_path); + RECORD_PATH(nearly_long_file_path); + RECORD_PATH(nearly_long_file_unc_path); + RECORD_PATH(file_rel_path); + RECORD_PATH(file_path); + RECORD_PATH(file_unc_path); + + create_rel_directory_w(nearly_long_rel_path); + create_rel_directory_w(long_rel_path); + create_rel_directory_w(empty_dir_rel_path); + create_rel_directory_w(not_empty_dir_rel_path); + create_rel_file_w(nearly_long_file_rel_path); + create_rel_file_w(file_rel_path); + + if (mode == BENCH) { + bench_path(nearly_long_path + 4); + bench_path(nearly_long_rel_path); + bench_path(nearly_long_file_path + 4); + bench_path(nearly_long_file_rel_path); + } else if (mode == EXAMPLES) { + printf("Working directory: %ls", cwd); + + if (dir_letter) { + static wchar_t top_buf[JVM_MAXPATHLEN]; + wchar_t* top_path = wcschr(cwd + 3, L'\\'); + + if (top_path) { + size_t top_len = (top_path - cwd) - 3; + + memcpy(top_buf, cwd + 3, top_len * 2); + top_buf[top_len] = L'\0'; + top_path = top_buf; + } + + print_attr_result(L"%lc:\\", dir_letter); + print_attr_result(L"%lc:\\.\\", dir_letter); + + if (top_path) { + print_attr_result(L"%lc:\\%ls\\..\\%ls\\", dir_letter, top_path, top_path); + } + + print_attr_result(L"%lc:", dir_letter); + print_attr_result(L"%lc:.", dir_letter); + print_attr_result(L"\\\\?\\UNC\\localhost\\%lc$\\", dir_letter); + print_attr_result(L"\\\\?\\UNC\\\\localhost\\%lc$\\", dir_letter); + print_attr_result(nearly_long_unc_path); + print_attr_result(L"%ls\\.\\", nearly_long_unc_path); + print_attr_result(L"%ls\\..\\%ls", nearly_long_unc_path, nearly_long_rel_path); + print_attr_result(L"\\\\?\\UNC\\%ls", nearly_long_unc_path + 2); + print_attr_result(file_unc_path); + print_attr_result(L"%ls\\%ls\\..\\%ls\\%ls", nearly_long_unc_path, not_empty_dir_name, not_empty_dir_name, file_name); + print_attr_result(L"%ls\\%ls\\.\\%ls", nearly_long_unc_path, not_empty_dir_name, file_name); + print_attr_result(L"\\\\?\\UNC\\%ls", file_unc_path + 2); + print_attr_result(L"\\\\?\\UNC\\%ls\\%ls\\.\\%ls", nearly_long_unc_path + 2, not_empty_dir_name, file_name); + print_attr_result(L"\\\\?\\UNC\\%ls\\%ls\\..\\%ls\\%ls", nearly_long_unc_path + 2, not_empty_dir_name, not_empty_dir_name, file_name); + } + + print_attr_result(nearly_long_rel_path); + print_attr_result(L"%ls\\.\\", nearly_long_rel_path); + print_attr_result(L"%ls\\..\\%ls", nearly_long_rel_path, nearly_long_rel_path); + print_attr_result(L"%\\\\?\\%ls", nearly_long_rel_path); + print_attr_result(L"\\\\?\\%ls\\.\\", nearly_long_rel_path); + print_attr_result(L"\\\\?\\%ls\\..\\%ls", nearly_long_rel_path, nearly_long_rel_path); + + print_attr_result(nearly_long_path + 4); + print_attr_result(L"%ls\\.\\", nearly_long_path + 4); + print_attr_result(L"%ls\\..\\%ls", nearly_long_path + 4, nearly_long_rel_path); + print_attr_result(nearly_long_path); + print_attr_result(L"%ls\\.\\", nearly_long_path); + print_attr_result(L"%ls\\..\\%ls", nearly_long_path, nearly_long_rel_path); + } else { + // Check relative paths + check_dir(nearly_long_rel_path, false); + check_dir(long_rel_path, true); + check_dir(empty_dir_rel_path, true); + check_dir(not_empty_dir_rel_path, false); + check_file(nearly_long_file_rel_path); + check_file(file_rel_path); + + // Check absolute paths + if (dir_letter) { + check_dir(root_dir_path, false); + check_dir(root_rel_dir_path, false); + } + + check_dir(cwd, false); + check_dir(nearly_long_path + 4, false); + check_dir(empty_dir_path + 4, true); + check_dir(not_empty_dir_path + 4, false); + check_file(nearly_long_file_path + 4); + check_file(file_path + 4); + + // Check UNC paths + if (dir_letter) { + check_dir(nearly_long_unc_path, false); + check_dir(empty_dir_unc_path, true); + check_dir(not_empty_dir_unc_path, false); + check_file(nearly_long_file_unc_path); + check_file(file_unc_path); + } + + // Check handling of :/../:/path/... + // The other drive letter should not overwrite the original one. + if (dir_letter) { + static wchar_t tmp[JVM_MAXPATHLEN]; + wchar_t* other_letter = dir_letter == L'D' ? L"C" : L"D"; + wsprintfW(tmp, L"%2ls\\..\\%ls:%ls", nearly_long_file_path, other_letter, nearly_long_file_path + 2); + check_file_not_present(tmp); + wsprintfW(tmp, L"%2ls\\..\\%ls:%ls", file_path, other_letter, file_path + 2); + check_file_not_present(tmp); + } + } + + delete_rel_file_w(file_rel_path); + delete_rel_file_w(nearly_long_file_rel_path); + delete_empty_rel_directory_w(not_empty_dir_rel_path); + delete_empty_rel_directory_w(empty_dir_rel_path); + delete_empty_rel_directory_w(long_rel_path); + delete_empty_rel_directory_w(nearly_long_rel_path); +} + #endif diff --git a/test/hotspot/jtreg/runtime/LoadClass/LongBCP.java b/test/hotspot/jtreg/runtime/LoadClass/LongBCP.java --- a/test/hotspot/jtreg/runtime/LoadClass/LongBCP.java +++ b/test/hotspot/jtreg/runtime/LoadClass/LongBCP.java @@ -126,13 +126,8 @@ 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); - } + output.shouldContain("Hello World") + .shouldHaveExitValue(0); // total relative path length exceeds MAX_PATH destDir = Paths.get(destDir.toString(), "yyyyyyyy"); @@ -144,12 +139,7 @@ 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); - } + output.shouldContain("Hello World") + .shouldHaveExitValue(0); } }