Skip to content

Commit d54db22

Browse files
dura0okcursoragent
andcommitted
gh-154690: Narrow ensure_distinct_paths() resolve() exception handling
Only catch AttributeError (no resolve) and OSError (realpath failed), and test the lexical fallback for both cases. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d228788 commit d54db22

2 files changed

Lines changed: 20 additions & 10 deletions

File tree

Lib/pathlib/_os.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,19 +265,14 @@ def ensure_distinct_paths(source, target):
265265
"""
266266
Raise OSError(EINVAL) if the other path is within this path.
267267
"""
268-
# Note: there is no straightforward, foolproof algorithm to determine
269-
# if one directory is within another (a particularly perverse example
270-
# would be a single network share mounted in one location via NFS, and
271-
# in another location via CIFS). Resolve when possible so relative vs
272-
# absolute paths and symlink aliases are compared like shutil._destinsrc
273-
# (realpath); otherwise fall back to a lexical check.
268+
# Prefer resolve() (like shutil._destinsrc); else compare lexically.
269+
# Cross-mount aliases (e.g. NFS vs CIFS) still cannot be detected reliably.
274270
source_orig, target_orig = source, target
275271
try:
276272
source = source.resolve(strict=False)
277273
target = target.resolve(strict=False)
278-
except (AttributeError, TypeError):
279-
pass
280-
except (OSError, ValueError, RuntimeError, NotImplementedError):
274+
except (AttributeError, OSError):
275+
# No resolve(), or realpath failed.
281276
pass
282277
if source == target:
283278
err = OSError(EINVAL, "Source and target are the same path")

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1454,7 +1454,6 @@ def test_copy_directory_symlink_into_itself(self):
14541454
self.assertFalse(target.exists())
14551455

14561456
def test_copy_dir_into_itself_relative_source_absolute_target(self):
1457-
# ensure_distinct_paths() must not be fooled by relative vs absolute.
14581457
with os_helper.change_cwd(self.base):
14591458
source = self.cls('dirC')
14601459
target = source.resolve() / 'copyC'
@@ -1481,6 +1480,22 @@ def test_copy_dir_into_itself_via_symlink(self):
14811480
self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
14821481
self.assertFalse((source / 'copyC').exists())
14831482

1483+
def test_copy_dir_into_itself_when_no_resolve(self):
1484+
base = self.cls(self.base)
1485+
source = base / 'dirC'
1486+
target = source / 'copyC'
1487+
with mock.patch.object(self.cls, 'resolve', side_effect=AttributeError):
1488+
self.assertRaises(OSError, source.copy, target)
1489+
self.assertFalse(target.exists())
1490+
1491+
def test_copy_dir_into_itself_when_resolve_raises_oserror(self):
1492+
base = self.cls(self.base)
1493+
source = base / 'dirC'
1494+
target = source / 'copyC'
1495+
with mock.patch.object(self.cls, 'resolve', side_effect=OSError):
1496+
self.assertRaises(OSError, source.copy, target)
1497+
self.assertFalse(target.exists())
1498+
14841499
@needs_symlinks
14851500
def test_copy_directory_symlink_to_existing_symlink(self):
14861501
base = self.cls(self.base)

0 commit comments

Comments
 (0)