8364277: (fs) BasicFileAttributes.isDirectory and isOther return true for NTFS directory junctions when links not followed

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2025-08-07 18:24:22 +00:00
parent 90ea42f716
commit 02e187119d
6 changed files with 228 additions and 25 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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[][] {

View file

@ -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 <string.h>
#include <windows.h>
#include <fileapi.h>
#include <handleapi.h>
#include <ioapiset.h>
#include <winioctl.h>
#include <errhandlingapi.h>
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 */