summaryrefslogtreecommitdiff
path: root/mdbp/mmdebstrap.py
diff options
context:
space:
mode:
authorHelmut Grohne <helmut@subdivi.de>2021-04-18 14:42:27 +0200
committerHelmut Grohne <helmut@subdivi.de>2021-04-18 14:42:27 +0200
commitcf999acb17c8123ddee407d0e486ca3b275a5d7c (patch)
treebfe9307dc9d2dd49fd46111bab0e3fbe324d6687 /mdbp/mmdebstrap.py
downloadmdbp-cf999acb17c8123ddee407d0e486ca3b275a5d7c.tar.gz
initial checkin of mdbp
Proof-of-concept status. Some things work.
Diffstat (limited to 'mdbp/mmdebstrap.py')
-rw-r--r--mdbp/mmdebstrap.py152
1 files changed, 152 insertions, 0 deletions
diff --git a/mdbp/mmdebstrap.py b/mdbp/mmdebstrap.py
new file mode 100644
index 0000000..df96e47
--- /dev/null
+++ b/mdbp/mmdebstrap.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: MIT
+"""mdbp backend using mmdebstrap"""
+
+import argparse
+import pathlib
+import shlex
+import subprocess
+import tempfile
+import typing
+
+from .common import buildjson, compute_env, get_dsc, get_dsc_files, \
+ profile_option
+
+# pylint: disable=W0102 # as we do not modify env
+def priv_drop(cmd: typing.List[str], *, chroot: bool = False,
+ chdir: typing.Optional[str] = None,
+ setuid: typing.Optional[str] = None,
+ privnet: bool = False,
+ env: typing.Dict[str, str] = {}) -> str:
+ """Returns shlext.join(cmd) with certain privilege dropping operations
+ applied:
+ * When chroot is True, chroot to "$1".
+ * When chdir is set, chdir to it after chrooting. Should be absolute.
+ * When setuid is set, change the user id and group id. Names allowed.
+ * When privnet is True, create a new network namespace with an active
+ loopback interface.
+ * Environment variables from env will be set.
+ """
+ if chdir or env:
+ envcmd = ["env"]
+ if chdir:
+ envcmd.extend(["--chdir", chdir])
+ envcmd.extend(map("%s=%s".__mod__, env.items()))
+ cmd = envcmd + cmd
+ cmdstring = ""
+ if setuid or chroot:
+ cmdstring = "chroot "
+ if setuid:
+ cmdstring += "--userspec %s " % \
+ shlex.quote("%s:%s" % (setuid, setuid))
+ cmdstring += '"$1" ' if chroot else "/ "
+ cmdstring += shlex.join(cmd)
+ if privnet:
+ cmd = ["unshare", "--net",
+ "sh", "-c", "ip link set dev lo up && " + cmdstring]
+ cmdstring = shlex.join(cmd)
+ if chroot:
+ cmdstring += ' exec "$1"'
+ return cmdstring
+
+def main() -> None:
+ """Entry point for mdbp-mmdebstrap backend"""
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--mirror", type=str, action="store",
+ help="mirror url to fetch packages from")
+ parser.add_argument("buildjson", type=buildjson)
+ args = parser.parse_args()
+ build = args.buildjson
+
+ if build.get("lintian", {}).get("run"):
+ raise ValueError("running lintian not supported")
+ if build.get("bd-uninstallable-explainer") not in (None, "apt"):
+ raise ValueError("bd-uinstallable-explainer %r not supported" %
+ build.get("bd-uinstallable-explainer"))
+
+ buildarch = build.get("buildarch") or \
+ subprocess.check_output(["dpkg", "--print-architecture"],
+ encoding="ascii").strip()
+ hostarch = build.get("hostarch") or buildarch
+
+ if buildarch == hostarch:
+ buildessential = set(("build-essential",))
+ else:
+ buildessential = set(("crossbuild-essential-" + hostarch,
+ "libc-dev:" + hostarch,
+ "libstdc++-dev:" + hostarch))
+ buildpath = pathlib.PurePath(build.get("buildpath", "/build/build"))
+
+ with get_dsc(build) as dscpath, \
+ tempfile.NamedTemporaryFile("w+") as script:
+ dscfiles = [dscpath] + get_dsc_files(dscpath)
+
+ script.write("#!/bin/sh\nset -u\nset -e\n")
+ script.write("%s\n" % priv_drop(["chown", "-R", "build:build",
+ str(buildpath.parent)],
+ chroot=True))
+ cmd = ["apt-get", "build-dep", "--yes",
+ "--host-architecture", hostarch]
+ cmd.extend(dict(any=["--arch-only"],
+ all=["--indep-only"]).get(build.get("type"), []))
+ cmd.extend(profile_option(build, "--build-profiles"))
+ cmd.append(str(buildpath.parent / dscpath.name))
+ if build.get("bd-uninstallable-explainer") == "apt":
+ script.write("if ! %s\nthen\n" % priv_drop(cmd, chroot=True))
+ cmd[-1:-1] = ['-oDebug::pkgProblemResolver=true',
+ '-oDebug::pkgDepCache::Marker=1',
+ '-oDebug::pkgDepCache::AutoInstall=1',
+ '-oDebug::BuildDeps=1']
+ script.write("%s\nfi\n" % priv_drop(cmd, chroot=True))
+ else:
+ script.write("%s\n" % priv_drop(cmd, chroot=True))
+ script.write("%s\n" % priv_drop(
+ ["dpkg-source", "--no-check", "--extract", dscpath.name,
+ buildpath.name],
+ setuid="build", chroot=True, chdir=str(buildpath.parent)))
+ script.write("%s\n" % priv_drop(["rm"] + [f.name for f in dscfiles],
+ chroot=True,
+ chdir=str(buildpath.parent)))
+ cmd = ["dpkg-buildpackage", "-uc", "--host-arch=" + hostarch,
+ "--build=" + build.get("type", "binary")]
+ cmd.extend(profile_option(build, "--build-profiles="))
+ script.write("%s\n" % priv_drop(
+ cmd, chroot=True, setuid="build",
+ privnet=not build.get("network") in ("enable", "try-enable"),
+ chdir=str(buildpath), env=compute_env(build)))
+ script.write("%s\n" % priv_drop(["rm", "-R", str(buildpath)],
+ chroot=True))
+ # Only close the file object, script.close would delete it.
+ # Unfortunatly, mypy doesn't grok the indirection and thinks that
+ # script already is the file.
+ script.file.close() # type: ignore[attr-defined]
+ # World readable as the hook may be run by a different uid.
+ pathlib.Path(script.name).chmod(0o755)
+ cmd = [
+ "mmdebstrap",
+ "--verbose",
+ "--mode=unshare",
+ "--variant=apt",
+ "--architectures=" +
+ ",".join(dict.fromkeys((buildarch, hostarch))),
+ "--include=" + ",".join(buildessential),
+ '--customize-hook=chroot "$1" useradd --user-group --create-home '
+ '--home-dir %s build --skel /nonexistent' %
+ shlex.quote(str(buildpath.parent)),
+ "--customize-hook=copy-in %s %s" %
+ (shlex.join(map(str, dscfiles)),
+ shlex.quote(str(buildpath.parent))),
+ "--customize-hook=" + script.name,
+ "--customize-hook=sync-out " +
+ shlex.join([str(buildpath.parent),
+ build["output"]["directory"]]),
+ build["distribution"],
+ "/dev/null",
+ args.mirror or "http://deb.debian.org/debian",
+ ]
+ cmd.extend(build.get("extrarepositories", ()))
+ proc = subprocess.Popen(cmd)
+ proc.wait()
+
+if __name__ == "__main__":
+ main()