New submission from Jo Henke <free.softw...@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 <rep...@bugs.python.org>
<https://bugs.python.org/issue37612>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to