diff --git a/src/java.base/windows/classes/java/io/WinNTFileSystem.java b/src/java.base/windows/classes/java/io/WinNTFileSystem.java index cf58d980bbe..10e02b4ba72 100644 --- a/src/java.base/windows/classes/java/io/WinNTFileSystem.java +++ b/src/java.base/windows/classes/java/io/WinNTFileSystem.java @@ -490,12 +490,26 @@ final class WinNTFileSystem extends FileSystem { return path; return "" + ((char) (c-32)) + ':' + '\\'; } - return canonicalize0(path); + String canonicalPath = canonicalize0(path); + String finalPath = null; + try { + finalPath = getFinalPath(canonicalPath); + } catch (IOException ignored) { + finalPath = canonicalPath; + } + return finalPath; } private native String canonicalize0(String path) throws IOException; + private String getFinalPath(String path) throws IOException { + return getFinalPath0(path); + } + + private native String getFinalPath0(String path) + throws IOException; + /* -- Attribute accessors -- */ diff --git a/src/java.base/windows/native/libjava/WinNTFileSystem_md.c b/src/java.base/windows/native/libjava/WinNTFileSystem_md.c index 43ce9216271..2598eaa6fec 100644 --- a/src/java.base/windows/native/libjava/WinNTFileSystem_md.c +++ b/src/java.base/windows/native/libjava/WinNTFileSystem_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, 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 @@ -46,31 +46,15 @@ static struct { jfieldID path; } ids; -/** - * GetFinalPathNameByHandle is available on Windows Vista and newer - */ -typedef BOOL (WINAPI* GetFinalPathNameByHandleProc) (HANDLE, LPWSTR, DWORD, DWORD); -static GetFinalPathNameByHandleProc GetFinalPathNameByHandle_func; - JNIEXPORT void JNICALL Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls) { - HMODULE handle; jclass fileClass; fileClass = (*env)->FindClass(env, "java/io/File"); CHECK_NULL(fileClass); ids.path = (*env)->GetFieldID(env, fileClass, "path", "Ljava/lang/String;"); CHECK_NULL(ids.path); - - // GetFinalPathNameByHandle requires Windows Vista or newer - if (GetModuleHandleExW((GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | - GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT), - (LPCWSTR)&CreateFileW, &handle) != 0) - { - GetFinalPathNameByHandle_func = (GetFinalPathNameByHandleProc) - GetProcAddress(handle, "GetFinalPathNameByHandleW"); - } } /* -- Path operations -- */ @@ -88,10 +72,6 @@ static WCHAR* getFinalPath(JNIEnv *env, const WCHAR *path) WCHAR *result; DWORD error; - /* Need Windows Vista or newer to get the final path */ - if (GetFinalPathNameByHandle_func == NULL) - return NULL; - h = CreateFileW(path, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE | @@ -109,13 +89,13 @@ static WCHAR* getFinalPath(JNIEnv *env, const WCHAR *path) */ result = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR)); if (result != NULL) { - DWORD len = (*GetFinalPathNameByHandle_func)(h, result, MAX_PATH, 0); + DWORD len = GetFinalPathNameByHandleW(h, result, MAX_PATH, 0); if (len >= MAX_PATH) { /* retry with a buffer of the right size */ WCHAR* newResult = (WCHAR*)realloc(result, (len+1) * sizeof(WCHAR)); if (newResult != NULL) { result = newResult; - len = (*GetFinalPathNameByHandle_func)(h, result, len, 0); + len = GetFinalPathNameByHandleW(h, result, len, 0); } else { len = 0; JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); @@ -351,6 +331,25 @@ Java_java_io_WinNTFileSystem_canonicalizeWithPrefix0(JNIEnv *env, jobject this, return rv; } +JNIEXPORT jstring JNICALL +Java_java_io_WinNTFileSystem_getFinalPath0(JNIEnv* env, jobject this, jstring pathname) { + jstring rv = NULL; + + WITH_UNICODE_STRING(env, pathname, path) { + WCHAR* finalPath = getFinalPath(env, path); + if (finalPath != NULL) { + rv = (*env)->NewString(env, finalPath, (jsize)wcslen(finalPath)); + free(finalPath); + } + } END_UNICODE_STRING(env, path); + + if (rv == NULL && !(*env)->ExceptionCheck(env)) { + JNU_ThrowIOExceptionWithLastError(env, "Bad pathname"); + } + + return rv; +} + /* -- Attribute accessors -- */ /* Check whether or not the file name in "path" is a Windows reserved diff --git a/test/jdk/java/io/File/GetCanonicalPath.java b/test/jdk/java/io/File/GetCanonicalPath.java index e54227e44b4..d69e90fbbfc 100644 --- a/test/jdk/java/io/File/GetCanonicalPath.java +++ b/test/jdk/java/io/File/GetCanonicalPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, 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 @@ -22,17 +22,21 @@ */ /* @test - * @bug 4899022 + * @bug 4899022 8003887 * @summary Look for erroneous representation of drive letter * @run junit GetCanonicalPath */ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; @@ -122,4 +126,88 @@ public class GetCanonicalPath { String path = new File("c:/").getCanonicalPath(); assertFalse(path.length() > 3, "Drive letter incorrectly represented"); } + + // Create a File with the given pathname and return the File as a Path + private static Path createFile(String pathname) throws IOException { + File file = new File(pathname); + file.deleteOnExit(); + return file.toPath(); + } + + private static boolean supportsLinks = true; + private static String linkMessage; + + private static Path link; + private static Path sublink; + private static Path subsub; + + @BeforeAll + static void createSymlinks() throws IOException { + final String DIR = "dir"; + final String SUBDIR = "subdir"; + final String TARGET = "target.txt"; + final String LINK = "link"; + final String SUBLINK = "sublink"; + final String FILE = "file.txt"; + + // Create directories dir/subdir + Path dir = createFile(DIR); + Path subdir = createFile(dir.resolve(SUBDIR).toString()); + Files.createDirectories(subdir); + + // Create file dir/subdir/target.txt + Path target = createFile(subdir.resolve(TARGET).toString()); + Files.createFile(target); + + // Create symbolic link link -> dir + link = createFile(Path.of(LINK).toString()); + try { + Files.createSymbolicLink(link, dir); + } catch (UnsupportedOperationException | IOException x) { + if (OS.WINDOWS.isCurrentOs()) { + supportsLinks = false; + linkMessage = "\"" + x.getMessage() + "\""; + return; + } else { + throw x; + } + } + + sublink = createFile(Path.of(DIR, SUBDIR, SUBLINK).toString()); + Path file = createFile(Path.of(DIR, SUBDIR, FILE).toString()); + Files.createFile(file); + + // Create symbolic link dir/subdir/sublink -> file.txt + Files.createSymbolicLink(sublink, Path.of(FILE)); + sublink.toFile().deleteOnExit(); + + subsub = createFile(Path.of(LINK, SUBDIR, SUBLINK).toString()); + } + + @Test + void linkToDir() throws IOException { + Assumptions.assumeTrue(supportsLinks, linkMessage); + + // Check link evaluates to dir + assertEquals(link.toRealPath().toString(), + link.toFile().getCanonicalPath()); + } + + @Test + void linkToFile() throws IOException { + Assumptions.assumeTrue(supportsLinks, linkMessage); + + // Check sublink evaluates to file.txt + assertEquals(sublink.toRealPath().toString(), + sublink.toFile().getCanonicalPath()); + } + + @Test + void linkToFileInSubdir() throws IOException { + Assumptions.assumeTrue(supportsLinks, linkMessage); + + // Check link/subdir/sublink evaluates to dir/subdir/file.txt + assertEquals(subsub.toRealPath().toString(), + subsub.toFile().getCanonicalPath()); + } }