From 8d37962b43e5074bc0421611267dc41593114f85 Mon Sep 17 00:00:00 2001 From: Zebediah Figura Date: Thu, 26 Aug 2021 20:54:10 -0500 Subject: [PATCH] ntoskrnl/tests: Add more tests for IRP status handling. Signed-off-by: Zebediah Figura Signed-off-by: Alexandre Julliard --- dlls/ntoskrnl.exe/tests/driver.c | 99 +++--- dlls/ntoskrnl.exe/tests/driver.h | 40 ++- dlls/ntoskrnl.exe/tests/driver_pnp.c | 6 +- dlls/ntoskrnl.exe/tests/ntoskrnl.c | 439 ++++++++++++++++++++++----- 4 files changed, 454 insertions(+), 130 deletions(-) diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c index ce74b9e9512..0d40053b0e6 100644 --- a/dlls/ntoskrnl.exe/tests/driver.c +++ b/dlls/ntoskrnl.exe/tests/driver.c @@ -2395,22 +2395,64 @@ static NTSTATUS get_fscontext(IRP *irp, IO_STACK_LOCATION *stack, ULONG_PTR *inf return STATUS_SUCCESS; } -static NTSTATUS return_status(IRP *irp, IO_STACK_LOCATION *stack, ULONG_PTR *info) +static NTSTATUS return_status(IRP *irp, IO_STACK_LOCATION *stack, ULONG code) { - char *buffer = irp->AssociatedIrp.SystemBuffer; - NTSTATUS ret; + ULONG input_length = stack->Parameters.DeviceIoControl.InputBufferLength; + ULONG output_length = stack->Parameters.DeviceIoControl.OutputBufferLength; + const struct return_status_params *input_buffer; + struct return_status_params params; + void *output_buffer; - if (!buffer) + if (code == IOCTL_WINETEST_RETURN_STATUS_NEITHER) + { + input_buffer = stack->Parameters.DeviceIoControl.Type3InputBuffer; + output_buffer = irp->UserBuffer; + } + else if (code == IOCTL_WINETEST_RETURN_STATUS_DIRECT) + { + input_buffer = irp->AssociatedIrp.SystemBuffer; + output_buffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority); + } + else + { + input_buffer = irp->AssociatedIrp.SystemBuffer; + output_buffer = irp->AssociatedIrp.SystemBuffer; + } + + if (!input_buffer || !output_buffer) + { + irp->IoStatus.Status = STATUS_ACCESS_VIOLATION; + IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_ACCESS_VIOLATION; + } - if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(DWORD) - || stack->Parameters.DeviceIoControl.OutputBufferLength < 3) + if (input_length < sizeof(params) || output_length < 6) + { + irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; + IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_BUFFER_TOO_SMALL; + } - ret = *(DWORD *)irp->AssociatedIrp.SystemBuffer; - memcpy(buffer, "ghi", 3); - *info = 3; - return ret; + params = *input_buffer; + + if (params.ret_status == STATUS_PENDING && !params.pending) + { + /* this causes kernel hangs under certain conditions */ + irp->IoStatus.Status = STATUS_INVALID_PARAMETER; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_INVALID_PARAMETER; + } + + if (params.pending) + IoMarkIrpPending(irp); + + /* intentionally report the wrong information (and status) */ + memcpy(output_buffer, "ghijkl", 6); + irp->IoStatus.Information = 3; + irp->IoStatus.Status = params.iosb_status; + IoCompleteRequest(irp, IO_NO_INCREMENT); + + return params.ret_status; } static NTSTATUS test_load_driver_ioctl(IRP *irp, IO_STACK_LOCATION *stack, ULONG_PTR *info) @@ -2430,34 +2472,6 @@ static NTSTATUS test_load_driver_ioctl(IRP *irp, IO_STACK_LOCATION *stack, ULONG return ZwUnloadDriver(&name); } -static NTSTATUS test_mismatched_status_ioctl(IRP *irp, IO_STACK_LOCATION *stack, ULONG_PTR *info) -{ - ULONG length = stack->Parameters.DeviceIoControl.OutputBufferLength; - char *buffer = irp->UserBuffer; - - if (!buffer) - { - irp->IoStatus.Status = STATUS_ACCESS_VIOLATION; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return STATUS_ACCESS_VIOLATION; - } - - if (length < sizeof(teststr)) - { - irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return STATUS_BUFFER_TOO_SMALL; - } - - memcpy(buffer, teststr, sizeof(teststr)); - - /* This is deliberate; some broken drivers do this */ - *info = 0; - irp->IoStatus.Status = STATUS_UNSUCCESSFUL; - IoCompleteRequest(irp, IO_NO_INCREMENT); - return STATUS_SUCCESS; -} - static NTSTATUS completion_ioctl(DEVICE_OBJECT *device, IRP *irp, IO_STACK_LOCATION *stack) { if (device == upper_device) @@ -2564,15 +2578,14 @@ static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp) case IOCTL_WINETEST_GET_FSCONTEXT: status = get_fscontext(irp, stack, &irp->IoStatus.Information); break; - case IOCTL_WINETEST_RETURN_STATUS: - status = return_status(irp, stack, &irp->IoStatus.Information); - break; + case IOCTL_WINETEST_RETURN_STATUS_BUFFERED: + case IOCTL_WINETEST_RETURN_STATUS_DIRECT: + case IOCTL_WINETEST_RETURN_STATUS_NEITHER: + return return_status(irp, stack, stack->Parameters.DeviceIoControl.IoControlCode); case IOCTL_WINETEST_DETACH: IoDetachDevice(lower_device); status = STATUS_SUCCESS; break; - case IOCTL_WINETEST_MISMATCHED_STATUS: - return test_mismatched_status_ioctl(irp, stack, &irp->IoStatus.Information); case IOCTL_WINETEST_COMPLETION: return completion_ioctl(device, irp, stack); default: diff --git a/dlls/ntoskrnl.exe/tests/driver.h b/dlls/ntoskrnl.exe/tests/driver.h index 8c41010c503..9126e3cf2bf 100644 --- a/dlls/ntoskrnl.exe/tests/driver.h +++ b/dlls/ntoskrnl.exe/tests/driver.h @@ -22,21 +22,20 @@ /* All custom IOCTLs need to have a function value >= 0x800. */ -#define IOCTL_WINETEST_BASIC_IOCTL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_MAIN_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_LOAD_DRIVER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_RESET_CANCEL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_TEST_CANCEL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_GET_CANCEL_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_DETACH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_GET_CREATE_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_GET_CLOSE_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_GET_FSCONTEXT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_RETURN_STATUS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80a, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_MISMATCHED_STATUS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80b, METHOD_NEITHER, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_COMPLETION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80c, METHOD_NEITHER, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_MARK_PENDING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80d, METHOD_NEITHER, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_CHECK_REMOVED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80e, METHOD_NEITHER, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_BASIC_IOCTL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_MAIN_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_LOAD_DRIVER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_RESET_CANCEL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_TEST_CANCEL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_GET_CANCEL_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_DETACH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_GET_CREATE_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_GET_CLOSE_COUNT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_GET_FSCONTEXT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_RETURN_STATUS_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80a, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_RETURN_STATUS_DIRECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80a, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_RETURN_STATUS_NEITHER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80a, METHOD_NEITHER, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_COMPLETION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80c, METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_WINETEST_BUS_MAIN CTL_CODE(FILE_DEVICE_BUS_EXTENDER, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_WINETEST_BUS_REGISTER_IFACE CTL_CODE(FILE_DEVICE_BUS_EXTENDER, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) @@ -45,7 +44,9 @@ #define IOCTL_WINETEST_BUS_ADD_CHILD CTL_CODE(FILE_DEVICE_BUS_EXTENDER, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_WINETEST_BUS_REMOVE_CHILD CTL_CODE(FILE_DEVICE_BUS_EXTENDER, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) -#define IOCTL_WINETEST_CHILD_GET_ID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_CHILD_GET_ID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_CHILD_MARK_PENDING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_CHILD_CHECK_REMOVED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS) static const char teststr[] = "Wine is not an emulator"; @@ -65,6 +66,13 @@ struct main_test_input ULONG64 *modified_value; }; +struct return_status_params +{ + NTSTATUS ret_status; + NTSTATUS iosb_status; + BOOL pending; +}; + static const GUID control_class = {0xdeadbeef, 0x29ef, 0x4538, {0xa5, 0xfd, 0xb6, 0x95, 0x73, 0xa3, 0x62, 0xc0}}; #define SERVER_LISTEN_PORT 9374 diff --git a/dlls/ntoskrnl.exe/tests/driver_pnp.c b/dlls/ntoskrnl.exe/tests/driver_pnp.c index 98ca2ff7961..aa078ab0b2f 100644 --- a/dlls/ntoskrnl.exe/tests/driver_pnp.c +++ b/dlls/ntoskrnl.exe/tests/driver_pnp.c @@ -276,7 +276,7 @@ static NTSTATUS pdo_pnp(DEVICE_OBJECT *device_obj, IRP *irp) } case IRP_MN_REMOVE_DEVICE: - /* should've been checked and reset by IOCTL_WINETEST_CHECK_REMOVED */ + /* should've been checked and reset by IOCTL_WINETEST_CHILD_CHECK_REMOVED */ ok(remove_device_count == 0, "expected no IRP_MN_REMOVE_DEVICE\n"); todo_wine ok(surprise_removal_count == 0, "expected no IRP_MN_SURPRISE_REMOVAL\n"); ok(query_remove_device_count == 0, "expected no IRP_MN_QUERY_REMOVE_DEVICE\n"); @@ -665,12 +665,12 @@ static NTSTATUS pdo_ioctl(DEVICE_OBJECT *device_obj, IRP *irp, IO_STACK_LOCATION irp->IoStatus.Information = sizeof(device->id); return STATUS_SUCCESS; - case IOCTL_WINETEST_MARK_PENDING: + case IOCTL_WINETEST_CHILD_MARK_PENDING: IoMarkIrpPending(irp); irp_queue_push(&device->irp_queue, irp); return STATUS_PENDING; - case IOCTL_WINETEST_CHECK_REMOVED: + case IOCTL_WINETEST_CHILD_CHECK_REMOVED: ok(remove_device_count == 0, "expected IRP_MN_REMOVE_DEVICE\n"); ok(surprise_removal_count == 1, "expected IRP_MN_SURPRISE_REMOVAL\n"); ok(query_remove_device_count == 0, "expected no IRP_MN_QUERY_REMOVE_DEVICE\n"); diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 82d04fa1bec..dc10497caee 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -430,18 +430,6 @@ static void test_basic_ioctl(void) ok(!strcmp(buf, "Wine is no"), "got '%s'\n", buf); } -static void test_mismatched_status_ioctl(void) -{ - DWORD written; - char buf[32]; - BOOL res; - - res = DeviceIoControl(device, IOCTL_WINETEST_MISMATCHED_STATUS, NULL, 0, buf, - sizeof(buf), &written, NULL); - todo_wine ok(res, "DeviceIoControl failed: %u\n", GetLastError()); - todo_wine ok(!strcmp(buf, teststr), "got '%s'\n", buf); -} - static void test_overlapped(void) { OVERLAPPED overlapped, overlapped2, *o; @@ -631,72 +619,376 @@ static void test_file_handles(void) ok(count == 3, "got %u\n", count); } -static void test_return_status(void) +static unsigned int got_return_status_apc; + +static void WINAPI return_status_apc(void *apc_user, IO_STATUS_BLOCK *io, ULONG reserved) { - NTSTATUS status; + ++got_return_status_apc; + ok(apc_user == (void *)456, "got %p\n", apc_user); + ok(!reserved, "got reserved %#x\n", reserved); +} + +static void do_return_status(ULONG ioctl, struct return_status_params *params) +{ + const char *expect_buffer; + LARGE_INTEGER zero = {{0}}; + HANDLE file, port, event; + NTSTATUS expect_status; + ULONG_PTR key, value; + IO_STATUS_BLOCK io; char buffer[7]; - DWORD ret_size; + DWORD size; BOOL ret; - strcpy(buffer, "abcdef"); - status = STATUS_SUCCESS; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - ok(ret, "ioctl failed\n"); - ok(GetLastError() == 0xdeadbeef, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "ghidef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + if (params->ret_status == STATUS_PENDING && !params->pending) + { + /* this causes kernel hangs under certain conditions */ + return; + } + + event = CreateEventW(NULL, TRUE, FALSE, NULL); + + if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + expect_buffer = "ghijkl"; + else if (NT_ERROR(params->iosb_status)) + expect_buffer = "abcdef"; + else + expect_buffer = "ghidef"; + + /* Test the non-overlapped case. */ + + expect_status = (params->ret_status == STATUS_PENDING ? params->iosb_status : params->ret_status); strcpy(buffer, "abcdef"); - status = STATUS_TIMEOUT; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - todo_wine ok(ret, "ioctl failed\n"); - todo_wine ok(GetLastError() == 0xdeadbeef, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "ghidef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + size = 0xdeadf00d; + SetLastError(0xdeadf00d); + ret = DeviceIoControl(device, ioctl, params, sizeof(*params), buffer, sizeof(buffer), &size, NULL); + todo_wine_if (NT_SUCCESS(expect_status) != !params->iosb_status) + ok(ret == NT_SUCCESS(expect_status), "got %d\n", ret); + if (NT_SUCCESS(expect_status)) + { + todo_wine_if (params->iosb_status) + ok(GetLastError() == 0xdeadf00d, "got error %u\n", GetLastError()); + } + else + { + todo_wine_if (RtlNtStatusToDosError(expect_status) != RtlNtStatusToDosError(params->iosb_status) + || params->iosb_status == STATUS_PENDING) + ok(GetLastError() == RtlNtStatusToDosError(expect_status), "got error %u\n", GetLastError()); + } + if (NT_ERROR(expect_status)) + todo_wine ok(size == 0xdeadf00d, "got size %u\n", size); + else if (!NT_ERROR(params->iosb_status)) + ok(size == 3, "got size %u\n", size); + /* size is garbage if !NT_ERROR(expect_status) && NT_ERROR(iosb_status) */ + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); strcpy(buffer, "abcdef"); - status = 0x0eadbeef; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - todo_wine ok(ret, "ioctl failed\n"); - todo_wine ok(GetLastError() == 0xdeadbeef, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "ghidef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(device, NULL, NULL, NULL, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if ((params->ret_status != params->iosb_status && params->ret_status != STATUS_PENDING) + || params->iosb_status == STATUS_PENDING) + ok(ret == expect_status, "got %#x\n", ret); + if (NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); + + /* Test the overlapped case. */ + + file = CreateFileA("\\\\.\\WineTestDriver", FILE_ALL_ACCESS, + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + ok(file != INVALID_HANDLE_VALUE, "failed to open device, error %u\n", GetLastError()); + port = CreateIoCompletionPort(file, NULL, 123, 0); + ok(port != NULL, "failed to create port, error %u\n", GetLastError()); + + ret = WaitForSingleObject(file, 0); + todo_wine ok(!ret, "got %d\n", ret); + + ResetEvent(event); + strcpy(buffer, "abcdef"); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(file, event, NULL, (void *)456, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if (params->ret_status != params->iosb_status || params->ret_status == STATUS_PENDING) + ok(ret == params->ret_status + || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + "got %#x\n", ret); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + todo_wine ok(ret == WAIT_TIMEOUT, "got %d\n", ret); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + ok(!ret, "got %d\n", ret); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); + + ret = WaitForSingleObject(file, 0); + ok(ret == WAIT_TIMEOUT, "got %d\n", ret); + + key = 0xdeadf00d; + value = 0xdeadf00d; + memset(&io, 0xcc, sizeof(io)); + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(ret == STATUS_TIMEOUT, "got %#x\n", ret); + } + else + { + ok(!ret, "got %#x\n", ret); + ok(key == 123, "got key %Iu\n", key); + ok(value == 456, "got value %Iu\n", value); + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got iosb status %#x\n", io.Status); + ok(io.Information == 3, "got information %Iu\n", io.Information); + } + + /* As above, but set the event first, to show that the event is always + * reset. */ + ResetEvent(event); + strcpy(buffer, "abcdef"); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(file, event, NULL, NULL, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if (params->ret_status != params->iosb_status || params->ret_status == STATUS_PENDING) + ok(ret == params->ret_status + || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + "got %#x\n", ret); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + todo_wine ok(ret == WAIT_TIMEOUT, "got %d\n", ret); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + ok(!ret, "got %d\n", ret); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); + + /* As above, but use the file handle instead of an event. */ + ret = WaitForSingleObject(file, 0); + ok(ret == WAIT_TIMEOUT, "got %d\n", ret); strcpy(buffer, "abcdef"); - status = 0x4eadbeef; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - todo_wine ok(ret, "ioctl failed\n"); - todo_wine ok(GetLastError() == 0xdeadbeef, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "ghidef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(file, NULL, NULL, NULL, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if (params->ret_status != params->iosb_status || params->ret_status == STATUS_PENDING) + ok(ret == params->ret_status + || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + "got %#x\n", ret); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(file, 0); + todo_wine ok(ret == WAIT_TIMEOUT, "got %d\n", ret); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(file, 0); + ok(!ret, "got %d\n", ret); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); + + /* Test FILE_SKIP_COMPLETION_PORT_ON_SUCCESS. */ + + if (pSetFileCompletionNotificationModes) + { + ret = pSetFileCompletionNotificationModes(file, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS); + ok(ret, "got error %u\n", GetLastError()); + + SetEvent(event); + strcpy(buffer, "abcdef"); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(file, event, NULL, (void *)456, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if (params->ret_status != params->iosb_status || params->ret_status == STATUS_PENDING) + ok(ret == params->ret_status + || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + "got %#x\n", ret); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + todo_wine ok(ret == WAIT_TIMEOUT, "got %d\n", ret); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + ret = WaitForSingleObject(event, 0); + ok(!ret, "got %d\n", ret); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); + + key = 0xdeadf00d; + value = 0xdeadf00d; + memset(&io, 0xcc, sizeof(io)); + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + if (!params->pending) + { + /* Completion is skipped on non-pending NT_ERROR regardless of file + * options. Windows < 8 interprets + * FILE_SKIP_COMPLETION_PORT_ON_SUCCESS to mean that !NT_ERROR + * should also be skipped. Windows >= 8 restricts this to + * NT_SUCCESS, which has the weird effect that non-pending + * NT_WARNING does *not* skip completion. It's not clear whether + * this is a bug or not—it looks like one, but on the other hand it + * arguably follows the letter of the documentation more closely. */ + ok(ret == STATUS_TIMEOUT || (NT_WARNING(params->iosb_status) && !ret), "got %#x\n", ret); + } + else + { + todo_wine ok(!ret, "got %#x\n", ret); + } + if (!ret) + { + ok(key == 123, "got key %Iu\n", key); + ok(value == 456, "got value %Iu\n", value); + ok(io.Status == params->iosb_status, "got iosb status %#x\n", io.Status); + ok(io.Information == 3, "got information %Iu\n", io.Information); + } + } + + ret = CloseHandle(file); + ok(ret, "failed to close file, error %u\n", GetLastError()); + ret = CloseHandle(port); + ok(ret, "failed to close port, error %u\n", GetLastError()); + + /* Test with an APC. */ + + got_return_status_apc = 0; + + file = CreateFileA("\\\\.\\WineTestDriver", FILE_ALL_ACCESS, + 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + ok(file != INVALID_HANDLE_VALUE, "failed to open device, error %u\n", GetLastError()); strcpy(buffer, "abcdef"); - status = 0x8eadbeef; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - ok(!ret, "ioctl succeeded\n"); - ok(GetLastError() == ERROR_MR_MID_NOT_FOUND, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "ghidef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + ret = NtDeviceIoControlFile(file, NULL, return_status_apc, (void *)456, &io, + ioctl, params, sizeof(*params), buffer, sizeof(buffer)); + todo_wine_if (params->ret_status != params->iosb_status || params->ret_status == STATUS_PENDING) + ok(ret == params->ret_status, "got %#x\n", ret); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(io.Status == 0xdeadf00d, "got %#x\n", io.Status); + todo_wine ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); + } + else + { + todo_wine_if (params->iosb_status == STATUS_PENDING) + ok(io.Status == params->iosb_status, "got %#x\n", io.Status); + ok(io.Information == 3, "got size %Iu\n", io.Information); + } + todo_wine_if (ioctl != IOCTL_WINETEST_RETURN_STATUS_BUFFERED) + ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer); - strcpy(buffer, "abcdef"); - status = 0xceadbeef; - SetLastError(0xdeadbeef); - ret = DeviceIoControl(device, IOCTL_WINETEST_RETURN_STATUS, &status, - sizeof(status), buffer, sizeof(buffer), &ret_size, NULL); - ok(!ret, "ioctl succeeded\n"); - ok(GetLastError() == ERROR_MR_MID_NOT_FOUND, "got error %u\n", GetLastError()); - ok(!strcmp(buffer, "abcdef"), "got buffer %s\n", buffer); - ok(ret_size == 3, "got size %u\n", ret_size); + ret = SleepEx(0, TRUE); + if (!params->pending && NT_ERROR(params->iosb_status)) + { + todo_wine ok(!ret, "got %d\n", ret); + todo_wine ok(!got_return_status_apc, "got %u APC calls\n", got_return_status_apc); + } + else + { + ok(ret == WAIT_IO_COMPLETION, "got %d\n", ret); + ok(got_return_status_apc == 1, "got %u APC calls\n", got_return_status_apc); + } + + ret = CloseHandle(file); + ok(ret, "failed to close file, error %u\n", GetLastError()); + + CloseHandle(event); +} + +static void test_return_status(void) +{ + struct return_status_params params; + unsigned int i, j, k; + + static const ULONG method_tests[] = + { + IOCTL_WINETEST_RETURN_STATUS_BUFFERED, + IOCTL_WINETEST_RETURN_STATUS_DIRECT, + IOCTL_WINETEST_RETURN_STATUS_NEITHER, + }; + + static const NTSTATUS status_tests[] = + { + STATUS_SUCCESS, + STATUS_PENDING, + STATUS_TIMEOUT, + 0x0eadbeef, + 0x4eadbeef, + STATUS_BUFFER_OVERFLOW, + 0x8eadbeef, + STATUS_NOT_IMPLEMENTED, + 0xceadbeef, + }; + + for (i = 0; i < ARRAY_SIZE(status_tests); ++i) + { + for (j = 0; j < ARRAY_SIZE(status_tests); ++j) + { + for (params.pending = 0; params.pending <= 1; ++params.pending) + { + for (k = 0; k < ARRAY_SIZE(method_tests); ++k) + { + params.ret_status = status_tests[i]; + params.iosb_status = status_tests[j]; + + winetest_push_context("return 0x%08x, iosb 0x%08x, pending %d, method %u", + params.ret_status, params.iosb_status, params.pending, method_tests[k] & 3); + + do_return_status(method_tests[k], ¶ms); + + winetest_pop_context(); + } + } + } + } } static BOOL compare_unicode_string(const WCHAR *buffer, ULONG len, const WCHAR *expect) @@ -765,13 +1057,25 @@ static void test_object_info(void) ok(compare_unicode_string(file_info->FileName, file_info->FileNameLength, L"\\subfile"), "wrong name %s\n", debugstr_wn(file_info->FileName, file_info->FileNameLength / sizeof(WCHAR))); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; status = NtQueryVolumeInformationFile(file, &io, buffer, sizeof(buffer), FileFsVolumeInformation); ok(!status, "got %#x\n", status); + ok(!io.Status, "got status %#x\n", io.Status); + size = offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + volume_info->VolumeLabelLength; + ok(io.Information == size, "expected information %Iu, got %Iu\n", size, io.Information); ok(volume_info->VolumeSerialNumber == 0xdeadbeef, "wrong serial number 0x%08x\n", volume_info->VolumeSerialNumber); ok(compare_unicode_string(volume_info->VolumeLabel, volume_info->VolumeLabelLength, L"WineTestDriver"), "wrong name %s\n", debugstr_wn(volume_info->VolumeLabel, volume_info->VolumeLabelLength / sizeof(WCHAR))); + io.Status = 0xdeadf00d; + io.Information = 0xdeadf00d; + status = NtQueryVolumeInformationFile(file, &io, buffer, sizeof(buffer), FileFsAttributeInformation); + ok(status == STATUS_NOT_IMPLEMENTED, "got %#x\n", status); + ok(io.Status == 0xdeadf00d, "got status %#x\n", io.Status); + ok(io.Information == 0xdeadf00d, "got information %Iu\n", io.Information); + CloseHandle(file); file = CreateFileA("\\\\.\\WineTestDriver\\notimpl", 0, 0, NULL, OPEN_EXISTING, 0, NULL); @@ -1354,7 +1658,7 @@ static void test_pnp_devices(void) ret = NtOpenFile(&child, SYNCHRONIZE, &attr, &io, 0, 0); ok(!ret, "failed to open child: %#x\n", ret); - ret = DeviceIoControl(child, IOCTL_WINETEST_MARK_PENDING, NULL, 0, NULL, 0, &size, &ovl); + ret = DeviceIoControl(child, IOCTL_WINETEST_CHILD_MARK_PENDING, NULL, 0, NULL, 0, &size, &ovl); ok(!ret, "DeviceIoControl succeeded\n"); ok(GetLastError() == ERROR_IO_PENDING, "got error %u\n", GetLastError()); ok(size == 0, "got size %u\n", size); @@ -1367,7 +1671,7 @@ static void test_pnp_devices(void) ok(got_child_arrival == 1, "got %u child arrival messages\n", got_child_arrival); ok(got_child_removal == 1, "got %u child removal messages\n", got_child_removal); - ret = DeviceIoControl(child, IOCTL_WINETEST_CHECK_REMOVED, NULL, 0, NULL, 0, &size, NULL); + ret = DeviceIoControl(child, IOCTL_WINETEST_CHILD_CHECK_REMOVED, NULL, 0, NULL, 0, &size, NULL); todo_wine ok(ret, "got error %u\n", GetLastError()); ret = NtOpenFile(&tmp, SYNCHRONIZE, &attr, &io, 0, FILE_SYNCHRONOUS_IO_NONALERT); @@ -3162,7 +3466,6 @@ START_TEST(ntoskrnl) ok(device != INVALID_HANDLE_VALUE, "failed to open device: %u\n", GetLastError()); test_basic_ioctl(); - test_mismatched_status_ioctl(); main_test(); todo_wine ok(modified_value == 0xdeadbeeffeedcafe, "Got unexpected value %#I64x.\n", modified_value);