Skip to content

Commit

Permalink
Use resource_path() to access files in our own module
Browse files Browse the repository at this point in the history
__file__ doesn't work if mkosi is packaged up as a zipapp, let's
use resource_path() which is specifically intended to solve this
problem and works regardless of whether we're in a zipapp or not.
  • Loading branch information
DaanDeMeyer committed Jan 23, 2025
1 parent 10918a9 commit 0a815a2
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 111 deletions.
12 changes: 6 additions & 6 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
make_executable,
one_zero,
read_env_file,
resource_path,
scopedenv,
)
from mkosi.versioncomp import GenericVersion
Expand Down Expand Up @@ -3902,12 +3903,11 @@ def run_sandbox(args: Args, config: Config) -> None:
# trying to run a zipapp created from a packaged version of mkosi. While zipapp.create_archive()
# supports a filter= argument, trying to use this within a site-packages directory is rather slow
# so we copy the mkosi package to a temporary directory instead which is much faster.
with tempfile.TemporaryDirectory(prefix="mkosi-zipapp-") as tmp:
copy_tree(
Path(__file__).parent,
Path(tmp) / Path(__file__).parent.name,
sandbox=config.sandbox,
)
with (
tempfile.TemporaryDirectory(prefix="mkosi-zipapp-") as tmp,
resource_path(sys.modules[__name__]) as mkosi,
):
copy_tree(mkosi, Path(tmp) / mkosi.name, sandbox=config.sandbox)
zipapp.create_archive(
source=tmp,
target=Path(d) / "mkosi",
Expand Down
215 changes: 110 additions & 105 deletions mkosi/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
from types import TracebackType
from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, Protocol

import mkosi.sandbox
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, die
from mkosi.sandbox import acquire_privileges, joinpath, umask
from mkosi.util import _FILE, PathString, current_home_dir, flatten, one_zero, unique
from mkosi.util import _FILE, PathString, current_home_dir, flatten, one_zero, resource_path, unique

SD_LISTEN_FDS_START = 3

Expand Down Expand Up @@ -559,102 +558,108 @@ def sandbox_cmd(
) -> Iterator[list[PathString]]:
assert not (overlay and relaxed)

cmdline: list[PathString] = [
*setup,
*(["strace", "--detach-on=execve"] if ARG_DEBUG_SANDBOX.get() else []),
sys.executable, "-SI", mkosi.sandbox.__file__,
"--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",
*network_options(network=network),
] # fmt: skip

if overlay and (overlay / "usr").exists():
cmdline += [
"--overlay-lowerdir", tools / "usr",
"--overlay-lowerdir", overlay / "usr",
"--overlay", "/usr",
] # fmt: skip
else:
cmdline += ["--ro-bind", tools / "usr", "/usr"]

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)]

# If we're using /usr from a tools tree, we have to use /etc/alternatives and /etc/ld.so.cache from the
# tools tree as well if they exists since those are directly related to /usr. In relaxed mode, we only do
# this if the mountpoint 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).
for p in (Path("etc/alternatives"), Path("etc/ld.so.cache")):
if (tools / p).exists() and (not relaxed or (Path("/") / p).exists()):
cmdline += ["--ro-bind", tools / p, Path("/") / p]

if (tools / "nix/store").exists():
cmdline += ["--bind", tools / "nix/store", "/nix/store"]

if relaxed:
for p in Path("/").iterdir():
if p not in (
Path("/home"),
Path("/proc"),
Path("/usr"),
Path("/nix"),
Path("/bin"),
Path("/sbin"),
Path("/lib"),
Path("/lib32"),
Path("/lib64"),
):
if p.is_symlink():
cmdline += ["--symlink", p.readlink(), p]
else:
cmdline += ["--bind", p, p]

# /etc might be full of symlinks to /usr/share/factory, so make sure we use /usr/share/factory
# from the host and not from the tools tree.
if (
tools != Path("/")
and (tools / "usr/share/factory").exists()
and (factory := Path("/usr/share/factory")).exists()
):
cmdline += ["--bind", factory, factory]

if home := current_home_dir():
cmdline += ["--bind", home, home]
else:
cmdline += [
"--dir", "/var/tmp",
"--dir", "/var/log",
"--unshare-ipc",
# apivfs_script_cmd() and chroot_script_cmd() are executed from within the sandbox, but they
# still use sandbox.py, so we make sure it is available inside the sandbox so it can be executed
# there as well.
"--ro-bind", Path(mkosi.sandbox.__file__), "/sandbox.py",
with contextlib.ExitStack() as stack:
module = stack.enter_context(resource_path(sys.modules[__name__]))

cmdline: list[PathString] = [
*setup,
*(["strace", "--detach-on=execve"] if ARG_DEBUG_SANDBOX.get() else []),
sys.executable, "-SI", module / "sandbox.py",
"--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",
*network_options(network=network),
] # fmt: skip

if devices:
cmdline += ["--bind", "/sys", "/sys", "--bind", "/dev", "/dev"]
if overlay and (overlay / "usr").exists():
cmdline += [
"--overlay-lowerdir", tools / "usr",
"--overlay-lowerdir", overlay / "usr",
"--overlay", "/usr",
] # fmt: skip
else:
cmdline += ["--ro-bind", tools / "usr", "/usr"]

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)]

