From: Daan De Meyer Date: Sun, 16 Feb 2025 12:06:48 +0000 (+0100) Subject: tree: Implement file attributes logic with ioctls instead of tools X-Git-Tag: v26~378^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc4b94c307e767b921e2496238703e0525f19eaf;p=thirdparty%2Fmkosi.git tree: Implement file attributes logic with ioctls instead of tools --- diff --git a/mkosi/qemu.py b/mkosi/qemu.py index d342703e7..d01c5b737 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -45,7 +45,7 @@ from mkosi.config import ( from mkosi.log import ARG_DEBUG, die from mkosi.partition import finalize_root, find_partitions from mkosi.run import AsyncioThread, find_binary, fork_and_wait, run, spawn, workdir -from mkosi.tree import copy_tree, make_nocow, rmtree +from mkosi.tree import copy_tree, maybe_make_nocow, rmtree from mkosi.user import INVOKING_USER, become_root_in_subuid_range, become_root_in_subuid_range_cmd from mkosi.util import ( PathString, @@ -521,7 +521,7 @@ def start_journal_remote(config: Config, sockfd: int) -> Iterator[None]: # at the same time. d.mkdir(exist_ok=True, parents=True) # Make sure COW is disabled so systemd-journal-remote doesn't complain on btrfs filesystems. - make_nocow(d, sandbox=config.sandbox) + maybe_make_nocow(d) INVOKING_USER.chown(d) with tempfile.NamedTemporaryFile(mode="w", prefix="mkosi-journal-remote-config-") as f: @@ -799,7 +799,7 @@ def finalize_drive(config: Config, drive: Drive) -> Iterator[Path]: dir=drive.directory or "/var/tmp", prefix=f"mkosi-drive-{drive.id}", ) as file: - make_nocow(Path(file.name), sandbox=config.sandbox) + maybe_make_nocow(Path(file.name)) file.truncate(round_up(drive.size, resource.getpagesize())) yield Path(file.name) diff --git a/mkosi/sandbox.py b/mkosi/sandbox.py index d4d08fd34..d949d574d 100755 --- a/mkosi/sandbox.py +++ b/mkosi/sandbox.py @@ -34,6 +34,8 @@ ENOENT = 2 ENOSYS = 38 F_DUPFD = 0 F_GETFD = 1 +FS_IOC_GETFLAGS = 0x80086601 +FS_NOCOW_FL = 0x00800000 LINUX_CAPABILITY_U32S_3 = 2 LINUX_CAPABILITY_VERSION_3 = 0x20080522 MNT_DETACH = 2 @@ -244,6 +246,39 @@ def seccomp_suppress_chown() -> None: libseccomp.seccomp_release(seccomp) +def lsattr(path: str) -> int: + attr = ctypes.c_int() + r = 0 + + fd = os.open(path, os.O_CLOEXEC | os.O_RDONLY) + + libc.ioctl.argtypes = (ctypes.c_int, ctypes.c_long, ctypes.c_void_p) + if libc.ioctl(fd, FS_IOC_GETFLAGS, ctypes.byref(attr)) < 0: + r = ctypes.get_errno() + + os.close(fd) + + if r != 0: + raise OSError(r, os.strerror(r), path) + + return attr.value + + +def chattr(path: str, attr: int) -> None: + cattr = ctypes.c_int(attr) + fd = os.open(path, os.O_CLOEXEC | os.O_RDONLY) + r = 0 + + libc.ioctl.argtypes = (ctypes.c_int, ctypes.c_long, ctypes.c_void_p) + if libc.ioctl(fd, FS_IOC_GETFLAGS, ctypes.byref(cattr)) < 0: + r = ctypes.get_errno() + + os.close(fd) + + if r != 0: + raise OSError(r, os.strerror(r), path) + + def join_new_session_keyring() -> None: libkeyutils = ctypes.CDLL("libkeyutils.so.1") if libkeyutils is None: diff --git a/mkosi/tree.py b/mkosi/tree.py index 630abfef9..d3f272044 100644 --- a/mkosi/tree.py +++ b/mkosi/tree.py @@ -13,7 +13,7 @@ from pathlib import Path from mkosi.config import ConfigFeature from mkosi.log import ARG_DEBUG, die from mkosi.run import SandboxProtocol, nosandbox, run, workdir -from mkosi.sandbox import BTRFS_SUPER_MAGIC, OVERLAYFS_SUPER_MAGIC, statfs +from mkosi.sandbox import BTRFS_SUPER_MAGIC, FS_NOCOW_FL, OVERLAYFS_SUPER_MAGIC, chattr, lsattr, statfs from mkosi.util import PathString, flatten from mkosi.versioncomp import GenericVersion @@ -79,13 +79,12 @@ def preserve_target_directories_stat(src: Path, dst: Path) -> Iterator[None]: shutil.copystat(tmp / d, dst / d) -def make_nocow(path: Path, *, sandbox: SandboxProtocol = nosandbox) -> None: - run( - ["chattr", "+C", workdir(path)], - check=False, - stderr=subprocess.DEVNULL if not ARG_DEBUG.get() else None, - sandbox=sandbox(options=["--bind", path, workdir(path)]), - ) +def maybe_make_nocow(path: Path) -> None: + try: + chattr(os.fspath(path), lsattr(os.fspath(path))) + except OSError as e: + if e.errno not in (errno.ENOTTY, errno.EOPNOTSUPP, errno.EINVAL): + raise def copy_tree( @@ -117,16 +116,15 @@ def copy_tree( def copy() -> None: if src.is_file(): - attr = run( - ["lsattr", "-l", workdir(src)], - sandbox=sandbox(options=["--ro-bind", src, workdir(src)]), - stdout=subprocess.PIPE, - ).stdout + try: + attr = lsattr(os.fspath(src)) + except OSError: + attr = 0 - if "No_COW" in attr: + if attr & FS_NOCOW_FL: fdst = dst / src.name if dst.is_dir() else dst fdst.touch() - make_nocow(fdst, sandbox=sandbox) + maybe_make_nocow(fdst) cmdline: list[PathString] = [ "cp",