log_process_failure,
run,
)
-from mkosi.sandbox import chroot_cmd, finalize_crypto_mounts, finalize_passwd_mounts
+from mkosi.sandbox import Mount, chroot_cmd, finalize_crypto_mounts, finalize_passwd_mounts
from mkosi.tree import copy_tree, move_tree, rmtree
from mkosi.types import PathString
from mkosi.user import CLONE_NEWNS, INVOKING_USER, become_root, unshare
finalize_config_json(context.config) as json,
):
for script in context.config.sync_scripts:
- options = [
+ mounts = [
*sources,
*finalize_crypto_mounts(context.config.tools()),
- "--ro-bind", script, "/work/sync",
- "--ro-bind", json, "/work/config.json",
- "--chdir", "/work/src",
+ Mount(script, "/work/sync", ro=True),
+ Mount(json, "/work/config.json", ro=True),
]
if (p := INVOKING_USER.home()).exists():
- # We use --bind here instead of --ro-bind to keep git worktrees working which encode absolute paths to
- # the parent git repository and might need to modify the git config in the parent git repository when
- # submodules are in use as well.
- options += ["--bind", p, p]
+ # We use a writable mount here to keep git worktrees working which encode absolute paths to the parent
+ # git repository and might need to modify the git config in the parent git repository when submodules
+ # are in use as well.
+ mounts += [Mount(p, p)]
if (p := Path(f"/run/user/{INVOKING_USER.uid}")).exists():
- options += ["--ro-bind", p, p]
+ mounts += [Mount(p, p, ro=True)]
with complete_step(f"Running sync script {script}…"):
run(
["/work/sync", "final"],
env=env | context.config.environment,
stdin=sys.stdin,
- sandbox=context.sandbox(network=True, options=options),
+ sandbox=context.sandbox(
+ network=True,
+ mounts=mounts,
+ options=["--dir", "/work/src", "--chdir", "/work/src"]
+ ),
)
stdin=sys.stdin,
sandbox=context.sandbox(
network=True,
- options=sources + [
- "--ro-bind", script, "/work/prepare",
- "--ro-bind", json, "/work/config.json",
- "--ro-bind", cd, "/work/scripts",
- "--bind", context.root, context.root,
+ mounts=[
+ *sources,
+ Mount(script, "/work/prepare", ro=True),
+ Mount(json, "/work/config.json", ro=True),
+ Mount(cd, "/work/scripts", ro=True),
+ Mount(context.root, context.root),
*context.config.distribution.package_manager(context.config).mounts(context),
- "--chdir", "/work/src",
],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
)
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
- options=sources + [
- "--ro-bind", script, "/work/build-script",
- "--ro-bind", json, "/work/config.json",
- "--ro-bind", cd, "/work/scripts",
- "--bind", context.root, context.root,
- "--bind", context.install_dir, "/work/dest",
- "--bind", context.staging, "/work/out",
+ mounts=[
+ *sources,
+ Mount(script, "/work/build-script", ro=True),
+ Mount(json, "/work/config.json", ro=True),
+ Mount(cd, "/work/scripts", ro=True),
+ Mount(context.root, context.root),
+ Mount(context.install_dir, "/work/dest"),
+ Mount(context.staging, "/work/out"),
*(
- ["--bind", os.fspath(context.config.build_dir), "/work/build"]
+ [Mount(context.config.build_dir, "/work/build")]
if context.config.build_dir
else []
),
*context.config.distribution.package_manager(context.config).mounts(context),
- "--chdir", "/work/src",
],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
)
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
- options=sources + [
- "--ro-bind", script, "/work/postinst",
- "--ro-bind", json, "/work/config.json",
- "--ro-bind", cd, "/work/scripts",
- "--bind", context.root, context.root,
- "--bind", context.staging, "/work/out",
+ mounts=[
+ *sources,
+ Mount(script, "/work/postinst", ro=True),
+ Mount(json, "/work/config.json", ro=True),
+ Mount(cd, "/work/scripts", ro=True),
+ Mount(context.root, context.root),
+ Mount(context.staging, "/work/out"),
*context.config.distribution.package_manager(context.config).mounts(context),
- "--chdir", "/work/src",
],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
)
stdin=sys.stdin,
sandbox=context.sandbox(
network=context.config.with_network,
- options=sources + [
- "--ro-bind", script, "/work/finalize",
- "--ro-bind", json, "/work/config.json",
- "--ro-bind", cd, "/work/scripts",
- "--bind", context.root, context.root,
- "--bind", context.staging, "/work/out",
+ mounts=[
+ *sources,
+ Mount(script, "/work/finalize", ro=True),
+ Mount(json, "/work/config.json", ro=True),
+ Mount(cd, "/work/scripts", ro=True),
+ Mount(context.root, context.root),
+ Mount(context.staging, "/work/out"),
*context.config.distribution.package_manager(context.config).mounts(context),
- "--chdir", "/work/src",
],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
scripts=hd,
) + (chroot if script.suffix == ".chroot" else []),
)
"-in", certificate,
],
stdout=subprocess.PIPE,
- sandbox=context.sandbox(options=["--ro-bind", certificate, certificate]),
+ sandbox=context.sandbox(mounts=[Mount(certificate, certificate, ro=True)]),
).stdout
for line in output.splitlines():
],
stdout=f,
sandbox=context.sandbox(
- options=[
- "--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key,
- "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
+ mounts=[
+ Mount(context.config.secure_boot_key, context.config.secure_boot_key, ro=True),
+ Mount(context.config.secure_boot_certificate, context.config.secure_boot_certificate, ro=True),
],
),
)
"-d", context.workspace / "pesign",
],
sandbox=context.sandbox(
- options=[
- "--ro-bind", context.workspace / "secure-boot.p12", context.workspace / "secure-boot.p12",
- "--bind", context.workspace / "pesign", context.workspace / "pesign",
+ mounts=[
+ Mount(context.workspace / "secure-boot.p12", context.workspace / "secure-boot.p12", ro=True),
+ Mount(context.workspace / "pesign", context.workspace / "pesign"),
],
),
)
"--cert", context.config.secure_boot_certificate,
"--output", "/dev/stdout",
]
- options: list[PathString] = [
- "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
- "--ro-bind", input, input,
+ mounts = [
+ Mount(context.config.secure_boot_certificate, context.config.secure_boot_certificate, ro=True),
+ Mount(input, input, ro=True),
]
if context.config.secure_boot_key_source.type == KeySource.Type.engine:
cmd += ["--engine", context.config.secure_boot_key_source.source]
if context.config.secure_boot_key.exists():
- options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+ mounts += [Mount(context.config.secure_boot_key, context.config.secure_boot_key, ro=True)]
cmd += [input]
run(
cmd,
stdout=f,
sandbox=context.sandbox(
- options=options,
+ mounts=mounts,
devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
)
)
],
stdout=f,
sandbox=context.sandbox(
- options=[
- "--ro-bind", context.workspace / "pesign", context.workspace / "pesign",
- "--ro-bind", input, input,
+ mounts=[
+ Mount(context.workspace / "pesign", context.workspace / "pesign", ro=True),
+ Mount(input, input, ro=True),
]
),
)
run(
["bootctl", "install", "--root", context.root, "--all-architectures", "--no-variables"],
env={"SYSTEMD_ESP_PATH": "/efi", "SYSTEMD_XBOOTLDR_PATH": "/boot"},
- sandbox=context.sandbox(options=["--bind", context.root, context.root]),
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]),
)
if context.config.shim_bootloader != ShimBootloader.none:
],
stdout=f,
sandbox=context.sandbox(
- options=[
- "--ro-bind",
- context.config.secure_boot_certificate,
- context.config.secure_boot_certificate,
+ mounts=[
+ Mount(
+ context.config.secure_boot_certificate,
+ context.config.secure_boot_certificate,
+ ro=True
+ ),
],
),
)
],
stdout=f,
sandbox=context.sandbox(
- options=["--ro-bind", context.workspace / "mkosi.der", context.workspace / "mkosi.der"]
+ mounts=[Mount(context.workspace / "mkosi.der", context.workspace / "mkosi.der", ro=True)]
),
)
"--cert", context.config.secure_boot_certificate,
"--output", "/dev/stdout",
]
- options: list[PathString] = [
- "--ro-bind",
- context.config.secure_boot_certificate,
- context.config.secure_boot_certificate,
- "--ro-bind", context.workspace / "mkosi.esl", context.workspace / "mkosi.esl",
+ mounts = [
+ Mount(
+ context.config.secure_boot_certificate,
+ context.config.secure_boot_certificate,
+ ro=True
+ ),
+ Mount(context.workspace / "mkosi.esl", context.workspace / "mkosi.esl", ro=True),
]
if context.config.secure_boot_key_source.type == KeySource.Type.engine:
cmd += ["--engine", context.config.secure_boot_key_source.source]
if context.config.secure_boot_key.exists():
- options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+ mounts += [Mount(context.config.secure_boot_key, context.config.secure_boot_key, ro=True)]
cmd += [db, context.workspace / "mkosi.esl"]
run(
cmd,
stdout=f,
sandbox=context.sandbox(
- options=options,
+ mounts=mounts,
devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
),
)
*modules,
],
sandbox=context.sandbox(
- options=[
- "--bind", context.root, context.root,
- "--ro-bind", earlyconfig.name, earlyconfig.name,
- *(["--ro-bind", str(sbat), str(sbat)] if sbat else []),
+ mounts=[
+ Mount(context.root, context.root),
+ Mount(earlyconfig.name, earlyconfig.name, ro=True),
+ *([Mount(str(sbat), str(sbat), ro=True)] if sbat else []),
],
),
)
context.staging / context.config.output_with_format,
],
sandbox=context.sandbox(
- options=[
- "--bind", context.root, context.root,
- "--bind", context.staging, context.staging,
- "--bind", mountinfo.name, mountinfo.name,
+ mounts=[
+ Mount(context.root, context.root),
+ Mount(context.staging, context.staging),
+ Mount(mountinfo.name, mountinfo.name),
],
),
)
sandbox=config.sandbox(
devices=True,
network=True,
- options=["--ro-bind", src, src, "--bind", t.parent, t.parent],
+ mounts=[Mount(src, src, ro=True), Mount(t.parent, t.parent)],
),
)
else:
[python_binary(context.config)],
input=pefile,
stdout=f,
- sandbox=context.sandbox(options=["--ro-bind", binary, binary])
+ sandbox=context.sandbox(mounts=[Mount(binary, binary, ro=True)])
)
return output
"--uname", kver,
]
- options: list[PathString] = [
- "--bind", output.parent, output.parent,
- "--ro-bind", context.workspace / "cmdline", context.workspace / "cmdline",
- "--ro-bind", context.root / "usr/lib/os-release", context.root / "usr/lib/os-release",
- "--ro-bind", stub, stub,
+ mounts = [
+ Mount(output.parent, output.parent),
+ Mount(context.workspace / "cmdline", context.workspace / "cmdline", ro=True),
+ Mount(context.root / "usr/lib/os-release", context.root / "usr/lib/os-release", ro=True),
+ Mount(stub, stub, ro=True),
]
if context.config.secure_boot:
"--secureboot-certificate",
context.config.secure_boot_certificate,
]
- options += [
- "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
+ mounts += [
+ Mount(context.config.secure_boot_certificate, context.config.secure_boot_certificate, ro=True),
]
if context.config.secure_boot_key_source.type == KeySource.Type.engine:
cmd += ["--signing-engine", context.config.secure_boot_key_source.source]
if context.config.secure_boot_key.exists():
- options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+ mounts += [Mount(context.config.secure_boot_key, context.config.secure_boot_key, ro=True)]
else:
pesign_prepare(context)
cmd += [
"--secureboot-certificate-name",
certificate_common_name(context, context.config.secure_boot_certificate),
]
- options += ["--ro-bind", context.workspace / "pesign", context.workspace / "pesign"]
+ mounts += [Mount(context.workspace / "pesign", context.workspace / "pesign", ro=True)]
if want_signed_pcrs(context.config):
cmd += [
"--pcr-banks", "sha1,sha256",
]
if context.config.secure_boot_key.exists():
- options += ["--ro-bind", context.config.secure_boot_key, context.config.secure_boot_key]
+ mounts += [Mount(context.config.secure_boot_key, context.config.secure_boot_key)]
if context.config.secure_boot_key_source.type == KeySource.Type.engine:
cmd += [
"--signing-engine", context.config.secure_boot_key_source.source,
"--pcr-public-key", context.config.secure_boot_certificate,
]
- options += [
- "--ro-bind", context.config.secure_boot_certificate, context.config.secure_boot_certificate,
+ mounts += [
+ Mount(context.config.secure_boot_certificate, context.config.secure_boot_certificate, ro=True),
]
cmd += ["build", "--linux", kimg]
- options += ["--ro-bind", kimg, kimg]
+ mounts += [Mount(kimg, kimg, ro=True)]
for initrd in initrds:
cmd += ["--initrd", initrd]
- options += ["--ro-bind", initrd, initrd]
+ mounts += [Mount(initrd, initrd, ro=True)]
with complete_step(f"Generating unified kernel image for kernel version {kver}"):
run(
cmd,
sandbox=context.sandbox(
- options=options,
+ mounts=mounts,
devices=context.config.secure_boot_key_source.type != KeySource.Type.file,
),
)
return context.config.image_id or context.config.distribution.name
output = json.loads(run(["kernel-install", "--root", context.root, "--json=pretty", "inspect"],
- sandbox=context.sandbox(options=["--ro-bind", context.root, context.root]),
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root, ro=True)]),
stdout=subprocess.PIPE,
env={"SYSTEMD_ESP_PATH": "/efi", "SYSTEMD_XBOOTLDR_PATH": "/boot"}).stdout)
logging.debug(json.dumps(output, indent=4))
if sys.stderr.isatty():
env |= dict(GPGTTY=os.ttyname(sys.stderr.fileno()))
- options: list[PathString] = ["--perms", "755", "--dir", home, "--bind", home, home]
+ options: list[PathString] = ["--perms", "755", "--dir", home]
+ mounts = [Mount(home, home)]
# gpg can communicate with smartcard readers via this socket so bind mount it in if it exists.
if (p := Path("/run/pcscd/pcscd.comm")).exists():
- options += ["--perms", "755", "--dir", p.parent, "--bind", p, p]
+ options += ["--perms", "755", "--dir", p.parent]
+ mounts += [Mount(p, p)]
with (
complete_step("Signing SHA256SUMS…"),
open(context.staging / context.config.output_checksum, "rb") as i,
open(context.staging / context.config.output_signature, "wb") as o,
):
- run(cmdline, env=env, stdin=i, stdout=o, sandbox=context.sandbox(options=options))
+ run(cmdline, env=env, stdin=i, stdout=o, sandbox=context.sandbox(mounts=mounts, options=options))
def dir_size(path: Union[Path, os.DirEntry[str]]) -> int:
with complete_step(f"Running depmod for {kver}"):
run(["depmod", "--all", "--basedir", context.root, kver],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
def run_sysusers(context: Context) -> None:
with complete_step("Generating system users"):
run(["systemd-sysusers", "--root", context.root],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
def run_tmpfiles(context: Context) -> None:
]
sandbox = context.sandbox(
- options=[
- "--bind", context.root, context.root,
+ mounts=[
+ Mount(context.root, context.root),
# systemd uses acl.h to parse ACLs in tmpfiles snippets which uses the host's passwd so we have to
# mount the image's passwd over it to make ACL parsing work.
*finalize_passwd_mounts(context.root)
with complete_step("Applying presets…"):
run(["systemctl", "--root", context.root, "preset-all"],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
run(["systemctl", "--root", context.root, "--global", "preset-all"],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
def run_hwdb(context: Context) -> None:
with complete_step("Generating hardware database"):
run(["systemd-hwdb", "--root", context.root, "--usr", "--strict", "update"],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
# Remove any existing hwdb in /etc in favor of the one we just put in /usr.
(context.root / "etc/udev/hwdb.bin").unlink(missing_ok=True)
with complete_step("Applying first boot settings"):
run(["systemd-firstboot", "--root", context.root, "--force", *options],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]))
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]))
# Initrds generally don't ship with only /usr so there's not much point in putting the credentials in
# /usr/lib/credstore.
with complete_step(f"Relabeling files using {policy} policy"):
run(["setfiles", "-mFr", context.root, "-c", binpolicy, fc, context.root],
- sandbox=context.sandbox(options=["--bind", context.root, context.root]),
+ sandbox=context.sandbox(mounts=[Mount(context.root, context.root)]),
check=context.config.selinux_relabel == ConfigFeature.enabled)
"--seed", str(context.config.seed),
context.staging / context.config.output_with_format,
]
- options: list[PathString] = ["--bind", context.staging, context.staging]
+ mounts = [Mount(context.staging, context.staging)]
if root:
cmdline += ["--root", root]
- options += ["--bind", root, root]
+ mounts += [Mount(root, root)]
if not context.config.architecture.is_native():
cmdline += ["--architecture", str(context.config.architecture)]
if not (context.staging / context.config.output_with_format).exists():
cmdline += ["--empty=create"]
if context.config.passphrase:
cmdline += ["--key-file", context.config.passphrase]
- options += ["--ro-bind", context.config.passphrase, context.config.passphrase]
+ mounts += [Mount(context.config.passphrase, context.config.passphrase, ro=True)]
if context.config.verity_key:
cmdline += ["--private-key", context.config.verity_key]
if context.config.verity_key_source.type != KeySource.Type.file:
cmdline += ["--private-key-source", str(context.config.verity_key_source)]
if context.config.verity_key.exists():
- options += ["--ro-bind", context.config.verity_key, context.config.verity_key]
+ mounts += [Mount(context.config.verity_key, context.config.verity_key, ro=True)]
if context.config.verity_certificate:
cmdline += ["--certificate", context.config.verity_certificate]
- options += ["--ro-bind", context.config.verity_certificate, context.config.verity_certificate]
+ mounts += [Mount(context.config.verity_certificate, context.config.verity_certificate, ro=True)]
if skip:
cmdline += ["--defer-partitions", ",".join(skip)]
if split:
for d in definitions:
cmdline += ["--definitions", d]
- options += ["--ro-bind", d, d]
+ mounts += [Mount(d, d, ro=True)]
with complete_step(msg):
output = json.loads(
not context.config.repart_offline or
context.config.verity_key_source.type != KeySource.Type.file
),
- options=options,
+ mounts=mounts,
),
).stdout
)
"--size=auto",
output,
]
- options: list[PathString] = [
- "--bind", output.parent, output.parent,
- "--ro-bind", context.root, context.root,
+ mounts = [
+ Mount(output.parent, output.parent),
+ Mount(context.root, context.root, ro=True),
]
if not context.config.architecture.is_native():
cmdline += ["--architecture", str(context.config.architecture)]
if context.config.passphrase:
cmdline += ["--key-file", context.config.passphrase]
- options += ["--ro-bind", context.config.passphrase, context.config.passphrase]
+ mounts += [Mount(context.config.passphrase, context.config.passphrase, ro=True)]
if context.config.verity_key:
cmdline += ["--private-key", context.config.verity_key]
if context.config.verity_key_source.type != KeySource.Type.file:
cmdline += ["--private-key-source", str(context.config.verity_key_source)]
if context.config.verity_key.exists():
- options += ["--ro-bind", context.config.verity_key, context.config.verity_key]
+ mounts += [Mount(context.config.verity_key, context.config.verity_key, ro=True)]
if context.config.verity_certificate:
cmdline += ["--certificate", context.config.verity_certificate]
- options += ["--ro-bind", context.config.verity_certificate, context.config.verity_certificate]
+ mounts += [Mount(context.config.verity_certificate, context.config.verity_certificate, ro=True)]
if context.config.sector_size:
cmdline += ["--sector-size", str(context.config.sector_size)]
with complete_step(f"Building {context.config.output_format} extension image"):
r = context.resources / f"repart/definitions/{context.config.output_format}.repart.d"
- options += ["--ro-bind", r, r]
+ mounts += [Mount(r, r, ro=True)]
run(
cmdline + ["--definitions", r],
env=env,
not context.config.repart_offline or
context.config.verity_key_source.type != KeySource.Type.file
),
- options=options,
+ mounts=mounts,
),
)
# cp doesn't support excluding directories but we can imitate it by bind mounting an empty directory
# over the directories we want to exclude.
- exclude = flatten(["--ro-bind", tmp, os.fspath(p)] for p in caches)
+ exclude = [Mount(tmp, p, ro=True) for p in caches]
dst = context.package_cache_dir / d / subdir
with umask(~0o755):
dst.mkdir(parents=True, exist_ok=True)
- def sandbox(*, options: Sequence[PathString] = ()) -> list[PathString]:
- return context.sandbox(options=[*options, *exclude])
+ def sandbox(*, mounts: Sequence[Mount] = ()) -> list[PathString]:
+ return context.sandbox(mounts=[*mounts, *exclude])
copy_tree(
src, dst,
],
# Supply files via stdin so we don't clutter --debug run output too much
input="\n".join([str(root), *(os.fspath(p) for p in root.rglob("*") if p.is_dir())]),
- sandbox=config.sandbox(options=["--bind", root, root]),
+ sandbox=config.sandbox(mounts=[Mount(root, root)]),
)
# getfacl complains about absolute paths so make sure we pass a relative one.
if root.exists():
- sandbox = config.sandbox(options=["--bind", root, root, "--chdir", root])
+ sandbox = config.sandbox(mounts=[Mount(root, root)], options=["--chdir", root])
has_acl = f"user:{uid}:rwx" in run(["getfacl", "-n", "."], sandbox=sandbox, stdout=subprocess.PIPE).stdout
if not has_acl and not always:
],
stdin=sys.stdin,
env=config.environment,
- sandbox=config.sandbox(network=True, devices=True, options=["--bind", fname, fname]),
+ sandbox=config.sandbox(network=True, devices=True, mounts=[Mount(fname, fname)]),
)
if config.output_format == OutputFormat.directory:
from mkosi.log import log_step
from mkosi.run import find_binary, run
-from mkosi.sandbox import SandboxProtocol, finalize_passwd_mounts, nosandbox
+from mkosi.sandbox import Mount, SandboxProtocol, finalize_passwd_mounts, nosandbox
from mkosi.util import umask
],
stdout=f,
# Make sure tar uses user/group information from the root directory instead of the host.
- sandbox=sandbox(options=["--ro-bind", src, src, *finalize_passwd_mounts(src)]),
+ sandbox=sandbox(mounts=[Mount(src, src, ro=True), *finalize_passwd_mounts(src)]),
)
stdin=f,
sandbox=sandbox(
# Make sure tar uses user/group information from the root directory instead of the host.
- options=["--ro-bind", src, src, "--bind", dst, dst, *finalize_passwd_mounts(dst)]
+ mounts=[Mount(src, src, ro=True), Mount(dst, dst), *finalize_passwd_mounts(dst)]
),
)
],
input="\0".join(os.fspath(f.relative_to(src)) for f in files),
stdout=f,
- sandbox=sandbox(options=["--ro-bind", src, src, *finalize_passwd_mounts(src)]),
+ sandbox=sandbox(mounts=[Mount(src, src, ro=True), *finalize_passwd_mounts(src)]),
)
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SHELL, Style, die
from mkosi.pager import page
from mkosi.run import find_binary, run
-from mkosi.sandbox import sandbox_cmd
+from mkosi.sandbox import Mount, sandbox_cmd
from mkosi.types import PathString, SupportsRead
from mkosi.user import INVOKING_USER
from mkosi.util import (
devices: bool = False,
relaxed: bool = False,
scripts: Optional[Path] = None,
+ mounts: Sequence[Mount] = (),
options: Sequence[PathString] = (),
) -> list[PathString]:
- mounts: list[PathString] = (
- flatten(("--ro-bind", d, d) for d in self.extra_search_paths)
- if not relaxed and not self.tools_tree
- else []
- )
+ mounts = [
+ *[Mount(d, d, ro=True) for d in self.extra_search_paths if not relaxed and not self.tools_tree],
+ *mounts,
+ ]
return sandbox_cmd(
network=network,
relaxed=relaxed,
scripts=scripts,
tools=self.tools(),
- options=[*mounts, *options],
+ mounts=mounts,
+ options=options,
)
return None
policy = run(["sh", "-c", f". {selinux} && echo $SELINUXTYPE"],
- sandbox=config.sandbox(options=["--ro-bind", selinux, selinux]),
+ sandbox=config.sandbox(mounts=[Mount(selinux, selinux, ro=True)]),
stdout=subprocess.PIPE).stdout.strip()
if not policy:
if fatal and config.selinux_relabel == ConfigFeature.enabled:
# SPDX-License-Identifier: LGPL-2.1+
-import os
from collections.abc import Sequence
from pathlib import Path
from typing import Optional
from mkosi.config import Args, Config
+from mkosi.sandbox import Mount
from mkosi.tree import make_tree
from mkosi.types import PathString
-from mkosi.util import flatten, umask
+from mkosi.util import umask
class Context:
network: bool = False,
devices: bool = False,
scripts: Optional[Path] = None,
+ mounts: Sequence[Mount] = (),
options: Sequence[PathString] = (),
) -> list[PathString]:
return self.config.sandbox(
network=network,
devices=devices,
scripts=scripts,
- options=[
- "--uid", "0",
- "--gid", "0",
- "--cap-add", "ALL",
+ mounts=[
# These mounts are writable so bubblewrap can create extra directories or symlinks inside of it as
# needed. This isn't a problem as the package manager directory is created by mkosi and thrown away
# when the build finishes.
- *flatten(
- ["--bind", os.fspath(self.pkgmngr / "etc" / p.name), f"/etc/{p.name}"]
+ *[
+ Mount(self.pkgmngr / "etc" / p.name, f"/etc/{p.name}")
for p in (self.pkgmngr / "etc").iterdir()
- ),
+ ],
+ *mounts,
+ Mount(self.pkgmngr / "var/log", "/var/log"),
+ *([Mount(p, p, ro=True)] if (p := self.pkgmngr / "usr").exists() else []),
+ ],
+ options=[
+ "--uid", "0",
+ "--gid", "0",
+ "--cap-add", "ALL",
*options,
- "--bind", self.pkgmngr / "var/log", "/var/log",
- *(["--ro-bind", os.fspath(p), os.fspath(p)] if (p := self.pkgmngr / "usr").exists() else []),
],
) + (
[
from mkosi.installer.apt import Apt
from mkosi.log import die
from mkosi.run import run
+from mkosi.sandbox import Mount
from mkosi.util import listify, umask
f"-oDPkg::Pre-Install-Pkgs::=cat >{f.name}",
"?essential", "?exact-name(usr-is-merged)",
],
- mounts=("--bind", f.name, f.name),
+ mounts=[Mount(f.name, f.name)],
)
essential = f.read().strip().splitlines()
from mkosi.installer.zypper import Zypper
from mkosi.log import die
from mkosi.run import find_binary, run
-from mkosi.sandbox import finalize_crypto_mounts
+from mkosi.sandbox import Mount, finalize_crypto_mounts
from mkosi.util import listify, sort_packages
],
sandbox=context.sandbox(
network=True,
- options=["--bind", d, d, *finalize_crypto_mounts(context.config.tools())],
+ mounts=[Mount(d, d), *finalize_crypto_mounts(context.config.tools())],
),
)
xml = (Path(d) / "repomd.xml").read_text()
# SPDX-License-Identifier: LGPL-2.1+
-import os
from pathlib import Path
from mkosi.config import Config, ConfigFeature, OutputFormat
from mkosi.context import Context
from mkosi.run import find_binary
-from mkosi.sandbox import finalize_crypto_mounts
+from mkosi.sandbox import Mount, finalize_crypto_mounts
from mkosi.tree import copy_tree, rmtree
from mkosi.types import PathString
-from mkosi.util import flatten, startswith
+from mkosi.util import startswith
class PackageManager:
return {}
@classmethod
- def mounts(cls, context: Context) -> list[PathString]:
- mounts: list[PathString] = [
+ def mounts(cls, context: Context) -> list[Mount]:
+ mounts = [
*finalize_crypto_mounts(tools=context.config.tools()),
- "--bind", context.packages, "/work/packages",
+ Mount(context.packages, "/work/packages"),
]
if context.config.local_mirror and (mirror := startswith(context.config.local_mirror, "file://")):
- mounts += ["--ro-bind", mirror, mirror]
+ mounts += [Mount(mirror, mirror, ro=True)]
subdir = context.config.distribution.package_manager(context.config).subdir(context.config)
for d in ("cache", "lib"):
src = context.package_cache_dir / d / subdir
- mounts += ["--bind", src, Path("/var") / d / subdir]
+ mounts += [Mount(src, Path("/var") / d / subdir)]
# If we're not operating on the configured package cache directory, we're operating on a snapshot of the
# repository metadata in the image root directory. To make sure any downloaded packages are still cached in
# configured package cache directory.
if d == "cache" and context.package_cache_dir != context.config.package_cache_dir_or_default():
caches = context.config.distribution.package_manager(context.config).cache_subdirs(src)
- mounts += flatten(
- [
- "--bind",
- os.fspath(context.config.package_cache_dir_or_default() / d / subdir / p.relative_to(src)),
+ mounts += [
+ Mount(
+ context.config.package_cache_dir_or_default() / d / subdir / p.relative_to(src),
Path("/var") / d / subdir / p.relative_to(src),
- ]
+ )
for p in caches
if (context.config.package_cache_dir_or_default() / d / subdir / p.relative_to(src)).exists()
- )
+ ]
return mounts
from mkosi.log import die
from mkosi.mounts import finalize_source_mounts
from mkosi.run import find_binary, run
-from mkosi.sandbox import apivfs_cmd
+from mkosi.sandbox import Mount, apivfs_cmd
from mkosi.types import _FILE, CompletedProcess, PathString
from mkosi.util import umask
arguments: Sequence[str] = (),
*,
apivfs: bool = False,
- mounts: Sequence[PathString] = (),
+ mounts: Sequence[Mount] = (),
stdout: _FILE = None,
) -> CompletedProcess:
with finalize_source_mounts(
sandbox=(
context.sandbox(
network=True,
- options=[
- "--bind", context.root, context.root,
- *cls.mounts(context),
- *sources,
- *mounts,
- "--chdir", "/work/src",
- ],
+ mounts=[Mount(context.root, context.root), *cls.mounts(context), *sources, *mounts],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
) + (apivfs_cmd(context.root) if apivfs else [])
),
env=context.config.environment,
["dpkg-scanpackages", "."],
stdout=f,
sandbox=context.sandbox(
- options=[
- "--ro-bind", context.packages, context.packages,
- "--chdir", context.packages,
- ],
+ mounts=[Mount(context.packages, context.packages, ro=True)],
+ options=["--chdir", context.packages],
),
)
from mkosi.log import ARG_DEBUG
from mkosi.mounts import finalize_source_mounts
from mkosi.run import find_binary, run
-from mkosi.sandbox import apivfs_cmd
+from mkosi.sandbox import Mount, apivfs_cmd
from mkosi.types import _FILE, CompletedProcess, PathString
sandbox=(
context.sandbox(
network=True,
- options=[
- "--bind", context.root, context.root,
- *cls.mounts(context),
- *sources,
- "--chdir", "/work/src",
- ],
+ mounts=[Mount(context.root, context.root), *cls.mounts(context), *sources],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
) + (apivfs_cmd(context.root) if apivfs else [])
),
env=context.config.environment,
@classmethod
def createrepo(cls, context: Context) -> None:
run(["createrepo_c", context.packages],
- sandbox=context.sandbox(options=["--bind", context.packages, context.packages]))
+ sandbox=context.sandbox(mounts=[Mount(context.packages, context.packages)]))
(context.pkgmngr / "etc/yum.repos.d/mkosi-local.repo").write_text(
textwrap.dedent(
from mkosi.installer import PackageManager
from mkosi.mounts import finalize_source_mounts
from mkosi.run import run
-from mkosi.sandbox import apivfs_cmd
+from mkosi.sandbox import Mount, apivfs_cmd
from mkosi.types import _FILE, CompletedProcess, PathString
from mkosi.util import umask
from mkosi.versioncomp import GenericVersion
}
@classmethod
- def mounts(cls, context: Context) -> list[PathString]:
- mounts: list[PathString] = [
+ def mounts(cls, context: Context) -> list[Mount]:
+ mounts = [
*super().mounts(context),
# pacman writes downloaded packages to the first writable cache directory. We don't want it to write to our
# local repository directory so we expose it as a read-only directory to pacman.
- "--ro-bind", context.packages, "/var/cache/pacman/mkosi",
+ Mount(context.packages, "/var/cache/pacman/mkosi", ro=True),
]
if (context.root / "var/lib/pacman/local").exists():
# pacman reuses the same directory for the sync databases and the local database containing the list of
# installed packages. The former should go in the cache directory, the latter should go in the image, so we
# bind mount the local directory from the image to make sure that happens.
- mounts += ["--bind", context.root / "var/lib/pacman/local", "/var/lib/pacman/local"]
+ mounts += [Mount(context.root / "var/lib/pacman/local", "/var/lib/pacman/local")]
if (
(context.config.tools() / "etc/makepkg.conf").exists() and
not (context.pkgmngr / "etc/makepkg.conf").exists()
):
- mounts += ["--ro-bind", context.config.tools() / "etc/makepkg.conf", "/etc/makepkg.conf"]
+ mounts += [Mount(context.config.tools() / "etc/makepkg.conf", "/etc/makepkg.conf", ro=True)]
return mounts
sandbox=(
context.sandbox(
network=True,
- options=[
- "--bind", context.root, context.root,
- *cls.mounts(context),
- *sources,
- "--chdir", "/work/src",
- ],
+ mounts=[Mount(context.root, context.root), *cls.mounts(context), *sources],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
) + (apivfs_cmd(context.root) if apivfs else [])
),
env=context.config.environment,
context.packages / "mkosi.db.tar",
*sorted(context.packages.glob("*.pkg.tar*"), key=lambda p: GenericVersion(Path(p).name))
],
- sandbox=context.sandbox(options=["--bind", context.packages, context.packages]),
+ sandbox=context.sandbox(mounts=[Mount(context.packages, context.packages)]),
)
(context.pkgmngr / "etc/mkosi-local.conf").write_text(
from mkosi.installer.rpm import RpmRepository, rpm_cmd
from mkosi.mounts import finalize_source_mounts
from mkosi.run import run
-from mkosi.sandbox import apivfs_cmd
+from mkosi.sandbox import Mount, apivfs_cmd
from mkosi.types import _FILE, CompletedProcess, PathString
sandbox=(
context.sandbox(
network=True,
- options=[
- "--bind", context.root, context.root,
- *cls.mounts(context),
- *sources,
- "--chdir", "/work/src",
- ],
+ mounts=[Mount(context.root, context.root), *cls.mounts(context), *sources],
+ options=["--dir", "/work/src", "--chdir", "/work/src"],
) + (apivfs_cmd(context.root) if apivfs else [])
),
env=context.config.environment,
@classmethod
def createrepo(cls, context: Context) -> None:
run(["createrepo_c", context.packages],
- sandbox=context.sandbox(options=["--bind", context.packages, context.packages]))
+ sandbox=context.sandbox(mounts=[Mount(context.packages, context.packages)]))
(context.pkgmngr / "etc/zypp/repos.d/mkosi-local.repo").write_text(
textwrap.dedent(
from mkosi.log import complete_step, log_step
from mkosi.run import run
-from mkosi.sandbox import SandboxProtocol, nosandbox
+from mkosi.sandbox import Mount, SandboxProtocol, nosandbox
def loaded_modules() -> list[str]:
info += run(
["modinfo", "--basedir", root, "--set-version", kver, "--null", *chunk],
stdout=subprocess.PIPE,
- sandbox=sandbox(options=["--ro-bind", root, root])
+ sandbox=sandbox(mounts=[Mount(root, root, ro=True)]),
).stdout.strip()
log_step("Calculating required kernel modules and firmware")
from mkosi.installer.apt import Apt
from mkosi.log import complete_step
from mkosi.run import run
+from mkosi.sandbox import Mount
@dataclasses.dataclass
"--queryformat", r"%{NEVRA}\t%{SOURCERPM}\t%{NAME}\t%{ARCH}\t%{LONGSIZE}\t%{INSTALLTIME}\n",
],
stdout=subprocess.PIPE,
- sandbox=self.context.sandbox(options=["--ro-bind", self.context.root, self.context.root]),
+ sandbox=self.context.sandbox(mounts=[Mount(self.context.root, self.context.root)]),
)
packages = sorted(c.stdout.splitlines())
],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
- sandbox=self.context.sandbox(options=["--ro-bind", self.context.root, self.context.root]),
+ sandbox=self.context.sandbox(mounts=[Mount(self.context.root, self.context.root, ro=True)]),
)
changelog = c.stdout.strip()
source = SourcePackageManifest(srpm, changelog)
r'${Package}\t${source:Package}\t${Version}\t${Architecture}\t${Installed-Size}\t${db-fsys:Last-Modified}\n',
],
stdout=subprocess.PIPE,
- sandbox=self.context.sandbox(options=["--ro-bind", self.context.root, self.context.root]),
+ sandbox=self.context.sandbox(mounts=[Mount(self.context.root, self.context.root, ro=True)]),
)
packages = sorted(c.stdout.splitlines())
from mkosi.config import Config
from mkosi.run import run
+from mkosi.sandbox import Mount
from mkosi.types import PathString
from mkosi.util import umask
from mkosi.versioncomp import GenericVersion
@contextlib.contextmanager
-def finalize_source_mounts(config: Config, *, ephemeral: bool) -> Iterator[list[PathString]]:
+def finalize_source_mounts(config: Config, *, ephemeral: bool) -> Iterator[list[Mount]]:
with contextlib.ExitStack() as stack:
- mounts = (
+ sources = (
(stack.enter_context(mount_overlay([source])) if ephemeral else source, target)
for source, target
in {t.with_prefix(Path("/work/src")) for t in config.build_sources}
)
- options: list[PathString] = ["--dir", "/work/src"]
- for src, target in sorted(mounts, key=lambda s: s[1]):
- options += ["--bind", src, target]
-
- yield options
+ yield [Mount(src, target) for src, target in sorted(sources, key=lambda s: s[1])]
from mkosi.log import die
from mkosi.run import run
-from mkosi.sandbox import SandboxProtocol, nosandbox
+from mkosi.sandbox import Mount, SandboxProtocol, nosandbox
@dataclasses.dataclass(frozen=True)
["systemd-repart", "--json=short", image],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
- sandbox=sandbox(options=["--ro-bind", image, image]),
+ sandbox=sandbox(mounts=[Mount(image, image, ro=True)]),
).stdout
)
return [Partition.from_dict(d) for d in output]
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
+from mkosi.sandbox import Mount
from mkosi.tree import copy_tree, rmtree
from mkosi.types import PathString
from mkosi.user import INVOKING_USER, become_root
type = run(
["bootctl", "kernel-identify", path],
stdout=subprocess.PIPE,
- sandbox=config.sandbox(options=["--ro-bind", path, path]),
+ sandbox=config.sandbox(mounts=[Mount(path, path, ro=True)]),
).stdout.strip()
try:
with tempfile.TemporaryDirectory(prefix="mkosi-swtpm") as state:
# swtpm_setup is noisy and doesn't have a --quiet option so we pipe it's stdout to /dev/null.
run(["swtpm_setup", "--tpm-state", state, "--tpm2", "--pcr-banks", "sha256", "--config", "/dev/null"],
- sandbox=config.sandbox(options=["--bind", state, state]),
+ sandbox=config.sandbox(mounts=[Mount(state, state)]),
stdout=None if ARG_DEBUG.get() else subprocess.DEVNULL)
cmdline = ["swtpm", "socket", "--tpm2", "--tpmstate", f"dir={state}"]
with spawn(
cmdline,
pass_fds=(sock.fileno(),),
- sandbox=config.sandbox(options=["--bind", state, state]),
+ sandbox=config.sandbox(mounts=[Mount(state, state)]),
) as proc:
try:
yield path
group=INVOKING_USER.gid if uidmap else None,
preexec_fn=become_root if not uidmap else None,
sandbox=config.sandbox(
- options=[
- "--uid", "0",
- "--gid", "0",
- "--cap-add", "all",
- "--bind", directory, directory,
- ],
+ mounts=[Mount(directory, directory)],
+ options=["--uid", "0", "--gid", "0", "--cap-add", "all"],
),
) as proc:
try:
"--loglevel", "WARNING",
],
sandbox=config.sandbox(
- options=[
- "--bind", ovmf_vars.name, ovmf_vars.name,
- "--ro-bind", config.secure_boot_certificate, config.secure_boot_certificate,
+ mounts=[
+ Mount(ovmf_vars.name, ovmf_vars.name),
+ Mount(config.secure_boot_certificate, config.secure_boot_certificate, ro=True),
],
),
)
"--copy-from", src,
fname,
],
- sandbox=config.sandbox(options=["--bind", fname.parent, fname.parent, "--ro-bind", src, src]),
+ sandbox=config.sandbox(mounts=[Mount(fname.parent, fname.parent), Mount(src, src, ro=True)]),
)
stack.callback(lambda: fname.unlink())
elif config.ephemeral and config.output_format not in (OutputFormat.cpio, OutputFormat.uki):
"--offline=yes",
fname,
],
- sandbox=config.sandbox(options=["--bind", fname, fname]),
+ sandbox=config.sandbox(mounts=[Mount(fname, fname)]),
)
if (
run(
[f"mkfs.{fs}", "-L", "scratch", *extra.split(), scratch.name],
stdout=subprocess.DEVNULL,
- sandbox=config.sandbox(options=["--bind", scratch.name, scratch.name]),
+ sandbox=config.sandbox(mounts=[Mount(scratch.name, scratch.name)]),
)
cmdline += [
"-drive", f"if=none,id=scratch,file={scratch.name},format=raw",
import uuid
from collections.abc import Sequence
from pathlib import Path
-from typing import Optional, Protocol
+from typing import NamedTuple, Optional, Protocol
from mkosi.types import PathString
from mkosi.user import INVOKING_USER
from mkosi.util import flatten, one_zero, startswith
+class Mount(NamedTuple):
+ src: PathString
+ dst: PathString
+ devices: bool = False
+ ro: bool = False
+ required: bool = True
+
+ def __hash__(self) -> int:
+ return hash((Path(self.src), Path(self.dst), self.devices, self.ro, self.required))
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Mount):
+ return False
+
+ return self.__hash__() == other.__hash__()
+
+ def options(self) -> list[str]:
+ if self.devices:
+ opt = "--dev-bind" if self.required else "--dev-bind-try"
+ elif self.ro:
+ opt = "--ro-bind" if self.required else "--ro-bind-try"
+ else:
+ opt = "--bind" if self.required else "--bind-try"
+
+ return [opt, os.fspath(self.src), os.fspath(self.dst)]
+
+
class SandboxProtocol(Protocol):
- def __call__(self, *, options: Sequence[PathString] = ()) -> list[PathString]: ...
+ def __call__(self, *, mounts: Sequence[Mount] = ()) -> list[PathString]: ...
-def nosandbox(*, options: Sequence[PathString] = ()) -> list[PathString]:
+def nosandbox(*, mounts: Sequence[Mount] = ()) -> list[PathString]:
return []
return (int(hexcap, 16) & (1 << capability.value)) != 0
-def finalize_passwd_mounts(root: Path) -> list[PathString]:
+def finalize_passwd_mounts(root: Path) -> list[Mount]:
"""
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.
"""
- options: list[PathString] = []
-
- for f in ("passwd", "group", "shadow", "gshadow"):
- options += ["--ro-bind-try", root / "etc" / f, f"/etc/{f}"]
-
- return options
+ return [
+ Mount(root / "etc" / f, f"/etc/{f}", ro=True, required=False)
+ for f in ("passwd", "group", "shadow", "gshadow")
+ ]
-def finalize_crypto_mounts(tools: Path = Path("/")) -> list[PathString]:
+def finalize_crypto_mounts(tools: Path = Path("/")) -> list[Mount]:
mounts = [
(tools / subdir, Path("/") / subdir)
for subdir in (
if (tools / subdir).exists()
]
- return flatten(
- ["--ro-bind", src, target]
+ return [
+ Mount(src, target, ro=True)
for src, target
in sorted(set(mounts), key=lambda s: s[1])
- )
+ ]
def sandbox_cmd(
scripts: Optional[Path] = None,
tools: Path = Path("/"),
relaxed: bool = False,
+ mounts: Sequence[Mount] = (),
options: Sequence[PathString] = (),
) -> list[PathString]:
cmdline: list[PathString] = []
cmdline += [
"bwrap",
- "--ro-bind", tools / "usr", "/usr",
*(["--unshare-net"] if not network and have_effective_cap(Capability.CAP_NET_ADMIN) else []),
"--die-with-parent",
"--proc", "/proc",
# We mounted a subdirectory of TMPDIR to /var/tmp so we unset TMPDIR so that /tmp or /var/tmp are used instead.
"--unsetenv", "TMPDIR",
]
+ allmounts = [Mount(tools / "usr", "/usr", ro=True)]
if relaxed:
- cmdline += ["--bind", "/tmp", "/tmp"]
+ allmounts += [Mount("/tmp", "/tmp")]
else:
cmdline += [
"--tmpfs", "/tmp",
]
if (tools / "nix/store").exists():
- cmdline += ["--bind", tools / "nix/store", "/nix/store"]
+ allmounts += [Mount(tools / "nix/store", "/nix/store")]
if devices or relaxed:
- cmdline += [
- "--bind", "/sys", "/sys",
- "--bind", "/run", "/run",
- "--dev-bind", "/dev", "/dev",
+ allmounts += [
+ Mount("/sys", "/sys"),
+ Mount("/run", "/run"),
+ Mount("/dev", "/dev", devices=True),
]
else:
cmdline += ["--dev", "/dev"]
for d in dirs:
if Path(d).exists():
- cmdline += ["--bind", d, d]
+ allmounts += [Mount(d, d)]
if len(Path.cwd().parents) >= 2:
# `Path.parents` only supports slices and negative indexing from Python 3.10 onwards.
d = ""
if d and d not in (*dirs, "/home", "/usr", "/nix", "/tmp"):
- cmdline += ["--bind", d, d]
+ allmounts += [Mount(d, d)]
if vartmp:
- cmdline += ["--bind", vartmp, "/var/tmp"]
+ allmounts += [Mount(vartmp, "/var/tmp")]
for d in ("bin", "sbin", "lib", "lib32", "lib64"):
if (p := tools / d).is_symlink():
cmdline += ["--symlink", p.readlink(), Path("/") / p.relative_to(tools)]
elif p.is_dir():
- cmdline += ["--ro-bind", p, Path("/") / p.relative_to(tools)]
+ allmounts += [Mount(p, Path("/") / p.relative_to(tools), ro=True)]
path = "/usr/bin:/usr/sbin" if tools != Path("/") else os.environ["PATH"]
- cmdline += [
- "--setenv", "PATH", f"/scripts:{path}",
- *options,
- ]
+ cmdline += ["--setenv", "PATH", f"/scripts:{path}", *options]
+ allmounts = [*allmounts, *mounts]
if not relaxed:
cmdline += ["--symlink", "../proc/self/mounts", "/etc/mtab"]
# already exists on the host as otherwise we'd modify the host's /etc by creating the mountpoint ourselves (or
# fail when trying to create it).
if (tools / "etc/alternatives").exists() and (not relaxed or Path("/etc/alternatives").exists()):
- cmdline += ["--ro-bind", tools / "etc/alternatives", "/etc/alternatives"]
+ allmounts += [Mount(tools / "etc/alternatives", "/etc/alternatives", ro=True)]
if scripts:
- cmdline += ["--ro-bind", scripts, "/scripts"]
+ allmounts += [Mount(scripts, "/scripts", ro=True)]
if network and not relaxed and Path("/etc/resolv.conf").exists():
- cmdline += ["--bind", "/etc/resolv.conf", "/etc/resolv.conf"]
+ allmounts += [Mount("/etc/resolv.conf", "/etc/resolv.conf")]
+
+ cmdline += flatten(mount.options() for mount in allmounts)
# bubblewrap creates everything with a restricted mode so relax stuff as needed.
ops = []
"--ro-bind-try", root / "etc/machine-id", root / "etc/machine-id",
# Nudge gpg to create its sockets in /run by making sure /run/user/0 exists.
"--dir", root / "run/user/0",
- *finalize_passwd_mounts(root),
+ *flatten(mount.options() for mount in finalize_passwd_mounts(root)),
"sh", "-c",
f"chmod 1777 {root / 'tmp'} {root / 'var/tmp'} {root / 'dev/shm'} && "
f"chmod 755 {root / 'run'} && "
from mkosi.config import ConfigFeature
from mkosi.log import ARG_DEBUG, die
from mkosi.run import find_binary, run
-from mkosi.sandbox import SandboxProtocol, nosandbox
+from mkosi.sandbox import Mount, SandboxProtocol, nosandbox
from mkosi.types import PathString
-from mkosi.util import flatten
from mkosi.versioncomp import GenericVersion
def statfs(path: Path, *, sandbox: SandboxProtocol = nosandbox) -> str:
return run(["stat", "--file-system", "--format", "%T", path],
- sandbox=sandbox(options=["--ro-bind", path, path]), stdout=subprocess.PIPE).stdout.strip()
+ sandbox=sandbox(mounts=[Mount(path, path, ro=True)]), stdout=subprocess.PIPE).stdout.strip()
def is_subvolume(path: Path, *, sandbox: SandboxProtocol = nosandbox) -> bool:
if use_subvolumes != ConfigFeature.disabled and find_binary("btrfs", root=tools) is not None:
result = run(["btrfs", "subvolume", "create", path],
- sandbox=sandbox(options=["--bind", path.parent, path.parent]),
+ sandbox=sandbox(mounts=[Mount(path.parent, path.parent)]),
check=use_subvolumes == ConfigFeature.enabled).returncode
else:
result = 1
if cp_version(sandbox=sandbox) >= "9.5":
copy += ["--keep-directory-symlink"]
- options: list[PathString] = ["--ro-bind", src, src, "--bind", dst.parent, dst.parent]
+ mounts = [Mount(src, src, ro=True), Mount(dst.parent, dst.parent)]
# If the source and destination are both directories, we want to merge the source directory with the
# destination directory. If the source if a file and the destination is a directory, we want to copy
if not preserve
else contextlib.nullcontext()
):
- run(copy, sandbox=sandbox(options=options))
+ run(copy, sandbox=sandbox(mounts=mounts))
return dst
# btrfs can't snapshot to an existing directory so make sure the destination does not exist.
dst.rmdir()
result = run(["btrfs", "subvolume", "snapshot", src, dst],
- check=use_subvolumes == ConfigFeature.enabled, sandbox=sandbox(options=options)).returncode
+ check=use_subvolumes == ConfigFeature.enabled, sandbox=sandbox(mounts=mounts)).returncode
if result != 0:
with (
preserve_target_directories_stat(src, dst)
if not preserve
else contextlib.nullcontext()
):
- run(copy, sandbox=sandbox(options=options))
+ run(copy, sandbox=sandbox(mounts=mounts))
return dst
# btrfs filesystem is mounted with user_subvol_rm_allowed.
run(["btrfs", "subvolume", "delete", *subvolumes],
check=False,
- sandbox=sandbox(options=flatten(["--bind", p, p] for p in parents)),
+ sandbox=sandbox(mounts=[Mount(p, p) for p in parents]),
stdout=subprocess.DEVNULL if not ARG_DEBUG.get() else None,
stderr=subprocess.DEVNULL if not ARG_DEBUG.get() else None)
parents = sorted(set(p.parent for p in paths))
parents = [p for p in parents if all(p == q or not p.is_relative_to(q) for q in parents)]
run(["rm", "-rf", "--", *paths],
- sandbox=sandbox(options=flatten(["--bind", p, p] for p in parents)))
+ sandbox=sandbox(mounts=[Mount(p, p) for p in parents]))
def move_tree(
find_ovmf_firmware,
)
from mkosi.run import run
+from mkosi.sandbox import Mount
from mkosi.types import PathString
from mkosi.util import flock_or_die
"--offline=yes",
fname,
],
- sandbox=config.sandbox(options=["--bind", fname, fname]),
+ sandbox=config.sandbox(mounts=[Mount(fname, fname)]),
)
kcl = config.kernel_command_line_extra