mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-06 14:10:36 +00:00
8350869: os::stat doesn't follow symlinks on Windows
Reviewed-by: dholmes, mgronlun
This commit is contained in:
parent
d9b6e4b132
commit
85b24c3c4e
@ -4626,6 +4626,63 @@ static void set_path_prefix(char* buf, LPCWSTR* prefix, int* prefix_off, bool* n
|
||||
}
|
||||
}
|
||||
|
||||
// This method checks if a wide path is actually a symbolic link
|
||||
static bool is_symbolic_link(const wchar_t* wide_path) {
|
||||
WIN32_FIND_DATAW fd;
|
||||
HANDLE f = ::FindFirstFileW(wide_path, &fd);
|
||||
if (f != INVALID_HANDLE_VALUE) {
|
||||
const bool result = fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && fd.dwReserved0 == IO_REPARSE_TAG_SYMLINK;
|
||||
if (::FindClose(f) == 0) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("is_symbolic_link() failed to FindClose: GetLastError->%ld.", errno);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("is_symbolic_link() failed to FindFirstFileW: GetLastError->%ld.", errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// This method dereferences a symbolic link
|
||||
static WCHAR* get_path_to_target(const wchar_t* wide_path) {
|
||||
HANDLE hFile = CreateFileW(wide_path, GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("get_path_to_target() failed to CreateFileW: GetLastError->%ld.", errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Returned value includes the terminating null character.
|
||||
const size_t target_path_size = ::GetFinalPathNameByHandleW(hFile, nullptr, 0,
|
||||
FILE_NAME_NORMALIZED);
|
||||
if (target_path_size == 0) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("get_path_to_target() failed to GetFinalPathNameByHandleW: GetLastError->%ld.", errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WCHAR* path_to_target = NEW_C_HEAP_ARRAY(WCHAR, target_path_size, mtInternal);
|
||||
|
||||
// The returned size is the length excluding the terminating null character.
|
||||
const size_t res = ::GetFinalPathNameByHandleW(hFile, path_to_target, static_cast<DWORD>(target_path_size),
|
||||
FILE_NAME_NORMALIZED);
|
||||
if (res != target_path_size - 1) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("get_path_to_target() failed to GetFinalPathNameByHandleW: GetLastError->%ld.", errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (::CloseHandle(hFile) == 0) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("get_path_to_target() failed to CloseHandle: GetLastError->%ld.", errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return path_to_target;
|
||||
}
|
||||
|
||||
// Returns the given path as an absolute wide path in unc format. The returned path is null
|
||||
// on error (with err being set accordingly) and should be freed via os::free() otherwise.
|
||||
// additional_space is the size of space, in wchar_t, the function will additionally add to
|
||||
@ -4694,15 +4751,35 @@ int os::stat(const char *path, struct stat *sbuf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
WIN32_FILE_ATTRIBUTE_DATA file_data;;
|
||||
BOOL bret = ::GetFileAttributesExW(wide_path, GetFileExInfoStandard, &file_data);
|
||||
os::free(wide_path);
|
||||
const bool is_symlink = is_symbolic_link(wide_path);
|
||||
WCHAR* path_to_target = nullptr;
|
||||
|
||||
if (is_symlink) {
|
||||
path_to_target = get_path_to_target(wide_path);
|
||||
if (path_to_target == nullptr) {
|
||||
// it is a symbolic link, but we failed to resolve it,
|
||||
// errno has been set in the call to get_path_to_target(),
|
||||
// no need to overwrite it
|
||||
os::free(wide_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
WIN32_FILE_ATTRIBUTE_DATA file_data;;
|
||||
BOOL bret = ::GetFileAttributesExW(is_symlink ? path_to_target : wide_path, GetFileExInfoStandard, &file_data);
|
||||
|
||||
// if getting attributes failed, GetLastError should be called immediately after that
|
||||
if (!bret) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("os::stat() failed to GetFileAttributesExW: GetLastError->%ld.", errno);
|
||||
os::free(wide_path);
|
||||
os::free(path_to_target);
|
||||
return -1;
|
||||
}
|
||||
|
||||
os::free(wide_path);
|
||||
os::free(path_to_target);
|
||||
|
||||
file_attribute_data_to_stat(sbuf, file_data);
|
||||
return 0;
|
||||
}
|
||||
@ -4887,12 +4964,30 @@ int os::open(const char *path, int oflag, int mode) {
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
int fd = ::_wopen(wide_path, oflag | O_BINARY | O_NOINHERIT, mode);
|
||||
os::free(wide_path);
|
||||
|
||||
const bool is_symlink = is_symbolic_link(wide_path);
|
||||
WCHAR* path_to_target = nullptr;
|
||||
|
||||
if (is_symlink) {
|
||||
path_to_target = get_path_to_target(wide_path);
|
||||
if (path_to_target == nullptr) {
|
||||
// it is a symbolic link, but we failed to resolve it,
|
||||
// errno has been set in the call to get_path_to_target(),
|
||||
// no need to overwrite it
|
||||
os::free(wide_path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int fd = ::_wopen(is_symlink ? path_to_target : wide_path, oflag | O_BINARY | O_NOINHERIT, mode);
|
||||
|
||||
// if opening files failed, GetLastError should be called immediately after that
|
||||
if (fd == -1) {
|
||||
errno = ::GetLastError();
|
||||
log_debug(os)("os::open() failed to _wopen: GetLastError->%ld.", errno);
|
||||
}
|
||||
os::free(wide_path);
|
||||
os::free(path_to_target);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
102
test/hotspot/jtreg/runtime/LoadClass/TestSymlinkLoad.java
Normal file
102
test/hotspot/jtreg/runtime/LoadClass/TestSymlinkLoad.java
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2025, 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 loading class via symlink on windows
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run testng/othervm TestSymlinkLoad
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import jdk.test.lib.compiler.CompilerUtils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.util.FileUtils;
|
||||
import org.testng.SkipException;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class TestSymlinkLoad {
|
||||
|
||||
@Test
|
||||
public void testSymlinkClassLoading() throws Exception {
|
||||
Path sourceDir = Paths.get(System.getProperty("test.src"), "test-classes");
|
||||
|
||||
String subPath = "compiled";
|
||||
Path destDir = Paths.get(System.getProperty("test.classes"), subPath);
|
||||
|
||||
CompilerUtils.compile(sourceDir, destDir);
|
||||
|
||||
String bootCP = "-Xbootclasspath/a:" + destDir.toString();
|
||||
|
||||
String className = "Hello";
|
||||
|
||||
// try to load a class itself directly, i.e. not via a symlink
|
||||
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
|
||||
bootCP, className);
|
||||
|
||||
// make sure it runs as expected
|
||||
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||
output.shouldContain("Hello World")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
// create a symlink to the classfile in a subdir with a given name
|
||||
Path classFile = Path.of(destDir + File.separator + className + ".class");
|
||||
final String subdir = "remote";
|
||||
final String pathToFolderForSymlink = destDir + File.separator + subdir + File.separator;
|
||||
createLinkInSeparateFolder(pathToFolderForSymlink, classFile, className);
|
||||
|
||||
// try to load class via its symlink, which is in a different directory
|
||||
pb = ProcessTools.createLimitedTestJavaProcessBuilder(
|
||||
bootCP + File.separator + subdir, className);
|
||||
output = new OutputAnalyzer(pb.start());
|
||||
output.shouldContain("Hello World")
|
||||
.shouldHaveExitValue(0);
|
||||
|
||||
// remove the subdir
|
||||
FileUtils.deleteFileTreeWithRetry(Path.of(pathToFolderForSymlink));
|
||||
}
|
||||
|
||||
public static void createLinkInSeparateFolder(final String pathToFolderForSymlink, final Path target, final String className) throws IOException {
|
||||
File theDir = new File(pathToFolderForSymlink);
|
||||
if (!theDir.exists()) {
|
||||
theDir.mkdirs();
|
||||
}
|
||||
Path link = Paths.get(pathToFolderForSymlink, className + ".class");
|
||||
if (Files.exists(link)) {
|
||||
Files.delete(link);
|
||||
}
|
||||
try {
|
||||
Files.createSymbolicLink(link, target);
|
||||
} catch (UnsupportedOperationException uoe) {
|
||||
throw new SkipException("Symbolic link creation not supported.", uoe);
|
||||
} catch (IOException ioe) {
|
||||
throw new SkipException("Probably insufficient privileges to create symbolic links (Windows)", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user