Skip to content

Commit 54a9f12

Browse files
Stop OSError leaks past rmtree's ignore_errors (#357)
rmtree's top-level islink probe ran outside any try-except, so an OSError there bypassed ignore_errors=True. ignore_errors installs a no-op onerror, not a global suppressor, so any unguarded call reaches the caller.
1 parent 888b350 commit 54a9f12

2 files changed

Lines changed: 30 additions & 5 deletions

File tree

src/smbclient/shutil.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,12 +411,13 @@ def onerror(*args):
411411
def onerror(*args):
412412
raise
413413

414-
if islink(path, **kwargs):
415-
try:
414+
# islink issues SMB requests, so route failures through onerror.
415+
try:
416+
if islink(path, **kwargs):
416417
raise OSError("Cannot call rmtree on a symbolic link")
417-
except OSError:
418-
onerror(islink, path, sys.exc_info())
419-
return
418+
except OSError:
419+
onerror(islink, path, sys.exc_info())
420+
return
420421

421422
scandir_gen = scandir(path, **kwargs)
422423
while True:

tests/test_smbclient_shutil.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,3 +1583,27 @@ def callback(*args):
15831583
assert callback_args[0][0].__name__ == "islink"
15841584
assert callback_args[0][1] == dst_dirname
15851585
assert isinstance(callback_args[0][2][1], OSError)
1586+
1587+
1588+
def test_rmtree_islink_failure_invokes_onerror(mocker):
1589+
# islink issues SMB requests and can fail. Verify rmtree routes that failure
1590+
# through onerror so ignore_errors=True still suppresses it.
1591+
fake_path = r"\\server\share\dst"
1592+
1593+
def _failing_islink(*args, **kwargs):
1594+
raise SMBOSError(NtStatus.STATUS_DELETE_PENDING, fake_path)
1595+
1596+
_failing_islink.__name__ = "islink"
1597+
mocker.patch("smbclient.shutil.islink", new=_failing_islink)
1598+
1599+
# ignore_errors=True path: must not raise, must return cleanly.
1600+
rmtree(fake_path, ignore_errors=True)
1601+
1602+
# onerror path: callback receives (islink, path, exc_info).
1603+
callback_args = []
1604+
rmtree(fake_path, onerror=lambda *args: callback_args.append(args))
1605+
1606+
assert len(callback_args) == 1
1607+
assert callback_args[0][0].__name__ == "islink"
1608+
assert callback_args[0][1] == fake_path
1609+
assert isinstance(callback_args[0][2][1], SMBOSError)

0 commit comments

Comments
 (0)