This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients Billy McCulloch, eryksun, georg.brandl, ncoghlan, paul.moore, python-dev, rupole, serhiy.storchaka, steve.dower, takluyver, tim.golden, zach.ware
Date 2016-05-07.03:49:58
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1462593001.64.0.130303022272.issue22107@psf.upfronthosting.co.za>
In-reply-to
Content
The Windows API loses information when mapping kernel status values to Windows error codes. For example, the following status values are all mapped to ERROR_ACCESS_DENIED:

    STATUS_INVALID_LOCK_SEQUENCE   0xc000001e
    STATUS_INVALID_VIEW_SIZE       0xc000001f
    STATUS_ALREADY_COMMITTED       0xc0000021
    STATUS_ACCESS_DENIED           0xc0000022
    STATUS_PORT_CONNECTION_REFUSED 0xc0000041
    STATUS_THREAD_IS_TERMINATING   0xc000004b
    STATUS_DELETE_PENDING          0xc0000056
    STATUS_FILE_IS_A_DIRECTORY     0xc00000ba
    STATUS_FILE_RENAMED            0xc00000d5
    STATUS_PROCESS_IS_TERMINATING  0xc000010a
    STATUS_CANNOT_DELETE           0xc0000121
    STATUS_FILE_DELETED            0xc0000123

    Encrypting File System
    STATUS_ENCRYPTION_FAILED       0xc000028a
    STATUS_DECRYPTION_FAILED       0xc000028b
    STATUS_NO_RECOVERY_POLICY      0xc000028d
    STATUS_NO_EFS                  0xc000028e
    STATUS_WRONG_EFS               0xc000028f
    STATUS_NO_USER_KEYS            0xc0000290

STATUS_ACCESS_DENIED is from a failed NtAccessCheck. STATUS_FILE_IS_A_DIRECTORY is from trying to open a directory as a file, because NT doesn't allow accessing the anonymous data stream of a directory, such as "dirname::$DATA", which is the same as trying to open "dirname" as a file. It only allows creating a named data stream for a directory, such as "dirname:streamname:$DATA".

The original status value may still be available, but only by calling the undocumented runtime library function, RtlGetLastNtStatus, which was added in XP (NT 5.1). After a failed system call, the Windows base API calls BaseSetLastNTError, which calls RtlNtStatusToDosError to get the Win32/DOS error code for a given NT status value. This in turn caches the last NT status in the LastStatusValue field of the thread environment block (TEB). RtlGetLastNtStatus gets this value from the TEB.

Possibly PyErr_SetExcFromWindowsErrWithFilenameObjects could capture the most recent kernel status value from RtlGetLastNtStatus(), to add this as a new "ntstatus" attribute of OSError. This wouldn't always be meaningful, since the thread's LastErrorValue (returned by GetLastError) isn't always related to a failed system call, but it can help in cases such as this, to distinguish a genuine denial of access from some other failure, and without suffering from race conditions. 

For example, I added a "testdir" subdirectory to a directory and then modified the DACL of the parent directory to deny write/append access for all users. The following experiment checks the NT status using ctypes:

    STATUS_ACCESS_DENIED = ctypes.c_long(0xC0000022).value
    STATUS_FILE_IS_A_DIRECTORY = ctypes.c_long(0xC00000BA).value

    ntdll = ctypes.WinDLL('ntdll')

    try: open('testdir', 'w')
    except: status_dir = ntdll.RtlGetLastNtStatus()

    try: open('test', 'w')
    except: status_access = ntdll.RtlGetLastNtStatus()

    >>> status_dir == STATUS_FILE_IS_A_DIRECTORY
    True
    >>> status_access == STATUS_ACCESS_DENIED
    True

Obviously using ctypes isn't recommended, since the status value needs to be captured ASAP after the call fails, so here's an example in C:

    #define UNICODE
    #include <Windows.h>
    #include <fcntl.h>
    #include <stdio.h>

    typedef NTSTATUS (NTAPI *RTLGETLASTNTSTATUS)(VOID);

    int main()
    {
        HMODULE hNtdll = GetModuleHandle(L"ntdll");
        RTLGETLASTNTSTATUS RtlGetLastNtStatus = (RTLGETLASTNTSTATUS)
            GetProcAddress(hNtdll, "RtlGetLastNtStatus");

        if (_open("testdir", _O_CREAT | _O_EXCL) == -1)
            printf("status_dir: %#08x\n", RtlGetLastNtStatus());

        if (_open("test", _O_CREAT | _O_EXCL) == -1)
            printf("status_access: %#08x\n", RtlGetLastNtStatus());
     
       return 0;
    }

output:

    status_dir: 0xc00000ba
    status_access: 0xc0000022
History
Date User Action Args
2016-05-07 03:50:01eryksunsetrecipients: + eryksun, georg.brandl, paul.moore, ncoghlan, rupole, tim.golden, python-dev, takluyver, zach.ware, serhiy.storchaka, steve.dower, Billy McCulloch
2016-05-07 03:50:01eryksunsetmessageid: <1462593001.64.0.130303022272.issue22107@psf.upfronthosting.co.za>
2016-05-07 03:50:01eryksunlinkissue22107 messages
2016-05-07 03:49:59eryksuncreate