This fixes Trials Fusion often crashing when disconnecting a controller while there are more still connected.
-- v14: ntoskrnl/tests: Use the 'Nt' version of the CancelIo APIs. ntoskrnl/tests: Test the thread ID the cancellation routine runs from. ntoskrnl/tests: Add more cancellation tests. ntoskrnl/tests: Fix tests on current Windows 10 / 11. ntdll/tests: Test IOSB values of the cancel operation. ntdll/tests: Add more NtCancelIoFile[Ex]() tests. ntdll: Wait for all asyncs to handle cancel in NtCancelIoFile(). server: Factor out a cancel_async() function. server: Introduce a find_async_from_user helper. ntdll: Factor out a cancel_io() function.
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntdll/unix/file.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 958ae9a6937..6803e4ec3ec 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -6476,19 +6476,16 @@ NTSTATUS WINAPI NtFlushBuffersFileEx( HANDLE handle, ULONG flags, void *params, }
-/************************************************************************** - * NtCancelIoFile (NTDLL.@) - */ -NTSTATUS WINAPI NtCancelIoFile( HANDLE handle, IO_STATUS_BLOCK *io_status ) +static NTSTATUS cancel_io( HANDLE handle, IO_STATUS_BLOCK *io, IO_STATUS_BLOCK *io_status, + BOOL only_thread ) { unsigned int status;
- TRACE( "%p %p\n", handle, io_status ); - SERVER_START_REQ( cancel_async ) { req->handle = wine_server_obj_handle( handle ); - req->only_thread = TRUE; + req->iosb = wine_server_client_ptr( io ); + req->only_thread = only_thread; if (!(status = wine_server_call( req ))) { io_status->Status = status; @@ -6501,28 +6498,25 @@ NTSTATUS WINAPI NtCancelIoFile( HANDLE handle, IO_STATUS_BLOCK *io_status ) }
+/************************************************************************** + * NtCancelIoFile (NTDLL.@) + */ +NTSTATUS WINAPI NtCancelIoFile( HANDLE handle, IO_STATUS_BLOCK *io_status ) +{ + TRACE( "%p %p\n", handle, io_status ); + + return cancel_io( handle, NULL, io_status, TRUE ); +} + + /************************************************************************** * NtCancelIoFileEx (NTDLL.@) */ NTSTATUS WINAPI NtCancelIoFileEx( HANDLE handle, IO_STATUS_BLOCK *io, IO_STATUS_BLOCK *io_status ) { - unsigned int status; - TRACE( "%p %p %p\n", handle, io, io_status );
- SERVER_START_REQ( cancel_async ) - { - req->handle = wine_server_obj_handle( handle ); - req->iosb = wine_server_client_ptr( io ); - if (!(status = wine_server_call( req ))) - { - io_status->Status = status; - io_status->Information = 0; - } - } - SERVER_END_REQ; - - return status; + return cancel_io( handle, io, io_status, FALSE ); }
From: Rémi Bernon rbernon@codeweavers.com
--- server/async.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/server/async.c b/server/async.c index 4068f744567..1602746a969 100644 --- a/server/async.c +++ b/server/async.c @@ -576,6 +576,16 @@ int async_waiting( struct async_queue *queue ) return !async->terminated; }
+static struct async *find_async_from_user( struct process *process, client_ptr_t user ) +{ + struct async *async; + + LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) + if (async->data.user == user) return async; + + return NULL; +} + static int cancel_async( struct process *process, struct object *obj, struct thread *thread, client_ptr_t iosb ) { struct async *async; @@ -830,17 +840,10 @@ DECL_HANDLER(cancel_async) /* get async result from associated iosb */ DECL_HANDLER(get_async_result) { - struct iosb *iosb = NULL; struct async *async; + struct iosb *iosb;
- LIST_FOR_EACH_ENTRY( async, ¤t->process->asyncs, struct async, process_entry ) - if (async->data.user == req->user_arg) - { - iosb = async->iosb; - break; - } - - if (!iosb) + if (!(async = find_async_from_user( current->process, req->user_arg )) || !(iosb = async->iosb)) { set_error( STATUS_INVALID_PARAMETER ); return;
From: Matteo Bruni mbruni@codeweavers.com
--- server/async.c | 49 ++++++++++++++++++++++++------------------------ server/file.h | 2 +- server/process.c | 2 +- 3 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/server/async.c b/server/async.c index 1602746a969..764596642db 100644 --- a/server/async.c +++ b/server/async.c @@ -54,7 +54,7 @@ struct async unsigned int direct_result :1;/* a flag if we're passing result directly from request instead of APC */ unsigned int alerted :1; /* fd is signaled, but we are waiting for client-side I/O */ unsigned int terminated :1; /* async has been terminated */ - unsigned int canceled :1; /* have we already queued cancellation for this async? */ + unsigned int cancelled :1; /* have we already queued cancellation for this async? */ unsigned int unknown_status :1; /* initial status is not known yet */ unsigned int blocking :1; /* async is blocking */ unsigned int is_system :1; /* background system operation not affecting userspace visible state. */ @@ -276,7 +276,7 @@ struct async *create_async( struct fd *fd, struct thread *thread, const struct a async->direct_result = 0; async->alerted = 0; async->terminated = 0; - async->canceled = 0; + async->cancelled = 0; async->unknown_status = 0; async->blocking = !is_fd_overlapped( fd ); async->is_system = 0; @@ -576,6 +576,12 @@ int async_waiting( struct async_queue *queue ) return !async->terminated; }
+static void cancel_async( struct async *async ) +{ + async->cancelled = 1; + fd_cancel_async( async->fd, async ); +} + static struct async *find_async_from_user( struct process *process, client_ptr_t user ) { struct async *async; @@ -586,25 +592,24 @@ static struct async *find_async_from_user( struct process *process, client_ptr_t return NULL; }
-static int cancel_async( struct process *process, struct object *obj, struct thread *thread, client_ptr_t iosb ) +static int cancel_process_async( struct process *process, struct object *obj, struct thread *thread, client_ptr_t iosb ) { struct async *async; int woken = 0;
- /* FIXME: it would probably be nice to replace the "canceled" flag with a - * single LIST_FOR_EACH_ENTRY_SAFE, but currently cancelling an async can - * cause other asyncs to be removed via async_reselect() */ + /* We can't simply use LIST_FOR_EACH_ENTRY_SAFE here, because currently + * cancelling an async can cause other asyncs to be removed via + * async_reselect() */
restart: LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { - if (async->terminated || async->canceled || async->is_system) continue; + if (async->terminated || async->cancelled || async->is_system) continue; if ((!obj || (get_fd_user( async->fd ) == obj)) && (!thread || async->thread == thread) && (!iosb || async->data.iosb == iosb)) { - async->canceled = 1; - fd_cancel_async( async->fd, async ); + cancel_async( async ); woken++; goto restart; } @@ -620,12 +625,11 @@ static int cancel_blocking( struct process *process, struct thread *thread, clie restart: LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { - if (async->terminated || async->canceled) continue; + if (async->terminated || async->cancelled) continue; if (async->blocking && async->thread == thread && (!iosb || async->data.iosb == iosb)) { - async->canceled = 1; - fd_cancel_async( async->fd, async ); + cancel_async( async ); woken++; goto restart; } @@ -633,16 +637,15 @@ restart: return woken; }
-void cancel_process_asyncs( struct process *process ) +void cancel_terminating_process_asyncs( struct process *process ) { struct async *async;
restart: LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { - if (async->terminated || async->canceled) continue; - async->canceled = 1; - fd_cancel_async( async->fd, async ); + if (async->terminated || async->cancelled) continue; + cancel_async( async ); goto restart; } } @@ -659,11 +662,9 @@ int async_close_obj_handle( struct object *obj, struct process *process, obj_han restart: LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { - if (async->terminated || async->canceled || get_fd_user( async->fd ) != obj) continue; + if (async->terminated || async->cancelled || get_fd_user( async->fd ) != obj) continue; if (!async->completion || !async->data.apc_context || async->event) continue; - - async->canceled = 1; - fd_cancel_async( async->fd, async ); + cancel_async( async ); goto restart; } return 1; @@ -676,12 +677,10 @@ void cancel_terminating_thread_asyncs( struct thread *thread ) restart: LIST_FOR_EACH_ENTRY( async, &thread->process->asyncs, struct async, process_entry ) { - if (async->thread != thread || async->terminated || async->canceled) continue; + if (async->thread != thread || async->terminated || async->cancelled) continue; if (async->completion && async->data.apc_context && !async->event) continue; if (async->is_system) continue; - - async->canceled = 1; - fd_cancel_async( async->fd, async ); + cancel_async( async ); goto restart; } } @@ -831,7 +830,7 @@ DECL_HANDLER(cancel_async)
if (obj) { - int count = cancel_async( current->process, obj, thread, req->iosb ); + int count = cancel_process_async( current->process, obj, thread, req->iosb ); if (!count && !thread) set_error( STATUS_NOT_FOUND ); release_object( obj ); } diff --git a/server/file.h b/server/file.h index 567194bf00a..7f2d0185bd4 100644 --- a/server/file.h +++ b/server/file.h @@ -278,7 +278,7 @@ extern void fd_copy_completion( struct fd *src, struct fd *dst ); extern struct iosb *async_get_iosb( struct async *async ); extern struct thread *async_get_thread( struct async *async ); extern struct async *find_pending_async( struct async_queue *queue ); -extern void cancel_process_asyncs( struct process *process ); +extern void cancel_terminating_process_asyncs( struct process *process ); extern void cancel_terminating_thread_asyncs( struct thread *thread ); extern int async_close_obj_handle( struct object *obj, struct process *process, obj_handle_t handle );
diff --git a/server/process.c b/server/process.c index 8e0f9189a61..28fcfc02f5e 100644 --- a/server/process.c +++ b/server/process.c @@ -987,7 +987,7 @@ static void process_killed( struct process *process ) close_process_desktop( process ); process->winstation = 0; process->desktop = 0; - cancel_process_asyncs( process ); + cancel_terminating_process_asyncs( process ); close_process_handles( process ); if (process->idle_event) release_object( process->idle_event ); process->idle_event = NULL;
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntdll/unix/file.c | 14 ++- include/wine/server_protocol.h | 4 +- server/async.c | 155 +++++++++++++++++++++++++++++++-- server/protocol.def | 2 + server/request_handlers.h | 2 + server/request_trace.h | 7 +- 6 files changed, 169 insertions(+), 15 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 6803e4ec3ec..4cdbbd30ff4 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -6479,6 +6479,7 @@ NTSTATUS WINAPI NtFlushBuffersFileEx( HANDLE handle, ULONG flags, void *params, static NTSTATUS cancel_io( HANDLE handle, IO_STATUS_BLOCK *io, IO_STATUS_BLOCK *io_status, BOOL only_thread ) { + HANDLE cancel_handle; unsigned int status;
SERVER_START_REQ( cancel_async ) @@ -6487,13 +6488,18 @@ static NTSTATUS cancel_io( HANDLE handle, IO_STATUS_BLOCK *io, IO_STATUS_BLOCK * req->iosb = wine_server_client_ptr( io ); req->only_thread = only_thread; if (!(status = wine_server_call( req ))) - { - io_status->Status = status; - io_status->Information = 0; - } + cancel_handle = wine_server_ptr_handle( reply->cancel_handle ); } SERVER_END_REQ;
+ if (!status && cancel_handle) + NtWaitForMultipleObjects( 1, &cancel_handle, FALSE, TRUE, NULL ); + else if (status == STATUS_INVALID_HANDLE) + return status; + + io_status->Status = status; + io_status->Information = 0; + return status; }
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 781b4a4e59f..244cb4646a0 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -3297,6 +3297,8 @@ struct cancel_async_request struct cancel_async_reply { struct reply_header __header; + obj_handle_t cancel_handle; + char __pad_12[4]; };
@@ -6886,6 +6888,6 @@ union generic_reply struct get_inproc_sync_fd_reply get_inproc_sync_fd_reply; };
-#define SERVER_PROTOCOL_VERSION 893 +#define SERVER_PROTOCOL_VERSION 894
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/async.c b/server/async.c index 764596642db..f8635520f41 100644 --- a/server/async.c +++ b/server/async.c @@ -34,6 +34,96 @@ #include "process.h" #include "handle.h"
+struct async_cancel +{ + struct object obj; /* object header */ + struct event_sync *sync; /* sync object for wait/signal */ + unsigned int count; /* count of the asyncs in the cancel group */ + obj_handle_t wait_handle; /* handle to wait for all the cancels to complete */ + struct process *process; /* referenced process, only used to close wait_handle */ +}; + +static void async_cancel_dump( struct object *obj, int verbose ); +static void async_cancel_satisfied( struct object *obj, struct wait_queue_entry *entry ); +static struct object *async_cancel_get_sync( struct object *obj ); +static void async_cancel_destroy( struct object *obj ); + +static const struct object_ops async_cancel_ops = +{ + sizeof(struct async_cancel), /* size */ + &no_type, /* type */ + async_cancel_dump, /* dump */ + NULL, /* add_queue */ + NULL, /* remove_queue */ + NULL, /* signaled */ + async_cancel_satisfied, /* satisfied */ + no_signal, /* signal */ + no_get_fd, /* get_fd */ + async_cancel_get_sync, /* get_sync */ + default_map_access, /* map_access */ + default_get_sd, /* get_sd */ + default_set_sd, /* set_sd */ + no_get_full_name, /* get_full_name */ + no_lookup_name, /* lookup_name */ + no_link_name, /* link_name */ + NULL, /* unlink_name */ + no_open_file, /* open_file */ + no_kernel_obj_list, /* get_kernel_obj_list */ + no_close_handle, /* close_handle */ + async_cancel_destroy /* destroy */ +}; + +static void async_cancel_dump( struct object *obj, int verbose ) +{ + struct async_cancel *cancel = (struct async_cancel *)obj; + assert( obj->ops == &async_cancel_ops ); + fprintf( stderr, "async_cancel %p\n", cancel ); +} + +static void async_cancel_satisfied( struct object *obj, struct wait_queue_entry *entry ) +{ + struct async_cancel *cancel = (struct async_cancel *)obj; + assert( obj->ops == &async_cancel_ops ); + + /* close wait handle here to avoid extra server round trip */ + close_handle( cancel->process, cancel->wait_handle ); + cancel->wait_handle = 0; +} + +static struct object *async_cancel_get_sync( struct object *obj ) +{ + struct async_cancel *cancel = (struct async_cancel *)obj; + + assert( obj->ops == &async_cancel_ops ); + return grab_object( cancel->sync ); +} + +static void async_cancel_destroy( struct object *obj ) +{ + struct async_cancel *cancel = (struct async_cancel *)obj; + + assert( obj->ops == &async_cancel_ops ); + if (cancel->sync) release_object( cancel->sync ); +} + +static struct async_cancel *create_async_cancel( struct process *process ) +{ + struct async_cancel *cancel; + + if (!(cancel = alloc_object( &async_cancel_ops ))) return NULL; + cancel->sync = NULL; + cancel->count = 0; + cancel->wait_handle = 0; + cancel->process = process; + + if (!(cancel->sync = create_event_sync( 1, 0 ))) + { + release_object( cancel ); + return NULL; + } + return cancel; +} + struct async { struct object obj; /* object header */ @@ -63,6 +153,7 @@ struct async unsigned int comp_flags; /* completion flags */ async_completion_callback completion_callback; /* callback to be called on completion */ void *completion_callback_private; /* argument to completion_callback */ + struct async_cancel *async_cancel; /* cancel object if async is being cancelled */ };
static void async_dump( struct object *obj, int verbose ); @@ -146,6 +237,7 @@ static void async_destroy( struct object *obj ) struct async *async = (struct async *)obj; assert( obj->ops == &async_ops );
+ assert( !async->async_cancel ); list_remove( &async->process_entry );
if (async->queue) @@ -284,6 +376,7 @@ struct async *create_async( struct fd *fd, struct thread *thread, const struct a async->comp_flags = 0; async->completion_callback = NULL; async->completion_callback_private = NULL; + async->async_cancel = NULL;
if (iosb) async->iosb = (struct iosb *)grab_object( iosb ); else async->iosb = NULL; @@ -485,6 +578,20 @@ static void add_async_completion( struct async *async, apc_param_t cvalue, unsig if (async->completion) add_completion( async->completion, async->comp_key, cvalue, status, information ); }
+static void async_complete_cancel( struct async *async ) +{ + struct async_cancel *cancel; + + if (!(cancel = async->async_cancel)) return; + async->async_cancel = NULL; + + if (!--cancel->count) + { + signal_sync( cancel->sync ); + release_object( cancel ); + } +} + /* store the result of the client-side async callback */ void async_set_result( struct object *obj, unsigned int status, apc_param_t total ) { @@ -543,6 +650,7 @@ void async_set_result( struct object *obj, unsigned int status, apc_param_t tota }
async_call_completion_callback( async ); + async_complete_cancel( async );
if (async->queue) { @@ -592,10 +700,16 @@ static struct async *find_async_from_user( struct process *process, client_ptr_t return NULL; }
-static int cancel_process_async( struct process *process, struct object *obj, struct thread *thread, client_ptr_t iosb ) +static int cancel_process_async( struct process *process, struct object *obj, struct thread *thread, client_ptr_t iosb, obj_handle_t *wait_handle ) { - struct async *async; - int woken = 0; + struct async_cancel *cancel = NULL; + struct async *async, *next_async; + struct list tracked; + int count = 0; + + if (thread && !(cancel = create_async_cancel( process ))) return 0; + + list_init( &tracked );
/* We can't simply use LIST_FOR_EACH_ENTRY_SAFE here, because currently * cancelling an async can cause other asyncs to be removed via @@ -604,17 +718,37 @@ static int cancel_process_async( struct process *process, struct object *obj, st restart: LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { - if (async->terminated || async->cancelled || async->is_system) continue; + if (async->terminated || async->is_system) continue; if ((!obj || (get_fd_user( async->fd ) == obj)) && (!thread || async->thread == thread) && (!iosb || async->data.iosb == iosb)) { - cancel_async( async ); - woken++; + if (!async->cancelled) cancel_async( async ); + if (cancel) + { + assert( !async->async_cancel ); + async->async_cancel = cancel; + cancel->count++; + } + list_remove( &async->process_entry ); + list_add_tail( &tracked, &async->process_entry ); + count++; goto restart; } } - return woken; + /* Put the asyncs back into the process list */ + LIST_FOR_EACH_ENTRY_SAFE( async, next_async, &tracked, struct async, process_entry ) + { + list_remove( &async->process_entry ); + list_add_tail( &process->asyncs, &async->process_entry ); + } + if (cancel) + { + if (!cancel->count) release_object( cancel ); + else cancel->wait_handle = alloc_handle( process, cancel, SYNCHRONIZE, 0 ); + *wait_handle = cancel->wait_handle; + } + return count; }
static int cancel_blocking( struct process *process, struct thread *thread, client_ptr_t iosb ) @@ -672,10 +806,11 @@ restart:
void cancel_terminating_thread_asyncs( struct thread *thread ) { + struct process *process = thread->process; struct async *async;
restart: - LIST_FOR_EACH_ENTRY( async, &thread->process->asyncs, struct async, process_entry ) + LIST_FOR_EACH_ENTRY( async, &process->asyncs, struct async, process_entry ) { if (async->thread != thread || async->terminated || async->cancelled) continue; if (async->completion && async->data.apc_context && !async->event) continue; @@ -827,11 +962,13 @@ DECL_HANDLER(cancel_async) { struct object *obj = get_handle_obj( current->process, req->handle, 0, NULL ); struct thread *thread = req->only_thread ? current : NULL; + obj_handle_t wait_handle = 0;
if (obj) { - int count = cancel_process_async( current->process, obj, thread, req->iosb ); + int count = cancel_process_async( current->process, obj, thread, req->iosb, &wait_handle ); if (!count && !thread) set_error( STATUS_NOT_FOUND ); + else reply->cancel_handle = wait_handle; release_object( obj ); } } diff --git a/server/protocol.def b/server/protocol.def index 59c6436fa88..539719208ad 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2474,6 +2474,8 @@ enum message_type obj_handle_t handle; /* handle to comm port, socket or file */ client_ptr_t iosb; /* I/O status block (NULL=all) */ int only_thread; /* cancel matching this thread */ +@REPLY + obj_handle_t cancel_handle; @END
diff --git a/server/request_handlers.h b/server/request_handlers.h index cfaf03d1b67..6561cb5fa0c 100644 --- a/server/request_handlers.h +++ b/server/request_handlers.h @@ -1409,6 +1409,8 @@ C_ASSERT( offsetof(struct cancel_async_request, handle) == 12 ); C_ASSERT( offsetof(struct cancel_async_request, iosb) == 16 ); C_ASSERT( offsetof(struct cancel_async_request, only_thread) == 24 ); C_ASSERT( sizeof(struct cancel_async_request) == 32 ); +C_ASSERT( offsetof(struct cancel_async_reply, cancel_handle) == 8 ); +C_ASSERT( sizeof(struct cancel_async_reply) == 16 ); C_ASSERT( offsetof(struct get_async_result_request, user_arg) == 16 ); C_ASSERT( sizeof(struct get_async_result_request) == 24 ); C_ASSERT( sizeof(struct get_async_result_reply) == 8 ); diff --git a/server/request_trace.h b/server/request_trace.h index f2fe556cd5f..36b0397cfda 100644 --- a/server/request_trace.h +++ b/server/request_trace.h @@ -1579,6 +1579,11 @@ static void dump_cancel_async_request( const struct cancel_async_request *req ) fprintf( stderr, ", only_thread=%d", req->only_thread ); }
+static void dump_cancel_async_reply( const struct cancel_async_reply *req ) +{ + fprintf( stderr, " cancel_handle=%04x", req->cancel_handle ); +} + static void dump_get_async_result_request( const struct get_async_result_request *req ) { dump_uint64( " user_arg=", &req->user_arg ); @@ -3835,7 +3840,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = NULL, NULL, NULL, - NULL, + (dump_func)dump_cancel_async_reply, (dump_func)dump_get_async_result_reply, (dump_func)dump_set_async_direct_result_reply, (dump_func)dump_read_reply,
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntdll/tests/pipe.c | 57 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-)
diff --git a/dlls/ntdll/tests/pipe.c b/dlls/ntdll/tests/pipe.c index 14a6148a2c5..64bdb7e3b66 100644 --- a/dlls/ntdll/tests/pipe.c +++ b/dlls/ntdll/tests/pipe.c @@ -569,14 +569,33 @@ static void test_nonalertable(void) CloseHandle(hPipe); }
-static void test_cancelio(void) +struct cancelio_ctx { - IO_STATUS_BLOCK iosb; + HANDLE pipe; + HANDLE event; + IO_STATUS_BLOCK *iosb; + BOOL null_iosb; +}; + +static DWORD WINAPI cancelioex_thread_func(void *arg) +{ + struct cancelio_ctx *ctx = arg; IO_STATUS_BLOCK cancel_sb; - HANDLE hEvent; - HANDLE hPipe; NTSTATUS res;
+ res = pNtCancelIoFileEx(ctx->pipe, ctx->null_iosb ? NULL : ctx->iosb, &cancel_sb); + ok(!res, "NtCancelIoFileEx returned %lx\n", res); + + return 0; +} + +static void test_cancelio(void) +{ + IO_STATUS_BLOCK cancel_sb, iosb; + HANDLE hEvent, hPipe, thread; + struct cancelio_ctx ctx; + NTSTATUS res, status; + hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); ok(hEvent != INVALID_HANDLE_VALUE, "can't create event, GetLastError: %lx\n", GetLastError());
@@ -589,9 +608,13 @@ static void test_cancelio(void) ok(res == STATUS_PENDING, "NtFsControlFile returned %lx\n", res);
res = pNtCancelIoFile(hPipe, &cancel_sb); + /* Save Status first thing after the call. We want to make very unlikely + * that the kernel APC updating Status could be executed after the call + * but before peeking at Status here. */ + status = iosb.Status; ok(!res, "NtCancelIoFile returned %lx\n", res);
- ok(iosb.Status == STATUS_CANCELLED, "Wrong iostatus %lx\n", iosb.Status); + ok(status == STATUS_CANCELLED, "Wrong iostatus %lx\n", status); ok(WaitForSingleObject(hEvent, 0) == 0, "hEvent not signaled\n");
ok(!ioapc_called, "IOAPC ran too early\n"); @@ -616,9 +639,10 @@ static void test_cancelio(void) ok(res == STATUS_PENDING, "NtFsControlFile returned %lx\n", res);
res = pNtCancelIoFileEx(hPipe, &iosb, &cancel_sb); + status = iosb.Status; ok(!res, "NtCancelIoFileEx returned %lx\n", res);
- ok(iosb.Status == STATUS_CANCELLED, "Wrong iostatus %lx\n", iosb.Status); + ok(status == STATUS_CANCELLED, "Wrong iostatus %lx\n", status); ok(WaitForSingleObject(hEvent, 0) == 0, "hEvent not signaled\n");
iosb.Status = 0xdeadbeef; @@ -626,6 +650,27 @@ static void test_cancelio(void) ok(res == STATUS_NOT_FOUND, "NtCancelIoFileEx returned %lx\n", res); ok(iosb.Status == 0xdeadbeef, "Wrong iostatus %lx\n", iosb.Status);
+ memset(&iosb, 0x55, sizeof(iosb)); + res = listen_pipe(hPipe, hEvent, &iosb, FALSE); + ok(res == STATUS_PENDING, "NtFsControlFile returned %lx\n", res); + + ctx.pipe = hPipe; + ctx.event = hEvent; + ctx.iosb = &iosb; + ctx.null_iosb = FALSE; + thread = CreateThread(NULL, 0, cancelioex_thread_func, &ctx, 0, NULL); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + memset(&iosb, 0x55, sizeof(iosb)); + res = listen_pipe(hPipe, hEvent, &iosb, FALSE); + ok(res == STATUS_PENDING, "NtFsControlFile returned %lx\n", res); + + ctx.null_iosb = TRUE; + thread = CreateThread(NULL, 0, cancelioex_thread_func, &ctx, 0, NULL); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + CloseHandle(hPipe); } else
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntdll/tests/pipe.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/dlls/ntdll/tests/pipe.c b/dlls/ntdll/tests/pipe.c index 64bdb7e3b66..5efb969bcf0 100644 --- a/dlls/ntdll/tests/pipe.c +++ b/dlls/ntdll/tests/pipe.c @@ -586,6 +586,9 @@ static DWORD WINAPI cancelioex_thread_func(void *arg) res = pNtCancelIoFileEx(ctx->pipe, ctx->null_iosb ? NULL : ctx->iosb, &cancel_sb); ok(!res, "NtCancelIoFileEx returned %lx\n", res);
+ ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); + return 0; }
@@ -613,6 +616,8 @@ static void test_cancelio(void) * but before peeking at Status here. */ status = iosb.Status; ok(!res, "NtCancelIoFile returned %lx\n", res); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information);
ok(status == STATUS_CANCELLED, "Wrong iostatus %lx\n", status); ok(WaitForSingleObject(hEvent, 0) == 0, "hEvent not signaled\n"); @@ -626,6 +631,8 @@ static void test_cancelio(void) res = pNtCancelIoFile(hPipe, &cancel_sb); ok(!res, "NtCancelIoFile returned %lx\n", res); ok(iosb.Status == STATUS_CANCELLED, "Wrong iostatus %lx\n", iosb.Status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information);
CloseHandle(hPipe);
@@ -641,6 +648,8 @@ static void test_cancelio(void) res = pNtCancelIoFileEx(hPipe, &iosb, &cancel_sb); status = iosb.Status; ok(!res, "NtCancelIoFileEx returned %lx\n", res); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information);
ok(status == STATUS_CANCELLED, "Wrong iostatus %lx\n", status); ok(WaitForSingleObject(hEvent, 0) == 0, "hEvent not signaled\n"); @@ -649,6 +658,8 @@ static void test_cancelio(void) res = pNtCancelIoFileEx(hPipe, NULL, &cancel_sb); ok(res == STATUS_NOT_FOUND, "NtCancelIoFileEx returned %lx\n", res); ok(iosb.Status == 0xdeadbeef, "Wrong iostatus %lx\n", iosb.Status); + ok(cancel_sb.Status == STATUS_NOT_FOUND, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information);
memset(&iosb, 0x55, sizeof(iosb)); res = listen_pipe(hPipe, hEvent, &iosb, FALSE); @@ -735,6 +746,8 @@ static void test_cancelsynchronousio(void) ok(ret == WAIT_TIMEOUT, "WaitForSingleObject returned %lu (error %lu)\n", ret, GetLastError()); memset(&iosb, 0x55, sizeof(iosb)); res = pNtCancelSynchronousIoFile(thread, NULL, &iosb); + ok(ctx.iosb.Status == 0xdeadbabe, "Unexpected Status %lx\n", ctx.iosb.Status); + ok(ctx.iosb.Information == 0xdeadbeef, "Unexpected Information %Iu\n", ctx.iosb.Information); ok(res == STATUS_SUCCESS, "Failed to cancel I/O\n"); ok(iosb.Status == STATUS_SUCCESS, "iosb.Status got changed to %lx\n", iosb.Status); ok(iosb.Information == 0, "iosb.Information got changed to %Iu\n", iosb.Information);
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntoskrnl.exe/tests/ntoskrnl.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 87f41895511..8e2132c73b1 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -695,15 +695,21 @@ static void do_return_status(ULONG ioctl, struct return_status_params *params) ret = NtDeviceIoControlFile(device, NULL, NULL, NULL, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == expect_status, "got %#x\n", ret); - if (NT_ERROR(params->iosb_status)) + if (NT_ERROR(params->iosb_status) && !params->pending) { ok(io.Status == 0xdeadf00d, "got %#lx\n", io.Status); ok(io.Information == 0xdeadf00d, "got size %Iu\n", io.Information); } else { - ok(io.Status == params->iosb_status, "got %#lx\n", io.Status); - ok(io.Information == 3, "got size %Iu\n", io.Information); + todo_wine_if (NT_ERROR(params->iosb_status) && params->pending) + ok(io.Status == params->iosb_status /* newer win10/11 */ + || broken(NT_ERROR(params->iosb_status) && params->pending && io.Status == 0xdeadf00d), /* older win10 */ + "got %#lx\n", io.Status); + todo_wine_if (NT_ERROR(params->iosb_status) && params->pending) + ok(io.Information == 3 /* newer win10/11 */ + || broken(NT_ERROR(params->iosb_status) && params->pending && io.Information == 0xdeadf00d), /* older win10 */ + "got size %Iu\n", io.Information); } ok(!strcmp(buffer, expect_buffer), "got buffer %s\n", buffer);
@@ -725,7 +731,7 @@ static void do_return_status(ULONG ioctl, struct return_status_params *params) ret = NtDeviceIoControlFile(file, event, NULL, (void *)456, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == params->ret_status - || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + || (NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* newer win10/11 */ "got %#x\n", ret); if (!params->pending && NT_ERROR(params->iosb_status)) { @@ -772,7 +778,7 @@ static void do_return_status(ULONG ioctl, struct return_status_params *params) ret = NtDeviceIoControlFile(file, event, NULL, NULL, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == params->ret_status - || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + || (NT_WARNING(params->ret_status) && ret == STATUS_PENDING), "got %#x\n", ret); if (!params->pending && NT_ERROR(params->iosb_status)) { @@ -800,7 +806,7 @@ static void do_return_status(ULONG ioctl, struct return_status_params *params) ret = NtDeviceIoControlFile(file, NULL, NULL, NULL, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == params->ret_status - || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + || (NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* newer win10/11 */ "got %#x\n", ret); if (!params->pending && NT_ERROR(params->iosb_status)) { @@ -832,7 +838,7 @@ static void do_return_status(ULONG ioctl, struct return_status_params *params) ret = NtDeviceIoControlFile(file, event, NULL, (void *)456, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == params->ret_status - || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + || (NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* newer win10/11 */ "got %#x\n", ret); if (!params->pending && NT_ERROR(params->iosb_status)) { @@ -1132,8 +1138,10 @@ static void test_blocking_irp(void) io.Information = 0xdeadf00d; status = NtQueryVolumeInformationFile(file, &io, buffer, sizeof(buffer), FileFsFullSizeInformation); ok(status == STATUS_DEVICE_NOT_READY, "got %#lx\n", status); - ok(io.Status == 0xdeadf00d, "got iosb status %#lx\n", io.Status); - ok(io.Information == 0xdeadf00d, "got information %#Ix\n", io.Information); + todo_wine ok(io.Status == STATUS_DEVICE_NOT_READY || broken(io.Status == 0xdeadf00d), /* older win10 */ + "got iosb status %#lx\n", io.Status); + todo_wine ok(io.Information == 0 || broken(io.Information == 0xdeadf00d), /* older win10 */ + "got information %#Ix\n", io.Information);
CloseHandle(file);
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntoskrnl.exe/tests/driver.c | 32 ++++++ dlls/ntoskrnl.exe/tests/driver.h | 2 + dlls/ntoskrnl.exe/tests/ntoskrnl.c | 174 ++++++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 1 deletion(-)
diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c index dda8806ba74..c153fa8d957 100644 --- a/dlls/ntoskrnl.exe/tests/driver.c +++ b/dlls/ntoskrnl.exe/tests/driver.c @@ -58,6 +58,9 @@ static int kmemcmp( const void *ptr1, const void *ptr2, size_t n ) static DRIVER_OBJECT *driver_obj; static DEVICE_OBJECT *lower_device, *upper_device;
+static IRP *queued_async_irps[2]; +static unsigned int queued_async_count; + static POBJECT_TYPE *pExEventObjectType, *pIoFileObjectType, *pPsThreadType, *pIoDriverObjectType; static PEPROCESS *pPsInitialSystemProcess; static void *create_caller_thread; @@ -2682,10 +2685,17 @@ static NTSTATUS WINAPI driver_Create(DEVICE_OBJECT *device, IRP *irp) return STATUS_SUCCESS; }
+static void cancel_complete(IRP *irp) +{ + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); +} + static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); NTSTATUS status = STATUS_NOT_SUPPORTED; + unsigned int i;
switch (stack->Parameters.DeviceIoControl.IoControlCode) { @@ -2699,6 +2709,7 @@ static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp) status = test_load_driver_ioctl(irp, stack, &irp->IoStatus.Information); break; case IOCTL_WINETEST_RESET_CANCEL: + ok(!queued_async_count, "expected no queued asyncs left\n"); cancel_cnt = 0; status = STATUS_SUCCESS; break; @@ -2706,6 +2717,27 @@ static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp) IoSetCancelRoutine(irp, cancel_ioctl_irp); IoMarkIrpPending(irp); return STATUS_PENDING; + case IOCTL_WINETEST_QUEUE_ASYNC: + if (queued_async_count >= ARRAY_SIZE(queued_async_irps)) + { + ok(FALSE, "unexpected queued_async_count\n"); + status = ERROR_TOO_MANY_CMDS; + break; + } + queued_async_irps[queued_async_count++] = irp; + IoMarkIrpPending(irp); + return STATUS_PENDING; + case IOCTL_WINETEST_COMPLETE_ASYNC: + /* complete the pending IRPs gathered from + * IOCTL_WINETEST_QUEUE_ASYNC */ + for (i = 0; i < queued_async_count; i++) + { + cancel_complete(queued_async_irps[i]); + queued_async_irps[i] = NULL; + } + queued_async_count = 0; + status = STATUS_SUCCESS; + break; case IOCTL_WINETEST_GET_CANCEL_COUNT: status = get_dword(irp, stack, &irp->IoStatus.Information, cancel_cnt); break; diff --git a/dlls/ntoskrnl.exe/tests/driver.h b/dlls/ntoskrnl.exe/tests/driver.h index e22d756e69e..524ba6abeed 100644 --- a/dlls/ntoskrnl.exe/tests/driver.h +++ b/dlls/ntoskrnl.exe/tests/driver.h @@ -36,6 +36,8 @@ #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_QUEUE_ASYNC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80d, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_COMPLETE_ASYNC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80e, METHOD_BUFFERED, 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) diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 8e2132c73b1..009d5a14c8f 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -438,12 +438,95 @@ static void test_basic_ioctl(void) ok(!strcmp(buf, "Wine is no"), "got '%s'\n", buf); }
+static BOOL cancelioex_returned; + +enum cancel_test +{ + cancel, + cancelex, + cancelex_cancel, + cancel_cancelex, + cancelsync, +}; + +struct cancel_thread_ctx +{ + HANDLE file; + enum cancel_test test; +}; + +static DWORD WINAPI test_cancel_thread(void *param) +{ + struct cancel_thread_ctx *ctx = param; + DWORD cancel_cnt, size; + OVERLAPPED o, o2, o3; + BOOL res; + + res = DeviceIoControl(ctx->file, IOCTL_WINETEST_RESET_CANCEL, NULL, 0, NULL, 0, NULL, &o); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + + if (ctx->test != cancelsync) + { + res = DeviceIoControl(ctx->file, IOCTL_WINETEST_QUEUE_ASYNC, NULL, 0, NULL, 0, NULL, &o); + ok(!res && GetLastError() == ERROR_IO_PENDING, "DeviceIoControl failed: %lu\n", GetLastError()); + + res = DeviceIoControl(ctx->file, IOCTL_WINETEST_QUEUE_ASYNC, NULL, 0, NULL, 0, NULL, &o2); + ok(!res && GetLastError() == ERROR_IO_PENDING, "DeviceIoControl failed: %lu\n", GetLastError()); + } + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(ctx->file, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), NULL, &o3); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 0, "cancel_cnt = %lu\n", cancel_cnt); + + if (ctx->test == cancelsync) + { + /* This hangs */ + if (0) + { + res = DeviceIoControl(device, IOCTL_WINETEST_QUEUE_ASYNC, NULL, 0, NULL, 0, &size, NULL); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + } + /* This hangs if the cancel routine doesn't call IoCompleteRequest() + * (regardless of Status / Information values) */ + res = DeviceIoControl(device, IOCTL_WINETEST_TEST_CANCEL, NULL, 0, NULL, 0, &size, NULL); + ok(!res && GetLastError() == ERROR_OPERATION_ABORTED, "DeviceIoControl failed: %lu\n", GetLastError()); + } + + if (ctx->test == cancel || ctx->test == cancel_cancelex) + { + res = CancelIo(ctx->file); + ok(res, "CancelIo failed: %lu\n", GetLastError()); + } + else if (ctx->test == cancelex || ctx->test == cancelex_cancel) + { + res = CancelIoEx(ctx->file, NULL); + ok(res, "CancelIoEx failed: %lu\n", GetLastError()); + res = CancelIoEx(ctx->file, NULL); + ok(res, "CancelIoEx failed: %lu\n", GetLastError()); + } + if (ctx->test == cancelex_cancel) + { + cancelioex_returned = TRUE; + res = CancelIo(ctx->file); + ok(res, "CancelIo failed: %lu\n", GetLastError()); + } + else if (ctx->test == cancel_cancelex) + { + res = CancelIoEx(ctx->file, NULL); + ok(!res && GetLastError() == ERROR_NOT_FOUND, "CancelIoEx failed: %lu\n", GetLastError()); + } + return res; +} + static void test_overlapped(void) { OVERLAPPED overlapped, overlapped2, *o; + struct cancel_thread_ctx ctx; + HANDLE file, port, thread; DWORD cancel_cnt, size; - HANDLE file, port; ULONG_PTR key; + DWORD ret; BOOL res;
overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); @@ -502,6 +585,94 @@ static void test_overlapped(void) ok(cancel_cnt == 2, "cancel_cnt = %lu\n", cancel_cnt); }
+ /* test cancelling uncancellable requests - CancelIo */ + ctx.file = file; + ctx.test = cancel; + thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); + ret = WaitForSingleObject(thread, 100); + ok(ret == WAIT_TIMEOUT, "CancelIo didn't block\n"); + res = DeviceIoControl(file, IOCTL_WINETEST_COMPLETE_ASYNC, NULL, 0, NULL, 0, NULL, &overlapped); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(file, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), NULL, &overlapped2); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 0, "cancel_cnt = %lu\n", cancel_cnt); + + /* test cancelling uncancellable requests - CancelIoEx */ + ctx.file = file; + ctx.test = cancelex; + thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); + ret = WaitForSingleObject(thread, 100); + todo_wine ok(ret == WAIT_TIMEOUT, "CancelIoEx didn't block\n"); + res = DeviceIoControl(file, IOCTL_WINETEST_COMPLETE_ASYNC, NULL, 0, NULL, 0, NULL, &overlapped); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(file, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), NULL, &overlapped2); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 0, "cancel_cnt = %lu\n", cancel_cnt); + + /* test cancelling uncancellable requests - CancelIoEx + CancelIo */ + ctx.file = file; + ctx.test = cancelex_cancel; + cancelioex_returned = FALSE; + thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); + ret = WaitForSingleObject(thread, 100); + ok(ret == WAIT_TIMEOUT, "Thread already terminated\n"); + ok(cancelioex_returned, "CancelIoEx did block\n"); + res = DeviceIoControl(file, IOCTL_WINETEST_COMPLETE_ASYNC, NULL, 0, NULL, 0, NULL, &overlapped); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(file, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), NULL, &overlapped2); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 0, "cancel_cnt = %lu\n", cancel_cnt); + + /* test cancelling uncancellable requests - CancelIo + CancelIoEx */ + ctx.file = file; + ctx.test = cancel_cancelex; + thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); + ret = WaitForSingleObject(thread, 100); + ok(ret == WAIT_TIMEOUT, "CancelIo didn't block\n"); + res = CancelIoEx(file, NULL); + ok(res, "CancelIoEx failed: %lu\n", GetLastError()); + ret = WaitForSingleObject(thread, 100); + ok(ret == WAIT_TIMEOUT, "CancelIo didn't block\n"); + res = DeviceIoControl(file, IOCTL_WINETEST_COMPLETE_ASYNC, NULL, 0, NULL, 0, NULL, &overlapped); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(file, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), NULL, &overlapped2); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 0, "cancel_cnt = %lu\n", cancel_cnt); + + /* test cancelling uncancellable requests - CancelSynchronousIo */ + ctx.file = device; + ctx.test = cancelsync; + thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); + WaitForSingleObject(thread, 100); + ok(ret == WAIT_TIMEOUT, "Synchronous DeviceIoControl() already returned\n"); + res = CancelSynchronousIo(thread); + ok(res, "CancelSynchronousIo failed: %lu\n", GetLastError()); + ret = WaitForSingleObject(thread, INFINITE); + ok(ret == WAIT_OBJECT_0, "CancelIoEx did block\n"); + CloseHandle(thread); + + cancel_cnt = 0xdeadbeef; + res = DeviceIoControl(device, IOCTL_WINETEST_GET_CANCEL_COUNT, NULL, 0, &cancel_cnt, sizeof(cancel_cnt), &size, NULL); + ok(res, "DeviceIoControl failed: %lu\n", GetLastError()); + ok(cancel_cnt == 1, "cancel_cnt = %lu\n", cancel_cnt); + + /* test completion port */ port = CreateIoCompletionPort(file, NULL, 0xdeadbeef, 0); ok(port != NULL, "CreateIoCompletionPort failed, error %lu\n", GetLastError()); res = GetQueuedCompletionStatus(port, &size, &key, &o, 0); @@ -1265,6 +1436,7 @@ static void test_driver_netio(struct testsign_context *ctx) hthread = CreateThread(NULL, 0, wsk_test_thread, NULL, 0, NULL); main_test(); WaitForSingleObject(hthread, INFINITE); + CloseHandle(hthread);
CloseHandle(device);
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntoskrnl.exe/tests/driver.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/dlls/ntoskrnl.exe/tests/driver.c b/dlls/ntoskrnl.exe/tests/driver.c index c153fa8d957..960f61407dc 100644 --- a/dlls/ntoskrnl.exe/tests/driver.c +++ b/dlls/ntoskrnl.exe/tests/driver.c @@ -1081,12 +1081,14 @@ static void test_call_driver(DEVICE_OBJECT *device) }
static int cancel_cnt; +static HANDLE thread_id;
static void WINAPI cancel_irp(DEVICE_OBJECT *device, IRP *irp) { IoReleaseCancelSpinLock(irp->CancelIrql); ok(irp->Cancel == TRUE, "Cancel = %x\n", irp->Cancel); ok(!irp->CancelRoutine, "CancelRoutine = %p\n", irp->CancelRoutine); + ok(PsGetCurrentThreadId() == thread_id, "Unexpected thread_id %p\n", PsGetCurrentThreadId()); irp->IoStatus.Status = STATUS_CANCELLED; irp->IoStatus.Information = 0; cancel_cnt++; @@ -1095,6 +1097,7 @@ static void WINAPI cancel_irp(DEVICE_OBJECT *device, IRP *irp) static void WINAPI cancel_ioctl_irp(DEVICE_OBJECT *device, IRP *irp) { IoReleaseCancelSpinLock(irp->CancelIrql); + todo_wine ok(PsGetCurrentThreadId() == thread_id, "Unexpected thread_id %p\n", PsGetCurrentThreadId()); irp->IoStatus.Status = STATUS_CANCELLED; irp->IoStatus.Information = 0; cancel_cnt++; @@ -1117,6 +1120,8 @@ static void test_cancel_irp(DEVICE_OBJECT *device) BOOLEAN r; NTSTATUS status;
+ thread_id = PsGetCurrentThreadId(); + /* cancel IRP with no cancel routine */ irp = IoBuildAsynchronousFsdRequest(IRP_MJ_FLUSH_BUFFERS, device, NULL, 0, NULL, &iosb);
@@ -2714,6 +2719,7 @@ static NTSTATUS WINAPI driver_IoControl(DEVICE_OBJECT *device, IRP *irp) status = STATUS_SUCCESS; break; case IOCTL_WINETEST_TEST_CANCEL: + thread_id = PsGetCurrentThreadId(); IoSetCancelRoutine(irp, cancel_ioctl_irp); IoMarkIrpPending(irp); return STATUS_PENDING;
From: Matteo Bruni mbruni@codeweavers.com
--- dlls/ntoskrnl.exe/tests/ntoskrnl.c | 48 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/dlls/ntoskrnl.exe/tests/ntoskrnl.c b/dlls/ntoskrnl.exe/tests/ntoskrnl.c index 009d5a14c8f..bf21d533bd3 100644 --- a/dlls/ntoskrnl.exe/tests/ntoskrnl.c +++ b/dlls/ntoskrnl.exe/tests/ntoskrnl.c @@ -458,6 +458,8 @@ struct cancel_thread_ctx static DWORD WINAPI test_cancel_thread(void *param) { struct cancel_thread_ctx *ctx = param; + NTSTATUS status = STATUS_SUCCESS; + IO_STATUS_BLOCK cancel_sb; DWORD cancel_cnt, size; OVERLAPPED o, o2, o3; BOOL res; @@ -495,28 +497,38 @@ static DWORD WINAPI test_cancel_thread(void *param)
if (ctx->test == cancel || ctx->test == cancel_cancelex) { - res = CancelIo(ctx->file); - ok(res, "CancelIo failed: %lu\n", GetLastError()); + status = NtCancelIoFile(ctx->file, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); } else if (ctx->test == cancelex || ctx->test == cancelex_cancel) { - res = CancelIoEx(ctx->file, NULL); - ok(res, "CancelIoEx failed: %lu\n", GetLastError()); - res = CancelIoEx(ctx->file, NULL); - ok(res, "CancelIoEx failed: %lu\n", GetLastError()); + status = NtCancelIoFileEx(ctx->file, NULL, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); + status = NtCancelIoFileEx(ctx->file, NULL, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); } if (ctx->test == cancelex_cancel) { cancelioex_returned = TRUE; - res = CancelIo(ctx->file); - ok(res, "CancelIo failed: %lu\n", GetLastError()); + status = NtCancelIoFile(ctx->file, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); } else if (ctx->test == cancel_cancelex) { - res = CancelIoEx(ctx->file, NULL); - ok(!res && GetLastError() == ERROR_NOT_FOUND, "CancelIoEx failed: %lu\n", GetLastError()); + status = NtCancelIoFileEx(ctx->file, NULL, &cancel_sb); + ok(status, "Unexpected status %lu\n", status); + ok(cancel_sb.Status == STATUS_NOT_FOUND, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); } - return res; + return status; }
static void test_overlapped(void) @@ -524,7 +536,9 @@ static void test_overlapped(void) OVERLAPPED overlapped, overlapped2, *o; struct cancel_thread_ctx ctx; HANDLE file, port, thread; + IO_STATUS_BLOCK cancel_sb; DWORD cancel_cnt, size; + NTSTATUS status; ULONG_PTR key; DWORD ret; BOOL res; @@ -641,8 +655,10 @@ static void test_overlapped(void) thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); ret = WaitForSingleObject(thread, 100); ok(ret == WAIT_TIMEOUT, "CancelIo didn't block\n"); - res = CancelIoEx(file, NULL); - ok(res, "CancelIoEx failed: %lu\n", GetLastError()); + status = NtCancelIoFileEx(file, NULL, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); ret = WaitForSingleObject(thread, 100); ok(ret == WAIT_TIMEOUT, "CancelIo didn't block\n"); res = DeviceIoControl(file, IOCTL_WINETEST_COMPLETE_ASYNC, NULL, 0, NULL, 0, NULL, &overlapped); @@ -661,8 +677,10 @@ static void test_overlapped(void) thread = CreateThread(NULL, 0, test_cancel_thread, &ctx, 0, NULL); WaitForSingleObject(thread, 100); ok(ret == WAIT_TIMEOUT, "Synchronous DeviceIoControl() already returned\n"); - res = CancelSynchronousIo(thread); - ok(res, "CancelSynchronousIo failed: %lu\n", GetLastError()); + status = NtCancelSynchronousIoFile(thread, NULL, &cancel_sb); + ok(!status, "Unexpected status %lu\n", status); + ok(!cancel_sb.Status, "Unexpected Status %lx\n", cancel_sb.Status); + ok(!cancel_sb.Information, "Unexpected Information %Iu\n", cancel_sb.Information); ret = WaitForSingleObject(thread, INFINITE); ok(ret == WAIT_OBJECT_0, "CancelIoEx did block\n"); CloseHandle(thread);
On Wed Sep 10 18:11:22 2025 +0000, Elizabeth Figura wrote:
7/10:
- ok(io.Status == params->iosb_status, "got %#lx\n", io.Status); - ok(io.Information == 3, "got size %Iu\n", io.Information); + todo_wine_if (NT_ERROR(params->iosb_status) && params->pending) + ok(io.Status == params->iosb_status || broken(NT_ERROR(params->iosb_status) && params->pending), "got %#lx\n", io.Status); + todo_wine_if (NT_ERROR(params->iosb_status) && params->pending) + ok(io.Information == 3 || broken(NT_ERROR(params->iosb_status) && params->pending), "got size %Iu\n", io.Information);
We should also be testing the actual values in those broken() blocks. Also, can we please mark which versions have which results in a comment?
ret = NtDeviceIoControlFile(file, event, NULL, (void *)456, &io, ioctl, params, sizeof(*params), buffer, sizeof(buffer)); ok(ret == params->ret_status - || broken(NT_WARNING(params->ret_status) && ret == STATUS_PENDING), /* win10 */ + || (NT_WARNING(params->ret_status) && ret == STATUS_PENDING), "got %#x\n", ret); if (!params->pending && NT_ERROR(params->iosb_status)) {
Similarly, no objection to removing broken(), but can we please keep the comment?
- ok(io.Status == 0xdeadf00d, "got iosb status %#lx\n", io.Status); - ok(io.Information == 0xdeadf00d, "got information %#Ix\n", io.Information); + todo_wine ok(io.Status == 0xc00000a3 || broken(io.Status == 0xdeadf00d), /* older win10 */ + "got iosb status %#lx\n", io.Status); + todo_wine ok(io.Information == 0 || broken(io.Information == 0xdeadf00d), /* older win10 */ + "got information %#Ix\n", io.Information);
c00000a3 is STATUS_DEVICE_NOT_READY. 8/10:
+ case IOCTL_WINETEST_TEST_CANCEL_UNCANCELLABLE_INIT: + KeInitializeDeviceQueue(&device_queue); + ok(IsListEmpty(&device_queue.DeviceListHead), "expected empty queue list\n"); + status = STATUS_SUCCESS; + break;
Or just initialize it once in DriverEntry(), and then you don't need a dedicated ioctl.
+#define IOCTL_WINETEST_TEST_CANCEL_UNCANCELLABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_WINETEST_TEST_CANCEL_COMPLETE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS)
I'd be inclined to just make these IOCTL_WINETEST_QUEUE_ASYNC and IOCTL_WINETEST_COMPLETE_ASYNC, since they don't necessarily have anything to do with cancellation, and not upend the rest of the definitions...
+ /* put the IRP into the queue */ + ret = KeInsertDeviceQueue(&device_queue, &irp->Tail.Overlay.DeviceQueueEntry); + /* or store it elsewhere if it's the one we're supposing to be + * working on "currently" */ + if (!ret) + current_irp = irp;
KDEVICE_QUEUE is an odd API and I feel like it honestly makes this test more confusing. I'd just have a simple "queued_async_irps[2]" array and stash them there. Incidentally, because there is some loose threading involved I would be inclined to validate everything we can from the kernel side. Make sure there really is an IRP queued before we complete it, etc. I know I usually say "there's no use avoiding a crash; a failure is a failure", but bluescreens are a different story, because they make iterating a pain.
+ returned = FALSE; + ctx.file = file; + ctx.ex = FALSE; + thread = CreateThread(NULL, 0, test_cancel_complete_thread, &ctx, 0, NULL); + res = CancelIo(file); + ok(res, "CancelIo failed: %lu\n", GetLastError()); + returned = TRUE; + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread);
The way I tend to prefer writing this kind of test is to do the blocking action from the thread, check (from the main thread) that the thread doesn't complete immediately (e.g. WaitForSingleObject(200)), and then perform the action from the main thread that should complete it and check that it completes within another reasonable amount of time. I think it's a little easier to read. If you feel this is fine though I won't object.
Alright, I think I fixed up all of the above. I have some comments though...
I don't have particularly good "versions" to put in the `broken` test comments in patch 7/10. Basically, current Windows 10 AND 11's behavior is different from older Windows 10 / 11. Also, for Windows 10 at least, we're talking about the same 22H2 (or 19045) version.
There is a "revision" value accessible through the registry which can help a bit (see e.g. https://learn.microsoft.com/en-us/windows/release-health/release-information, that's the value after the dot under the Build column) Notice also that those revision numbers are specific for each different OS version and can, and will, clash among different versions. I have a small patch to print that value from winetest, since it seems to be generally useful information to have.
That doesn't help a ton here though. I know that the newest Win10 revision on the testbot and test.winehq.org (19045.2311) has the "old" ntoskrnl tests behavior, while my own 19045.6216 has the "new" one. I could make the comments a bit more thorough but I'm not sure that's much of an improvement.
On Wed Sep 10 18:11:22 2025 +0000, Matteo Bruni wrote:
Alright, I think I fixed up all of the above. I have some comments though... I don't have particularly good "versions" to put in the `broken` test comments in patch 7/10. Basically, current Windows 10 AND 11's behavior is different from older Windows 10 / 11. Also, for Windows 10 at least, we're talking about the same 22H2 (or 19045) version. There is a "revision" value accessible through the registry which can help a bit (see e.g. https://learn.microsoft.com/en-us/windows/release-health/release-information, that's the value after the dot under the Build column) Notice also that those revision numbers are specific for each different OS version and can, and will, clash among different versions. I have a small patch to print that value from winetest, since it seems to be generally useful information to have. That doesn't help a ton here though. I know that the newest Win10 revision on the testbot and test.winehq.org (19045.2311) has the "old" ntoskrnl tests behavior, while my own 19045.6216 has the "new" one. I could make the comments a bit more thorough but I'm not sure that's much of an improvement.
The other comment is about the new ntoskrnl tests. I don't understand why `CancelIoEx()` "by itself" seems to block in this version of the tests. It's quite puzzling given that effectively the same `CancelIoEx()` doesn't seem to block in the later test when it's followed by `CancelIo()` (which does block as expected).
I'm going to try some more later but there's a good chance that there's some issue with the tests or I'm missing something obvious. Any help is appreciated!