Skip to content

Commit

Permalink
support for resolved symlinks in copytree
Browse files Browse the repository at this point in the history
  • Loading branch information
brno32 committed Jul 19, 2023
1 parent b81d5f1 commit ae8e4f4
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 6 deletions.
29 changes: 24 additions & 5 deletions src/smbclient/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,16 @@ def copytree(
:param kwargs: Common arguments used to build the SMB Session for any UNC paths.
:return: The dst path.
"""

class LocalDirEntry:
"""Mimics the structure of os.DirEntry which is not exposed https://github.com/python/cpython/issues/71225"""

def __init__(self, path) -> None:
self.path = path

def is_dir(self):
return os.path.isdir(self.path)

if is_remote_path(src):
dir_entries = list(scandir(src, **kwargs))
else:
Expand All @@ -315,20 +325,29 @@ def copytree(
src_path = _join_local_or_remote_path(src, dir_entry.name)
dst_path = _join_local_or_remote_path(dst, dir_entry.name)

is_smb_dir_entry = isinstance(dir_entry, SMBDirEntry)

try:
if dir_entry.is_symlink():
if not isinstance(dir_entry, SMBDirEntry):
raise AssertionError("copytree doesn't yet support symlinks for local to remote operations")

link_target = readlink(src_path, **kwargs)
if is_smb_dir_entry:
link_target = readlink(src_path, **kwargs)
else:
link_target = os.readlink(src_path)
if symlinks:
if not is_smb_dir_entry:
raise AssertionError(

Check warning on line 338 in src/smbclient/shutil.py

View check run for this annotation

Codecov / codecov/patch

src/smbclient/shutil.py#L338

Added line #L338 was not covered by tests
"copytree doesn't yet support unresolved symlinks for local to remote operations"
)
symlink(link_target, dst_path, **kwargs)
copystat(src_path, dst_path, follow_symlinks=False)
continue
else:
# Manually override the dir_entry with a new one that is the link target and copy that below.
try:
dir_entry = SMBDirEntry.from_path(link_target, **kwargs)
if not is_remote_path(link_target):
dir_entry = LocalDirEntry(link_target)
else:
dir_entry = SMBDirEntry.from_path(link_target, **kwargs)
except OSError as err:
if err.errno == errno.ENOENT and ignore_dangling_symlinks:
continue
Expand Down
10 changes: 9 additions & 1 deletion tests/test_smbclient_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1188,19 +1188,27 @@ def test_copytree_with_local_src(smb_share, tmp_path):
with open(os.path.join(src_dirname, "dir1", "subdir1", "file3.txt"), mode="w") as fd:
fd.write("file3.txt")

path_to_symlink = os.path.join(src_dirname, "dir1", "subdir1", "symlink.txt")
path_to_symlink_target = os.path.join(tmp_path, "file4.txt")
with open(path_to_symlink_target, mode="w") as fd:
fd.write("file4.txt")
os.symlink(path_to_symlink_target, path_to_symlink)

actual = copytree(src_dirname, dst_dirname)
assert actual == dst_dirname

assert sorted(list(listdir(dst_dirname))) == ["dir1", "file1.txt"]
assert sorted(list(listdir("%s\\dir1" % dst_dirname))) == ["file2.txt", "subdir1"]
assert sorted(list(listdir("%s\\dir1\\subdir1" % dst_dirname))) == ["file3.txt"]
assert sorted(list(listdir("%s\\dir1\\subdir1" % dst_dirname))) == ["file3.txt", "file4.txt"]

with open_file("%s\\file1.txt" % dst_dirname) as fd:
assert fd.read() == "file1.txt"
with open_file("%s\\dir1\\file2.txt" % dst_dirname) as fd:
assert fd.read() == "file2.txt"
with open_file("%s\\dir1\\subdir1\\file3.txt" % dst_dirname) as fd:
assert fd.read() == "file3.txt"
with open_file("%s\\dir1\\subdir1\\file4.txt" % dst_dirname) as fd:
assert fd.read() == "file4.txt"


@pytest.mark.skipif(
Expand Down

0 comments on commit ae8e4f4

Please sign in to comment.