Skip to content

Commit

Permalink
Ensure that we preserve metadata on root directory (#14581)
Browse files Browse the repository at this point in the history
This commit ensures that our copytree preserves xattrs, acls,
timestamp, etc from source directory on target directory. An
explicit test for this is added as well.

(cherry picked from commit 9062d23)

Co-authored-by: Andrew Walker <awalker@ixsystems.com>
  • Loading branch information
bugclerk and anodos325 authored Sep 26, 2024
1 parent 0ab9482 commit c445228
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 6 deletions.
21 changes: 15 additions & 6 deletions src/middlewared/middlewared/pytest/unit/utils/test_copytree.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import pytest
import random
import stat
import subprocess

from middlewared.utils.filesystem import copy
from operator import eq, ne
Expand Down Expand Up @@ -76,11 +75,20 @@ def create_test_data(target: str, symlink_target_path) -> None:
Basic tree of files and directories including some symlinks
"""
os.mkdir(os.path.join(target, 'SOURCE'))
create_test_files(os.path.join(target, 'SOURCE'), symlink_target_path)
source = os.path.join(target, 'SOURCE')
os.mkdir(source)

for xat_name, xat_data in TEST_DIR_XATTRS:
os.setxattr(source, xat_name, xat_data)

os.chown(source, JENNY + 10, JENNY + 11)
os.utime(source, ns=(JENNY + 5, JENNY + 6))
os.chmod(source, 0o777)

create_test_files(source, symlink_target_path)

for dirname in TEST_DIRS:
path = os.path.join(target, 'SOURCE', dirname)
path = os.path.join(source, dirname)
os.mkdir(path)
os.chmod(path, 0o777)
os.chown(path, JENNY, JENNY)
Expand Down Expand Up @@ -249,7 +257,6 @@ def validate_the_things(
def validate_copy_tree(
src: str,
dst: str,

flags: copy.CopyFlags
):
with os.scandir(src) as it:
Expand All @@ -264,6 +271,8 @@ def validate_copy_tree(
if f.is_dir() and not f.is_symlink():
validate_copy_tree(new_src, new_dst, flags)

validate_the_things(src, dst, flags)


def test__copytree_default(directory_for_test, fd_count):
""" test basic behavior of copytree """
Expand Down Expand Up @@ -293,7 +302,7 @@ def test__copytree_exclude_ctldir(directory_for_test, fd_count, is_ctldir):

snapdir = os.path.join(src, '.zfs', 'snapshot', 'now')
os.makedirs(snapdir)
with open(os.path.join(snapdir, 'canary'), 'w') as f:
with open(os.path.join(snapdir, 'canary'), 'w'):
pass

if is_ctldir:
Expand Down
24 changes: 24 additions & 0 deletions src/middlewared/middlewared/utils/filesystem/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
fchown,
fstat,
getxattr,
listxattr,
lseek,
makedev,
mkdir,
Expand Down Expand Up @@ -604,6 +605,29 @@ def copytree(
try:
with DirectoryIterator(src, request_mask=int(dir_request_mask), as_dict=False) as d_iter:
_copytree_impl(d_iter, dst, dst_fd, CLONETREE_ROOT_DEPTH, config, fstat(dst_fd), stats)

# Ensure that root level directory also gets metadata copied
try:
xattrs = listxattr(d_iter.dir_fd)
if config.flags.value & CopyFlags.PERMISSIONS.value:
copy_permissions(d_iter.dir_fd, dst_fd, xattrs, d_iter.stat.stx_mode)

if config.flags.value & CopyFlags.XATTRS.value:
copy_xattrs(d_iter.dir_fd, dst_fd, xattrs)

if config.flags.value & CopyFlags.OWNER.value:
fchown(dst_fd, d_iter.stat.stx_uid, d_iter.stat.stx_gid)

if config.flags.value & CopyFlags.TIMESTAMPS.value:
ns_ts = (
timespec_convert_int(d_iter.stat.stx_atime),
timespec_convert_int(d_iter.stat.stx_mtime)
)
utime(dst_fd, ns=ns_ts)
except Exception:
if config.raise_error:
raise

finally:
close(dst_fd)

Expand Down

0 comments on commit c445228

Please sign in to comment.