]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
tree: Implement file attributes logic with ioctls instead of tools
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 16 Feb 2025 12:06:48 +0000 (13:06 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 16 Feb 2025 12:33:41 +0000 (13:33 +0100)
mkosi/qemu.py
mkosi/sandbox.py
mkosi/tree.py

index d342703e73514b02231c91f95302866ff3ac9c4c..d01c5b737bbbd4627fdc3b053f75944ccbae806e 100644 (file)
@@ -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)
 
index d4d08fd34032e288bd78183945f0272dab7c3e31..d949d574d1e6a6fcc53b636f288268528a2b7619 100755 (executable)
@@ -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:
index 630abfef9cd2dd5d27888deae8bf9b9034c8df2e..d3f27204487009519921cea4c38a3b5490e246de 100644 (file)
@@ -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",