Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does WriteFile signal the event if it completes synchronously

Tags:

c

windows

winapi

Does the WriteFile function signal the event passed in via the lpOverlapped parameter if it completes synchronously and succeeds? Does it signal the event if it fails synchronously? I have opened the handle to a file with the FILE_FLAG_OVERLAPPED flag. I wasn't to able to figure this out from the documentation and couldn't repro this case easily in code.

like image 737
tcb Avatar asked Feb 04 '26 07:02

tcb


1 Answers

first of all this question related not only to WriteFile but to any asynchronous I/O function - almost all functions which get pointer to an OVERLAPPED structure. because for all this functions IRP (I/O request packet) (look it definition in wdm.h) is allocated. hEvent handle from OVERLAPPED converted to object pointer and stored in PKEVENT UserEvent; member of IRP. the event is set (or not set) exactly when IRP is completed in IopCompleteRequest routine. the IRP completion function is common for all I/O api, so and rules (when completion fire) is related to all. unfortunately this is very bad documented. the win32 layer (compare NT layer) added additional ,very thin, issues here.

based on wrk src code, we can see that I/O Manager fire completion (was 3 types - event, apc and iocp (mutually exclusive)) for asynchronous io when !NT_ERROR( irp->IoStatus.Status ) or irp->PendingReturned.

if we use native api, which direct return NTSTATUS - when (ULONG)status < 0xc0000000. but here was very problematic range 0x80000000 <= status < 0xc0000000 or NT_WARNING(status) when unclear - are completion (even set, apc or packet to iocp queue) will be set. this is because before allocate IRP I/O Manager do some basic checks and can return error from here. usually I/O Manager return errors from NT_ERROR(status) , which correct mean that will be no completion (event will be not set)), but exist and rarely exceptions. for example for ReadDirectoryChangesW (or ZwNotifyChangeDirectoryFile) the lpBuffer pointer must be DWORD-aligned (aligned exactly as FILE_NOTIFY_INFORMATION) otherwise I/O Manager return STATUS_DATATYPE_MISALIGNMENT (0x80000002) from NT_WARNING range. but will be no completion (event set) in this case, because function fail before allocate IRP. from another case, if we call FSCTL_FILESYSTEM_GET_STATISTICS with not large enough buffer - file system driver (not I/O Manager ) return STATUS_BUFFER_OVERFLOW (0x80000005). but because at this point IRP already allocated and code not from NT_ERROR range - will be event set.

so if error from I/O Manager (before IRP allocated) - will be no completion. otherwise if error from driver (to which passed IRP) completion will be if function return !NT_ERROR(status). as result if we get:

  • NT_SUCCESS(status) (the STATUS_PENDING (0x103) is part of this) - will be completion
  • NT_ERROR(status) will be no completion
  • NT_WARNING(status) - unclear, depend this error from I/O Manager (no) or driver(yes)

but with win32 layer make situation more worse. because unclear how it interpret NT_WARNING(status) - most win32 api interpret this as error - return false and set last error (converted from status). but some api - for example ReadDirectoryChangesW interpret this as success code - return true and not set last error. as result if we call ReadDirectoryChangesW with bad aligned buffer (but valid other parameters) - it return.. true and not set any error. but api call is really fail. the ZwNotifyChangeDirectoryFile internal return STATUS_DATATYPE_MISALIGNMENT here.

from another side, if DeviceIoControl for FSCTL_FILESYSTEM_GET_STATISTICS fail (return false) with code ERROR_MORE_DATA (converted from STATUS_BUFFER_OVERFLOW) event(completion) will be set in this case.

also by win32 error code we can not understand - are initial status was NT_ERROR or NT_WARNING code - conversion (RtlNtStatusToDosError) status to win32 error lost this info

problem with NT_WARNING(status) range, begin from vista, can be resolved if we use IOCP completion (instead event) and set FILE_SKIP_COMPLETION_PORT_ON_SUCCESS on file - in this case I/O manager queue a completion entry to the port, when and only when STATUS_PENDING will be returned by native api call. for win32 layer this usually mean that api return false and last error is ERROR_IO_PENDING. exceptions - WriteFileEx, ReadFileEx which return true here. however this not help in case ReadDirectoryChangesW anyway (I assume that this is windows bug)

also read FILE_SKIP_SET_EVENT_ON_HANDLE section - this implicitly say when explicit event (from overlapped) set in case asynchronous function - when request returns with a success code, or the error returned is ERROR_IO_PENDING. but here question - what is success code ? true returned by win32 api ? not always, as visible from FSCTL_FILESYSTEM_GET_STATISTICS - the ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW) also success code. or STATUS_NO_MORE_FILES returned by NtQueryDirectoryFile also success code - event (apc or iocp completion) will be set. but same NtQueryDirectoryFile can return STATUS_DATATYPE_MISALIGNMENT, when FileInformation bad aligned - this is fail code, because returned from I/O Manager before allocate IRP

