-5

It seems that I recently discovered a big fat bug in Windows 7 ultimate 64 Bit. The weird thing is that I cannot find anything in Google or MSDN about it. It seems impossible that I am the first to discover a bug in an API so important as WriteFileEx in an operating system that is on the market since a long time ?!?!?!?

But my code is too simple to be wrong.

Apart from that my code works perfectly on: Windows XP prof 32 Bit, Windows Vista ultimate 32 Bit, Windows 7 ultimate 32 Bit, and it even works on Windows 7 ultimate 64 Bit if compiled as 64 Bit.

The only failure happens on a Windows 7 - 64 Bit if compiled as 32 Bit.

What happens is that the file is written correctly to disk, the completion routine is called, but the completion routine reports that zero bytes have been written and the most weird thing is that the OVERLAPPED structure has the wrong address and invalid content!!

Can anybody please confirm that this is a bug ?

void WINAPI CompletionRoutine(DWORD u32_ErrorCode, DWORD u32_BytesTransfered,
                              OVERLAPPED* pk_Overlapped)
{
    printf("CompletionRoutine: Transferred: %d Bytes, AddrOverlapped: 0x%X\n", 
           u32_BytesTransfered, (DWORD_PTR)pk_Overlapped);
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    printf("Compiled as: %d Bit\n", sizeof(DWORD_PTR) == 8 ? 64 : 32);

    HANDLE h_File = CreateFileW(L"E:\\Temp\\Test.txt", GENERIC_WRITE, 
                                FILE_SHARE_READ|FILE_SHARE_WRITE, 0, 
                                CREATE_ALWAYS, 0, 0);

    OVERLAPPED k_Over = {0};
    printf("Before WriteFileEx AddrOverlapped: 0x%X\n", (DWORD_PTR)&k_Over);

    WriteFileEx(h_File, "ABCDEF", 6, &k_Over, CompletionRoutine);

    printf("Before SleepEx\n");

    SleepEx(1000, TRUE);

    printf("Exit\n");
    return 0;
}

Here the results:

enter image description here

Elmue
  • 7,602
  • 3
  • 47
  • 57
  • Please don't "beg bugs", especially in titles. This question will be received more favorably if simply stating the observed behavior - without additional commentary. – user2864740 Jun 13 '14 at 01:14
  • OK I changed the title. Are you happy now ? – Elmue Jun 13 '14 at 01:17
  • 4
    You didn't specify `FILE_FLAG_OVERLAPPED`. From the docs: `If this flag is not specified, then I/O operations are serialized, even if the calls to the read and write functions specify an OVERLAPPED structure.` – Retired Ninja Jun 13 '14 at 01:25
  • Thanks! Yes when I add the FILE_FLAG_OVERLAPPED it works! But it stays still weird that only one of multiple operating systems cares about it. – Elmue Jun 13 '14 at 01:38
  • 1
    Are you sure you screamed BUG!!!! loud enough? You'll know you did if you're **really, really** humiliated for proclaiming at the top of your lungs that you discovered a bug and then find out you've just failed to read the documentation properly. (The moral of the story: Never **assume** that you've found a bug that millions of other users never found, particularly in an API function that is used so heavily internally within the OS itself. Always know that odds are very high that the problem is in your code instead.) – Ken White Jun 13 '14 at 01:45
  • I answered your question below. – Elmue Jun 13 '14 at 02:04

2 Answers2

3

You are not doing any error checking, or ensuring the OVERLAPPED structure stays alive until the completion routine is actually called (it could take more than 1 second).

Try something more like this instead:

