From a8f488aa7267510e953979175d8d9045120d0291 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 17:35:52 +0300 Subject: [PATCH 01/11] gh-81793: Always call linkat() from os.link(), if available 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 <37883863+jo-he@users.noreply.github.com> Co-authored-by: Thomas Kluyver --- Doc/library/os.rst | 1 + Lib/test/test_posix.py | 39 +++++++++ ...5-04-14-17-24-50.gh-issue-81793.OhRTTT.rst | 7 ++ Modules/clinic/posixmodule.c.h | 4 +- Modules/posixmodule.c | 87 +++++++++---------- 5 files changed, 91 insertions(+), 47 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 54a5d3b98e8662..1e54cfec609bd2 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2338,6 +2338,7 @@ features: This function can support specifying *src_dir_fd* and/or *dst_dir_fd* to supply :ref:`paths relative to directory descriptors `, and :ref:`not following symlinks `. + The default value of *follow_symlinks* is ``False`` on Windows. .. audit-event:: os.link src,dst,src_dir_fd,dst_dir_fd os.link diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index c9cbe1541e733e..6e077eecea2aed 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1521,6 +1521,45 @@ def test_pidfd_open(self): self.assertEqual(cm.exception.errno, errno.EINVAL) os.close(os.pidfd_open(os.getpid(), 0)) + @unittest.skipUnless(hasattr(os, "link"), "test needs os.link()") + def test_link_follow_symlinks(self): + orig = os_helper.TESTFN + symlink = orig + 'symlink' + posix.symlink(orig, symlink) + self.addCleanup(os_helper.unlink, symlink) + + link = orig + 'link' + posix.link(symlink, link) + self.addCleanup(os_helper.unlink, link) + default_follow = sys.platform.startswith(('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly')) + default_no_follow = sys.platform.startswith(('win32', 'linux', 'sunos5')) + if os.link in os.supports_follow_symlinks or default_follow: + self.assertEqual(posix.lstat(link), posix.lstat(orig)) + elif default_no_follow: + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) + + # follow_symlinks=False -> duplicate the symlink itself + link_nofollow = orig + 'link_nofollow' + try: + posix.link(symlink, link_nofollow, follow_symlinks=False) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_no_follow: + raise + else: + self.addCleanup(os_helper.unlink, link_nofollow) + self.assertEqual(posix.lstat(link_nofollow), posix.lstat(symlink)) + + # follow_symlinks=True -> duplicate the target file + link_following = orig + 'link_following' + try: + posix.link(symlink, link_following, follow_symlinks=True) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_follow: + raise + else: + self.addCleanup(os_helper.unlink, link_following) + self.assertEqual(posix.lstat(link_following), posix.lstat(orig)) + # tests for the posix *at functions follow class TestPosixDirFd(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst new file mode 100644 index 00000000000000..15fe8ba9e9a902 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst @@ -0,0 +1,7 @@ +Fix :func:`os.link` on platforms (like Linux and OpenIndiana) where the +system :c:finc:`!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 :c:finc:`!linkat` +function is not available at runtime. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index d03f68ab8fb9aa..2c0f4d9401e2ee 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -1530,7 +1530,7 @@ os_link(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwn path_t dst = PATH_T_INITIALIZE_P("link", "dst", 0, 0, 0, 0); int src_dir_fd = DEFAULT_DIR_FD; int dst_dir_fd = DEFAULT_DIR_FD; - int follow_symlinks = 1; + int follow_symlinks = -1; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -13398,4 +13398,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=35dd8edb53b50537 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=27f684c2f99c83c6 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b7300def8dc75f..f18b785de6d3af 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -427,7 +427,7 @@ extern char *ctermid_r(char *); /* Something to implement in autoconf, not present in autoconf 2.69 */ # define HAVE_STRUCT_STAT_ST_FSTYPE 1 #endif - +#undef HAVE_LINKAT // --- Apple __builtin_available() macros ----------------------------------- @@ -4323,7 +4323,7 @@ os.link * src_dir_fd : dir_fd = None dst_dir_fd : dir_fd = None - follow_symlinks: bool = True + follow_symlinks: bool(c_default="-1", py_default="(os.name != 'nt')") = PLACEHOLDER Create a hard link to a file. @@ -4341,25 +4341,48 @@ src_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your static PyObject * os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd, int dst_dir_fd, int follow_symlinks) -/*[clinic end generated code: output=7f00f6007fd5269a input=b0095ebbcbaa7e04]*/ +/*[clinic end generated code: output=7f00f6007fd5269a input=f6a681a558380a15]*/ { #ifdef MS_WINDOWS BOOL result = FALSE; #else int result; #endif -#if defined(HAVE_LINKAT) - int linkat_unavailable = 0; -#endif -#ifndef HAVE_LINKAT - if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) { - argument_unavailable_error("link", "src_dir_fd and dst_dir_fd"); - return NULL; +#ifdef HAVE_LINKAT + if (HAVE_LINKAT_RUNTIME) { + if (follow_symlinks < 0) { + follow_symlinks = 1; + } } + else #endif + { + if ((src_dir_fd != DEFAULT_DIR_FD) || (dst_dir_fd != DEFAULT_DIR_FD)) { + argument_unavailable_error("link", "src_dir_fd and dst_dir_fd"); + return NULL; + } +/* See issue 41355: link() on Linux works like linkat without AT_SYMLINK_FOLLOW, + but on Mac it works like linkat *with* AT_SYMLINK_FOLLOW. */ +#if defined(MS_WINDOWS) || defined(__linux__) || (defined(__sun) && defined(__SVR4)) + if (follow_symlinks == 1) { + argument_unavailable_error("link", "follow_symlinks=True"); + return NULL; + } +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) + if (follow_symlinks == 0) { + argument_unavailable_error("link", "follow_symlinks=False"); + return NULL; + } +#else + if (follow_symlinks >= 0) { + argument_unavailable_error("link", "follow_symlinks"); + return NULL; + } +#endif + } -#ifndef MS_WINDOWS +#ifdef MS_WINDOWS if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) { PyErr_SetString(PyExc_NotImplementedError, "link: src and dst must be the same type"); @@ -4383,44 +4406,18 @@ 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)) { - - if (HAVE_LINKAT_RUNTIME) { - - result = linkat(src_dir_fd, src->narrow, - dst_dir_fd, dst->narrow, - follow_symlinks ? AT_SYMLINK_FOLLOW : 0); - - } -#ifdef __APPLE__ - else { - if (src_dir_fd == DEFAULT_DIR_FD && dst_dir_fd == DEFAULT_DIR_FD) { - /* See issue 41355: This matches the behaviour of !HAVE_LINKAT */ - result = link(src->narrow, dst->narrow); - } else { - linkat_unavailable = 1; - } - } -#endif + if (HAVE_LINKAT_RUNTIME) { + result = linkat(src_dir_fd, src->narrow, + dst_dir_fd, dst->narrow, + follow_symlinks ? AT_SYMLINK_FOLLOW : 0); } else -#endif /* HAVE_LINKAT */ +#endif + { + /* linkat not available */ result = link(src->narrow, dst->narrow); - Py_END_ALLOW_THREADS - -#ifdef HAVE_LINKAT - if (linkat_unavailable) { - /* Either or both dir_fd arguments were specified */ - if (src_dir_fd != DEFAULT_DIR_FD) { - argument_unavailable_error("link", "src_dir_fd"); - } else { - argument_unavailable_error("link", "dst_dir_fd"); - } - return NULL; } -#endif + Py_END_ALLOW_THREADS if (result) return path_error2(src, dst); From 4ba224e4e1c5c385b416173218a6bbe1eef6bc7f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 18:05:48 +0300 Subject: [PATCH 02/11] Fix typo and regenerate clinic files. --- .../Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst | 4 ++-- Modules/clinic/posixmodule.c.h | 4 ++-- Modules/posixmodule.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst index 15fe8ba9e9a902..f0246b181bdd2e 100644 --- a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst +++ b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst @@ -1,7 +1,7 @@ Fix :func:`os.link` on platforms (like Linux and OpenIndiana) where the -system :c:finc:`!link` function does not follow symlinks. On Linux and +system :c:func:`!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 :c:finc:`!linkat` +``follow_symlinks=False`` is passed and the system :c:func:`!linkat` function is not available at runtime. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 2c0f4d9401e2ee..0d970a6a050f5c 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -1471,7 +1471,7 @@ os_getcwdb(PyObject *module, PyObject *Py_UNUSED(ignored)) PyDoc_STRVAR(os_link__doc__, "link($module, /, src, dst, *, src_dir_fd=None, dst_dir_fd=None,\n" -" follow_symlinks=True)\n" +" follow_symlinks=(os.name != \'nt\'))\n" "--\n" "\n" "Create a hard link to a file.\n" @@ -13398,4 +13398,4 @@ os__emscripten_debugger(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__EMSCRIPTEN_DEBUGGER_METHODDEF #define OS__EMSCRIPTEN_DEBUGGER_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_DEBUGGER_METHODDEF) */ -/*[clinic end generated code: output=27f684c2f99c83c6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=12932e9cd3f9afb9 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index f18b785de6d3af..70c235278015c4 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4341,7 +4341,7 @@ src_dir_fd, dst_dir_fd, and follow_symlinks may not be implemented on your static PyObject * os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd, int dst_dir_fd, int follow_symlinks) -/*[clinic end generated code: output=7f00f6007fd5269a input=f6a681a558380a15]*/ +/*[clinic end generated code: output=7f00f6007fd5269a input=1d5e602d115fed7b]*/ { #ifdef MS_WINDOWS BOOL result = FALSE; From 5881b2a0eb6ba177468b750bf8b2026c89743d36 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 18:51:54 +0300 Subject: [PATCH 03/11] Fix OpenIndiana. --- Lib/test/test_posix.py | 5 +++-- .../Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst | 6 +++--- Modules/posixmodule.c | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 6e077eecea2aed..7e093d1c850d81 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1531,8 +1531,9 @@ def test_link_follow_symlinks(self): link = orig + 'link' posix.link(symlink, link) self.addCleanup(os_helper.unlink, link) - default_follow = sys.platform.startswith(('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly')) - default_no_follow = sys.platform.startswith(('win32', 'linux', 'sunos5')) + default_follow = sys.platform.startswith( + ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5')) + default_no_follow = sys.platform.startswith(('win32', 'linux')) if os.link in os.supports_follow_symlinks or default_follow: self.assertEqual(posix.lstat(link), posix.lstat(orig)) elif default_no_follow: diff --git a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst index f0246b181bdd2e..933c4b63181d12 100644 --- a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst +++ b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst @@ -1,6 +1,6 @@ -Fix :func:`os.link` on platforms (like Linux and OpenIndiana) where the -system :c:func:`!link` function does not follow symlinks. On Linux and -OpenIndiana, it now follows symlinks by default and if +Fix :func:`os.link` on platforms (like Linux) where the +system :c:func:`!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 :c:func:`!linkat` diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 70c235278015c4..39c5369671b9f8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4364,12 +4364,12 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd, } /* See issue 41355: link() on Linux works like linkat without AT_SYMLINK_FOLLOW, but on Mac it works like linkat *with* AT_SYMLINK_FOLLOW. */ -#if defined(MS_WINDOWS) || defined(__linux__) || (defined(__sun) && defined(__SVR4)) +#if defined(MS_WINDOWS) || defined(__linux__) if (follow_symlinks == 1) { argument_unavailable_error("link", "follow_symlinks=True"); return NULL; } -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || (defined(__sun) && defined(__SVR4)) if (follow_symlinks == 0) { argument_unavailable_error("link", "follow_symlinks=False"); return NULL; From d2f233b9450724238aadbbf1126872d1b9209acc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 19:05:12 +0300 Subject: [PATCH 04/11] Fix test_inspect. --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index daae990458d708..05f97337b0fa69 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5799,7 +5799,7 @@ def test_operator_module_has_signatures(self): self._test_module_has_signatures(operator) def test_os_module_has_signatures(self): - unsupported_signature = {'chmod', 'utime'} + unsupported_signature = {'chmod', 'link', 'utime'} unsupported_signature |= {name for name in ['get_terminal_size', 'posix_spawn', 'posix_spawnp', 'register_at_fork', 'startfile'] From 95f55e3192cde90fe34ce05fe0fcae9246a323ec Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 19:08:12 +0300 Subject: [PATCH 05/11] Enable linkat(). --- Modules/posixmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 39c5369671b9f8..a6e18ffd8e1026 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -427,7 +427,7 @@ extern char *ctermid_r(char *); /* Something to implement in autoconf, not present in autoconf 2.69 */ # define HAVE_STRUCT_STAT_ST_FSTYPE 1 #endif -#undef HAVE_LINKAT + // --- Apple __builtin_available() macros ----------------------------------- From 2eeb438c1289ad5962b68ed21ec1b1f8724b0661 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 14 Apr 2025 20:49:29 +0300 Subject: [PATCH 06/11] Apply suggestions from code review Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- .../Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst | 6 +++--- Modules/posixmodule.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst index 933c4b63181d12..842e973b821744 100644 --- a/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst +++ b/Misc/NEWS.d/next/Library/2025-04-14-17-24-50.gh-issue-81793.OhRTTT.rst @@ -1,7 +1,7 @@ Fix :func:`os.link` on platforms (like Linux) where the system :c:func:`!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 +it now follows symlinks by default or if +``follow_symlinks=True`` is specified. On Windows, it now raises an error if +``follow_symlinks=True`` is passed. On macOS, it now raises an error if ``follow_symlinks=False`` is passed and the system :c:func:`!linkat` function is not available at runtime. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a6e18ffd8e1026..3512e6de5e9ece 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4362,7 +4362,7 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd, argument_unavailable_error("link", "src_dir_fd and dst_dir_fd"); return NULL; } -/* See issue 41355: link() on Linux works like linkat without AT_SYMLINK_FOLLOW, +/* See issue 85527: link() on Linux works like linkat without AT_SYMLINK_FOLLOW, but on Mac it works like linkat *with* AT_SYMLINK_FOLLOW. */ #if defined(MS_WINDOWS) || defined(__linux__) if (follow_symlinks == 1) { From b4be5269d76ecf29069eb66114a8ffbc183b0b53 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 10:03:21 +0300 Subject: [PATCH 07/11] Use subTest(). --- Lib/test/test_posix.py | 64 ++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 7e093d1c850d81..bb18e49c9e3f82 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1523,43 +1523,47 @@ def test_pidfd_open(self): @unittest.skipUnless(hasattr(os, "link"), "test needs os.link()") def test_link_follow_symlinks(self): + default_follow = sys.platform.startswith( + ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5')) + default_no_follow = sys.platform.startswith(('win32', 'linux')) orig = os_helper.TESTFN symlink = orig + 'symlink' posix.symlink(orig, symlink) self.addCleanup(os_helper.unlink, symlink) - link = orig + 'link' - posix.link(symlink, link) - self.addCleanup(os_helper.unlink, link) - default_follow = sys.platform.startswith( - ('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5')) - default_no_follow = sys.platform.startswith(('win32', 'linux')) - if os.link in os.supports_follow_symlinks or default_follow: - self.assertEqual(posix.lstat(link), posix.lstat(orig)) - elif default_no_follow: - self.assertEqual(posix.lstat(link), posix.lstat(symlink)) + with self.subTest('no follow_symlinks'): + # no follow_symlinks -> platform depending + link = orig + 'link' + posix.link(symlink, link) + self.addCleanup(os_helper.unlink, link) + if os.link in os.supports_follow_symlinks or default_follow: + self.assertEqual(posix.lstat(link), posix.lstat(orig)) + elif default_no_follow: + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) - # follow_symlinks=False -> duplicate the symlink itself - link_nofollow = orig + 'link_nofollow' - try: - posix.link(symlink, link_nofollow, follow_symlinks=False) - except NotImplementedError: - if os.link in os.supports_follow_symlinks or default_no_follow: - raise - else: - self.addCleanup(os_helper.unlink, link_nofollow) - self.assertEqual(posix.lstat(link_nofollow), posix.lstat(symlink)) + with self.subTest('follow_symlinks=False'): + # follow_symlinks=False -> duplicate the symlink itself + link = orig + 'link_nofollow' + try: + posix.link(symlink, link, follow_symlinks=False) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_no_follow: + raise + else: + self.addCleanup(os_helper.unlink, link) + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) - # follow_symlinks=True -> duplicate the target file - link_following = orig + 'link_following' - try: - posix.link(symlink, link_following, follow_symlinks=True) - except NotImplementedError: - if os.link in os.supports_follow_symlinks or default_follow: - raise - else: - self.addCleanup(os_helper.unlink, link_following) - self.assertEqual(posix.lstat(link_following), posix.lstat(orig)) + with self.subTest('follow_symlinks=True'): + # follow_symlinks=True -> duplicate the target file + link = orig + 'link_following' + try: + posix.link(symlink, link, follow_symlinks=True) + except NotImplementedError: + if os.link in os.supports_follow_symlinks or default_follow: + raise + else: + self.addCleanup(os_helper.unlink, link) + self.assertEqual(posix.lstat(link), posix.lstat(orig)) # tests for the posix *at functions follow From 80128e381b97ebccfe9c36d120afff0c528a9d40 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 12:02:15 +0300 Subject: [PATCH 08/11] Disable linkat() on WASI. --- Modules/posixmodule.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3512e6de5e9ece..909ecd502d9eef 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -573,7 +573,11 @@ extern char *ctermid_r(char *); # define HAVE_FACCESSAT_RUNTIME 1 # define HAVE_FCHMODAT_RUNTIME 1 # define HAVE_FCHOWNAT_RUNTIME 1 +#ifdef __wasi__ +# define HAVE_LINKAT_RUNTIME 0 +# else # define HAVE_LINKAT_RUNTIME 1 +# endif # define HAVE_FDOPENDIR_RUNTIME 1 # define HAVE_MKDIRAT_RUNTIME 1 # define HAVE_RENAMEAT_RUNTIME 1 From a45dfd9e18e44d43ef1bae00c0e2f2293c3dbe3f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 18:18:53 +0300 Subject: [PATCH 09/11] Test how link() actually works on WASI. --- Lib/test/test_posix.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index bb18e49c9e3f82..3015e579243ad8 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1540,6 +1540,9 @@ def test_link_follow_symlinks(self): self.assertEqual(posix.lstat(link), posix.lstat(orig)) elif default_no_follow: self.assertEqual(posix.lstat(link), posix.lstat(symlink)) + if sys.platform.startswith('wasi'): + self.assertEqual(posix.lstat(link), posix.lstat(orig)) + self.assertEqual(posix.lstat(link), posix.lstat(symlink)) with self.subTest('follow_symlinks=False'): # follow_symlinks=False -> duplicate the symlink itself From 557f1ee014ee27f6f7da7288bc1b365cbee2d6a5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 15 Apr 2025 18:47:05 +0300 Subject: [PATCH 10/11] Revert "Test how link() actually works on WASI." This reverts commit a45dfd9e18e44d43ef1bae00c0e2f2293c3dbe3f. --- Lib/test/test_posix.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 3015e579243ad8..bb18e49c9e3f82 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1540,9 +1540,6 @@ def test_link_follow_symlinks(self): self.assertEqual(posix.lstat(link), posix.lstat(orig)) elif default_no_follow: self.assertEqual(posix.lstat(link), posix.lstat(symlink)) - if sys.platform.startswith('wasi'): - self.assertEqual(posix.lstat(link), posix.lstat(orig)) - self.assertEqual(posix.lstat(link), posix.lstat(symlink)) with self.subTest('follow_symlinks=False'): # follow_symlinks=False -> duplicate the symlink itself From 2c436fbe85f398efb3921ab2a8c6d9a44f722148 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 21 Apr 2025 10:35:11 +0300 Subject: [PATCH 11/11] Remove redundant checks for same type. --- Modules/posixmodule.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 909ecd502d9eef..0a0597ebc317a5 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4386,14 +4386,6 @@ os_link_impl(PyObject *module, path_t *src, path_t *dst, int src_dir_fd, #endif } -#ifdef MS_WINDOWS - if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) { - PyErr_SetString(PyExc_NotImplementedError, - "link: src and dst must be the same type"); - return NULL; - } -#endif - if (PySys_Audit("os.link", "OOii", src->object, dst->object, src_dir_fd == DEFAULT_DIR_FD ? -1 : src_dir_fd, dst_dir_fd == DEFAULT_DIR_FD ? -1 : dst_dir_fd) < 0) { @@ -5913,12 +5905,6 @@ internal_rename(path_t *src, path_t *dst, int src_dir_fd, int dst_dir_fd, int is return path_error2(src, dst); #else - if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) { - PyErr_Format(PyExc_ValueError, - "%s: src and dst must be the same type", function_name); - return NULL; - } - Py_BEGIN_ALLOW_THREADS #ifdef HAVE_RENAMEAT if (dir_fd_specified) { @@ -10591,12 +10577,6 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst, #else - if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) { - PyErr_SetString(PyExc_ValueError, - "symlink: src and dst must be the same type"); - return NULL; - } - Py_BEGIN_ALLOW_THREADS #ifdef HAVE_SYMLINKAT if (dir_fd != DEFAULT_DIR_FD) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy