8350869: os::stat doesn't follow symlinks on Windows

Reviewed-by: dholmes, mgronlun
This commit is contained in:
Anton Artemov 2025-05-22 07:56:34 +00:00 committed by SendaoYan
parent d9b6e4b132
commit 85b24c3c4e
2 changed files with 202 additions and 5 deletions

View File

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

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