Skip to content

Improve error messages for Windows reserved file names #81698

Open

Description

@stevendaprano
BPO 37517
Nosy @pfmoore, @tjguk, @stevendaprano, @zware, @eryksun, @zooba, @https://github.com/cknoll/

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2019-07-07.00:00:34.838>
labels = ['type-feature', '3.8', 'OS-windows']
title = 'Improve error messages for Windows reserved file names'
updated_at = <Date 2019-07-10.08:38:13.659>
user = 'https://github.com/stevendaprano'

bugs.python.org fields:

activity = <Date 2019-07-10.08:38:13.659>
actor = 'eryksun'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Windows']
creation = <Date 2019-07-07.00:00:34.838>
creator = 'steven.daprano'
dependencies = []
files = []
hgrepos = []
issue_num = 37517
keywords = ['3.6regression']
message_count = 4.0
messages = ['347456', '347465', '347537', '347610']
nosy_count = 8.0
nosy_names = ['paul.moore', 'tim.golden', 'steven.daprano', 'SilentGhost', 'zach.ware', 'eryksun', 'steve.dower', 'CarK']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue37517'
versions = ['Python 3.8']

Activity

  1. stevendaprano commented on Jul 7, 2019

    @stevendaprano
    MemberAuthor

    See bpo-37515.

    Perhaps Windows builds can check for reserved file names and give a more descriptive error message in the event of IO error?

    (Eryksun also mentions two reserved names which Microsoft apparently does not document: "CONIN$" and "CONOUT$".)

  2. eryksun commented on Jul 7, 2019

    @eryksun
    Contributor

    Perhaps Windows builds can check for reserved file names and give a
    more descriptive error message in the event of IO error?

    An operation on a reserved DOS device name can also succeed with unexpected results. For example, a script may unintentionally write to the active console screen buffer, "conout$":

        >>> open('C:/conout$::. .::.dat', 'w').write('spam\n')
        spam
        5 

    There's also the issue of normalization that removes trailing spaces and dots from the final path component. All paths get normalized, except for device paths that begin with exactly "\\?\" (i.e. extended paths) in a create or open context. For example, say a script creates a file with the reserved name "spam. . .":

    >>> open(r'\\?\C:\Temp\spam. . .', 'w').close()
    

    Then later, it generically uses os.walk('C:/Temp'), without the "\\?\" prefix, and tries to remove the file:

        >>> os.remove('C:/Temp/spam. . .')
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        FileNotFoundError: [WinError 2] The system cannot find the file specified: 'C:/Temp/spam. . .'

    Without an extended path, "spam. . ." gets normalized as "spam". The script would need to use os.walk(r'\\?\C:\Temp'). Should we special case this error as well to suggest using an extended path?

    Eryksun also mentions two reserved names which Microsoft apparently
    does not document: "CONIN$" and "CONOUT$".

    The system's behavior with these two names depends on the Windows version. In Windows 7 and earlier, "CONIN$" and "CONOUT$" are special cased by CreateFileW, and only when it's just the bare names (case insensitive) without trailing colons, spaces, or an extension, and never in a directory. In Windows 8+, as part of updating the internal console implementation to use an I/O device (i.e. "\Device\ConDrv"), "CONIN$" and "CONOUT$" were added to the system runtime library's list of DOS devices, so they behave the same as other DOS device names, including "NUL", "CON", "AUX", "PRN", "COM<1-9>", and "LPT<1-9>". This change is undocumented.

  3. zooba commented on Jul 9, 2019

    @zooba
    Member

    Presumably this is linked to bpo-37515 (why not just repurpose that one?), but I'm inclined to think this is okay provided:

    • if the operation succeeds, no error/message is displayed
    • if the operation fails, we only update the error message if one of the special filenames is the target

    So probably this ought to be a special case in PyErr_SetFromWindowsErrWithFilename(). And either we need to search the string for the special names or find an API that will clarify it (GetFileAttributes?).

    I definitely do not want every error message suggesting that this edge case may be the cause, and I don't want to prevent people using these names or purpose for their actual uses.

  4. eryksun commented on Jul 10, 2019

    @eryksun
    Contributor

    if the operation succeeds, no error/message is displayed

    Some errors pass silently and may be confusing later on. For example, CreateDirectoryW calls NtCreateFile with the disposition FILE_CREATE and the option FILE_DIRECTORY_FILE (i.e. the call *must* create a new directory), but some non-filesystem devices ignore this and let the request succeed. For example:

    >>> os.mkdir('C:/Temp/nul')
    >>> os.mkdir('C:/Temp/conin$')
    

    Both calls 'succeed' but don't actually create a directory:

        >>> os.path.exists(r'\\?\C:\Temp\nul')
        False
        >>> os.path.exists(r'\\?\C:\Temp\conin$')
        False

    either we need to search the string for the special names or find
    an API that will clarify it

    GetFullPathNameW is a library function that shares the implementation that's used to normalize paths in a create or open context.

    If the path does not start with \\?\, then we resolve it via GetFullPathNameW. If it becomes a device path, or if the final component changes (e.g. a trailing dot or space is removed), then we can parenthetically include the resolved path. For example:

    os.stat('spam.'):

    FileNotFoundError: [WinError 2] The system cannot find the file specified: 'spam.' [resolved path: 'C:\\Temp\\spam']
    

    os.startfile('con'):

    OSError: [WinError 1200] The specified device name is invalid: 'con' [resolved path: '\\\\.\\con']
    

    os.open('C:/Temp/lpt9', 0):

    FileNotFoundError: [Errno 2] No such file or directory: 'C:/Temp/lpt9' [resolved path: '\\\\.\\lpt9']
    

    For os.open() and open(), we should switch to using PyErr_SetExcFromWindowsErrWithFilenameObject instead of PyErr_SetFromErrnoWithFilenameObject. _doserrno is valid in this context, so we should be using it anyway to retain the more specific winerror value. Then we only have to special case reserved names in the Windows-only function PyErr_SetExcFromWindowsErrWithFilenameObjects.

  5. transferred this issue fromon Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions