from mkosi.qemu import copy_ephemeral, machine_cid, run_qemu
from mkosi.run import become_root, bwrap, chroot_cmd, init_mount_namespace, run
from mkosi.state import MkosiState
-from mkosi.tree import copy_tree, move_tree, rmtree
+from mkosi.tree import archive_tree, copy_tree, extract_tree, move_tree, rmtree
from mkosi.types import PathString
from mkosi.util import (
InvokingUser,
if path.is_dir():
bases += [path]
elif path.suffix == ".tar":
- shutil.unpack_archive(path, d)
+ extract_tree(path, d)
bases += [d]
elif path.suffix == ".raw":
run(["systemd-dissect", "-M", path, d])
if path.is_dir():
copy_tree(state.config, path, state.root)
elif path.suffix == ".tar":
- shutil.unpack_archive(path, state.root)
+ extract_tree(path, state.root)
elif path.suffix == ".raw":
run(["systemd-dissect", "--copy-from", path, "/", state.root])
else:
if source.is_dir() or target:
copy_tree(state.config, source, t, preserve_owner=False)
else:
- shutil.unpack_archive(source, t)
+ extract_tree(source, t)
def install_package_manager_trees(state: MkosiState) -> None:
if source.is_dir() or target:
copy_tree(state.config, source, t, preserve_owner=False)
else:
- shutil.unpack_archive(source, t)
+ extract_tree(source, t)
def install_extra_trees(state: MkosiState) -> None:
if source.is_dir() or target:
copy_tree(state.config, source, t, preserve_owner=False)
else:
- shutil.unpack_archive(source, t)
+ extract_tree(source, t)
def install_build_dest(state: MkosiState) -> None:
return "pigz" if shutil.which("pigz") else "gzip"
-def tar_binary() -> str:
- # Some distros (Mandriva) install BSD tar as "tar", hence prefer
- # "gtar" if it exists, which should be GNU tar wherever it exists.
- # We are interested in exposing same behaviour everywhere hence
- # it's preferable to use the same implementation of tar
- # everywhere. In particular given the limited/different SELinux
- # support in BSD tar and the different command line syntax
- # compared to GNU tar.
- return "gtar" if shutil.which("gtar") else "tar"
-
-
def make_tar(state: MkosiState) -> None:
if state.config.output_format != OutputFormat.tar:
return
- cmd: list[PathString] = [
- tar_binary(),
- "-C", state.root,
- "-c", "--xattrs",
- "--xattrs-include=*",
- "--file", state.staging / state.config.output_with_format,
- ".",
- ]
-
with complete_step("Creating archiveā¦"):
- run(cmd)
+ archive_tree(state.root, state.staging / state.config.output_with_format)
def find_files(dir: Path, root: Path) -> Iterator[Path]:
from mkosi.log import die
from mkosi.run import run
from mkosi.state import MkosiState
+from mkosi.tree import extract_tree
class DebianInstaller(DistributionInstaller):
for deb in essential:
with tempfile.NamedTemporaryFile() as f:
run(["dpkg-deb", "--fsys-tarfile", deb], stdout=f)
- run(["tar", "-C", state.root, "--keep-directory-symlink", "--extract", "--file", f.name])
+ extract_tree(Path(f.name), state.root)
# Finally, run apt to properly install packages in the chroot without having to worry that maintainer
# scripts won't find basic tools that they depend on.
from mkosi.log import ARG_DEBUG, complete_step, die
from mkosi.run import apivfs_cmd, bwrap, chroot_cmd, run
from mkosi.state import MkosiState
-from mkosi.tree import copy_tree, rmtree
+from mkosi.tree import copy_tree, extract_tree, rmtree
from mkosi.types import PathString
from mkosi.util import flatten, sort_packages
if not any(stage3.iterdir()):
with complete_step(f"Extracting {stage3_tar.name} to {stage3}"):
- run(["tar",
- "--numeric-owner",
- "-C", stage3,
- "--extract",
- "--file", stage3_tar,
- "--exclude", "./dev/*",
- "--exclude", "./proc/*",
- "--exclude", "./sys/*"])
+ extract_tree(stage3_tar, stage3)
for d in ("binpkgs", "distfiles", "repos/gentoo"):
(state.cache_dir / d).mkdir(parents=True, exist_ok=True)
return result
+def finalize_passwd_mounts(root: Path) -> list[PathString]:
+ """
+ If passwd or a related file exists in the apivfs directory, bind mount it over the host files while we
+ run the command, to make sure that the command we run uses user/group information from the apivfs
+ directory instead of from the host. If the file doesn't exist yet, mount over /dev/null instead.
+ """
+ options: list[PathString] = []
+
+ for f in ("passwd", "group", "shadow", "gshadow"):
+ p = root / "etc" / f
+ if p.exists():
+ options += ["--bind", p, f"/etc/{f}"]
+ else:
+ options += ["--bind", "/dev/null", f"/etc/{f}"]
+
+ return options
+
+
def apivfs_cmd(root: Path) -> list[PathString]:
cmdline: list[PathString] = [
"bwrap",
# Make sure /etc/machine-id is not overwritten by any package manager post install scripts.
cmdline += ["--ro-bind", root / "etc/machine-id", root / "etc/machine-id"]
- # If passwd or a related file exists in the apivfs directory, bind mount it over the host files while
- # we run the command, to make sure that the command we run uses user/group information from the
- # apivfs directory instead of from the host. If the file doesn't exist yet, mount over /dev/null
- # instead.
- for f in ("passwd", "group", "shadow", "gshadow"):
- p = root / "etc" / f
- if p.exists():
- cmdline += ["--bind", p, f"/etc/{f}"]
- else:
- cmdline += ["--bind", "/dev/null", f"/etc/{f}"]
+ cmdline += finalize_passwd_mounts(root)
chmod = f"chmod 1777 {root / 'tmp'} {root / 'var/tmp'} {root / 'dev/shm'}"
# Make sure anything running in the root directory thinks it's in a container. $container can't always be
from mkosi.config import ConfigFeature, MkosiConfig
from mkosi.log import die
-from mkosi.run import run
+from mkosi.run import bwrap, finalize_passwd_mounts, run
from mkosi.types import PathString
+from mkosi.util import tar_binary
def statfs(path: Path) -> str:
copy_tree(config, src, dst)
rmtree(src)
+
+
+def tar_exclude_apivfs_tmp() -> list[str]:
+ return [
+ "--exclude", "./dev/*",
+ "--exclude", "./proc/*",
+ "--exclude", "./sys/*",
+ "--exclude", "./tmp/*",
+ "--exclude", "./run/*",
+ "--exclude", "./var/tmp/*",
+ ]
+
+
+def archive_tree(src: Path, dst: Path) -> None:
+ bwrap(
+ [
+ tar_binary(),
+ "--create",
+ "--file", dst,
+ "--directory", src,
+ "--acls",
+ "--selinux",
+ "--xattrs",
+ "--sparse",
+ "--force-local",
+ *tar_exclude_apivfs_tmp(),
+ ".",
+ ],
+ # Make sure tar uses user/group information from the root directory instead of the host.
+ options=finalize_passwd_mounts(src) if (src / "etc/passwd").exists() else [],
+ )
+
+
+def extract_tree(src: Path, dst: Path) -> None:
+ bwrap(
+ [
+ tar_binary(),
+ "--extract",
+ "--file", src,
+ "--directory", dst,
+ "--keep-directory-symlink",
+ "--no-overwrite-dir",
+ "--same-permissions",
+ "--same-owner" if (dst / "etc/passwd").exists() else "--numeric-owner",
+ "--same-order",
+ "--acls",
+ "--selinux",
+ "--xattrs",
+ "--force-local",
+ *tar_exclude_apivfs_tmp(),
+ ],
+ # Make sure tar uses user/group information from the root directory instead of the host.
+ options=finalize_passwd_mounts(dst) if (dst / "etc/passwd").exists() else [],
+ )
import pwd
import re
import resource
+import shutil
import stat
import sys
import tempfile
@classmethod
def values(cls) -> list[str]:
return list(map(str, cls))
+
+
+def tar_binary() -> str:
+ # Some distros (Mandriva) install BSD tar as "tar", hence prefer
+ # "gtar" if it exists, which should be GNU tar wherever it exists.
+ # We are interested in exposing same behaviour everywhere hence
+ # it's preferable to use the same implementation of tar
+ # everywhere. In particular given the limited/different SELinux
+ # support in BSD tar and the different command line syntax
+ # compared to GNU tar.
+ return "gtar" if shutil.which("gtar") else "tar"