[APITESTS:NPFS] Add GetConnectionAttributes

This commit adds a new APITEST to verify NPFS behavior for handling
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE and FSCTL_PIPE_SET_CONNECTION_ATTRI-
BUTE IOCTLs.

The test is guaranteed to pass on Windows 8.1.

CORE-20709
This commit is contained in:
Mohammad Amin Mollazadeh
2026-07-28 19:33:24 +03:30
committed by George Bișoc
parent b1f6a49426
commit 09aa05056f
3 changed files with 323 additions and 0 deletions

View File

@@ -40,6 +40,7 @@ add_subdirectory(mspatcha)
add_subdirectory(msvcrt)
add_subdirectory(netapi32)
add_subdirectory(netshell)
add_subdirectory(npfs)
add_subdirectory(ntdll)
add_subdirectory(ole32)
add_subdirectory(opengl32)

View File

@@ -0,0 +1,10 @@
list(APPEND SOURCE
GetConnectionAttributes.c)
add_executable(npfs_apitest ${SOURCE})
set_module_type(npfs_apitest win32cui)
target_link_libraries(npfs_apitest winetestlib)
add_importlibs(npfs_apitest msvcrt kernel32 ntdll)
add_rostests_file(TARGET npfs_apitest)

View File

@@ -0,0 +1,312 @@
/*
* PROJECT: ReactOS API tests
* LICENSE: MIT (https://spdx.org/licenses/MIT)
* PURPOSE: Named Pipe File System (NPFS) Extended Attributes test
* COPYRIGHT: Copyright 2026 Mohammad Amin Mollazadeh <madamin@proton.me>
*/
#include <apitest.h>
#include <ndk/ntndk.h>
static char ClientProcessIdAttributeName[] = "ClientProcessId";
static char ClientSessionIdAttributeName[] = "ClientSessionId";
static char NonExistentAttributeName[] = "NonExistentAttribute";
static char CustomAttributeName1[] = "MyOwnAttribute1";
static char CustomAttributeName2[] = "MyOwnAttribute2";
static char CustomAttributeName1Upper[] = "MYOWNATTRIBUTE1";
static int TestValue1 = 0xC001D00D;
static char TestValue2[] = "The Test Value";
static char TestValue3[] = "The Third Test Value";
static void NamedPipeClientChildProcess(void)
{
HANDLE hPipe = INVALID_HANDLE_VALUE;
DWORD BytesRead = 0;
BYTE Buffer[255];
hPipe = CreateFileW(L"\\\\.\\pipe\\NpfsApiTestPipe",
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
skip("Failed to open the named pipe: 0x%08X\n", GetLastError());
return;
}
/* Keep the child process alive while the pipe is still connected. */
ReadFile(hPipe, Buffer, sizeof(Buffer), &BytesRead, NULL);
CloseHandle(hPipe);
}
static BOOL RunChildProcess(PPROCESS_INFORMATION ppi)
{
char cmdline[MAX_PATH];
char exe[MAX_PATH];
STARTUPINFOA si = { sizeof(si) };
GetModuleFileNameA(NULL, exe, sizeof(exe));
sprintf(cmdline, "\"%s\" %s %s", exe, "NpfsGetConnectionAttributes", "subtest");
return CreateProcessA(exe, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, ppi);
}
START_TEST(NpfsGetConnectionAttributes)
{
NTSTATUS Status;
HANDLE hPipe = INVALID_HANDLE_VALUE;
char Buffer[256]; /* For extended attribute data */
IO_STATUS_BLOCK IoStatusBlock = {0};
PROCESS_INFORMATION ProcessInfo = {0};
DWORD SessionId;
char** argv;
int argc = winetest_get_mainargs(&argv);
if (argc > 2)
{
NamedPipeClientChildProcess();
return;
}
hPipe = CreateNamedPipeW(L"\\\\.\\pipe\\NpfsApiTestPipe",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1,
256, 256, /* Pipe Read/Write buffer size */
0, NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
skip("Failed to create/open the named pipe: 0x%08X\n", GetLastError());
return;
}
/* Query for a non-existence attribute */
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
NonExistentAttributeName, sizeof(NonExistentAttributeName),
Buffer, sizeof(Buffer));
ok_eq_hex(Status, STATUS_NOT_FOUND);
/* Set a custom attribute and query it back */
RtlCopyMemory(Buffer, CustomAttributeName1, sizeof(CustomAttributeName1));
RtlCopyMemory(Buffer + sizeof(CustomAttributeName1), &TestValue1, sizeof(TestValue1));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE,
Buffer, sizeof(CustomAttributeName1) + sizeof(TestValue1),
NULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, 0);
*(int*)Buffer = 0xdeadbeef;
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName1, sizeof(CustomAttributeName1),
Buffer, sizeof(int));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(int));
ok_eq_hex(*(int*)Buffer, 0xC001D00D);
/* Set a second custom attribute and query attributes back */
RtlCopyMemory(Buffer, CustomAttributeName2, sizeof(CustomAttributeName2));
RtlCopyMemory(Buffer + sizeof(CustomAttributeName2), TestValue2, sizeof(TestValue2));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE,
Buffer, sizeof(CustomAttributeName2) + sizeof(TestValue2),
NULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, 0);
*(int*)Buffer = 0xdeadbeef;
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName1, sizeof(CustomAttributeName1),
Buffer, sizeof(int));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(int));
ok_eq_hex(*(int*)Buffer, 0xC001D00D);
RtlZeroMemory(Buffer, sizeof(Buffer));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName2, sizeof(CustomAttributeName2),
Buffer, sizeof(TestValue2));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(TestValue2));
ok_eq_str(Buffer, TestValue2);
/* Set the first attribute to a bigger value */
RtlCopyMemory(Buffer, CustomAttributeName1, sizeof(CustomAttributeName1));
RtlCopyMemory(Buffer + sizeof(CustomAttributeName1), TestValue3, sizeof(TestValue3));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE,
Buffer, sizeof(CustomAttributeName1) + sizeof(TestValue3),
NULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, 0);
RtlZeroMemory(Buffer, sizeof(TestValue3));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName1, sizeof(CustomAttributeName1),
Buffer, sizeof(TestValue3));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(TestValue3));
ok_eq_str(Buffer, TestValue3);
/* Query NULL attribute */
RtlZeroMemory(Buffer, sizeof(Buffer));
RtlZeroMemory(&IoStatusBlock, sizeof(IoStatusBlock));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
NULL, 0,
Buffer, sizeof(Buffer));
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
ok_eq_ulong(IoStatusBlock.Information, 0);
/* Query with NULL as buffer */
RtlZeroMemory(Buffer, sizeof(Buffer));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName2, sizeof(CustomAttributeName2),
NULL, 0);
ok_eq_hex(Status, STATUS_BUFFER_TOO_SMALL);
ok_eq_ulong(IoStatusBlock.Information, 0);
/* Query with a small buffer */
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName2, sizeof(CustomAttributeName2),
Buffer, sizeof(char));
ok_eq_hex(Status, STATUS_BUFFER_TOO_SMALL);
ok_eq_ulong(IoStatusBlock.Information, 0);
/* Query case-sensitivity */
RtlZeroMemory(Buffer, sizeof(TestValue1));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName1Upper, sizeof(CustomAttributeName1Upper),
Buffer, sizeof(TestValue1));
ok_eq_hex(Status, STATUS_NOT_FOUND);
/* Zero-length value: Unset the attribute */
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE,
CustomAttributeName1, sizeof(CustomAttributeName1),
NULL, 0);
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, 0);
RtlZeroMemory(Buffer, sizeof(TestValue1));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
CustomAttributeName1, sizeof(CustomAttributeName1),
Buffer, sizeof(TestValue1));
ok_eq_hex(Status, STATUS_NOT_FOUND);
ok_eq_ulong(IoStatusBlock.Information, 0);
/* Empty name */
RtlZeroMemory(Buffer, sizeof(Buffer));
RtlCopyMemory(Buffer + 1, &TestValue1, sizeof(TestValue1));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE,
Buffer, 1 + sizeof(TestValue1),
NULL, 0);
ok_eq_hex(Status, STATUS_INVALID_PARAMETER);
RtlZeroMemory(Buffer, sizeof(Buffer));
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
"", sizeof(""),
Buffer, sizeof(TestValue1));
ok_eq_hex(Status, STATUS_NOT_FOUND);
/* Query for the PID and process session ID attributes.
* These attributes are set by NpCreateClientEnd. */
/* ClientProcessId and ClientSessionId should return STATUS_NOT_FOUND
* when there's no client connected to the pipe. */
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
ClientProcessIdAttributeName, sizeof(ClientProcessIdAttributeName),
Buffer, sizeof(DWORD));
ok_eq_hex(Status, STATUS_NOT_FOUND);
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
ClientSessionIdAttributeName, sizeof(ClientSessionIdAttributeName),
Buffer, sizeof(DWORD));
ok_eq_hex(Status, STATUS_NOT_FOUND);
if (!RunChildProcess(&ProcessInfo))
{
skip("Failed to create the child process: 0x%08X\n", GetLastError());
goto Cleanup;
}
/* Wait for client connection */
if (!ConnectNamedPipe(hPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
{
skip("Failed to connect the named pipe: 0x%08X\n", GetLastError());
goto Cleanup;
}
*(DWORD*)Buffer = 0xdeadbeef;
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
ClientProcessIdAttributeName, sizeof(ClientProcessIdAttributeName),
Buffer, sizeof(DWORD));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(DWORD));
ok_eq_ulong(*(DWORD*)Buffer, ProcessInfo.dwProcessId);
if (!ProcessIdToSessionId(ProcessInfo.dwProcessId, &SessionId))
{
skip("Failed to get the session ID of the client process: 0x%08X\n", GetLastError());
goto Cleanup;
}
Status = NtFsControlFile(hPipe, NULL, NULL, NULL,
&IoStatusBlock,
FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE,
ClientSessionIdAttributeName, sizeof(ClientSessionIdAttributeName),
Buffer, sizeof(DWORD));
ok_eq_hex(Status, STATUS_SUCCESS);
ok_eq_ulong(IoStatusBlock.Information, sizeof(DWORD));
ok_eq_ulong(*(DWORD*)Buffer, SessionId);
/* Disconnect the named pipe and wait for the child process to exit */
DisconnectNamedPipe(hPipe);
winetest_wait_child_process(ProcessInfo.hProcess);
Cleanup:
if (ProcessInfo.hThread != NULL)
CloseHandle(ProcessInfo.hThread);
if (ProcessInfo.hProcess != NULL)
CloseHandle(ProcessInfo.hProcess);
if (hPipe != INVALID_HANDLE_VALUE)
NtClose(hPipe);
}