[New-bugs-announce] [issue37612] os.link(..., follow_symlinks=True) broken on Linux

Jo Henke report at bugs.python.org
Wed Jul 17 19:04:55 EDT 2019


New submission from Jo Henke <free.software at gmx.com>:

Regarding link() POSIX states (https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html):

  "If path1 names a symbolic link, it is implementation-defined whether link() follows the symbolic link, or creates a new link to the symbolic link itself."

In Linux, link() does _not_ follow symlinks (http://man7.org/linux/man-pages/man2/link.2.html):

  "By default, linkat(), does not dereference oldpath if it is a symbolic link (like link())."

But Python 3 assumes the opposite to be always the case:

  https://github.com/python/cpython/blob/8cb65d1381b027f0b09ee36bfed7f35bb4dec9a9/Modules/posixmodule.c#L3517

...which suits e.g. NetBSD (https://netbsd.gw.com/cgi-bin/man-cgi?link+2):

  "When operating on a symlink, link() resolves the symlink and creates a hard link on the target."


Therefore, I recommend to always call linkat(), if the platform provides it. That's the modern superset of link() with clearly defined behavior.


Here are some commands to reproduce the issue on Linux (should hard link 'file' -> 'link', but tries '/tmp/symlink' -> 'link'):

~$ : >file
~$ ln -s "$PWD/file" /tmp/symlink
~$ strace -e link,linkat python -c 'import os; os.link("/tmp/symlink", "link", follow_symlinks=True)'
link("/tmp/symlink", "link")            = -1 EXDEV (Cross-device link)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 18] Cross-device link: '/tmp/symlink' -> 'link'
+++ exited with 1 +++


For comparison, calling linkat() without AT_SYMLINK_FOLLOW results in the same error:

~$ strace -e link,linkat python -c 'import os; os.link("/tmp/symlink", "link", follow_symlinks=False)'
linkat(AT_FDCWD, "/tmp/symlink", AT_FDCWD, "link", 0) = -1 EXDEV (Cross-device link)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 18] Cross-device link: '/tmp/symlink' -> 'link'
+++ exited with 1 +++


Currently, the only way to call linkat() with AT_SYMLINK_FOLLOW from Python, is to provide a directory file descriptor != AT_FDCWD:

~$ strace -e link,linkat python -c 'import os; d=os.open(".", 0); os.link("/tmp/symlink", "link", dst_dir_fd=d, follow_symlinks=True)'
linkat(AT_FDCWD, "/tmp/symlink", 3, "link", AT_SYMLINK_FOLLOW) = 0
+++ exited with 0 +++

----------
components: Library (Lib)
messages: 348086
nosy: jo-he
priority: normal
severity: normal
status: open
title: os.link(..., follow_symlinks=True) broken on Linux
type: behavior
versions: Python 3.5, Python 3.6, Python 3.7, Python 3.8, Python 3.9

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue37612>
_______________________________________


More information about the New-bugs-announce mailing list