From d39bfb1b931dcf0f85aeea2e9dc506f2c79e06de Mon Sep 17 00:00:00 2001 From: Ajey Awasthi <115537579+dropps07@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:28:58 +0530 Subject: [PATCH 1/5] gh-161241: Fall back when FICLONE fails with EPERM in pathlib copyfileobj --- Lib/pathlib/_os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 79a1969d5f83d6d..b91a1228a122f63 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -127,7 +127,7 @@ def copyfileobj(source_f, target_f): _ficlone(source_fd, target_fd) return except OSError as err: - if err.errno not in (EBADF, EOPNOTSUPP, ETXTBSY, EXDEV): + if err.errno not in (EBADF, EOPNOTSUPP, EPERM, ETXTBSY, EXDEV): raise err # Use OS copy where available. From 07eb44bac40a1072632f0bbd199216e6359753e2 Mon Sep 17 00:00:00 2001 From: Ajey Awasthi <115537579+dropps07@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:39:27 +0530 Subject: [PATCH 2/5] gh-161241: Fall back when FICLONE fails with EPERM in pathlib copyfileobj --- Lib/test/test_pathlib/test_copy.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pathlib/test_copy.py b/Lib/test/test_pathlib/test_copy.py index 5f4cf82a0314c46..c4ebc9180b8306d 100644 --- a/Lib/test/test_pathlib/test_copy.py +++ b/Lib/test/test_pathlib/test_copy.py @@ -170,5 +170,30 @@ class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase): target_ground = LocalPathGround(Path) -if __name__ == "__main__": +class CopyFileobjFallbackTest(unittest.TestCase): + def test_ficlone_eperm_falls_back(self): + # FICLONE can fail with EPERM on filesystems that block reflinks + # (e.g. overlayfs, or an ioctl blocked by SELinux). copyfileobj + # must fall back to another copy method instead of failing. + import errno + import tempfile + + import unittest.mock + from pathlib import _os as pathlib_os + def failing_ficlone(source_fd, target_fd): + raise OSError(errno.EPERM, 'Operation not permitted') + with tempfile.TemporaryDirectory() as tmpdir: + source = f'{tmpdir}/source.txt' + target = f'{tmpdir}/target.txt' + with open(source, 'wb') as f: + f.write(b'hello world') + with open(source, 'rb') as source_f, open(target, 'wb') as target_f: + with unittest.mock.patch.object( + pathlib_os, '_ficlone', failing_ficlone): + pathlib_os.copyfileobj(source_f, target_f) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'hello world') + + + if __name__ == "__main__": unittest.main() From 67b2212aecdeb5e6aadc166c6bb99b2ee0bd98bf Mon Sep 17 00:00:00 2001 From: Ajey Awasthi <115537579+dropps07@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:40:12 +0530 Subject: [PATCH 3/5] gh-161241: Fall back when FICLONE fails with EPERM in pathlib copyfileobj --- .../next/Library/2026-11-03-14-30-00.gh-issue-161241.abcdef.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-11-03-14-30-00.gh-issue-161241.abcdef.rst diff --git a/Misc/NEWS.d/next/Library/2026-11-03-14-30-00.gh-issue-161241.abcdef.rst b/Misc/NEWS.d/next/Library/2026-11-03-14-30-00.gh-issue-161241.abcdef.rst new file mode 100644 index 000000000000000..aff9c6255d8eabb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-03-14-30-00.gh-issue-161241.abcdef.rst @@ -0,0 +1 @@ +Fix pathlib.Path.copy failing with PermissionError when the FICLONE reflink ioctl returns EPERM; it now falls back to other copy methods. From 0ffc36bb354ca8001660b5fa0f29b34b9afa681a Mon Sep 17 00:00:00 2001 From: Ajey Awasthi <115537579+dropps07@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:42:11 +0530 Subject: [PATCH 4/5] gh-161241: Fall back when FICLONE fails with EPERM in pathlib copyfileobj --- Lib/test/test_pathlib/test_copy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_pathlib/test_copy.py b/Lib/test/test_pathlib/test_copy.py index c4ebc9180b8306d..772d91dd93d76dc 100644 --- a/Lib/test/test_pathlib/test_copy.py +++ b/Lib/test/test_pathlib/test_copy.py @@ -177,7 +177,6 @@ def test_ficlone_eperm_falls_back(self): # must fall back to another copy method instead of failing. import errno import tempfile - import unittest.mock from pathlib import _os as pathlib_os def failing_ficlone(source_fd, target_fd): From cf851d9ed965841a93a780b5aae8ff3f0ef362fc Mon Sep 17 00:00:00 2001 From: Ajey Awasthi <115537579+dropps07@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:17:16 +0530 Subject: [PATCH 5/5] gh-161241: Fix test file indentation mangled by web editor --- Lib/test/test_pathlib/test_copy.py | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_pathlib/test_copy.py b/Lib/test/test_pathlib/test_copy.py index 772d91dd93d76dc..e798d84ecdf58ec 100644 --- a/Lib/test/test_pathlib/test_copy.py +++ b/Lib/test/test_pathlib/test_copy.py @@ -171,28 +171,30 @@ class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase): class CopyFileobjFallbackTest(unittest.TestCase): - def test_ficlone_eperm_falls_back(self): - # FICLONE can fail with EPERM on filesystems that block reflinks - # (e.g. overlayfs, or an ioctl blocked by SELinux). copyfileobj - # must fall back to another copy method instead of failing. - import errno - import tempfile + def test_ficlone_eperm_falls_back(self): + # FICLONE can fail with EPERM on filesystems that block reflinks + # (e.g. overlayfs, or an ioctl blocked by SELinux). copyfileobj + # must fall back to another copy method instead of failing. + import errno + import tempfile import unittest.mock - from pathlib import _os as pathlib_os + from pathlib import _os as pathlib_os + def failing_ficlone(source_fd, target_fd): - raise OSError(errno.EPERM, 'Operation not permitted') - with tempfile.TemporaryDirectory() as tmpdir: - source = f'{tmpdir}/source.txt' - target = f'{tmpdir}/target.txt' - with open(source, 'wb') as f: - f.write(b'hello world') - with open(source, 'rb') as source_f, open(target, 'wb') as target_f: - with unittest.mock.patch.object( - pathlib_os, '_ficlone', failing_ficlone): - pathlib_os.copyfileobj(source_f, target_f) - with open(target, 'rb') as f: - self.assertEqual(f.read(), b'hello world') - - - if __name__ == "__main__": + raise OSError(errno.EPERM, 'Operation not permitted') + + with tempfile.TemporaryDirectory() as tmpdir: + source = f'{tmpdir}/source.txt' + target = f'{tmpdir}/target.txt' + with open(source, 'wb') as f: + f.write(b'hello world') + with open(source, 'rb') as source_f, open(target, 'wb') as target_f: + with unittest.mock.patch.object( + pathlib_os, '_ficlone', failing_ficlone): + pathlib_os.copyfileobj(source_f, target_f) + with open(target, 'rb') as f: + self.assertEqual(f.read(), b'hello world') + + +if __name__ == "__main__": unittest.main()