From 21b0ab709302bbb654ed9e182fdf735ed71c9b25 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 23 Mar 2021 17:04:37 +0000 Subject: [PATCH 1/3] Add failing test for os.link() with symlink --- Lib/test/test_posix.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 53a4c5f84d7be8..0efd85ff57cd10 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1190,6 +1190,24 @@ def test_link_dir_fd(self): posix.close(f) os_helper.unlink(os_helper.TESTFN + 'link') + @unittest.skipUnless(os.link in os.supports_follow_symlinks, "test needs follow_symlinks support in os.link()") + def test_link_follow_symlinks(self): + symlink_fn = os_helper.TESTFN + 'symlink' + link_following = os_helper.TESTFN + 'link_following' + link_nofollow = os_helper.TESTFN + 'link_nofollow' + posix.symlink(os_helper.TESTFN, symlink_fn) + self.teardown_files.append(symlink_fn) + + # follow_symlinks=False -> duplicate the symlink itself + posix.link(symlink_fn, link_nofollow, follow_symlinks=False) + self.teardown_files.append(link_nofollow) + self.assertEqual(posix.lstat(link_nofollow), posix.lstat(symlink_fn)) + + # follow_symlinks=True -> duplicate the target file + posix.link(symlink_fn, link_following, follow_symlinks=True) + self.teardown_files.append(link_following) + self.assertEqual(posix.lstat(link_following), posix.lstat(os_helper.TESTFN)) + @unittest.skipUnless(os.mkdir in os.supports_dir_fd, "test needs dir_fd support in os.mkdir()") def test_mkdir_dir_fd(self): f = posix.open(posix.getcwd(), posix.O_RDONLY) From 772b3e936a390a4f662c0e3e46a90a434c024c98 Mon Sep 17 00:00:00 2001 From: Joachim Henke <37883863+jo-he@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:37:55 +0000 Subject: [PATCH 2/3] bpo-37612: always call linkat() from os.link(), if available The issue with link() is that POSIX does not define its behavior regarding symbolic links: "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." And it is indeed implemented differently on Linux and NetBSD. --- Modules/posixmodule.c | 50 ++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b30ae80290535a..c24836caa972e8 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3930,31 +3930,41 @@ 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); - - } + 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; - } + else { + if (src_dir_fd == DEFAULT_DIR_FD && dst_dir_fd == DEFAULT_DIR_FD && follow_symlinks) { + /* See issue 41355: This matches the behaviour of !HAVE_LINKAT */ + result = link(src->narrow, dst->narrow); + } else { + linkat_unavailable = 1; } + } #endif + +#else /* linkat not available */ +/* See issue 41355: link() on Linux works like linkat without AT_SYMLINK_FOLLOW, + but on Mac it works like linkat *with* AT_SYMLINK_FOLLOW. */ +#ifdef __APPLE__ + if (!follow_symlinks) { + PyErr_SetString(PyExc_NotImplementedError, + "link: follow_symlinks=False unavailable on this platform"); + return NULL; + } else { +#else + if (follow_symlinks) { + PyErr_SetString(PyExc_NotImplementedError, + "link: follow_symlinks=True unavailable on this platform"); + return NULL; + } else { +#endif /* __APPLE__ */ + result = link(src->narrow, dst->narrow); } - else #endif /* HAVE_LINKAT */ - result = link(src->narrow, dst->narrow); Py_END_ALLOW_THREADS #ifdef HAVE_LINKAT From 0200894f16d66529a3cf78baac923a9ae612ff73 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2019 09:57:19 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2019-07-19-09-57-18.bpo-37612.yjjxx-.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-07-19-09-57-18.bpo-37612.yjjxx-.rst diff --git a/Misc/NEWS.d/next/Library/2019-07-19-09-57-18.bpo-37612.yjjxx-.rst b/Misc/NEWS.d/next/Library/2019-07-19-09-57-18.bpo-37612.yjjxx-.rst new file mode 100644 index 00000000000000..85d2775110ac9e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-07-19-09-57-18.bpo-37612.yjjxx-.rst @@ -0,0 +1 @@ +fix os.link() on platforms (like Linux) where the system link() function does not follow symlinks \ No newline at end of file