From 02e187119d0ca94d46e631a174c55db4945f3295 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Thu, 7 Aug 2025 18:24:22 +0000 Subject: [PATCH] 8364277: (fs) BasicFileAttributes.isDirectory and isOther return true for NTFS directory junctions when links not followed Reviewed-by: alanb --- .../classes/sun/nio/fs/WindowsConstants.java | 5 +- .../sun/nio/fs/WindowsFileAttributes.java | 12 +- .../sun/nio/fs/WindowsFileSystemProvider.java | 3 +- .../BasicFileAttributeView/Basic.java | 39 ++++- test/lib/jdk/test/lib/util/FileUtils.java | 36 +++- test/lib/jdk/test/lib/util/libFileUtils.c | 158 +++++++++++++++++- 6 files changed, 228 insertions(+), 25 deletions(-) diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java index 46315533515..b1de66ac4f2 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -72,8 +72,9 @@ class WindowsConstants { public static final int BACKUP_SPARSE_BLOCK = 0x00000009; // reparse point/symbolic link related constants - public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; public static final int IO_REPARSE_TAG_AF_UNIX = 0x80000023; + public static final int IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; + public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; public static final int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; public static final int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1; public static final int SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2; diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java index 6e544b1c926..3c94e8bc4a2 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -412,6 +412,10 @@ class WindowsFileAttributes return isSymbolicLink() && ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0); } + boolean isDirectoryJunction() { + return reparseTag == IO_REPARSE_TAG_MOUNT_POINT; + } + @Override public boolean isSymbolicLink() { return reparseTag == IO_REPARSE_TAG_SYMLINK; @@ -423,10 +427,8 @@ class WindowsFileAttributes @Override public boolean isDirectory() { - // ignore FILE_ATTRIBUTE_DIRECTORY attribute if file is a sym link - if (isSymbolicLink()) - return false; - return ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0); + return ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && + (fileAttrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0); } @Override diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java index 7c280d87f62..3a1bb416fe7 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java @@ -243,7 +243,8 @@ class WindowsFileSystemProvider try { // need to know if file is a directory or junction attrs = WindowsFileAttributes.get(file, false); - if (attrs.isDirectory() || attrs.isDirectoryLink()) { + if (attrs.isDirectory() || attrs.isDirectoryLink() || + attrs.isDirectoryJunction()) { RemoveDirectory(file.getPathForWin32Calls()); } else { DeleteFile(file.getPathForWin32Calls()); diff --git a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java index 1605910a2dd..c988d89a2c7 100644 --- a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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,9 +22,12 @@ */ /* @test - * @bug 4313887 6838333 + * @bug 4313887 6838333 8364277 * @summary Unit test for java.nio.file.attribute.BasicFileAttributeView - * @library ../.. + * @library ../.. /test/lib + * @build jdk.test.lib.Platform + * jdk.test.lib.util.FileUtils + * @run main/othervm --enable-native-access=ALL-UNNAMED Basic */ import java.nio.file.*; @@ -33,6 +36,9 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.io.*; +import jdk.test.lib.Platform; +import jdk.test.lib.util.FileUtils; + public class Basic { static void check(boolean okay, String msg) { @@ -97,6 +103,17 @@ public class Basic { check(!attrs.isOther(), "is not other"); } + static void checkAttributesOfJunction(Path junction) + throws IOException + { + BasicFileAttributes attrs = + Files.readAttributes(junction, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + check(!attrs.isSymbolicLink(), "is a link"); + check(!attrs.isDirectory(), "is a directory"); + check(!attrs.isRegularFile(), "is not a regular file"); + check(attrs.isOther(), "is other"); + } + static void attributeReadWriteTests(Path dir) throws IOException { @@ -114,12 +131,18 @@ public class Basic { Path link = dir.resolve("link"); try { Files.createSymbolicLink(link, file); - } catch (UnsupportedOperationException x) { - return; - } catch (IOException x) { - return; + checkAttributesOfLink(link); + } catch (IOException | UnsupportedOperationException x) { + if (!Platform.isWindows()) + return; + } + + // NTFS junctions are Windows-only + if (Platform.isWindows()) { + Path junction = dir.resolve("junction"); + FileUtils.createWinDirectoryJunction(junction, dir); + checkAttributesOfJunction(junction); } - checkAttributesOfLink(link); } public static void main(String[] args) throws IOException { diff --git a/test/lib/jdk/test/lib/util/FileUtils.java b/test/lib/jdk/test/lib/util/FileUtils.java index 8b99d1e9d54..3e537835c0b 100644 --- a/test/lib/jdk/test/lib/util/FileUtils.java +++ b/test/lib/jdk/test/lib/util/FileUtils.java @@ -24,6 +24,7 @@ package jdk.test.lib.util; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; @@ -33,6 +34,7 @@ import java.lang.management.ManagementFactory; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; @@ -64,6 +66,14 @@ public final class FileUtils { private static final int MAX_RETRY_DELETE_TIMES = IS_WINDOWS ? 15 : 0; private static volatile boolean nativeLibLoaded; + @SuppressWarnings("restricted") + private static void loadNativeLib() { + if (!nativeLibLoaded) { + System.loadLibrary("FileUtils"); + nativeLibLoaded = true; + } + } + /** * Deletes a file, retrying if necessary. * @@ -392,14 +402,10 @@ public final class FileUtils { } // Return the current process handle count - @SuppressWarnings("restricted") public static long getProcessHandleCount() { if (IS_WINDOWS) { - if (!nativeLibLoaded) { - System.loadLibrary("FileUtils"); - nativeLibLoaded = true; - } - return getWinProcessHandleCount(); + loadNativeLib(); + return getWinProcessHandleCount0(); } else { return ((UnixOperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean()).getOpenFileDescriptorCount(); } @@ -443,7 +449,23 @@ public final class FileUtils { Files.write(path, lines); } - private static native long getWinProcessHandleCount(); + // Create a directory junction with the specified target + public static boolean createWinDirectoryJunction(Path junction, Path target) + throws IOException + { + assert IS_WINDOWS; + + // Convert "target" to its real path + target = target.toRealPath(); + + // Create a directory junction + loadNativeLib(); + return createWinDirectoryJunction0(junction.toString(), target.toString()); + } + + private static native long getWinProcessHandleCount0(); + private static native boolean createWinDirectoryJunction0(String junction, + String target) throws IOException; // Possible command locations and arguments static String[][] lsCommands = new String[][] { diff --git a/test/lib/jdk/test/lib/util/libFileUtils.c b/test/lib/jdk/test/lib/util/libFileUtils.c index 1af90afff49..d0da04f9fb1 100644 --- a/test/lib/jdk/test/lib/util/libFileUtils.c +++ b/test/lib/jdk/test/lib/util/libFileUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -27,9 +27,49 @@ #ifdef _WIN32 #include "jni.h" +#include "jni_util.h" +#include #include +#include +#include +#include +#include +#include -JNIEXPORT jlong JNICALL Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCount(JNIEnv *env) +// Based on Microsoft documentation +#define MAX_REPARSE_BUFFER_SIZE 16384 + +// Unavailable in standard header files: +// copied from Microsoft documentation +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER; + +JNIEXPORT jlong JNICALL +Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCount0 + (JNIEnv* env) { DWORD handleCount; HANDLE handle = GetCurrentProcess(); @@ -40,4 +80,118 @@ JNIEXPORT jlong JNICALL Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCoun } } +void throwIOExceptionWithLastError(JNIEnv* env) { +#define BUFSIZE 256 + DWORD errval; + WCHAR buf[BUFSIZE]; + + if ((errval = GetLastError()) != 0) { + jsize n = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errval, 0, buf, BUFSIZE, NULL); + + jclass ioExceptionClass = (*env)->FindClass(env, "java/io/IOException"); + (*env)->ThrowNew(env, ioExceptionClass, (const char*) buf); + } +} + +JNIEXPORT jboolean JNICALL +Java_jdk_test_lib_util_FileUtils_createWinDirectoryJunction0 + (JNIEnv* env, jclass unused, jstring sjunction, jstring starget) +{ + BOOL error = FALSE; + + const jshort bpc = sizeof(wchar_t); // bytes per character + HANDLE hJunction = INVALID_HANDLE_VALUE; + + const jchar* junction = (*env)->GetStringChars(env, sjunction, NULL); + const jchar* target = (*env)->GetStringChars(env, starget, NULL); + if (junction == NULL || target == NULL) { + jclass npeClass = (*env)->FindClass(env, "java/lang/NullPointerException"); + (*env)->ThrowNew(env, npeClass, NULL); + error = TRUE; + } + + USHORT wlen = (USHORT)0; + USHORT blen = (USHORT)0; + void* lpInBuffer = NULL; + if (!error) { + wlen = (USHORT)wcslen(target); + blen = (USHORT)(wlen * sizeof(wchar_t)); + lpInBuffer = calloc(MAX_REPARSE_BUFFER_SIZE, sizeof(char)); + if (lpInBuffer == NULL) { + jclass oomeClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + (*env)->ThrowNew(env, oomeClass, NULL); + error = TRUE; + } + } + + if (!error) { + if (CreateDirectoryW(junction, NULL) == 0) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (!error) { + hJunction = CreateFileW(junction, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT + | FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hJunction == INVALID_HANDLE_VALUE) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (!error) { + PREPARSE_DATA_BUFFER reparseBuffer = (PREPARSE_DATA_BUFFER)lpInBuffer; + reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseBuffer->Reserved = 0; + WCHAR* prefix = L"\\??\\"; + USHORT prefixLength = (USHORT)(bpc * wcslen(prefix)); + reparseBuffer->MountPointReparseBuffer.SubstituteNameOffset = 0; + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength = + prefixLength + blen; + reparseBuffer->MountPointReparseBuffer.PrintNameOffset = + prefixLength + blen + sizeof(WCHAR); + reparseBuffer->MountPointReparseBuffer.PrintNameLength = blen; + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer, + prefix, prefixLength); + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer[prefixLength/bpc], + target, blen); + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer[prefixLength/bpc + blen/bpc + 1], + target, blen); + reparseBuffer->ReparseDataLength = + (USHORT)(sizeof(reparseBuffer->MountPointReparseBuffer) + + prefixLength + bpc*blen + bpc); + DWORD nInBufferSize = FIELD_OFFSET(REPARSE_DATA_BUFFER, + MountPointReparseBuffer) + reparseBuffer->ReparseDataLength; + BOOL result = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, + lpInBuffer, nInBufferSize, + NULL, 0, NULL, NULL); + if (result == 0) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (junction != NULL) { + (*env)->ReleaseStringChars(env, sjunction, junction); + if (target != NULL) { + (*env)->ReleaseStringChars(env, starget, target); + if (lpInBuffer != NULL) { + free(lpInBuffer); + if (hJunction != INVALID_HANDLE_VALUE) { + // Ignore any error in CloseHandle + CloseHandle(hJunction); + } + } + } + } + + return error ? JNI_FALSE : JNI_TRUE; +} + #endif /* _WIN32 */