Skip to content

[ntpath] realpath fails for broken symlinks with rooted target paths #85035

Description

@eryksun
BPO 40858
Nosy @pfmoore, @tjguk, @zware, @eryksun, @zooba

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 2020-06-03.23:24:39.011>
labels = ['type-bug', '3.8', '3.9', '3.10', 'library', 'OS-windows']
title = 'ntpath.realpath fails for broken symlinks with rooted target paths'
updated_at = <Date 2020-06-03.23:24:39.011>
user = 'https://github.com/eryksun'

bugs.python.org fields:

activity = <Date 2020-06-03.23:24:39.011>
actor = 'eryksun'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)', 'Windows']
creation = <Date 2020-06-03.23:24:39.011>
creator = 'eryksun'
dependencies = []
files = []
hgrepos = []
issue_num = 40858
keywords = []
message_count = 1.0
messages = ['370690']
nosy_count = 5.0
nosy_names = ['paul.moore', 'tim.golden', 'zach.ware', 'eryksun', 'steve.dower']
pr_nums = []
priority = 'normal'
resolution = None
stage = 'needs patch'
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue40858'
versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']

Activity

  1. eryksun commented on Jun 3, 2020

    @eryksun
    ContributorAuthor

    ntpath.realpath fails to resolve the non-strict path of a broken relative symlink if the target is a rooted path. For example:

        >>> os.readlink('symlink')
        '\\broken\\link'
        >>> os.path.realpath('symlink')
        '\\broken\\link'
    
        >>> os.path.abspath('symlink')
        'C:\\Temp\\symlink'

    The problem is that relative paths have to be specially handled by ntpath._readlink_deep, but ntpath.isabs incorrectly classifies r"\broken\link" as an absolute path. It's actually relative to the current device or drive in the access context. Other path libraries get this right, such as pathlib.Path.is_absolute and C++ path::is_absolute. The documented behavior of ntpath.isabs isn't something that we can change (i.e. "begins with a (back)slash after chopping off a potential drive letter"). So ntpath._readlink_deep needs a private implementation of isabs. For example:

        def _isabs(s):
            s = os.fspath(s)
            seps = _get_bothseps(s)
            s = s[1:2] if (s and s[0] in seps) else splitdrive(s)[1]
            return bool(s) and s[0] in seps

    This classifies UNC paths as absolute; rooted paths without a drive as relative; and otherwise depends on splitdrive() to get the root path, if any.


    Background

    The target of a relative symlink gets evaluated against the solid path that's used to access the link. A solid path contains directories and mountpoints, but no symlinks. In particular, a rooted target such as r"\spam\eggs" is relative to the root device of the solid path that's used to access the link. This may or may not be the device on which the link resides. It depends on how it's accessed. For example, if the volume that contains the symlink is accessed via its DOS device name "V:", then it resolves to r"V:\spam\eggs". Similarly, if the symlink is accessed via r"C:\Mount\VolumeSymlink", where "VolumeSymlink" is a directory symlink to "V:\\\\", then it also resolves to r"V:\spam\eggs". On the other hand, if the symlink is accessed via the mount point r"C:\Mount\VolumeMountpoint", then it resolves to r"C:\spam\eggs".

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    type-bugAn unexpected behavior, bug, or error
    on Jun 3, 2020
  3. transferred this issue fromon Apr 10, 2022
  4. changed the title [-]ntpath.realpath fails for broken symlinks with rooted target paths[/-] [+][ntpath] realpath fails for broken symlinks with rooted target paths[/+] on Aug 8, 2022
  5. serhiy-storchaka commented on Aug 8, 2026

    @serhiy-storchaka
    Member

    Fixed in 3.13 by GH-113829, which made ntpath.isabs() return False for a rooted path without a drive, so _readlink_deep() now joins such target with the location of the link.

    >>> os.readlink('symlink')
    '\\broken\\link'
    >>> os.path.realpath('symlink')
    'C:\\broken\\link'

    @eryksun, is this the result you expected, and can the issue be closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.10 (EOL)end of life3.8 (EOL)end of life3.9 (EOL)end of lifeOS-windowspendingThe issue will be closed if no feedback is providedstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions