Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shutil.move fails to move symlink (Invalid cross-device link) #70978

Closed
Unode mannequin opened this issue Apr 17, 2016 · 6 comments
Closed

shutil.move fails to move symlink (Invalid cross-device link) #70978

Unode mannequin opened this issue Apr 17, 2016 · 6 comments
Labels
3.11 only security fixes 3.12 bugs and security fixes 3.13 new features, bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@Unode
Copy link
Mannequin

Unode mannequin commented Apr 17, 2016

BPO 26791
Nosy @tarekziade, @websurfer5, @iritkatriel
PRs
  • bpo-26791: update shutil.move() to provide the same symlink move behavior as the mv shell when moving a symlink into a directory that is the target of the symlink #21759
  • 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 2016-04-17.16:58:17.148>
    labels = ['type-bug', 'library', '3.9', '3.10', '3.11']
    title = 'shutil.move fails to move symlink (Invalid cross-device link)'
    updated_at = <Date 2021-11-30.12:28:21.452>
    user = 'https://bugs.python.org/Unode'

    bugs.python.org fields:

    activity = <Date 2021-11-30.12:28:21.452>
    actor = 'iritkatriel'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2016-04-17.16:58:17.148>
    creator = 'Unode'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 26791
    keywords = ['patch']
    message_count = 6.0
    messages = ['263616', '263617', '263654', '374648', '374916', '407371']
    nosy_count = 5.0
    nosy_names = ['tarek', 'Unode', 'Jeffrey.Kintscher', 'iritkatriel', 'Murray Wilson']
    pr_nums = ['21759']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue26791'
    versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']

    @Unode
    Copy link
    Mannequin Author

    Unode mannequin commented Apr 17, 2016

    Hi everyone,

    I'm not really sure if this is a new issue but digging through the bug reports from the past I couldn't find an answer.
    There's http://bugs.python.org/issue1438480 but this seems to be a different issue.
    I also found http://bugs.python.org/issue9993 that addressed problems with symlinks but didn't correct the behavior reported here.

    The problem can be visualized with the following code.
    Code fails on python 2.7 as well as python 3.4+. Not tested in python <2.7 and <3.4.

        import shutil
        import os
        
        TMPDIR = "/tmp/tmpdir"
        TESTLINK = "test_dir"
        
        if not os.path.isdir(TMPDIR):
            os.mkdir(TMPDIR)
        
        if not os.path.islink(TESTLINK):
            os.symlink(TMPDIR, TESTLINK)
        
        shutil.move(TESTLINK, TMPDIR)

    When executed it gives me:

        % python3 test.py
        Traceback (most recent call last):
          File "test.py", line 14, in <module>
            shutil.move(TESTLINK, TMPDIR)
          File "/usr/lib64/python3.4/shutil.py", line 516, in move
            os.rename(src, dst)
        OSError: [Errno 18] Invalid cross-device link: 'test_dir' -> '/tmp/tmpdir'

    This happens because /tmp is:

    tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime,nodiratime)

    In the past the recommendation to handle this problem was to stop using os.rename and use shutil.move instead.
    This was even discussed in a bug report - http://bugs.python.org/issue14848

    If one searches for this exception there's plenty of advice [1][2][3][4] in the same direction.
    However, given that shutil.move uses os.rename internally, the problem returns.

    On the other end doing the equivalent action in the shell with 'mv' works fine.

    [1] - http://stackoverflow.com/a/15300474
    [2] - https://mail.python.org/pipermail/python-list/2005-February/342892.html
    [3] - http://www.thecodingforums.com/threads/errno-18-invalid-cross-device-link-using-os-rename.341597/
    [4] - pypa/pip#103

    @Unode Unode mannequin added the stdlib Python modules in the Lib dir label Apr 17, 2016
    @Unode
    Copy link
    Mannequin Author

    Unode mannequin commented Apr 17, 2016

    Also related to http://bugs.python.org/issue212317

    @SilentGhost
    Copy link
    Mannequin

    SilentGhost mannequin commented Apr 18, 2016

    This seems to be only triggered when moving a symlink into its destination. Assumption in the code is that when _samefile returns True, it must be due to case-insensitive filesystem, rather than this edge case.

    @SilentGhost SilentGhost mannequin added the type-bug An unexpected behavior, bug, or error label Apr 18, 2016
    @MurrayWilson
    Copy link
    Mannequin

    MurrayWilson mannequin commented Jul 31, 2020

    It also happens when moving a file in linux from an xfs filesystem to a NFS mounted filesystem.

    @websurfer5
    Copy link
    Mannequin

    websurfer5 mannequin commented Aug 6, 2020

    SilentGhost's analysis is correct. The provided example code causes the error because it is trying to move the symlink into its target when the target is a directory. Any cross-device moving issues are unrelated to this example code. Here is the relevant code in the master branch:

        if os.path.isdir(dst):
            if _samefile(src, dst):
                # We might be on a case insensitive filesystem,
                # perform the rename anyway.
                os.rename(src, dst)
                return

    shutil._samefile() considers the example link and its target to be the same. When _samefile() returns False, this code gets executed:

            real_dst = os.path.join(dst, _basename(src))
    
            if os.path.exists(real_dst):
                raise Error("Destination path '%s' already exists" % real_dst)
        try:
            os.rename(src, real_dst)
        except OSError:
            if os.path.islink(src):
                linkto = os.readlink(src)
                os.symlink(linkto, real_dst)
                os.unlink(src)

    A simple fix is to check whether src is a symlink when _samefile() returns True. The "Destination path...already exists" error isn't a problem for our symlink case because the shell mv command also returns an error.

    $ ls -l /tmp/tmpdir/
    total 0
    lrwxr-xr-x  1 jeff  staff  11 Aug  5 23:36 test_dir -> /tmp/tmpdir
    $ mv test_dir /tmp/tmpdir
    mv: test_dir and /tmp/tmpdir/test_dir are identical

    I will generate a pull request.

    @websurfer5 websurfer5 mannequin added 3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes 3.10 only security fixes labels Aug 6, 2020
    @iritkatriel
    Copy link
    Member

    Removing old versions.

    @iritkatriel iritkatriel added 3.11 only security fixes and removed 3.7 (EOL) end of life 3.8 only security fixes labels Nov 30, 2021
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @serhiy-storchaka serhiy-storchaka added 3.12 bugs and security fixes 3.13 new features, bugs and security fixes and removed 3.10 only security fixes 3.9 only security fixes labels Dec 27, 2023
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.11 only security fixes 3.12 bugs and security fixes 3.13 new features, bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    2 participants