void WINAPI CompletionRoutine(DWORD u32_ErrorCode, DWORD u32_BytesTransfered, LPOVERLAPPED pk_Overlapped)
{
    if (u32_ErrorCode != 0)
        printf("CompletionRoutine: Unable to write to file! Error: %u, AddrOverlapped: %p\n", u32_ErrorCode, pk_Overlapped);
    else
        printf("CompletionRoutine: Transferred: %u Bytes, AddrOverlapped: %p\n", u32_BytesTransfered, pk_Overlapped);

    SetEvent(pk_Overlapped->hEvent);
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
    printf("Compiled as: %d Bit\n", sizeof(DWORD_PTR) == 8 ? 64 : 32);

    printf("Creating file\n");

    const LPWSTR fileName = L"E:\\Temp\\Test.txt";

    HANDLE h_File = CreateFileW(fileName, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, 0);
    if (h_File == INVALID_HANDLE_VALUE)
    {
        printf("Unable to create file! Error: %u\n", GetLastError());
        return 0;
    }

    HANDLE h_Event = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (h_Event == NULL)
    {
        printf("Unable to create wait event! Error: %u\n", GetLastError());
        CloseHandle(h_File);
        DeleteFile(fileName);
        return 0;
    }

    OVERLAPPED k_Over = {0};
    k_Over.hEvent = h_Event;

    printf("Writing to file (AddrOverlapped: %p)\n", &k_Over);

    if (!WriteFileEx(h_File, "ABCDEF", 6, &k_Over, &CompletionRoutine))
    {
        printf("Unable to write to file! Error: %u\n", GetLastError());
        CloseHandle(h_File);
        DeleteFile(fileName);
        return 0;
    }

    printf("Waiting for write to complete\n");

    DWORD dwResult;
    do
    {
        dwResult = WaitForSingleObjectEx(k_Over.hEvent, 5000, TRUE);

        if (dwResult == WAIT_OBJECT_0)
        {
            printf("Write Completed\n");
            break;
        }

        if (dwResult != WAIT_IO_COMPLETION)
        {
            if (dwResult == WAIT_TIMEOUT)
                printf("Timeout waiting for write to complete!\n");
            else
                printf("Unable to wait for write to complete!\n");

            CancelIo(h_File);
            break;
        }
    }
    while (true);

    printf("Finished waiting\n");

    CloseHandle(h_File);
    CloseHandle(h_Event);

    printf("Exit\n");
    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • You did not understand what is the problem. Using CreateFile instead of CreateFileEx is not a solution for my case. – Elmue Jun 13 '14 at 02:10
  • 3
    I did not say anything about `CreateFileEx()`. I replaced `SleepEx()` with `WaitForSingleObjectEx()`, which is safer. But you are right that I did originally missed the root cause - the missing `FILE_FLAG_OVERLAPPED` flag, which I added to my answer after I saw it. – Remy Lebeau Jun 13 '14 at 02:22
  • The code I posted was only to reproduce the problem. Obviously I removed all error handling to make it more readable. And SleepEx works perfectly for that purpose. There is no need to replace it. Try it! – Elmue Jun 13 '14 at 02:59
  • 2
    You are assuming that the write will complete within 1000 ms. Maybe in this simplistic example, it does. But in a real-world production project, that is a dangerous assumption. If you put the `OVERLAPPED` struct on the stack and it disappears before the completion routine is called, bad things happen. The [documentation](http://msdn.microsoft.com/en-us/library/windows/desktop/aa365748.aspx) states: "The `OVERLAPPED` data structure must remain valid for the duration of the write operation. It should not be a variable that can go out of scope while the write operation is pending completion." – Remy Lebeau Jun 13 '14 at 03:03
  • 2
    That is why my example actually waits for the completion routine to be called before allowing the `OVERLAPPED` to disappear. In a more complex project, you would usually allocate the `OVERLAPPED` on the heap instead and let the completion routine free it when it is finished using it. – Remy Lebeau Jun 13 '14 at 03:04
  • I know all that! Please understand: This was never thought to be real-world production code! – Elmue Jun 13 '14 at 05:05
-1

Solved:

FILE_FLAG_OVERLAPPED is missing.

I still think it is definitely a severe bug that a completely crippled pointer is passed to a callback routine and the callback tells that it has written 0 bytes although it has in deed written 6 bytes!

And as I showed: Not all operating systems have this bug.

A correct behaviour would be to call the callback routine with the correct OVERLAPPED address as Windows XP does or return an ERROR_INVALID_PARAMETER if an OVERLAPPED structure is used with a file handle that was not opened with FILE_FLAG_OVERLAPPED.

What I discovered is very ugly because if you develop and test your application on XP, Vista, Win7 32 Bit you will never discover that there is a flag missing. And then you deliver your app to the end users who uses Win 7 - 64 bit and they tell you that your program does not work.

A correctly programmed Windows API must return an error if a wrong parameter is given but never pass a crippled pointer.

And 4 of 5 operating systems that I tested pass the correct address!

Elmue
  • 7,602
  • 3
  • 47
  • 57
  • 1
    You are not checking `WriteFileEx()` for an error code, and you are ignoring the error code reported by the completion routine. – Remy Lebeau Jun 13 '14 at 02:24
  • In the completion routine, `u32_BytesTransfered` will be 0 if `u32_ErrorCode` is not 0. – Remy Lebeau Jun 13 '14 at 02:30
  • I removed all error checking to make the code more readable. It is completely irrelevant here. And u32_ErrorCode is also zero. All you write has nothing to do with the question. – Elmue Jun 13 '14 at 03:02
  • 4
    Discovered bugs in WinAPI: 0, Bugs in code: 1 (at least). Programming is usually more productive when not immediately blaming well-vetted code, especially when such blame is mixed with personal notions on how it "should" work. – user2864740 Jun 13 '14 at 05:11
  • 2
    It is not the API's responsibility to check for every possible mistake the programmer might have made. If you don't follow the rules (which in this case are described very clearly in the documentation for WriteFileEx) anything might happen - it might even seem to work. – Harry Johnston Jun 13 '14 at 07:07
  • 1
    The bug is in your code. Windows is correct and behaves as designed. I don't understand why you are having so much trouble accepting that. – David Heffernan Jun 13 '14 at 08:10
  • I completely disagree with you. If you call CreateFile() with a path to a file that does not exist, this is your error. Right? And what does Windows do then? Does it give you an ERROR_FILE_NOT_FOUND or does it return a crippled HANDLE together with an ERROR_SUCCESS ??? – Elmue Jun 13 '14 at 17:30
  • You won't get anywhere if you continue to believe that Windows must fit into your world view. You ought to acknowledge that you are not the world expert on Windows and that you need to learn how it works. If you adopt that mindset you might advance. – David Heffernan Jun 13 '14 at 22:07