Skip to content

Commit d4af6ce

Browse files
fuse: add ACL support (Linux)
1 parent 2b655fc commit d4af6ce

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

src/borg/fuse.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,13 +630,23 @@ def getattr(self, inode, ctx=None):
630630
@async_wrapper
631631
def listxattr(self, inode, ctx=None):
632632
item = self.get_item(inode)
633-
return item.get("xattrs", {}).keys()
633+
xattrs = list(item.get("xattrs", {}).keys())
634+
if "acl_access" in item:
635+
xattrs.append(b"system.posix_acl_access")
636+
if "acl_default" in item:
637+
xattrs.append(b"system.posix_acl_default")
638+
return xattrs
634639

635640
@async_wrapper
636641
def getxattr(self, inode, name, ctx=None):
637642
item = self.get_item(inode)
638643
try:
639-
return item.get("xattrs", {})[name] or b""
644+
if name == b"system.posix_acl_access":
645+
return item["acl_access"]
646+
elif name == b"system.posix_acl_default":
647+
return item["acl_default"]
648+
else:
649+
return item.get("xattrs", {})[name] or b""
640650
except KeyError:
641651
raise llfuse.FUSEError(llfuse.ENOATTR) from None
642652

src/borg/testsuite/archiver/mount_cmds_test.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .. import has_lchflags, llfuse
1313
from .. import changedir, no_selinux, same_ts_ns
1414
from .. import are_symlinks_supported, are_hardlinks_supported, are_fifos_supported
15-
from ..platform_test import fakeroot_detected
15+
from ..platform_test import fakeroot_detected, skipif_not_linux, skipif_acls_not_working
1616
from . import RK_ENCRYPTION, cmd, assert_dirs_equal, create_regular_file, create_src_archive, open_archive, src_file
1717
from . import requires_hardlinks, _extract_hardlinks_setup, fuse_mount, create_test_files, generate_archiver_tests
1818

@@ -271,6 +271,56 @@ def test_fuse_mount_options(archivers, request):
271271
assert sorted(os.listdir(os.path.join(mountpoint))) == []
272272

273273

274+
@pytest.mark.skipif(not llfuse, reason="llfuse not installed")
275+
@skipif_not_linux
276+
@skipif_acls_not_working
277+
def test_fuse_acl_support(archivers, request):
278+
"""Test that ACLs are correctly exposed through the FUSE interface."""
279+
archiver = request.getfixturevalue(archivers)
280+
cmd(archiver, "repo-create", RK_ENCRYPTION)
281+
282+
# Create a file with access ACL
283+
file_path = os.path.join(archiver.input_path, "aclfile")
284+
with open(file_path, "w") as f:
285+
f.write("test content")
286+
access_acl = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-:0\ngroup:root:r--:0\n"
287+
platform.acl_set(file_path, {"acl_access": access_acl})
288+
289+
# Create a directory with default ACL
290+
dir_path = os.path.join(archiver.input_path, "acldir")
291+
os.mkdir(dir_path)
292+
default_acl = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:r--:0\ngroup:root:r--:0\n"
293+
platform.acl_set(dir_path, {"acl_access": access_acl, "acl_default": default_acl})
294+
295+
# Create an archive
296+
cmd(archiver, "create", "test", "input")
297+
298+
# Mount the archive
299+
mountpoint = os.path.join(archiver.tmpdir, "mountpoint")
300+
with fuse_mount(archiver, mountpoint, "-a", "test"):
301+
# Verify file ACLs are preserved
302+
mounted_file = os.path.join(mountpoint, "test", "aclfile")
303+
assert os.path.exists(mounted_file)
304+
assert os.path.isfile(mounted_file)
305+
file_acl = {}
306+
platform.acl_get(mounted_file, file_acl, os.stat(mounted_file))
307+
assert "acl_access" in file_acl
308+
assert b"user::rw-" in file_acl["acl_access"]
309+
assert b"user:root:rw-" in file_acl["acl_access"]
310+
# Verify directory ACLs are preserved
311+
mounted_dir = os.path.join(mountpoint, "test", "acldir")
312+
assert os.path.exists(mounted_dir)
313+
assert os.path.isdir(mounted_dir)
314+
dir_acl = {}
315+
platform.acl_get(mounted_dir, dir_acl, os.stat(mounted_dir))
316+
assert "acl_access" in dir_acl
317+
assert "acl_default" in dir_acl
318+
assert b"user::rw-" in dir_acl["acl_access"]
319+
assert b"user:root:rw-" in dir_acl["acl_access"]
320+
assert b"user::rw-" in dir_acl["acl_default"]
321+
assert b"user:root:r--" in dir_acl["acl_default"]
322+
323+
274324
@pytest.mark.skipif(not llfuse, reason="llfuse not installed")
275325
def test_migrate_lock_alive(archivers, request):
276326
"""Both old_id and new_id must not be stale during lock migration / daemonization."""

0 commit comments

Comments
 (0)