the NT_WARNING status in most case is success code (will be completion), but win32 layer in most case interpret it as fail code (return false).

code example for tests:

ULONG BOOL_TO_ERROR(BOOL fOk)
{
    return fOk ? NOERROR : GetLastError();
}

void CheckEventState(HANDLE hEvent, ULONG err, NTSTATUS status = RtlGetLastNtStatus())
{
    DbgPrint("error = %u(%x)", err, err ? status : STATUS_SUCCESS);

    switch (WaitForSingleObject(hEvent, 0))
    {
    case WAIT_OBJECT_0:
        DbgPrint("Signaled\n");
        break;
    case WAIT_TIMEOUT:
        DbgPrint("NON signaled\n");
        break;
    default:
        DbgPrint("error=%u\n", GetLastError());
    }
#if 1
    EVENT_BASIC_INFORMATION ebi;
    if (0 <= ZwQueryEvent(hEvent, EventBasicInformation, &ebi, sizeof(ebi), 0))
    {
        DbgPrint("EventState = %x\n", ebi.EventState);
    }
#endif
}

void demoIoEvent()
{
    WCHAR sz[MAX_PATH];
    GetSystemDirectoryW(sz, RTL_NUMBER_OF(sz));

    HANDLE hFile = CreateFileW(sz, 0, FILE_SHARE_VALID_FLAGS, 0, 
        OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, 0);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        FILESYSTEM_STATISTICS fs;

        OVERLAPPED ov = {};

        if (ov.hEvent = CreateEvent(0, TRUE, FALSE, 0))
        {
            FILE_NOTIFY_INFORMATION fni;
            IO_STATUS_BLOCK iosb;

            // STATUS_DATATYPE_MISALIGNMENT from I/O manager
            // event will be not set
            NTSTATUS status = ZwNotifyChangeDirectoryFile(hFile, ov.hEvent, 0, 0, &iosb, 
                (FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, FILE_NOTIFY_VALID_MASK, FALSE);

            CheckEventState(ov.hEvent, ERROR_NOACCESS, status);

            // windows bug ! ReadDirectoryChangesW return .. true and no last error
            // but really api fail. event will be not set and no notifications
            ULONG err = BOOL_TO_ERROR(ReadDirectoryChangesW(hFile,
                (FILE_NOTIFY_INFORMATION*)(1 + (PBYTE)&fni), 1, 0, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));

            CheckEventState(ov.hEvent, err);

            // fail with ERROR_INSUFFICIENT_BUFFER (STATUS_BUFFER_TOO_SMALL)
            // NT_ERROR(c0000023) - event will be not set
            err = BOOL_TO_ERROR(DeviceIoControl(hFile, 
                FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 0, 0, 0, &ov));

            CheckEventState(ov.hEvent, err);

            // ERROR_MORE_DATA (STATUS_BUFFER_OVERFLOW)
            // !NT_ERROR(80000005) - event will be set
            // note - win 32 api return false and error != ERROR_IO_PENDING
            err = BOOL_TO_ERROR(DeviceIoControl(hFile, 
                FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, &fs, sizeof(fs), 0, &ov));

            CheckEventState(ov.hEvent, err);

            if (err == ERROR_MORE_DATA)
            {
                SYSTEM_INFO si;
                GetSystemInfo(&si);

                ULONG cb = si.dwNumberOfProcessors * fs.SizeOfCompleteStructure;

                union {
                    PVOID pv;
                    PBYTE pb;
                    PFILESYSTEM_STATISTICS pfs;
                };

                pv = alloca(cb);

                // must be NOERROR(0) here
                // !NT_ERROR(0) - event will be set
                err = BOOL_TO_ERROR(DeviceIoControl(hFile, FSCTL_FILESYSTEM_GET_STATISTICS, 0, 0, 
                    pv, cb, 0, &ov));

                CheckEventState(ov.hEvent, err);

                if (!err && GetOverlappedResult(hFile, &ov, &cb, FALSE))
                {
                    do 
                    {
                        // use pfs here
                    } while (pb += fs.SizeOfCompleteStructure, --si.dwNumberOfProcessors);
                }
            }

            CloseHandle(ov.hEvent);
        }
        CloseHandle(hFile);
    }
}

and output:

error = 998(80000002)NON signaled
EventState = 0
error = 0(0)NON signaled
EventState = 0
error = 122(c0000023)NON signaled
EventState = 0
error = 234(80000005)Signaled
EventState = 1
error = 0(0)Signaled
EventState = 1
like image 184
RbMm Avatar answered Feb 05 '26 21:02

RbMm