# If we're using /usr from a tools tree, we have to use /etc/alternatives and /etc/ld.so.cache from
# the tools tree as well if they exists since those are directly related to /usr. In relaxed mode, we
# only do this if the mountpoint 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).
for p in (Path("etc/alternatives"), Path("etc/ld.so.cache")):
if (tools / p).exists() and (not relaxed or (Path("/") / p).exists()):
cmdline += ["--ro-bind", tools / p, Path("/") / p]

if (tools / "nix/store").exists():
cmdline += ["--bind", tools / "nix/store", "/nix/store"]

if relaxed:
for p in Path("/").iterdir():
if p not in (
Path("/home"),
Path("/proc"),
Path("/usr"),
Path("/nix"),
Path("/bin"),
Path("/sbin"),
Path("/lib"),
Path("/lib32"),
Path("/lib64"),
):
if p.is_symlink():
cmdline += ["--symlink", p.readlink(), p]
else:
cmdline += ["--bind", p, p]

# /etc might be full of symlinks to /usr/share/factory, so make sure we use
# /usr/share/factory from the host and not from the tools tree.
if (
tools != Path("/")
and (tools / "usr/share/factory").exists()
and (factory := Path("/usr/share/factory")).exists()
):
cmdline += ["--bind", factory, factory]

if home := current_home_dir():
cmdline += ["--bind", home, home]
else:
cmdline += ["--dev", "/dev"]
cmdline += [
"--dir", "/var/tmp",
"--dir", "/var/log",
"--unshare-ipc",
# apivfs_script_cmd() and chroot_script_cmd() are executed from within the sandbox, but they
# still use sandbox.py, so we make sure it is available inside the sandbox so it can be
# executed there as well.
"--ro-bind", module / "sandbox.py", "/sandbox.py",
] # fmt: skip

if devices:
cmdline += ["--bind", "/sys", "/sys", "--bind", "/dev", "/dev"]
else:
cmdline += ["--dev", "/dev"]

if network:
for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
if p.exists():
cmdline += ["--ro-bind", p, p]
if network:
for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
if p.exists():
cmdline += ["--ro-bind", p, p]

home = None
home = None

path = finalize_path(root=tools, extra=[Path("/scripts"), *extra] if scripts else extra, relaxed=relaxed)
cmdline += ["--setenv", "PATH", path]
path = finalize_path(
root=tools,
extra=[Path("/scripts"), *extra] if scripts else extra,
relaxed=relaxed,
)
cmdline += ["--setenv", "PATH", path]

if scripts:
cmdline += ["--ro-bind", scripts, "/scripts"]
if scripts:
cmdline += ["--ro-bind", scripts, "/scripts"]

with contextlib.ExitStack() as stack:
tmp: Optional[Path]

if not overlay and not relaxed:
Expand Down Expand Up @@ -739,23 +744,23 @@ def chroot_cmd(
network: bool = False,
options: Sequence[PathString] = (),
) -> Iterator[list[PathString]]:
cmdline: list[PathString] = [
sys.executable, "-SI", mkosi.sandbox.__file__,
"--bind", root, "/",
# We mounted a subdirectory of TMPDIR to /var/tmp so we unset TMPDIR so that /tmp or /var/tmp are
# used instead.
"--unsetenv", "TMPDIR",
*network_options(network=network),
*apivfs_options(root=Path("/")),
*chroot_options(),
] # fmt: skip
with vartmpdir() as dir, resource_path(sys.modules[__name__]) as module:
cmdline: list[PathString] = [
sys.executable, "-SI", module / "sandbox.py",
"--bind", root, "/",
# We mounted a subdirectory of TMPDIR to /var/tmp so we unset TMPDIR so that /tmp or /var/tmp are
# used instead.
"--unsetenv", "TMPDIR",
*network_options(network=network),
*apivfs_options(root=Path("/")),
*chroot_options(),
] # fmt: skip

if network:
for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
if p.exists():
cmdline += ["--ro-bind", p, p]
if network:
for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
if p.exists():
cmdline += ["--ro-bind", p, p]

with vartmpdir() as dir:
yield [*cmdline, "--bind", dir, "/var/tmp", *options]


Expand Down

0 comments on commit 0a815a2

Please sign in to comment.