8003887: File.getCanonicalFile() does not resolve symlinks on MS Windows

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2024-09-05 15:03:54 +00:00
parent b389bb4567
commit 042053c3a8
3 changed files with 127 additions and 26 deletions

View File

@ -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 -- */

View File

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

View File

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