2025-11-28 00:35:46 +09:00

460 lines
9.8 KiB
C

//======================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//======================================================================
/*++
Module Name:
IoCancellation.c
Abstract:
Implementation of the safe IO cancellation interface
--*/
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include "IoCancellation.h"
DWORD
IoCancellationCreate(
PIO_CANCELLATION_OBJECT* ppObject
)
/*++
Routine Description:
Creates new cancellation object.
Arguments:
ppObject - Pointer to receive new cancellation object.
Return values:
ERROR_NOT_SUPPORTED if the OS does not support synchronous cancellation.
ERROR_SUCCESS if the creation was successful.
Otherwise an appropriate error code is returned.
--*/
{
PIO_CANCELLATION_OBJECT pObject = NULL;
HANDLE hKernel32 = NULL;
PFN_CancelSynchronousIo pfnCancel = NULL;
BOOL fCS = FALSE;
DWORD err = 0;
//
// Load CancelSynchronousIo dynamically from kernel32.dll
//
hKernel32 = GetModuleHandleW(L"kernel32.dll");
if (!hKernel32) {
err = GetLastError();
goto cleanup;
}
//
// Return ERROR_NOT_SUPPORTED CancelSynchronousIo not found
//
pfnCancel = (PFN_CancelSynchronousIo)
GetProcAddress(hKernel32, "CancelSynchronousIo");
if (!pfnCancel) {
err = GetLastError();
if (err == ERROR_PROC_NOT_FOUND) {
err = ERROR_NOT_SUPPORTED;
}
goto cleanup;
}
//
// Allocate and initialize the object
//
pObject = (PIO_CANCELLATION_OBJECT)
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pObject));
if (!pObject) {
err = ERROR_OUTOFMEMORY;
goto cleanup;
}
fCS = InitializeCriticalSectionAndSpinCount(&pObject->aCS, 0);
if (!fCS) {
err = GetLastError();
goto cleanup;
}
//
// The hThread member will be intialized on demand
// by IoCancellationSectionEnter
//
pObject->pfnCancelSynchronousIo = pfnCancel;
//
// Detach and return
//
*ppObject = pObject;
pObject = NULL;
cleanup:
if (pObject) {
if (fCS) {
DeleteCriticalSection(&pObject->aCS);
}
HeapFree(GetProcessHeap(), 0, pObject);
}
return err;
}
VOID
IoCancellationClose(
PIO_CANCELLATION_OBJECT pObject)
/*++
Routine Description:
Closes a cancellation object.
Arguments:
pObject - Pointer to cancellation object.
--*/
{
//
// Lock used as memory barrier
//
EnterCriticalSection(&pObject->aCS);
assert(!pObject->fPendingIo);
if (pObject->hThread) {
CloseHandle(pObject->hThread);
}
LeaveCriticalSection(&pObject->aCS);
DeleteCriticalSection(&pObject->aCS);
HeapFree(GetProcessHeap(), 0, pObject);
}
DWORD
IoCancellationSectionEnter(
PIO_CANCELLATION_OBJECT pObject
)
/*++
Routine Description:
Used to indicate the start of a portion of code in which a
synchronous operation can be cancelled.
.
Arguments:
pObject - Pointer to a previously allocated cancellation object.
--*/
{
DWORD err = 0;
EnterCriticalSection(&pObject->aCS);
//
// Recursive calls are not allowed
//
assert(!pObject->fPendingIo);
//
// Check if already canceled
//
if (pObject->fCanceled) {
err = ERROR_OPERATION_ABORTED;
goto cleanup;
}
//
// Replace the thread handle if needed
//
if (pObject->hThread && pObject->dwThreadId != GetCurrentThreadId()) {
CloseHandle(pObject->hThread);
pObject->hThread = NULL;
pObject->dwThreadId = 0;
}
//
// GetCurrentThread returns pseudo handle for the calling thread
// and it always refers to the current thread; therefore it can't
// be used for CancelSynchronousIo.
// DuplicateHandle will return a normal handle that can
// be used cross thread.
//
if (!pObject->hThread) {
//
// CancelSynchronousIo actually requires only THREAD_TERMINATE
//
BOOL fOK = DuplicateHandle(
GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
&pObject->hThread,
THREAD_ALL_ACCESS,
FALSE, // not inherited
0
);
if (!fOK) {
err = GetLastError();
goto cleanup;
}
pObject->dwThreadId = GetCurrentThreadId();
}
assert(pObject->hThread != NULL);
assert(pObject->dwThreadId == GetCurrentThreadId());
pObject->fPendingIo = TRUE;
cleanup:
LeaveCriticalSection(&pObject->aCS);
return err;
}
DWORD
IoCancellationSectionLeave(
PIO_CANCELLATION_OBJECT pObject
)
/*++
Routine Description:
Used to indicate the end of a portion of code in which a
synchronous operation can be cancelled.
.
Arguments:
pObject - Pointer to a cancellation object that was used to
enter the cancellation section.
Return Value:
ERROR_OPERATION_ABORTED if the operation had cancellation attempted.
ERROR_SUCCESS otherwise.
--*/
{
DWORD err = ERROR_SUCCESS;
EnterCriticalSection(&pObject->aCS);
//
// Leave only allowed upon successful Enter from the same thread
//
assert(pObject->hThread != NULL);
assert(pObject->dwThreadId == GetCurrentThreadId());
assert(pObject->fPendingIo);
//
// Clearing the pending flag always succeeds
//
pObject->fPendingIo = FALSE;
if (pObject->fCanceled) {
err = ERROR_OPERATION_ABORTED;
goto cleanup;
}
cleanup:
LeaveCriticalSection(&pObject->aCS);
return err;
}
//
// IoCancellationSignal
//
VOID
IoCancellationSignal(
PIO_CANCELLATION_OBJECT pObject
)
/*++
Routine Description:
This routine will attempt to cancel the operation that
is represented by the cancellation object by calling
CancelSynchronousIo on the target thread.
Arguments:
pObject - Pointer to a cancellation object.
--*/
{
DWORD dwRetryCount = 0;
EnterCriticalSection(&pObject->aCS);
pObject->fCanceled = TRUE;
//
// Retry 3 times in case that the IO has not yet made it to
// the driver and CancelSynchronousIo returns ERROR_NOT_FOUND
//
for (dwRetryCount = 0 ; dwRetryCount < 3 ; ++dwRetryCount)
{
BOOL fOK = FALSE;
if (!pObject->fPendingIo) {
break;
}
assert(pObject->hThread != NULL);
//
// Ignore cancel errors since it's only optional for drivers
//
fOK = pObject->pfnCancelSynchronousIo(pObject->hThread);
if (fOK || GetLastError() != ERROR_NOT_FOUND) {
break;
}
//
// There is small window in which CancelSynchronousIo can be called before (or after)
// the actual IO operation and in this case we can retry to cancel after a short delay
// in which it's probable that we'll not miss the operation.
//
LeaveCriticalSection(&pObject->aCS);
//
// Retry after short sleep
//
SwitchToThread();
EnterCriticalSection(&pObject->aCS);
}
LeaveCriticalSection(&pObject->aCS);
}
DWORD
CancelableCreateFileW(
HANDLE* phFile,
PIO_CANCELLATION_OBJECT pCancellationObject,
LPCWSTR wszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
)
/*++
Routine Description:
Function that calls CreateFileW in a cancellation section and therefore
can be canceled using IoCancellationSignal from another thread.
Arguments:
phFile - Pointer to a handle that will be returned on successful create.
On failure this will be INVALID_HANDLE_VALUE.
pCancellationObject - Pointer to cancellation object created with
IoCancellationCreate.
wszFileName - File name to open.
dwDesiredAccess - Desired access for handle.
dwShareMode - Share mode.
lpSecurityAttributes - Optional pointer to security attributes.
dwCreationDisposition - Disposition for create.
dwFlagsAndAttributes - Flags for create.
hTemplateFile - Optional handle to template file.
Return value:
ERROR_SUCCESS on success.
ERROR_OPERATION_ABORTED on cancellation.
Otherwise use GetLastError to determine the cause of failure.
--*/
{
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD err = 0;
*phFile = INVALID_HANDLE_VALUE;
err = IoCancellationSectionEnter(pCancellationObject);
if (err) {
goto cleanup;
}
//
// Cancellation section contains a single call to CreateFileW
//
hFile = CreateFileW(
wszFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile
);
if (hFile == INVALID_HANDLE_VALUE) {
err = GetLastError();
}
//
// Leave must be called after successful Enter
//
err = IoCancellationSectionLeave(pCancellationObject);
if (err) {
goto cleanup;
}
if (hFile == INVALID_HANDLE_VALUE) {
err = GetLastError();
goto cleanup;
}
*phFile = hFile;
hFile = INVALID_HANDLE_VALUE;
cleanup:
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
}
return err;
}