-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
os.link(..., follow_symlinks=True) broken on Linux #81793
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
Comments
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: Line 3517 in 8cb65d1
...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)' |
Not always linkat() can be used instead of link(). But on Windows (where there is no linkat()) os.link() creates a new link to the symbolic link itself. This is yet one argument for making follow_symlinks=False by default and changing the default behavior of os.link() on NetBSD. |
Yes, CreateHardLinkW opens the source file by calling NtOpenFile with the option FILE_OPEN_REPARSE_POINT. So the behavior is follow_symlinks=False. Note, however, that Windows has distinct file and directory reparse points, so we can't hardlink to a directory symlink, or any other type of directory reparse point such as a junction mountpoint. In Unix, follow_symlinks=False (if implemented) allows creating a hardlink to a symlink that targets a directory. Also, I noticed that I can pass follow_symlinks=False in Windows, but this should raise NotImplementedError. It's supposed to be checked via follow_symlinks_specified(). |
The problem that POSIX does not define the behavior of link() regarding symlinks (and that Unix implementations differ indeed), is independent from Python's os.link() defaults. Since it makes no sense to call link(), when linkat() is available, I propose this change: --- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3512,15 +3512,11 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd,
#else
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_LINKAT
- if ((src_dir_fd != DEFAULT_DIR_FD) ||
- (dst_dir_fd != DEFAULT_DIR_FD) ||
- (!follow_symlinks))
- result = linkat(src_dir_fd, src->narrow,
- dst_dir_fd, dst->narrow,
- follow_symlinks ? AT_SYMLINK_FOLLOW : 0);
- else
+ result = linkat(src_dir_fd, src->narrow, dst_dir_fd, dst->narrow,
+ follow_symlinks ? AT_SYMLINK_FOLLOW : 0);
+#else
+ result = link(src->narrow, dst->narrow);
#endif /* HAVE_LINKAT */
- result = link(src->narrow, dst->narrow);
Py_END_ALLOW_THREADS
This fix also simplifies the code, and should be safe for back-porting. Whether Python's defaults should be changed regarding Windows and those (mostly obsolete) Unix platforms that do not provide linkat(), is another question. |
* Update ironic from branch 'master' to a42f23f47581b6b7e32a6e591e48baa65cbe8adf - Merge "Don't create a hardlink to a symlink when handling file:// URLs" - Don't create a hardlink to a symlink when handling file:// URLs While os.link is supposed to follow symlinks, it's actually broken [1] on Linux. As a result, Ironic may end up creating a hard link to a symlink. If the symlink is relative, chances are high accessing the resulting file will cause a FileNotFoundError. [1] python/cpython#81793 Change-Id: Ic52f0ddb0c94410dd854ee525e3c57b2e78ea84d
While os.link is supposed to follow symlinks, it's actually broken [1] on Linux. As a result, Ironic may end up creating a hard link to a symlink. If the symlink is relative, chances are high accessing the resulting file will cause a FileNotFoundError. [1] python/cpython#81793 Change-Id: Ic52f0ddb0c94410dd854ee525e3c57b2e78ea84d
While os.link is supposed to follow symlinks, it's actually broken [1] on Linux. As a result, Ironic may end up creating a hard link to a symlink. If the symlink is relative, chances are high accessing the resulting file will cause a FileNotFoundError. [1] python/cpython#81793 Change-Id: Ic52f0ddb0c94410dd854ee525e3c57b2e78ea84d (cherry picked from commit 0b3ed09)
While os.link is supposed to follow symlinks, it's actually broken [1] on Linux. As a result, Ironic may end up creating a hard link to a symlink. If the symlink is relative, chances are high accessing the resulting file will cause a FileNotFoundError. [1] python/cpython#81793 Change-Id: Ic52f0ddb0c94410dd854ee525e3c57b2e78ea84d (cherry picked from commit 0b3ed09)
This fixes os.link() on platforms (like Linux and OpenIndiana) where the system link() function does not follow symlinks. * On Linux and OpenIndiana, it now follows symlinks by default and if follow_symlinks=True is specified. * On Windows, it now raises error if follow_symlinks=True is passed. * On macOS, it now raises error if follow_symlinks=False is passed and the system linkat() function is not available at runtime. * On other platforms, it now raises error if follow_symlinks is passed with a value that does not match the system link() function behavior if if the behavior is not known. Co-authored-by: Joachim Henke <[email protected]> Co-authored-by: Thomas Kluyver <[email protected]>
I created an alternate PR #132517 based on work by @jo-he and @takluyver. The code is now clearer and all special cases are handled.
|
Interesting, on OpenIndiana the |
This fixes os.link() on platforms (like Linux and OpenIndiana) where the system link() function does not follow symlinks. * On Linux, it now follows symlinks by default and if follow_symlinks=True is specified. * On Windows, it now raises error if follow_symlinks=True is passed. * On macOS, it now raises error if follow_symlinks=False is passed and the system linkat() function is not available at runtime. * On other platforms, it now raises error if follow_symlinks is passed with a value that does not match the system link() function behavior if if the behavior is not known. Co-authored-by: Joachim Henke <[email protected]> Co-authored-by: Thomas Kluyver <[email protected]>
* origin/main: (111 commits) pythongh-91048: Add filename and line number to external inspection routines (pythonGH-133385) pythongh-131178: Add tests for `ast` command-line interface (python#133329) Regenerate pcbuild.sln in Visual Studio 2022 (python#133394) pythongh-133042: disable HACL* HMAC on Emscripten (python#133064) pythongh-133351: Fix remote PDB's multi-line block tab completion (python#133387) pythongh-109700: Improve stress tests for interpreter creation (pythonGH-109946) pythongh-81793: Skip tests for os.link() to symlink on Android (pythonGH-133388) pythongh-126835: Rename `ast_opt.c` to `ast_preprocess.c` and related stuff after moving const folding to the peephole optimizier (python#131830) pythongh-91048: Relax test_async_global_awaited_by to fix flakyness (python#133368) pythongh-132457: make staticmethod and classmethod generic (python#132460) pythongh-132805: annotationlib: Fix handling of non-constant values in FORWARDREF (python#132812) pythongh-132426: Add get_annotate_from_class_namespace replacing get_annotate_function (python#132490) pythongh-81793: Always call linkat() from os.link(), if available (pythonGH-132517) pythongh-122559: Synchronize C and Python implementation of the io module about pickling (pythonGH-122628) pythongh-69605: Add PyREPL import autocomplete feature to 'What's New' (python#133358) bpo-44172: Keep reference to original window in curses subwindow objects (pythonGH-26226) pythonGH-133231: Changes to executor management to support proposed `sys._jit` module (pythonGH-133287) pythongh-133363: Fix Cmd completion for lines beginning with `! ` (python#133364) pythongh-132983: Introduce `_zstd` bindings module (pythonGH-133027) pythonGH-91048: Add utils for printing the call stack for asyncio tasks (python#133284) ...
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:
bugs.python.org fields:
Linked PRs
The text was updated successfully, but these errors were encountered: