diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py index 79a1969d5f83d6d..b65d5110e61c5ac 100644 --- a/Lib/pathlib/_os.py +++ b/Lib/pathlib/_os.py @@ -127,7 +127,8 @@ 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 (EACCES, EBADF, ENOTTY, EOPNOTSUPP, + EPERM, ETXTBSY, EXDEV): raise err # Use OS copy where available. diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index aff66c8efedbbc0..a50687c86067809 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -1550,6 +1550,19 @@ def raiser(*args, **kwargs): with mock.patch(patchpoint, make_raiser(errno.ENOENT)): self.assertRaises(FileNotFoundError, source.copy, target) + @unittest.skipUnless(hasattr(fcntl, 'FICLONE'), 'requires fcntl.FICLONE') + def test_copy_ficlone_fallback(self): + base = self.cls(self.base) + source = base / 'fileA' + for err in (errno.EACCES, errno.ENOTTY, errno.EPERM): + with self.subTest(errno=err): + target = base / f'copy{err}' + error = OSError(err, os.strerror(err)) + with mock.patch('fcntl.ioctl', side_effect=error) as ioctl: + source.copy(target) + ioctl.assert_called_once() + self.assertEqual(target.read_bytes(), source.read_bytes()) + @unittest.skipIf(sys.platform == "win32" or sys.platform == "wasi", "directories are always readable on Windows and WASI") @requires_non_root_user def test_copy_dir_no_read_permission(self):