fix(xattr): apply --dirs directory xattrs fd-relative (#286)

This commit is contained in:
2026-09-22 16:05:18 +02:00
parent 8cc3dd993b
commit 3787695ba6
3 changed files with 161 additions and 6 deletions
+25
View File
@@ -6750,6 +6750,31 @@ class TestExtendedAttributes:
assert os.getxattr(received, "user.rootdir") == b"r"
assert os.getxattr(os.path.join(received, "sub"), "user.subdir") == b"s"
@pytest.mark.ci
@pytest.mark.parametrize("mt", [False, True])
def test_dirs_directory_xattr_applied(self, shared_server, mt):
"""#286.3: -d/-X must apply a transferred directory's user.* xattr at the
destination through the --dirs STATUS_MKDIR path (both the
single-threaded and -m/--threads receiver paths)."""
source, dest = self._source_and_dest("dirsxattr")
sub = os.path.join(source, "sub")
os.makedirs(sub)
if not _xattr_supported(sub):
pytest.skip("filesystem does not support user xattrs")
os.setxattr(sub, "user.dirsdir", b"dirs-value")
lst = os.path.join(TEST_DATA_DIR, "dirs_xattr_list.txt")
with open(lst, "wb") as fh:
fh.write(b"sub\n")
flags = ["--files-from", lst, "--dirs", "-R", "-X"] + (["--threads"] if mt else [])
result, _ = run_client(source, dest, flags=flags, port=shared_server.port)
assert result.returncode == 0, \
f"--dirs -X sync failed: {(result.stderr or result.stdout)[:300]}"
received = os.path.join(dest, "sub")
assert os.path.isdir(received), "--dirs directory entry was not created"
assert os.getxattr(received, "user.dirsdir") == b"dirs-value", \
"the --dirs directory's user.* xattr was not applied at the destination"
@pytest.mark.ci
def test_directory_default_acl_preserved(self, shared_server):
"""#286.3: -aA must preserve a directory's default POSIX ACL (the
+50
View File
@@ -2,6 +2,7 @@
#include "xattr.h"
#include "config.h"
#include "file.h"
#include "file_save.h"
#include "identity.h"
#include "protocol.h"
#include "test_utils.h"
@@ -540,6 +541,54 @@ static void test_xattr_list_clone() {
xattr_list_free(clone);
}
/* #286.3: an explicit directory entry (--dirs, STATUS_MKDIR) that carries a
* captured user.* xattr must have it applied fd-relative by the directory
* install path itself -- not only by the receiver's deferred DirTimeList, which
* a direct file_save_to_disk_full() caller does not use. */
static void test_file_save_directory_applies_xattrs() {
const char* root = "test_save_dir_xattr_tmp";
const char* leaf = "subdir";
const char* path = "test_save_dir_xattr_tmp/subdir";
rmdir(path);
rmdir(root);
EXPECT_EQ_INT(mkdir(root, 0700), 0);
/* The working directory may be a filesystem without user xattrs (e.g. some
tmpfs mounts): skip cleanly rather than fail the suite. */
if (setxattr(root, "user.fastsync-dirprobe", "p", 1, 0) != 0) {
rmdir(root);
return;
}
removexattr(root, "user.fastsync-dirprobe");
File* dir = file_create(leaf);
EXPECT_NOT_NULL(dir);
dir->is_dir = true;
FileXattrList* xattrs = xattr_list_new();
EXPECT_NOT_NULL(xattrs);
EXPECT_TRUE(xattr_list_append(xattrs, "user.dirxattr", "dirvalue", 8));
dir->xattrs = xattrs;
Config* config = config_create();
EXPECT_NOT_NULL(config);
config->use_metadata = true;
config->use_xattrs = true;
config->preserve_xattrs = true;
EXPECT_EQ_INT(file_save_to_disk_full(root, dir, config), FILE_SAVE_WRITTEN);
EXPECT_EQ_INT(access(path, F_OK), 0);
char value[32];
ssize_t got = getxattr(path, "user.dirxattr", value, sizeof(value));
EXPECT_EQ_INT((int)got, 8);
EXPECT_TRUE(got == 8 && memcmp(value, "dirvalue", 8) == 0);
file_destroy(dir);
config_delete(config);
removexattr(path, "user.dirxattr");
rmdir(path);
rmdir(root);
}
void test_xattr() {
test_xattr_list_clone();
test_xattr_wire_roundtrip();
@@ -553,4 +602,5 @@ void test_xattr() {
test_fake_super_restore();
test_fake_super_no_real_chown();
test_fake_super_storage_resolution();
test_file_save_directory_applies_xattrs();
}