mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-15 13:49:42 +02:00
8364277
: (fs) BasicFileAttributes.isDirectory and isOther return true for NTFS directory junctions when links not followed
Reviewed-by: alanb
This commit is contained in:
parent
90ea42f716
commit
02e187119d
6 changed files with 228 additions and 25 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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[][] {
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue