Skip to content

Handle leading // for posixpath.commonpath #117201

Closed as not planned
Closed as not planned

Description

@nineteendo

Bug report

Bug description:

>>> import posixpath
>>> posixpath.commonpath(["//foo/bar", "//foo/baz"])
'/foo'

Expected: //foo with precisely two leading slashes, see: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13

This can be quite easily solved using posixpath.splitroot, see the PR.

CPython versions tested on:

CPython main branch

Operating systems tested on:

macOS

Linked PRs

Activity

  1. changed the title [-]`posixpath.commonpath` doesn't handle leading `//` properly[/-] [+]Handle leading `//` for `posixpath.commonpath`[/+] on Mar 26, 2024
  2. serhiy-storchaka commented on Apr 4, 2024

    @serhiy-storchaka
    Member

    For reference, the original issue that introduced commonpath() is bpo-10395/gh-54604.
    And its behavior was discussed at https://mail.python.org/archives/list/python-ideas@python.org/thread/VHQTSU4ERTH262OPVZ6SEO2EYCFQF56G/.

  3. erlend-aasland commented on Apr 4, 2024

    @erlend-aasland
    Contributor
  4. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    This is the reason I don't like the current implementation:

    from posixpath import abspath, commonpath, splitdrive
    
    def isparent(path1, path2):
        drive1, tail1 = splitdrive(abspath(path1))
        drive2, tail2 = splitdrive(abspath(path2))
        return drive1 == drive2 and commonpath([tail1, tail2]) == tail1
    
    if isparent("//foo", "//foo/bar"):
        print("path1 is a parent of path2")
    else:
        print("path1 is not a parent of path2")

    Output: path1 is not a parent of path2.

  5. erlend-aasland commented on Apr 5, 2024

    @erlend-aasland
    Contributor

    If anything, this looks to me like a commonpath documentation issue.

    Moreover, if I were to write such a test, I would normalise tail1 before comparing it, or more probable: use a higher level API.

  6. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    If anything, this looks to me like a commonpath documentation issue.

    If that's the case, that's fine by me, but that should be confirmed by @ronaldoussoren.

    Moreover, if I were to write such a test, I would normalise tail1 before comparing it

    The thing is: the paths are already normalised here, but that won't replace precisely 2 leading slashes. Which is intended behaviour.

  7. erlend-aasland commented on Apr 5, 2024

    @erlend-aasland
    Contributor

    The thing is: the paths are already normalised here, but that won't eliminate a leading // as intended.

    splitdrive does not normalise the paths, if that is what you imply.

  8. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    No, the paths I'm passing to splitdrive() are already normalised (I added abspath() to make that clearer), but I would still need to apply commonpath() to the tail to make the comparison work. I shouldn't have to do that.

  9. erlend-aasland commented on Apr 5, 2024

    @erlend-aasland
    Contributor

    Well, it is still a backwards-incompatible change.

  10. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    Anyway, whether this will be accepted depends on whether this is intentional behaviour or not:

    • If it's intentional, we should document and test this.
    • If it isn't, we should document the new behaviour like how os.path.isabs() was changed to handle paths correctly starting with exactly one (back)slash on Windows.
  11. ronaldoussoren commented on Apr 5, 2024

    @ronaldoussoren
    Contributor

    IMHO we should consider dropping special handling for '//' at the start of a path for our path manipulation APIs. I cannot recall a Unix where '//foo' is different from '/foo', but it is an awfully long time that I've used anything other than Linux and macOS.

    The Open Group documents that '//foo' may be special ("may be treated in an implementation defined way"), but AFAIK commonly used UNIX-y systems don't use this possibility.

    Because most systems don't treat '//foo' specially we shouldn't change the behaviour of os.path.commonpath as this will only lead to confusing users and might have a security impact for code that expects the current behaviour.

  12. ronaldoussoren commented on Apr 5, 2024

    @ronaldoussoren
    Contributor

    To reply to myself: https://unix.stackexchange.com/questions/256497/on-what-systems-is-foo-bar-different-from-foo-bar

    Appearently Cygwin uses '//share/path' to access UNC shares, as does UWIN. And finally zOS uses '//path' for something implementation defined as well. The page also lists some no longer supported systems which used '//host/path' for networked file systems.

    This complicates things...

  13. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    Can we rediscuss the behaviour of commonpath(['//foo', '/foo']) then? As these paths would have a different meaning.
    I would prefer to return the empty string in this case as they're both absolute.

  14. ronaldoussoren commented on Apr 5, 2024

    @ronaldoussoren
    Contributor

    As I wrote, it's complicated. My gut feeling is that we shouldn't change the behaviour for all platforms because of most platforms the two paths do no have a different meaning.

    The platforms with special treatment for double slashes at the start of a path are not on our list of supported platforms, and are not tested in CI. As such we generally don't complicate the code base for them, although Cygwin used to be somewhat popular (but I haven't used Windows or Cygwin in a long time).

    Because of this I'm still of the opinion what we should consider dropping specific support for double slashes at the start of paths in os.path. That's something that needs discussion outside of this issue though, and that might lead to keeping support for this on a specific list of platforms such as Cygwin.

  15. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    OK, then I'll close this issue for the time being. Could you start a discussion about this?

  16. serhiy-storchaka commented on Apr 5, 2024

    @serhiy-storchaka
    Member

    How '//share/path/../../file' is resolved on Cygwin? Is it equal to /file or //share/file? The same question is for other platforms.

    Perhaps such platforms should have a separate os.path and pathlib.Path implementations. And have more complete support of normpath(), split(), splitroot(), commonpath() etc if //share is considered a different root.

  17. ronaldoussoren commented on Apr 5, 2024

    @ronaldoussoren
    Contributor

    How '//share/path/../../file' is resolved on Cygwin? Is it equal to /file or //share/file? The same question is for other platforms.

    I have no idea. On any platform where "//path/subpath" is special the behaviour is implementation defined, and hence not something we can implement generically in CPython.

    Perhaps such platforms should have a separate os.path and pathlib.Path implementations. And have more complete support of normpath(), split(), splitroot(), commonpath() etc if //share is considered a different root.

    Probably. That's one more reason to just drop the limited support for this in the stdlib and reconsider when someone proposes to add support for such a platform to CPython. On first glance the only support we have is that normpath doesn't remove the extraneous slash when there are exactly 2 slashes at the start of the path (introduced in bf222c9). Other path manipulation functions don't check for there double slashes at all.

  18. nineteendo commented on Apr 5, 2024

    @nineteendo
    ContributorAuthor

    Other path manipulation functions don't check for there double slashes at all.

    posixpath.splitroot():

    cpython/Lib/posixpath.py

    Lines 162 to 164 in 4d4a6f1

    # Precisely two leading slashes, e.g.: '//foo'. Implementation defined per POSIX, see
    # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
    return empty, p[:2], p[2:]

    The only function that doesn't handle it properly is posixpath.commonpath() (posixpath.realpath() works as intended: #117338).

  19. calestyo commented on Aug 25, 2025

    @calestyo
    Contributor

    Just for the records:

    I have no idea. On any platform where "//path/subpath" is special the behaviour is implementation defined, and hence not something we can implement generically in CPython.

    Is that really the case? POSIX probably (see my remarks in #138139 (comment)) does define well how such pathnames are e.g. normalised, because it says only the first component is implementation defined.

    At least to the point where Python just passes such pathnames on to some syscall or so, things should probably work, as then the OS would take care again of any interpretation of the first component.

    The only thing which Python in fact cannot really do generically, would be when e.g. comparing/joining/etc. two such special pathnames (i.e. any pathname operations).

    Cause e.g. in some implementation //foo/bar could be considered identical to //FOO/bar where in others not.

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

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions