From 1f08a3ede2445fb05d9700a1293d681ca89cbf5b Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Thu, 6 Nov 2025 16:01:37 +0000 Subject: [PATCH] 8355342: File.getCanonicalPath on Java 24 resolves paths on network drives to UNC format Reviewed-by: alanb --- .../classes/java/io/WinNTFileSystem.java | 17 +--- .../native/libjava/WinNTFileSystem_md.c | 35 ++----- .../windows/native/libjava/canonicalize_md.c | 97 ++++++++++++++++--- test/jdk/java/io/File/GetCanonicalPath.java | 51 +++++++++- 4 files changed, 141 insertions(+), 59 deletions(-) diff --git a/src/java.base/windows/classes/java/io/WinNTFileSystem.java b/src/java.base/windows/classes/java/io/WinNTFileSystem.java index e053f7f004c..5066374c0c9 100644 --- a/src/java.base/windows/classes/java/io/WinNTFileSystem.java +++ b/src/java.base/windows/classes/java/io/WinNTFileSystem.java @@ -482,27 +482,12 @@ final class WinNTFileSystem extends FileSystem { return path; return "" + ((char) (c-32)) + ':' + '\\'; } - String canonicalPath = canonicalize0(path); - String finalPath = null; - try { - finalPath = getFinalPath(canonicalPath); - } catch (IOException ignored) { - finalPath = canonicalPath; - } - return finalPath; + return canonicalize0(path); } 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 -- */ @Override diff --git a/src/java.base/windows/native/libjava/WinNTFileSystem_md.c b/src/java.base/windows/native/libjava/WinNTFileSystem_md.c index 974d5c11d7e..2834a449502 100644 --- a/src/java.base/windows/native/libjava/WinNTFileSystem_md.c +++ b/src/java.base/windows/native/libjava/WinNTFileSystem_md.c @@ -59,7 +59,7 @@ Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls) /* -- Path operations -- */ -extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len); +extern WCHAR* wcanonicalize(const WCHAR *path, WCHAR *out, int len); /** * Retrieves the fully resolved (final) path for the given path or NULL @@ -274,18 +274,23 @@ Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this, */ int len = (int)wcslen(path); len += currentDirLength(path, len); + WCHAR* fp; if (len > MAX_PATH_LENGTH - 1) { WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR)); if (cp != NULL) { - if (wcanonicalize(path, cp, len) >= 0) { - rv = (*env)->NewString(env, cp, (jsize)wcslen(cp)); + if ((fp = wcanonicalize(path, cp, len)) != NULL) { + rv = (*env)->NewString(env, fp, (jsize)wcslen(fp)); + if (fp != cp) + free(fp); } free(cp); } else { JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); } - } else if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) { - rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath)); + } else if ((fp = wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH)) != NULL) { + rv = (*env)->NewString(env, fp, (jsize)wcslen(fp)); + if (fp != canonicalPath) + free(fp); } } END_UNICODE_STRING(env, path); if (rv == NULL && !(*env)->ExceptionCheck(env)) { @@ -294,26 +299,6 @@ Java_java_io_WinNTFileSystem_canonicalize0(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/src/java.base/windows/native/libjava/canonicalize_md.c b/src/java.base/windows/native/libjava/canonicalize_md.c index 3719ec75d11..8596521509c 100644 --- a/src/java.base/windows/native/libjava/canonicalize_md.c +++ b/src/java.base/windows/native/libjava/canonicalize_md.c @@ -36,6 +36,7 @@ #include #include +#include #include /* We should also include jdk_util.h here, for the prototype of JDK_Canonicalize. @@ -82,9 +83,9 @@ wnextsep(WCHAR *start) /* Tell whether the given string contains any wildcard characters */ static int -wwild(WCHAR *start) +wwild(const WCHAR *start) { - WCHAR *p = start; + WCHAR *p = (WCHAR*)start; int c; while (c = *p) { if ((c == L'*') || (c == L'?')) @@ -146,12 +147,59 @@ lastErrorReportable() return 1; } -/* Convert a pathname to canonical form. The input orig_path is assumed to - have been converted to native form already, via JVM_NativePath(). This is - necessary because _fullpath() rejects duplicate separator characters on - Win95, though it accepts them on NT. */ -int -wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) +// +// Return the final path of 'path'. If 'finalPath' is long enough, the final +// path is placed in it. If not, a new character array is allocated for the +// return value. If the return value does not equal the original 'finalPath' +// value, then the calling code might need to free the memory of the +// parameter. Non-NULL is returned on success, NULL on error. +// +WCHAR* getFinalPath(WCHAR* path, WCHAR* finalPath, DWORD size) +{ + HANDLE h = CreateFileW(path, + FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (h != INVALID_HANDLE_VALUE) { + DWORD len = GetFinalPathNameByHandleW(h, finalPath, size, 0); + if (len >= size) { + if ((finalPath = (WCHAR*)malloc(len * sizeof(WCHAR))) == NULL) + return NULL; + len = GetFinalPathNameByHandleW(h, finalPath, len, 0); + } + CloseHandle(h); + if (len != 0) { + if (finalPath[0] == L'\\' && finalPath[1] == L'\\' && + finalPath[2] == L'?' && finalPath[3] == L'\\') + { + // Strip prefix (should be \\?\ or \\?\UNC) + int isUnc = (finalPath[4] == L'U' && + finalPath[5] == L'N' && + finalPath[6] == L'C'); + int prefixLen = (isUnc) ? 7 : 4; + // the amount to copy includes terminator + int amountToCopy = len - prefixLen + 1; + wmemmove(finalPath, finalPath + prefixLen, amountToCopy); + } + + return finalPath; + } + } + + return NULL; +} + +/* Convert a pathname to canonical form. If a reparse point is encountered + while traversing the path, then the final path is derived from the full path + and returned as the canonical pathname. + */ +WCHAR* +wcanonicalize(const WCHAR *orig_path, WCHAR *result, int size) { WIN32_FIND_DATAW fd; HANDLE h; @@ -161,11 +209,11 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) /* Reject paths that contain wildcards */ if (wwild(orig_path)) { errno = EINVAL; - return -1; + return NULL; } if ((path = (WCHAR*)malloc(size * sizeof(WCHAR))) == NULL) - return -1; + return NULL; /* Collapse instances of "foo\.." and ensure absoluteness. Note that contrary to the documentation, the _fullpath procedure does not require @@ -237,6 +285,18 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) if (h != INVALID_HANDLE_VALUE) { /* Lookup succeeded; append true name to result and continue */ FindClose(h); + + // If a reparse point is encountered, get the final path. + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + // Do not fail if the final path cannot be obtained as the + // canonicalization may still be otherwise correct + WCHAR* fp = NULL; + if ((fp = getFinalPath(path, result, size)) != NULL) { + free(path); + return fp; + } + } + if (!(dst = wcp(dst, dend, L'\\', fd.cFileName, fd.cFileName + wcslen(fd.cFileName)))){ goto err; @@ -261,11 +321,11 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) } *dst = L'\0'; free(path); - return 0; + return result; err: free(path); - return -1; + return NULL; } /* Non-Wide character version of canonicalize. @@ -274,6 +334,7 @@ JNIEXPORT int JDK_Canonicalize(const char *orig, char *out, int len) { wchar_t* wpath = NULL; wchar_t* wresult = NULL; + wchar_t* wcanon = NULL; int wpath_len; int ret = -1; @@ -297,12 +358,12 @@ JDK_Canonicalize(const char *orig, char *out, int len) { goto finish; } - if (wcanonicalize(wpath, wresult, len) != 0) { + if ((wcanon = wcanonicalize(wpath, wresult, len)) == NULL) { goto finish; } if (WideCharToMultiByte(CP_ACP, 0, - wresult, -1, out, len, NULL, NULL) == 0) { + wcanon, -1, out, len, NULL, NULL) == 0) { goto finish; } @@ -310,8 +371,12 @@ JDK_Canonicalize(const char *orig, char *out, int len) { ret = 0; finish: - free(wresult); - free(wpath); + if (wcanon != NULL && wcanon != wresult) + free(wcanon); + if (wresult != NULL) + free(wresult); + if (wpath != NULL) + free(wpath); return ret; } diff --git a/test/jdk/java/io/File/GetCanonicalPath.java b/test/jdk/java/io/File/GetCanonicalPath.java index d69e90fbbfc..14ef90260fc 100644 --- a/test/jdk/java/io/File/GetCanonicalPath.java +++ b/test/jdk/java/io/File/GetCanonicalPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -22,7 +22,7 @@ */ /* @test - * @bug 4899022 8003887 + * @bug 4899022 8003887 8355342 * @summary Look for erroneous representation of drive letter * @run junit GetCanonicalPath */ @@ -33,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.Assumptions; @@ -127,6 +128,52 @@ public class GetCanonicalPath { assertFalse(path.length() > 3, "Drive letter incorrectly represented"); } + @Test + @EnabledOnOs(OS.WINDOWS) + void mappedDrive() throws IOException { + // find the first unused drive letter + char drive = '['; + var roots = Set.of(new File(".").listRoots()); + for (int i = 4; i < 26; i++) { + char c = (char)('A' + i); + if (!roots.contains(new File(c + ":\\"))) { + drive = c; + break; + } + } + assertFalse(drive == '['); // '[' is next after 'Z' + + // map the first unused drive letter to the cwd + String cwd = System.getProperty("user.dir"); + Runtime rt = Runtime.getRuntime(); + String share = + "\\\\localhost\\" + cwd.charAt(0) + "$" + cwd.substring(2); + try { + Process p = rt.exec(new String[] {"net", "use", drive + ":", share}); + assertEquals(0, p.waitFor()); + } catch (InterruptedException x) { + fail(x); + } + + // check that the canonical path name and its content are as expected + try { + final String filename = "file.txt"; + final String text = "This is some text"; + Files.writeString(Path.of(share, filename), text); + File file = new File(drive + ":\\" + filename); + String canonicalPath = file.getCanonicalPath(); + assertEquals(drive + ":\\" + filename, canonicalPath); + assertEquals(text, Files.readString(Path.of(canonicalPath))); + } finally { + try { + Process p = rt.exec(new String[] {"net", "use", drive + ":", "/Delete"}); + assertEquals(0, p.waitFor()); + } catch (InterruptedException x) { + fail(x); + } + } + } + // 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);