mrrl

Minimal Reliable Reproducible Linux
git clone https://ccx.te2000.cz/git/mrrl
Log | Files | Refs | Submodules | README

commit ebd9f162ea1c2dea66096e90273aaa48e97d1169
parent 71b44db8d598f3c1d8b182f0c321803e79b2a4d2
Author: ccx <ccx@te2000.cz>
Date:   Thu, 17 Oct 2024 04:01:05 +0000

Move package manager code into submodules

Diffstat:
M.gitignore | 7+------
M.gitmodules | 6++++++
MMakefile | 37++++++++-----------------------------
Dcommand/pthbs-build | 316-------------------------------------------------------------------------------
Dcommand/pthbs-digest-files | 7-------
Dcommand/pthbs-digest-tree | 5-----
Dcommand/pthbs-download | 54------------------------------------------------------
Dcommand/pthbs-enter-gen | 100-------------------------------------------------------------------------------
Dcommand/pthbs-getenvhash | 2--
Dcommand/pthbs-install | 44--------------------------------------------
Dcommand/pthbs-link | 20--------------------
Dcommand/pthbs-makegen | 158-------------------------------------------------------------------------------
Dcommand/pthbs-makegen-downloads | 31-------------------------------
Dcommand/pthbs-namedenv | 55-------------------------------------------------------
Dgenpkg.py | 194-------------------------------------------------------------------------------
Dmake_vars.py | 70----------------------------------------------------------------------
Dns_sandbox.py | 632-------------------------------------------------------------------------------
Dpackages.aat | 45---------------------------------------------
Apthbs | 1+
Apthbs_genpkgpy | 1+
Dpy-cp311-cp311-musllinux_1_2_x86_64-requirements.txt | 38--------------------------------------
Dpy-requirements.in | 5-----
Dpython.mk | 60------------------------------------------------------------
Dscript_env.txt | 3---
Dscript_header.txt | 11-----------
25 files changed, 17 insertions(+), 1885 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,8 +1,3 @@ *.sw[op] -*.pyfmt -downloads -files/by-sha256 -sources/by-commit work -make -namedenv +cache diff --git a/.gitmodules b/.gitmodules @@ -95,3 +95,9 @@ [submodule "sources/easyseccomp"] path = sources/easyseccomp url = https://github.com/giuseppe/easyseccomp +[submodule "pthbs"] + path = pthbs + url = ./pthbs +[submodule "pthbs_genpkgpy"] + path = pthbs_genpkgpy + url = ./pthbs_genpkgpy diff --git a/Makefile b/Makefile @@ -1,35 +1,14 @@ -# default to homedir if not root -VERSIONS:=$(patsubst /root/%,/%,$(HOME)/versions) +pthbs:=./pthbs +pthbs_genpkgpy:=./pthbs_genpkgpy -export PATH:=$(CURDIR)/command:$(PATH) -export pthbs_versions:=$(VERSIONS) +packages:=packages +work:=work +cache:=cache +templates:=templates default: default.environment userspace.environment containers.environment mdevd.environment -$(VERSIONS)/environment.%/.env: - @echo mkdir "$$(dirname '$@')" - @echo touch '$@' - - -%/.exists: - mkdir -p '$*' - touch '$@' - -make/package.%.mk: packages/% make/.exists command/pthbs-makegen - pthbs-makegen 'packages/$*' >'$@.new' - mv '$@.new' '$@' - -make/downloads.%.mk: downloadlist.% make/.exists command/pthbs-makegen-downloads - pthbs-makegen-downloads '$*' 'downloadlist.$*' >'$@.new' - mv '$@.new' '$@' - -include make/downloads.sha256.mk - -pkg_files=$(wildcard packages/*) -#env_files=$(wildcard environments/*) -mk_files=$(patsubst packages/%,make/package.%.mk,$(pkg_files)) -include $(mk_files) - +include $(pthbs)/pthbs.mk ifneq (,$(filter py%,$(MAKECMDGOALS))) -include python.mk +include $(pthbs_genpkgpy)/genpkgpy.mk endif diff --git a/command/pthbs-build b/command/pthbs-build @@ -1,316 +0,0 @@ -#!/bin/sh -bsh=$(sha256sum $1) || exit $? -bsh=${bsh%% *} -basedir=$(dirname "$(dirname "$(realpath "$0")")") -if ! test -d "$basedir"; then - printf '%s\n' "Error: could not determine basedir" - exit 1 -fi -mkdir -p "$basedir/work/builddir.$$" || exit $? -workdir=$(realpath "$basedir/work/builddir.$$") -if ! test -d "$workdir"; then - printf '%s\n' "Error: could not determine workdir" - exit 1 -fi -script=$(realpath "$1") -if ! test -f "$script"; then - printf '%s\n' "Error: could not determine script path" - exit 1 -fi -if test -z "$pthbs_versions"; then - case $(id -u) in - (0) pthbs_versions=/versions ;; - (*) pthbs_versions=$HOME/versions ;; - esac - export pthbs_versions -fi - -pthbs_package=${1##*/} -pthbs_package=${pthbs_package%%:*}.$bsh - -printf "BUILDING %s => %s => %s\n" "$1" "$workdir" "$pthbs_package" - -eval "$( - awk ' -BEGIN { - settings["sandbox"] = 1 - settings["set_path"] = 1 - FS=":" -} -/^#@pragma:/ { - if($2 == "nosandbox") { - settings["sandbox"] = 0 - } else if($2 == "nopath") { - settings["set_path"] = 0 - } else { - fatal("unrecognized @pragma:") - } -} -END { - print "setting_sandbox=" (settings["sandbox"]?"true":"false") - print "setting_set_path=" (settings["set_path"]?"true":"false") -} -' "$1" )" - -if test -f "make/package.sha256.${bsh}.env"; then - envfile="make/package.sha256.${bsh}.env" - envhash=$(pthbs-getenvhash "$envfile") || exit $? - export pthbs_build_environment=/versions/env.$envhash - if $setting_set_path; then - if ! test -x "$pthbs_build_environment/command/pthbs-enter"; then - printf >&2 "Error: %s does not exist!" "$pthbs_build_environment/command/pthbs-enter" - exit 1 - fi - fi -fi - -mkdir -p "$workdir" -case $(id -u) in - (0) - sandbox_mode=root - export pthbs_uid=$(id -u pthbs) || exit $? - export pthbs_gid=$(id -g pthbs) || exit $? - export pthbs_install_uid=0 || exit $? - export pthbs_install_gid=$pthbs_gid || exit $? - ;; - (*) - sandbox_mode=userns - ;; -esac - -env \ - basedir="$basedir" \ - workdir="$workdir" \ - script="$script" \ - envdir="$pthbs_build_environment" \ - awk -v single_quote="'" -v sandbox_mode="$sandbox_mode" >"$workdir/pthbs-setup" ' -BEGIN { - settings["sandbox"] = 1 - settings["set_path"] = 1 - FS=":" - print "#!/bin/sh -e" - print "if test -n \"$pthbs_xtrace\"; then pthbs_xtrace=-x; set -x; fi" - print "cd "q(ENVIRON["workdir"]) - if(ENVIRON["pthbs_uid"]) { - print "export pthbs_uid=" ENVIRON["pthbs_uid"] - } - if(ENVIRON["pthbs_gid"]) { - print "export pthbs_gid=" ENVIRON["pthbs_gid"] - } -} - -function q(s) { # quote string for sh - gsub(single_quote, single_quote "\\" single_quote single_quote, s) - return single_quote s single_quote -} - -function dirname(s) { # strip the last path component - sub("/[^/]*$", "", s) - return s -} - -function basename(s) { # strip the last path component - sub("^.*/$", "", s) - return s -} - -function fatal(msg) { - printf "FATAL: pthbs-build: %s %s:%d: \"%s\"\n", msg, FILENAME, FNR, $0 >"/dev/stderr" - exit 1 -} - -function at_git(commit_id, dstdir){ - print "mkdir -p "q(dstdir) - print "(cd "q(ENVIRON["basedir"]"/sources/by-commit/"commit_id)" && git archive --format=tar "q(commit_id)" ) | tar --no-same-owner --no-same-permissions -xC "q(dstdir) -} - -function at_untar(extra_opts, hash_type, file_hash, dstdir){ - print "mkdir -p "q(dstdir) - print "tar -x "extra_opts" -C "q(dstdir)" -f "q(ENVIRON["basedir"]"/files/by-"hash_type"/"file_hash) -} - -function at_filehash(hash_type, file_hash, dst, dstdir){ - if(dst ~ /\//) { - dstdir = dst - sub("/[^/]*$", "", dstdir) - print "mkdir -p "q(dstdir) - } - print "cp -Lp "q(ENVIRON["basedir"]"/files/by-"hash_type"/"file_hash)" "q(dst) -} - -/^#!/ { next } -/^#\+/ { - if($0 == "#+*") { - settings["sandbox"] = 0 - } else if(!length(ENVIRON["envdir"])) { - fatal("dependency specified but no envfile:") - } - next -} -/^#@/ { - if($1 == "#@git") { - if(match($0, "^#@git:[0-9a-f]+:") == 0) { - fatal("invalid syntax for @git:") - } - at_git($2, substr($0, RLENGTH+1)) - } else if($1 == "#@sha256") { - if(match($0, "^#@sha256:[0-9a-f]+:") == 0) { - fatal("invalid syntax for @sha256:") - } - at_filehash("sha256", $2, substr($0, RLENGTH+1)) - } else if($1 == "#@untar") { - if(match($0, "^#@untar:[^:]*:sha256:[0-9a-f]+:") == 0) { - fatal("invalid syntax for @untar:") - } - at_untar($2, $3, $4, substr($0, RLENGTH+1)) - } else if($1 == "#@pragma") { - if($2 == "nosandbox") { - settings["sandbox"] = 0 - } else if($2 == "nopath") { - settings["set_path"] = 0 - } else { - fatal("unrecognized @pragma:") - } - } else { - fatal("unrecognized @command:") - } - next -} -/^$/ { - if(settings["sandbox"]) { - if(length(ENVIRON["PTHBS_SYD"])) { - sandbox_cmd=" SYD_NO_SYSLOG=1 SYD_LOG_FD=3 3>syd.log" - sandbox_cmd=sandbox_cmd " " ENVIRON["PTHBS_SYD"] " -m sandbox/stat:off -m sandbox/exec:off" - sandbox_cmd=sandbox_cmd " -m " q("allow/read+"ENVIRON["workdir"]"/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/write+"ENVIRON["workdir"]"/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/proc/loadavg") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/etc/passwd") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/etc/group") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/tmp/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/write+/tmp/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/dev/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/write+/dev/***") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+"ENVIRON["script"]) - sandbox_cmd=sandbox_cmd " -m " q("allow/read+"dirname(ENVIRON["envdir"])"/***") - sandbox_cmd=sandbox_cmd " -munshare/user:1" - sandbox_cmd=sandbox_cmd " -munshare/mount:1" - sandbox_cmd=sandbox_cmd " -mbind+" q(ENVIRON["basedir"]"/work/bin:/bin:ro,nosuid,nodev") - sandbox_cmd=sandbox_cmd " -m " q("allow/read+/bin/***") - sandbox_cmd=sandbox_cmd " -munshare/net:1 -munshare/ipc:1" - } else if(sandbox_mode == "userns") { - sandbox_cmd=" "q(ENVIRON["basedir"]"/ns_sandbox.py")" --mode=userns" - sandbox_cmd=sandbox_cmd" --vars="q(ENVIRON["basedir"]"/vars.yaml") - sandbox_cmd=sandbox_cmd" --extra-mount=tmpfs:"q(ENVIRON["basedir"]"/work") - sandbox_cmd=sandbox_cmd" --extra-mount=ro_bind:"q(ENVIRON["basedir"]"/packages:"ENVIRON["basedir"]"/packages") - sandbox_cmd=sandbox_cmd" --extra-mount=rw_bind:"q(ENVIRON["workdir"]":"ENVIRON["workdir"]) - sandbox_cmd=sandbox_cmd" --extra-mount=rw_bind:"q(ENVIRON["workdir"]"/.tmp:/tmp") - sandbox_cmd=sandbox_cmd" -- "q(ENVIRON["basedir"]"/work/root") - printf "%s\n", "mkdir -p "q(ENVIRON["workdir"]"/.tmp") - } else if(sandbox_mode == "root") { - sandbox_cmd=" "q(ENVIRON["basedir"]"/work/venv/bin/python")" "q(ENVIRON["basedir"]"/ns_sandbox.py")" --mode=root" - sandbox_cmd=sandbox_cmd" --untar="q(ENVIRON["basedir"]"/root.tar") - sandbox_cmd=sandbox_cmd" --vars="q(ENVIRON["basedir"]"/vars.yaml") - sandbox_cmd=sandbox_cmd" --chdir="q(ENVIRON["workdir"]) - sandbox_cmd=sandbox_cmd" --extra-mount=tmpfs:"q(ENVIRON["basedir"]"/work") - sandbox_cmd=sandbox_cmd" --extra-mount=ro_bind:"q(ENVIRON["basedir"]"/packages:"ENVIRON["basedir"]"/packages") - sandbox_cmd=sandbox_cmd" --extra-mount=rw_bind:"q(ENVIRON["workdir"]":"ENVIRON["workdir"]) - sandbox_cmd=sandbox_cmd" --extra-mount=rw_bind:"q(ENVIRON["workdir"]"/.tmp:/tmp") - sandbox_cmd=sandbox_cmd" -- "q(ENVIRON["basedir"]"/work/root") - printf "%s\n", "mkdir -p "q(ENVIRON["workdir"]"/.tmp") - } else { - fatal("unrecognized sanbox_mode " sandbox_mode) - } - } else if(ENVIRON["pthbs_uid"]){ - sandbox_cmd="busybox chpst -u \"$pthbs_uid:$pthbs_gid\" --" - } else { - sandbox_cmd="" - } - if(ENVIRON["pthbs_uid"]) { - printf "%s\n", "chown -R \"$pthbs_uid:$pthbs_gid\" "q(ENVIRON["workdir"]) - } - if(length(ENVIRON["envdir"])){ - cmd="env pthbs_build_environment="q(ENVIRON["envdir"]) - cmd=cmd" "sandbox_cmd - if(settings["set_path"]) { - cmd=cmd" "q(ENVIRON["envdir"]"/command/pthbs-enter") - } - cmd=cmd" sh -xe "q(ENVIRON["script"]) - } else { - cmd=sandbox_cmd" sh -xe "q(ENVIRON["script"]) - } - print "exec >build.log 2>&1 " cmd - exit 0 -} -{ - fatal("unexpected line") -} -' "$1" || exit $? - -if test -z "$JOBS"; then - JOBS=$(nproc) - if test -z "$JOBS"; then - JOBS=$(grep -ce '^processor' /proc/cpuinfo) - if test -z "$JOBS"; then - JOBS=1 - fi - fi -fi - -logdir=work/logs/$(date '+%Y-%m-%d-%H%M%S')-$pthbs_package -ret=0 -if test -n "$pthbs_xtrace"; then pthbs_xtrace=-x; set -x; fi -trap 'trap - INT' INT -if env -i \ - PATH="$PATH" \ - JOBS="$JOBS" \ - pthbs_script="$script" \ - pthbs_destdir="$workdir/destdir" \ - pthbs_package="$pthbs_package" \ - sh $pthbs_xtrace -e "$workdir/pthbs-setup" </dev/null; then - trap - INT - printf "BUILD SUCCESFUL :: %s\n" "$pthbs_package" - if ! pthbs-install "$workdir/destdir" "$pthbs_package"; then - ret=1 - echo "INSTALL FAILED" - fi -else - ret=$? - trap - INT - if test -n "$pthbs_fail_log_cmd"; then - $pthbs_fail_log_cmd "$workdir/build.log" - else - printf "Errors found in log:\n" - grep -C 1 -Eie '(error|fatal)[: ]' "$workdir/build.log" - fi - printf "BUILD FAILED: exitcode %s :: %s :: %s :: %s\n" "$ret" "$1" "$workdir" "$logdir" - mkdir -p "$logdir" - rm -v "$(dirname "$logdir")/last_failed_build" - ln -v -s -f "$(basename "$logdir")" "$(dirname "$logdir")/last_failed_build" - find "$workdir" -name config.log -exec cp -v --backup=numbered '{}' "$logdir/config.log" \; - pthbs-digest-tree >"$logdir/workdir-digest" "$workdir" -fi - -mkdir -p "$logdir" - -if test -e "$workdir/build.log"; then - mv "$workdir/build.log" "$logdir/log" - bzip2 "$logdir/log" -fi - -if test -e "$workdir/syd.log"; then - mv "$workdir/syd.log" "$logdir/syd.log" - bzip2 "$logdir/syd.log" -fi - -if test -e "$workdir/pthbs-setup"; then - mv "$workdir/pthbs-setup" "$logdir/" -fi - -if test -n "$pthbs_build_environment"; then - ln -s "$pthbs_build_environment" "$logdir/env" -fi - -if test -z "$pthbs_skip_cleanup"; then - rm -rf "$workdir" || ret=$? -fi -exit $ret diff --git a/command/pthbs-digest-files b/command/pthbs-digest-files @@ -1,7 +0,0 @@ -#!/bin/sh -for fname in "$@"; do - busybox stat -c "# %F %a %u:%g %sB %N" "$fname" || exit $? - if test -f "$fname"; then - sha256sum "$fname" || exit $? - fi -done diff --git a/command/pthbs-digest-tree b/command/pthbs-digest-tree @@ -1,5 +0,0 @@ -#!/bin/sh -if test -n "$1"; then - cd "$1" || exit $? -fi -busybox find -print0 | busybox sort -z | busybox xargs -0 pthbs-digest-files diff --git a/command/pthbs-download b/command/pthbs-download @@ -1,54 +0,0 @@ -#!/bin/sh -test $# -gt 3 || exit 2 -checksum_format=$1 -checksum=$2 -size=$3 -shift 3 || exit -filename=downloads/$checksum_format/$checksum - -case $checksum_format in - (md5);; - (sha1);; - (sha256);; - (sha3);; - (sha512);; - (*) - printf>&2 "FATAL: unrecognized checksum format '%s'\n" "$checksum_format" - exit 1 - ;; -esac - -check() { - test -e "$filename" || return 1 - ls -l "$filename" - find "$filename" -type f -size +"$size"c -delete - case $(find "$filename" -type f -size "$size"c -print) in - ($filename);; - (*) return 1;; - esac - if printf '%s %s\n' "$checksum" "$filename" | ${checksum_format}sum -c -; then - ln -sf "../../$filename" files/by-sha256 || exit 1 - return 0 - fi - rm -v "$filename" - exit 1 -} - -test -z "$pthbs_xtrace" || set -x - -mkdir -p "downloads/$checksum_format" || exit $? - -if check; then - exit 0 -fi - -while test $# -gt 0; do - wget -cqO "$filename" -- "$1" - if check; then - exit 0 - fi - shift -done - -printf>&2 "FATAL: could not download file\n" -exit 1 diff --git a/command/pthbs-enter-gen b/command/pthbs-enter-gen @@ -1,100 +0,0 @@ -#!/bin/sh -e - -if test $# != 1; then - printf >2 'usage: %s\n' "pthbs-enter-gen /path/to/env.hash" - printf >2 'Error: expected 1 argument, got%s\n' "$#" - exit 2 -fi -envtop=$1 -envdir=$envtop/env -envcommand=$envtop/command - -case $(basename "$envtop") in - (env.*) ;; - (*) - printf >&2 "Error: '%s' doesn't look like environment path!\n" "$envtop" - exit 1;; -esac - -mkdir -p "$envdir" - -if ! test -d "$envcommand"; then - printf >&2 "Error: '%s' doesn't exist!\n" "$envcommand" -fi -printf '%s\n' "$envcommand" >"$envdir/PATH" - -if test -d "$envtop/zsh"; then - printf '%s\n' "$envtop/zsh/site-functions:$envtop/zsh/functions" >"$envdir/FPATH" -fi - -if test -d "$envtop/library/pkgconfig"; then - printf '%s\n' "$envtop/library/pkgconfig" >"$envdir/PKG_CONFIG_LIBDIR" -fi - -mkscript() { - exe=$envcommand/$1 - cat >"$exe.new" - chmod +x "$exe.new" - mv "$exe.new" "$exe" -} - -exe=$envcommand/pthbs-enter -if test -x "$envcommand/execlineb" && test -x "$envcommand/s6-envdir"; then - mkscript pthbs-enter <<EOF -#!$envcommand/execlineb -S0 -"$envcommand/s6-envdir" "$envdir" "$envcommand/exec" \$@ -EOF - "$envcommand/pthbs-enter" true # TODO: don't run executables without sandbox - mkscript pthbs-enter-execlineb <<EOF -#!$envcommand/execlineb -S0 -"$envcommand/s6-envdir" "$envdir" "$envcommand/execlineb" \$@ -EOF - if test -x "$envcommand/sh"; then - mkscript pthbs-enter-sh <<EOF -#!$envcommand/execlineb -S0 -"$envcommand/s6-envdir" "$envdir" "$envcommand/sh" \$@ -EOF - fi -elif test -x "$envcommand/sh" && test -x "$envcommand/envdir"; then - mkscript pthbs-enter <<EOF -#!$envcommand/sh -exec "$envcommand/envdir" "$envdir" "$envcommand/env" -- "\$@" -EOF - "$envcommand/pthbs-enter" true # TODO: don't run executables without sandbox - mkscript pthbs-enter-sh <<EOF -#!$envcommand/sh -exec "$envcommand/envdir" "$envdir" "$envcommand/sh" "\$@" -EOF -fi - -if test -x "$envcommand/install-as-current-environment.postinstall"; then - mkscript install-as-current-environment <<EOF -#!$envcommand/execlineb -P -"$envcommand/s6-envdir" "$envdir" "$envcommand/exec" -ifelse -n { test -L /run/current } { - if { ln -s -f -v "$envtop" /run/current } - "$envcommand/install-as-current-environment.postinstall" -} -foreground { - fdmove -c 1 2 - printf "* Swapping /run/current from %s to " "$envtop" - readlink /run/command -} -getpid NS_PID -importas -i NS_PID NS_PID -if { mv -v /run/current /run/previous.\${NS_PID} } -if { ln -s -f -v "$envtop" /run/current } -ifelse { - "$envcommand/install-as-current-environment.postinstall" /run/previous.\${NS_PID} -} { } -foreground { - fdmove -c 1 2 - printf "* %s returned error, reverting to " "$envcommand/install-as-current-environment.postinstall" - readlink /run/previous.\${NS_PID} -} -if { rm -v /run/current } -if { mv -v /run/previous.\${NS_PID} /run/current } -#foreground { /run/current/command/install-as-current-environment.postinstall } -exit 1 -EOF -fi diff --git a/command/pthbs-getenvhash b/command/pthbs-getenvhash @@ -1,2 +0,0 @@ -#!/bin/sh -sort -u "$@" | sha256sum | sed 's/ .*//' diff --git a/command/pthbs-install b/command/pthbs-install @@ -1,44 +0,0 @@ -#!/bin/sh -e -test -z "$pthbs_xtrace" || set -x -if test -z "$pthbs_versions"; then - echo >&2 "ERROR: pthbs_versions is undefined" - exit 2 -elif ! test -d "$pthbs_versions"; then - echo >&2 "ERROR: pthbs_versions '$pthbs_versions' is not directory" - exit 1 -fi - -test "$#" -eq 2 || exit 2 - -pthbs_destdir="$1" -pthbs_package="$2" - -if ! test -f "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package/.install-links"; then - echo >&2 "ERROR: link file not found" - exit 1 -fi - -if test -n "$pthbs_install_uid"; then - chown -R "$pthbs_install_uid:$pthbs_install_gid" "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package" -fi -chmod -R ugo-w "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package" -pthbs-digest-tree >"$pthbs_destdir/pthbs-digest" "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package/" || exit $? -mv "$pthbs_destdir/pthbs-digest" "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package/.pthbs-digest" || exit $? - -if test -e "$pthbs_versions/$pthbs_package"; then - if ! diff -u "$pthbs_versions/$pthbs_package" "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package/.pthbs-digest"; then - echo >&2 "ERROR: digest file mismatch - packages with same buildhash differ" - exit 3 - fi - N=1 - while test -e "$pthbs_versions/$pthbs_package.$N"; do - N=$(1+$N) - done - echo >&2 "INFO: replacing previous package" - mv "$pthbs_versions/$pthbs_package" "$pthbs_versions/$pthbs_package.$N" - # remove dangling links - find "$pthbs_versions" -type l ! -exec test -e {} \; -exec rm -v {} + -fi - -mv "$pthbs_destdir/${pthbs_versions%/}/$pthbs_package" "$pthbs_versions/$pthbs_package" -find "$pthbs_destdir" -type d -o -exec printf 'WARNING: stray file in destdir: %s\n' '{}' + diff --git a/command/pthbs-link b/command/pthbs-link @@ -1,20 +0,0 @@ -#!/bin/sh -e -test -d "$1" || exit 1 -test -d "$2" || exit 1 -while IFS= read -r line; do - target=${line% *} - source=${line#* } - target=$1/${target#./} - source=$2/${source#./} - if test -e "$source"; then - printf >&2 'FATAL: link source already exists: "%s"\n' "$source" - ls >&2 -l "$source" - exit 1 - fi - if ! test -e "$target"; then - printf >&2 'FATAL: link target does not exists: "%s"\n' "$target" - exit 1 - fi - mkdir -p "${source%/*}" - ln -s "$target" "$source" -done <"$1/.install-links" diff --git a/command/pthbs-makegen b/command/pthbs-makegen @@ -1,158 +0,0 @@ -#!/bin/sh -#set -x -bsh=$(sha256sum $1) || exit $? -bsh=${bsh%% *} -basedir=$(dirname "$(dirname "$(realpath "$0")")") -workdir=$basedir/work/builddir.$$ -script=$(realpath "$1") - -pthbs_package=${1##*/} -pthbs_package=${pthbs_package%%:*}.$bsh - -exec env \ - basedir="$basedir" \ - workdir="$workdir" \ - script="$script" \ - scriptname="${script##*/}" \ - bsh="$bsh" \ - package="$pthbs_package" \ - awk -v single_quote="'" ' -BEGIN { - while(getline <"downloadlist.sha256") { - downloadable_hashes["sha256",$1] = 1 - } - close("downloadlist.sha256") - - settings["sandbox"] = 1 - settings["set_path"] = 1 - dep_count = 0 - env_count = 0 - is_envfile = ENVIRON["script"] ~ /\.environment$/ - if(is_envfile) { - envname = substr(ENVIRON["scriptname"], 1, length(ENVIRON["scriptname"])-12) - } - FS=":" -} - -function q(s) { # quote string for sh - gsub(single_quote, single_quote "\\" single_quote single_quote, s) - return single_quote s single_quote -} - -function fatal(msg) { - printf "FATAL: pthbs-makegen: %s %s:%d: \"%s\"\n", msg, FILENAME, FNR, $0 >"/dev/stderr" - exit 1 -} - -function have_file(hash_type, file_hash) { - if(!((hash_type, file_hash) in downloadable_hashes)) { - return - } - deps[++dep_count] = "make/file."hash_type"."file_hash".downloaded" -} - -function have_envdep(package) { - if(match(package, "[.][0-9a-f]{64}$") == 0) { - fatal("invalid syntax for @sha256:") - } - env[++env_count] = package - package_hash = substr(package, 64, RSTART+1) - envdep[env_count] = "$(VERSIONS)/"package"/.install-links" -} - -function make_envfile( n, envfile, envhash) { - if(!env_count) { - fatal("environment is empty") - } - envfile = "make/package.sha256."ENVIRON["bsh"]".env" - for(n=1; n<=env_count; n++) { - print env[n] >envfile - } - close(envfile) - if(("pthbs-getenvhash "q(envfile) | getline) < 1) { - fatal("Error getting envhash from "q(envfile)) - } - envhash=$0 - if(match(envhash, "^[0-9a-f]{64}$") == 0) { - fatal("received invalid envhash: "q(envhash)) - } - deps[++dep_count] = "$(VERSIONS)/env."envhash"/.pthbs-env" - printf "ifndef %s\n", "env_"envhash"_defined" - printf "%s:", "$(VERSIONS)/env."envhash"/.pthbs-env" - for(n=1; n<=env_count; n++) { - printf " %s", envdep[n] - } - printf "\n\t@%s", "printf "q("Creating environment for %s => %s\\n")" "q(FILENAME)" "q("$(VERSIONS)/env."envhash) - printf "\n\t%s", "if test -e "q("$(VERSIONS)/env."envhash)"; then rm -r "q("$(VERSIONS)/env."envhash)"; fi" - printf "\n\t%s", "mkdir -p "q("$(VERSIONS)/env."envhash"/env") - for(n=1; n<=env_count; n++) { - printf "\n\t%s", "pthbs-link "q("$(VERSIONS)/"env[n])" "q("$(VERSIONS)/env."envhash) - } - printf "\n\t%s", "pthbs-enter-gen "q("$(VERSIONS)/env."envhash) - printf "\n\t%s\n\n", "sort -u <"q(envfile)" >"q("$@") - printf "%s\n", "env_"envhash"_defined="ENVIRON["scriptname"] - printf "%s\n", "endif" - return envhash -} -/^#!/ { next } -/^#\+/ { - if($0 == "#+*") { - settings["sandbox"] = 0 - } else { - have_envdep(substr($0, 3)) - } - next -} -/^#@/ && !is_envfile { - if($1 == "#@git") { - next - } else if($1 == "#@pragma") { - if($2 == "nosandbox") { - settings["sandbox"] = 0 - } else if($2 == "nopath") { - settings["set_path"] = 0 - } else { - fatal("unrecognized @pragma:") - } - next - } else if($1 == "#@sha256") { - if(match($0, "^#@sha256:[0-9a-f]+:") == 0) { - fatal("invalid syntax for @sha256:") - } - have_file("sha256", $2) - } else if($1 == "#@untar") { - if(match($0, "^#@untar:[^:]*:sha256:[0-9a-f]+:") == 0) { - fatal("invalid syntax for @untar:") - } - have_file("sha256", $4) - } else { - fatal("unrecognized @command:") - } - next -} -/^$/ && !is_envfile { - if(env_count) { - make_envfile() - } - printf "%s", "$(VERSIONS)/"ENVIRON["package"]"/.install-links:" - for(n=1; n<=dep_count; n++) { - printf " %s", deps[n] - } - printf "\n\t%s\n", "if test -f "q("$@")"; then touch "q("$@")"; else pthbs-build "q(ENVIRON["script"])"; fi" - has_body = 1 - exit 0 -} -{ - fatal("unexpected line") -} -END{ - if(!is_envfile){ - if(has_body) { exit 0 } - fatal("no build script present") - } - env_installdir = "$(VERSIONS)/env." make_envfile() - printf "%s: %s %s\n", ENVIRON["scriptname"], env_installdir"/.pthbs-env", "namedenv/.exists" - printf "\t%s\n\n", "pthbs-namedenv "q(env_installdir)" "q(envname) - printf ".PHONY: %s\n", ENVIRON["scriptname"] -} -' "$script" diff --git a/command/pthbs-makegen-downloads b/command/pthbs-makegen-downloads @@ -1,31 +0,0 @@ -#!/bin/sh -format=$1 -shift || exit $? -exec awk -v format="$format" -v single_quote="'" ' -function q(s) { # quote string for sh - gsub(single_quote, single_quote "\\" single_quote single_quote, s) - return single_quote s single_quote -} - -function fatal(msg) { - printf "FATAL: pthbs-makegen: %s %s:%d: \"%s\"\n", msg, FILENAME, FNR, $0 >"/dev/stderr" - exit 1 -} - -/^$/ { next } -/^#/ { next } - -/^[0-9a-f]{64} [0-9]+ /{ - printf "%s:\n", "make/file."format"."$1".downloaded" - printf "\tpthbs-download sha256" - for(n=1; n<=NF; n++){ - printf " %s", q($n) - } - printf "\n\t%s\n\n", "touch "q("$@") - next -} - -{ - fatal("unexpected line") -} -' "$@" diff --git a/command/pthbs-namedenv b/command/pthbs-namedenv @@ -1,55 +0,0 @@ -#!/bin/sh -set -e -if test -n "$pthbs_xtrace"; then set -x; fi -basedir=$(dirname "$(dirname "$(realpath "$0")")") -if test $# != 2; then - printf '%s\n' >&2 'usage: pthbs-namedenv /path/to/env.hash name' - exit 2 -fi -env_installdir=$1 -env_name=$2 -shift -case $env_name in - (*/*) - (*:*) - printf '%s\n' >&2 "pthbs-namedenv: error: invalid name '$env_name'" - exit 2 - ;; -esac -case ${env_installdir##*/} in - (env.*) ;; - (*) - printf '%s\n' >&2 "pthbs-namedenv: error: invalid env installdir '$env_installdir'" - exit 2 - ;; -esac - -bd_link="$basedir/namedenv/$env_name" -v_link_base="${env_installdir%/*}/namedenv.$env_name" -printf "%s => %s\n" "$v_link_base" "${env_installdir##*/}" -if test -L "$bd_link"; then - unlink "$bd_link" -fi -busybox ln -sTf "$env_installdir" "$bd_link" -if test -L "${v_link_base}" && test x"$(realpath "$bd_link")" = x"$(realpath "$v_link_base")"; then - # already present - exit 0 -fi -if test -L "${v_link_base}.9"; then - unlink "${v_link_base}.9" -fi -mvl() { - if test -L "${v_link_base}$1"; then - mv "${v_link_base}$1" "${v_link_base}$2" - fi -} -mvl .8 .9 -mvl .7 .8 -mvl .6 .7 -mvl .5 .6 -mvl .4 .5 -mvl .3 .4 -mvl .2 .3 -mvl .1 .2 -mvl "" .1 -busybox ln -sTf "${env_installdir##*/}" "$v_link_base" diff --git a/genpkg.py b/genpkg.py @@ -1,194 +0,0 @@ -#!/usr/bin/env python3 -import hashlib -import os.path -import subprocess -from pathlib import Path - -import jinja2 -import yaml - - -class SubmoduleInfo: - def __init__(self): - self._current_commits = None - self._by_commit = Path('./sources/by-commit') - - @property - def current(self): - if self._current_commits is not None: - return self._current_commits - out = subprocess.check_output( - ("git", "submodule", "status", "--cached") - ).decode('utf8') - lines = out.strip('\n').split('\n') - records = [[line[0]] + line[1:].split() for line in lines] - self._current_commits = { - r[2][8:]: r[1] for r in records if r[2].startswith("sources/") - } - for repo, commit in self._current_commits.items(): - if not (self._by_commit / commit).exists(): - os.symlink("../" + repo, str(self._by_commit / commit)) - return self._current_commits - - def commit_info(self, commit_id): - assert '/' not in commit_id - assert '.' not in commit_id - assert (self._by_commit / commit_id).exists() - out = subprocess.check_output( - ('git', 'show', '-s', '--pretty=format:%ai by %an'), - cwd=(self._by_commit / commit_id).as_posix(), - ).decode('utf8') - return out - -class DownloadsInfo: - def __init__(self): - self._basenames = {} - with open('downloadlist.sha256', 'rt') as f: - for line in f: - if line[0] in '#\n': - continue - sha256, size, url = line.rstrip().split(maxsplit=2) - assert len(bytes.fromhex(sha256)) == 32 - assert int(size) >= 0 - basename = os.path.basename(url) - if basename in self._basenames: - self._basenames[basename] = ValueError( - 'Duplicate download name: ' + repr(basename) - ) - else: - self._basenames[basename] = 'sha256:' + sha256 - - def __getitem__(self, key): - value = self._basenames[key] - if isinstance(value, Exception): - raise value - return value - - -class FileInfo: - def __init__(self): - self._sha256_cache = {} - self._files_dir = Path('./files') - - def __getitem__(self, key): - if key in self._sha256_cache: - return self._sha256_cache[key] - fp = self._files_dir / key - with fp.open('rb') as f: - file_hash = hashlib.file_digest(f, "sha256").hexdigest() - self._sha256_cache[key] = file_hash - if not (self._files_dir / "by-sha256" / file_hash).exists(): - os.symlink("../" + key, str(self._files_dir / "by-sha256" / file_hash)) - return file_hash - - -class Main: - def __init__(self, out_dir="packages", template_dir="templates"): - self.out_dir = Path(out_dir) - self.template_dir = Path(template_dir) - self.env = jinja2.Environment( - loader=jinja2.FileSystemLoader(template_dir), - undefined=jinja2.StrictUndefined, - autoescape=False, - ) - self.env.globals["pkg_sha256"] = self.pkg_sha256 - self.env.globals["pkg_install_name"] = self.pkg_install_name - self.env.globals["pkg_install_dir"] = self.pkg_install_dir - self.env.globals["submodule"] = SubmoduleInfo() - self.env.globals["files"] = FileInfo() - self.env.globals["downloads"] = DownloadsInfo() - self.package_hashes = {} - self.rendering = [] - self.deps = {} - - def load_vars_yaml(self, fname="vars.yaml"): - with open(fname) as f: - self.env.globals.update(yaml.safe_load(f)) - - def pkg_env_sha256(self, name): - current = self.rendering[-1] - if current not in self.deps: - self.deps[current] = set((name,)) - else: - self.deps[current].add(name) - self._pkg_sha256(name) - envlist = ''.join(sorted('%s.%s\n' % (d, self.package_hashes[d]) for d in self.deps[name])) - return hashlib.sha256(envlist.encode()).hexdigest() - - def pkg_sha256(self, name): - current = self.rendering[-1] - if current not in self.deps: - self.deps[current] = set((name,)) - else: - self.deps[current].add(name) - return self._pkg_sha256(name) - - def _pkg_sha256(self, name): - if name in self.package_hashes: - return self.package_hashes[name] - if name in self.rendering: - raise RuntimeError("circular dependency: %r", self.rendering) - - t = self.env.get_template("pkg/" + name) - self.rendering.append(name) - data = bytes(t.render(name=name).encode('utf8')) - self.package_hashes[name] = hashlib.sha256(data).hexdigest() - lastname = self.rendering.pop() - assert name == lastname - - out_path = self.out_dir / name - old_hash = None - if out_path.exists(): - with out_path.open('rb') as f: - old_hash = hashlib.file_digest(f, "sha256").hexdigest() - if old_hash is None or old_hash != self.package_hashes[name]: - tmp_path = out_path.with_suffix('.new') - tmp_path.write_bytes(data) - tmp_path.replace(out_path) - - return self.package_hashes[name] - - def pkg_install_name(self, name): - rootname = name.split(":")[0] - if rootname.endswith('.environment'): - return "env.%s" % (self.pkg_env_sha256(name),) - else: - return "%s.%s" % (name.split(":")[0], self.pkg_sha256(name)) - - def pkg_install_dir(self, name): - return os.path.join( - self.env.globals["versions"], - self.pkg_install_name(name), - ) - - def list_packages(self): - for tplname in self.env.list_templates(): - if not tplname.startswith("pkg/"): - continue - if "/." in tplname: - continue - yield tplname[4:] - - def render_all(self): - for pkgname in self.list_packages(): - print("%s\t%s" % (pkgname, self._pkg_sha256(pkgname))) - for dep in sorted(self.deps.get(pkgname, ())): - print( - " > %s.%s" - % ( - dep, - self.package_hashes[dep], - ) - ) - - -if __name__ == '__main__': - from pprint import pprint as pp - - m = Main() - m.load_vars_yaml() - pp(m.env.list_templates(filter_func=lambda name: "/." not in name)) - m.render_all() - -# pylama:linters=pycodestyle,pyflakes:ignore=D212,D203,D100,D101,D102,D105,D107 -# vim: sts=4 ts=4 sw=4 et tw=88 efm=%A%f\:%l%\:%c\ %t%n\ %m diff --git a/make_vars.py b/make_vars.py @@ -1,70 +0,0 @@ -#!/usr/bin/env python - -from __future__ import ( - absolute_import, - division, - generators, - print_function, - with_statement, -) - -import sys - -try: - import venv -except ImportError: - venv = None - -try: - from packaging.tags import sys_tags - - def get_tag(): - tag = sys_tags().__next__() - return (tag.interpreter, tag.abi, tag.platform) - -except ImportError: - from wheel.pep425tags import get_abbr_impl, get_abi_tag, get_impl_ver, get_platform - - # sporked from wheel.bdist_wheel.get_tag - def get_tag(): - """Get the specific implementation name in a wheel-compatible format.""" - plat_name = get_platform().replace('-', '_').replace('.', '_') - impl_name = get_abbr_impl() - impl_ver = get_impl_ver() - impl = impl_name + impl_ver - abi_tag = str(get_abi_tag()).lower() - tag = (impl, abi_tag, plat_name) - return tag - - -PY2 = sys.version_info[0] == 2 - - -def main(): - tag = '%s-%s-%s' % get_tag() - mk_filename = 'make/python_vars_%s.mk' % tag - with open(mk_filename, 'wt') as f: - # TODO: add proper escaping whenever I feel like staring into the abyss - f.write('PYTHON_IMPL:=%s\n' % tag) - - f.write( - "PYTHON_VENV:=%s\n" - % ( - "virtualenv -p $(PYTHON_EXE)" - if venv is None - else "$(PYTHON_EXE) -m venv" - ) - ) - - f.write( - "PYTHON_VENV_INSTALL:=%s\n" - % ("'setuptools<45.0.0' 'pip<20.3' 'pip-tools<6'" if PY2 else "'pip-tools'") - ) - print("include %s" % mk_filename) - - -if __name__ == '__main__': - main() - -# pylama:linters=pycodestyle,pyflakes:ignore=D212,D203,D100,D101,D102,D107 -# vim: fileencoding=utf-8 ft=python et sw=4 ts=4 sts=4 tw=79 diff --git a/ns_sandbox.py b/ns_sandbox.py @@ -1,632 +0,0 @@ -#!/usr/bin/python3 -import argparse -import ctypes -import dataclasses -import enum -import errno -import fcntl -import os -import os.path -import pathlib -import select -import stat -import subprocess - -libc = ctypes.CDLL(None, use_errno=True) -CLONE_NEWNS = 0x00020000 # New mount namespace group -CLONE_NEWCGROUP = 0x02000000 # New cgroup namespace -CLONE_NEWUTS = 0x04000000 # New utsname namespace -CLONE_NEWIPC = 0x08000000 # New ipc namespace -CLONE_NEWUSER = 0x10000000 # New user namespace -CLONE_NEWPID = 0x20000000 # New pid namespace -CLONE_NEWNET = 0x40000000 # New network namespace -CLONE_NEWTIME = 0x00000080 # New time namespace - -SYS_pivot_root = 155 - -MNT_FORCE = 1 -MNT_DETACH = 2 -MNT_EXPIRE = 4 -UMOUNT_NOFOLLOW = 8 - - -class MountFlag(int, enum.Enum): - """Mount flags.""" - - #: Mount read-only. - RDONLY = 1 - #: Ignore suid and sgid bits. - NOSUID = 2 - #: Disallow access to device special files. - NODEV = 4 - #: Disallow program execution. - NOEXEC = 8 - #: Writes are synced at once. - SYNCHRONOUS = 16 - #: Alter flags of a mounted FS. - REMOUNT = 32 - #: Allow mandatory locks on an FS. - MANDLOCK = 64 - #: Directory modifications are synchronous. - DIRSYNC = 128 - #: Do not follow symlinks. - NOSYMFOLLOW = 256 - #: Do not update access times. - NOATIME = 1024 - #: Do not update directory access times. - NODIRATIME = 2048 - #: Bind directory at different place. - BIND = 4096 - MOVE = 8192 - REC = 16384 - SILENT = 32768 - #: VFS does not apply the umask. - POSIXACL = 1 << 16 - #: Change to unbindable. - UNBINDABLE = 1 << 17 - #: Change to private. - PRIVATE = 1 << 18 - #: Change to slave. - SLAVE = 1 << 19 - #: Change to shared. - SHARED = 1 << 20 - #: Update atime relative to mtime/ctime. - RELATIME = 1 << 21 - #: This is a kern_mount call. - KERNMOUNT = 1 << 22 - #: Update inode I_version field. - I_VERSION = 1 << 23 - #: Always perform atime updates. - STRICTATIME = 1 << 24 - #: Update the on-disk [acm]times lazily. - LAZYTIME = 1 << 25 - ACTIVE = 1 << 30 - NOUSER = 1 << 31 - - -_mount = libc.mount -_mount.restype = ctypes.c_int -_mount.argtypes = ( - ctypes.c_char_p, - ctypes.c_char_p, - ctypes.c_char_p, - ctypes.c_ulong, - ctypes.c_void_p, -) - -_umount = libc.umount -_umount.restype = ctypes.c_int -_umount.argtypes = (ctypes.c_char_p,) - -_umount2 = libc.umount2 -_umount2.restype = ctypes.c_int -_umount2.argtypes = (ctypes.c_char_p, ctypes.c_int) - -_unshare = libc.unshare -_unshare.restype = ctypes.c_int -_unshare.argtypes = (ctypes.c_int,) - - -def c_path(path): - if path is None: - return path - if isinstance(path, pathlib.PosixPath): - path = path.as_posix() - if isinstance(path, str): - path = path.encode() - return path - - -def c_error(): - return OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) - - -def unshare(flags): - if libc.unshare(flags) != 0: - raise c_error() - - -def pivot_root(new_root, put_old): - if libc.syscall(SYS_pivot_root, c_path(new_root), c_path(put_old)) != 0: - raise c_error() - - -def mount( - source: str, - target: str, - fstype: str, - flags: int = 0, - data: str = None, -): - """Mount filesystem. - - :param source: Device/source to mount. - :param target: Mountpoint. - :param fstype: Filesystem type. Available filesystem types can be found in /proc/filesystems. - :param flags: Mount flags. - :param data: Mount options for specified filesystem. - :raises OSError: If mount call failed with nonzero return code. - """ - if ( - _mount( - c_path(source), - c_path(target), - fstype.encode() if fstype is not None else fstype, - int(flags), - data.encode() if data is not None else data, - ) - != 0 - ): - raise c_error() - - -def bind_mount( - source: str, - target: str, - write: bool = False, -): - return mount( - source, - target, - None, - ( - MountFlag.BIND - | (0 if write else MountFlag.RDONLY) - | MountFlag.NOSUID - | MountFlag.NODEV - ), - ) - - -def umount(target: str): - """Unmount filesystem. - - :param target: Mountpoint. - :raises OSError: If umount call failed with nonzero return code. - """ - if _umount(c_path(target)) != 0: - raise c_error() - - -def lazy_umount(target): - target = c_path(target) - if _umount(target) != 0: - if _umount2(target, MNT_DETACH) != 0: - raise c_error() - - -@dataclasses.dataclass(frozen=True) -class MountInfo: - id: int - parent: int - dev: tuple - root: str - mountpoint: str - - def __post_init__(self): - assert isinstance(self.id, int) - assert isinstance(self.parent, int) - assert isinstance(self.dev, tuple) - minor, major = self.dev - assert isinstance(minor, int) - assert isinstance(major, int) - assert isinstance(self.root, str) - assert self.root[0] == '/' - assert isinstance(self.mountpoint, str) - assert self.mountpoint[0] == '/' - - @classmethod - def from_line(cls, line): - rec = line.split(maxsplit=5) - major, minor = rec[2].split(':') - return cls( - id=int(rec[0]), - parent=int(rec[1]), - dev=(int(major), int(minor)), - root=rec[3], - mountpoint=rec[4], - ) - - -def parse_mountinfo(mountinfo_path='/proc/self/mountinfo'): - root_id = None - mountinfo = {} - with open(mountinfo_path, 'rt') as f: - for line in f: - mi = MountInfo.from_line(line) - if mi.mountpoint == '/': - assert root_id is None - root_id = mi.id - assert mi.id not in mountinfo - mountinfo[mi.id] = mi - assert root_id is not None - return (root_id, mountinfo) - - -def umount_order(mount_id, mountinfo): - for mi in mountinfo.values(): - if mi.parent == mount_id: - yield from umount_order(mi.id, mountinfo) - yield mountinfo[mount_id] - - -def pivot_and_umount(new_root, put_old, umount_list): - mtp_prefix = '/' + put_old.relative_to(new_root).as_posix() - pivot_root(new_root, put_old) - os.chdir('/') # so we don't stand in the old root - for mtp in umount_list: - lazy_umount(mtp_prefix + mtp) - - -def nonblock_cloexec(fd): - return fcntl.fcntl( - fd, - fcntl.F_SETFD, - fcntl.fcntl(fd, fcntl.F_GETFD) | os.O_NONBLOCK | fcntl.FD_CLOEXEC, - ) - - -def exit_status(status): - sig = status & 0xFF - ret = status >> 8 - if sig: - raise SystemExit(128 + sig) - if ret >= 128: - raise SystemExit(128) - raise SystemExit(ret) - - -def exec_command(argv): - if argv[0][0] == '/': - os.execv(argv[0], argv) - for d in os.environ['PATH'].split(':'): - try: - os.execv(os.path.join(d, argv[0]), argv) - except FileNotFoundError: - continue - raise SystemExit(127) - - -def map_uid_gid(orig_uid, orig_gid): - with open('/proc/self/uid_map', 'wt') as f: - f.write(f'{orig_uid} {orig_uid} 1\n') - - with open('/proc/self/setgroups', 'wt') as f: - f.write('deny\n') - - with open('/proc/self/gid_map', 'wt') as f: - f.write(f'{orig_gid} {orig_gid} 1\n') - - os.setuid(orig_uid) - os.setgid(orig_gid) - - -def pidns_run(unshare_flags, run_pid1=True): - (parent_rfd, parent_wfd) = os.pipe() - nonblock_cloexec(parent_rfd) - nonblock_cloexec(parent_wfd) - orig_uid = os.getuid() - orig_gid = os.getgid() - unshare(CLONE_NEWPID | unshare_flags) - if unshare_flags & CLONE_NEWUSER: - map_uid_gid(orig_uid, orig_gid) - fork_pid = os.fork() - if fork_pid == 0: - # child - assert os.getpid() == 1 - os.close(parent_wfd) - if run_pid1: - return pidns_pid1(parent_rfd) - else: - return parent_rfd - else: - # parent - os.close(parent_rfd) - (pid, status) = os.waitpid(fork_pid, 0) - exit_status(status) - - -def pidns_pid1(parent_rfd): - fork2_pid = os.fork() - if fork2_pid == 0: - # child - return - else: - # parent - rlist, wlist, elist = (parent_rfd,), (), () - while True: - (pid, status) = os.waitpid(0, os.WNOHANG) - if pid == fork2_pid: - exit_status(status) - try: - r, w, x = select.select(rlist, wlist, elist, 1.0) - except select.error as e: - code, msg = e.args - # We might get interrupted by SIGCHLD here - if code != errno.EINTR: - raise - - -@dataclasses.dataclass(frozen=True) -class MountTMPFS: - path: pathlib.PosixPath - - def __post_init__(self): - assert isinstance(self.path, pathlib.PosixPath) - assert not self.path.is_absolute() - - def mount(self, root): - dst = root / self.path - dst.mkdir(parents=True, exist_ok=True) - mount('tmpfs', dst, 'tmpfs', MountFlag.NOSUID | MountFlag.NODEV) - - -@dataclasses.dataclass(frozen=True) -class MountBind: - src: pathlib.PosixPath - dst: pathlib.PosixPath - write: bool = False - - def __post_init__(self): - assert isinstance(self.src, pathlib.PosixPath) - assert self.src.is_absolute() - assert isinstance(self.dst, pathlib.PosixPath) - assert not self.dst.is_absolute() - - def mount(self, root): - dst = root / self.dst - if self.src.is_dir(): - dst.mkdir(parents=True, exist_ok=True) - bind_mount(self.src, dst, self.write) - - -def relpath(s): - p = pathlib.PosixPath(s) - return p.relative_to('/') if p.is_absolute() else p - - -def parse_mount(s): - m_type, rest = s.split(':', maxsplit=1) - if m_type == 'tmpfs': - return MountTMPFS(relpath(rest)) - elif m_type in ('rw_bind', 'ro_bind'): - write = m_type == 'rw_bind' - src, dst = rest.split(':', maxsplit=1) - return MountBind(pathlib.PosixPath(src), relpath(dst), write) - raise ValueError(m_type) - - -@dataclasses.dataclass(frozen=True) -class Settings: - versions: pathlib.PosixPath - root: pathlib.PosixPath - chdir: pathlib.PosixPath - vars: dict - command: tuple - extra_mount: tuple - drop_to: tuple = None - untar: pathlib.PosixPath = None - - def __post_init__(self): - assert isinstance(self.command, tuple) - assert all(isinstance(arg, (str, bytes)) for arg in self.command) - - assert isinstance(self.extra_mount, tuple) - assert all(isinstance(arg, (MountTMPFS, MountBind)) for arg in self.extra_mount) - - assert isinstance(self.chdir, pathlib.PosixPath) - assert self.chdir.is_absolute() - - assert isinstance(self.versions, pathlib.PosixPath) - assert self.versions.is_absolute() - assert self.versions.is_dir() - - if self.drop_to is not None: - assert isinstance(self.drop_to, tuple) - uid, gid = self.drop_to - assert isinstance(uid, int) - assert isinstance(gid, int) - - assert isinstance(self.untar, (pathlib.PosixPath, type(None))) - - assert isinstance(self.root, pathlib.PosixPath) - assert self.root.is_absolute() - assert self.root.is_dir(), self.root - if self.untar is None: - self._check_root() - - def _check_root(self): - assert (self.root / 'oldroot').is_dir() - assert (self.root / 'proc').is_dir() - assert (self.root / 'dev').is_dir() - assert (self.root / 'bin').is_dir() - assert (self.root / 'bin/sh').exists() - - @classmethod - def from_args_and_env(cls, args, env): - if args.vars: - import yaml - - with args.vars.open('rt') as f: - v = yaml.safe_load(f) - else: - v = {} - - return cls( - versions=(args.versions or pathlib.PosixPath(v['versions'])), - root=args.root_dir, - chdir=args.chdir, - vars=v, - command=tuple(args.command), - extra_mount=tuple(args.extra_mount) if args.extra_mount is not None else (), - drop_to=( - (int(env['pthbs_uid']), int(env['pthbs_gid'])) - if args.mode == 'root' - else None - ), - untar=args.untar and pathlib.PosixPath(args.untar), - ) - - -def userns_sandbox_run(settings): - assert settings.untar is None - assert settings.drop_to is None - mount('proc', settings.root / 'proc', 'proc', MountFlag.NOSUID | MountFlag.NODEV) - if not (settings.root / 'dev/null').is_char_device(): - mount( - '/dev', - settings.root / 'dev', - None, - (MountFlag.BIND | MountFlag.NOSUID | MountFlag.REC), - ) - - mountpoints = [ - MountTMPFS(relpath('/dev/shm')), - ] - mountpoints.extend(settings.extra_mount) - mountpoints.append(MountBind(settings.versions, settings.versions.relative_to('/'))) - for m in mountpoints: - m.mount(settings.root) - - os.chroot(str(settings.root)) - os.chdir(settings.chdir) - exec_command(settings.command) - - -def mkchardev(path, major, minor, mode): - if isinstance(path, pathlib.PosixPath): - path = path.as_posix() - os.mknod( - path, - mode=mode | stat.S_IFCHR, - device=os.makedev(major, minor), - ) - - -def mkblockdev(path, major, minor, mode): - if isinstance(path, pathlib.PosixPath): - path = path.as_posix() - os.mknod( - path, - mode=mode | stat.S_IFBLK, - device=os.makedev(major, minor), - ) - - -def mknod_dev(dev): - mkchardev(mode=0o666, major=1, minor=3, path=dev / "null") - mkchardev(mode=0o666, major=1, minor=7, path=dev / "full") - mkchardev(mode=0o666, major=5, minor=2, path=dev / "ptmx") - mkchardev(mode=0o644, major=1, minor=8, path=dev / "random") - mkchardev(mode=0o644, major=1, minor=9, path=dev / "urandom") - mkchardev(mode=0o666, major=1, minor=5, path=dev / "zero") - mkchardev(mode=0o666, major=5, minor=0, path=dev / "tty") - (dev / "fd").symlink_to("/proc/self/fd") - (dev / "stdin").symlink_to("/proc/self/fd/0") - (dev / "stdout").symlink_to("/proc/self/fd/1") - (dev / "stderr").symlink_to("/proc/self/fd/2") - - -def root_sandbox_setup(settings): - uid, gid = settings.drop_to - os.umask(0) - to_umount = [mi.mountpoint for mi in umount_order(*parse_mountinfo())] - r = settings.root - if settings.untar: - mount('sandbox_root', r, 'tmpfs', MountFlag.NOSUID) - (r / 'oldroot').mkdir() - subprocess.check_call( - ('/bin/tar', 'xpf', settings.untar.absolute()), - shell=False, - cwd=r, - ) - mount('proc', r / 'proc', 'proc', MountFlag.NOSUID | MountFlag.NODEV) - if not (r / 'dev/null').is_char_device(): - mknod_dev(r / 'dev') - - mountpoints = [ - MountTMPFS(relpath('/dev/shm')), - ] - mountpoints.extend(settings.extra_mount) - mountpoints.append(MountBind(settings.versions, settings.versions.relative_to('/'))) - for m in mountpoints: - m.mount(r) - - if settings.untar: - mount( - 'tmpfs', - r, - '', - (MountFlag.REMOUNT | MountFlag.RDONLY | MountFlag.NOSUID), - ) - pivot_and_umount(r, r / 'oldroot', to_umount) - os.setgid(gid) - os.setuid(uid) - os.chdir(settings.chdir) - - -def main(args, env): - settings = Settings.from_args_and_env(args, env) - if args.mode == 'userns': - pidns_run( - CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWPID, - ) - userns_sandbox_run(settings) - else: - pidns_run( - CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWPID, - ) - unshare(CLONE_NEWNS) - root_sandbox_setup(settings) - os.umask(0o022) - exec_command(settings.command) - - -argument_parser = argparse.ArgumentParser( - description="Linux namespaces based sandbox for pthbs", - allow_abbrev=False, -) -argument_parser.add_argument( - '--mode', - '-m', - required=True, - choices=('userns', 'root'), - help="sandbox mode", -) -argument_parser.add_argument( - '--vars', - '-y', - type=pathlib.PosixPath, - help="vars.yaml to read configuration from", -) -argument_parser.add_argument( - '--versions', - '-V', - type=pathlib.PosixPath, - help="versions dir (e.g. /versions)", -) -argument_parser.add_argument( - '--chdir', - '-C', - type=pathlib.PosixPath, - default=pathlib.PosixPath('/'), - help="set working directory inside sandbox", -) -argument_parser.add_argument( - '--untar', - '-f', - type=pathlib.PosixPath, - default=pathlib.PosixPath(os.getcwd()), - help="initial structure for build tmpfs", -) -argument_parser.add_argument('--extra-mount', action='append', type=parse_mount) -argument_parser.add_argument('root_dir', type=pathlib.PosixPath) -argument_parser.add_argument('command', nargs='+') - - -if __name__ == '__main__': - args = argument_parser.parse_args() - main(args, os.environ) - -# pylama:linters=pycodestyle,pyflakes:ignore=D212,D203,D100,D101,D102,D107 -# vim: sts=4 ts=4 sw=4 et tw=88 efm=%A%f\:%l\:%c\ %t%n\ %m diff --git a/packages.aat b/packages.aat @@ -1,45 +0,0 @@ -|start_package("musl-cross-make") - awk -vPWD="$PWD" '{gsub(/@@PWD@@/,PWD); print}' musl-cross-make.config.mak >musl-cross-make/config.mak.new || exit $? - mv musl-cross-make/config.mak.new musl-cross-make/config.mak || exit $? - make -C musl-cross-make -j$JOBS -l$JOBS || exit $? - make -C musl-cross-make install || exit $? -|end() -|start_package("skalibs") - git checkout --force || exit $? - git clean -fx || exit $? - ./configure --disable-shared --enable-static --enable-slashpackage="$slashpackage" || exit $? - make -j${jobs} || exit $? - make install || exit $? - make -L update || exit $? - make -L global-links || exit $? -|end() -|start_package("s6") - git checkout --force || exit $? - git clean -fx || exit $? - #patch -up 1 -i ../s6_clone_newpid.patch || exit $? - patch -up 1 -i ../s6_clone3_newpid.patch || exit $? - ./configure --disable-shared --enable-static --enable-allstatic --enable-static-libc --enable-slashpackage="$slashpackage" || exit $? - make -j${jobs} CFLAGS=-DWANT_CLONE_NEWPID || exit $? - make install || exit $? - make -L update || exit $? - make -L global-links || exit $? -|end() -@for p in packages -|start - git checkout --force || exit $? - git clean -fx || exit $? - ./configure --disable-shared --enable-static --enable-allstatic --enable-static-libc --enable-slashpackage="$slashpackage" || exit $? - make -j${jobs} || exit $? - make install || exit $? - make -L update || exit $? - make -L global-links || exit $? -|end() -@endfor -|# (cd execline && install_generic) || exit $? -|# (cd s6-rc && install_generic) || exit $? -|# (cd s6-portable-utils && install_generic) || exit $? -|# (cd s6-linux-utils && install_generic) || exit $? -|# (cd s6-linux-init && install_generic) || exit $? -|# (cd mdevd && install_generic) || exit $? -|# (cd s6-dns && install_generic) || exit $? -|# (cd s6-networking && install_generic) || exit $? diff --git a/pthbs b/pthbs @@ -0,0 +1 @@ +Subproject commit 1c067b19b7c431f8edbd48b38b33812f513ffac3 diff --git a/pthbs_genpkgpy b/pthbs_genpkgpy @@ -0,0 +1 @@ +Subproject commit b15529ebde7867aaf9779ecfcd2fbbace7472edb diff --git a/py-cp311-cp311-musllinux_1_2_x86_64-requirements.txt b/py-cp311-cp311-musllinux_1_2_x86_64-requirements.txt @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --output-file=py-cp311-cp311-musllinux_1_2_x86_64-requirements.txt.new py-requirements.in -# -black==24.2.0 - # via -r py-requirements.in -click==8.1.7 - # via black -isort==5.13.2 - # via -r py-requirements.in -jinja2==3.1.3 - # via -r py-requirements.in -markupsafe==2.1.5 - # via jinja2 -mccabe==0.7.0 - # via pylama -mypy-extensions==1.0.0 - # via black -packaging==23.2 - # via black -pathspec==0.12.1 - # via black -platformdirs==4.2.0 - # via black -pycodestyle==2.11.1 - # via pylama -pydocstyle==6.3.0 - # via pylama -pyflakes==3.2.0 - # via pylama -pylama==8.4.1 - # via -r py-requirements.in -pyyaml==6.0.1 - # via -r py-requirements.in -snowballstemmer==2.2.0 - # via pydocstyle diff --git a/py-requirements.in b/py-requirements.in @@ -1,5 +0,0 @@ -jinja2 -pyyaml -black -isort -pylama diff --git a/python.mk b/python.mk @@ -1,60 +0,0 @@ -PYTHONPATH:=$(abspath .) -PYTHON_EXE:=python3 -# re-evaluate each time because there's no sensible way to check -# whether the python interpreter changed -$(eval $(shell $(PYTHON_EXE) ./make_vars.py)) - -PY_SRC:=$(wildcard *.py) -PY_REQ:=py-$(PYTHON_IMPL)-requirements.txt -PY_WHL:=work/wheels/$(PYTHON_IMPL) -VENV:=work/virtualenvs/$(PYTHON_IMPL) -PYTHON_VENV_INSTALL=pip-tools wheel - -pycodestyle: $(patsubst %.py,.%.pyfmt,$(PY_SRC)) $(VENV)/.done - '$(VENV)/bin/pylama' -l 88 $(PY_SRC) || true - -.%.pyfmt: %.py $(VENV)/.done - '$(VENV)/bin/isort' - <'$<' >'$<.tmp1' - cp -a '$<' '$<.tmp2' - '$(VENV)/bin/black' -S - <'$<.tmp1' >'$<.tmp2' - rm '$<.tmp1' - if cmp -s '$<.tmp2' '$<'; then rm -v '$<.tmp2'; else mv -v '$<.tmp2' '$<'; fi - touch $@ - -.PHONY: py-requirements py-wheels py-venv py-virtualenv py-genpkg -py-requirements: $(PY_REQ) - -py-wheels: $(PY_WHL)/.done - -py-venv: $(VENV)/.done - -py-virtualenv: py-venv - -py-genpkg: $(VENV)/.done - '$(VENV)/bin/python' genpkg.py - -# -- requirement file rules - -$(PY_REQ): py-requirements.in $(VENV)/bin/pip-compile - '$(VENV)/bin/pip-compile' -v --annotate -o '$@.new' py-requirements.in - mv '$@.new' '$@' - -# -- wheel building rules - -$(PY_WHL)/.done: $(PY_REQ) - mkdir -p '$(PY_WHL)' - '$(VENV)/bin/python' -m pip wheel -w '$(PY_WHL)' -r '$(PY_REQ)' - touch '$@' - -# -- virtualenv rules - -$(VENV)/bin/pip-compile $(VENV)/bin/pip-sync: - if test -e '$(VENV)'; then rm -r '$(VENV)'; else true; fi - mkdir -p virtualenvs - $(PYTHON_VENV) '$(VENV)' - '$(VENV)/bin/pip' install -I $(PYTHON_VENV_INSTALL) - -$(VENV)/.done: $(PY_REQ) $(VENV)/bin/pip-sync $(PY_WHL)/.done - $(VENV)/bin/pip-sync --no-index -f '$(PY_WHL)' '$(PY_REQ)' - ln -sf 'virtualenvs/$(PYTHON_IMPL)' work/venv - touch '$@' diff --git a/script_env.txt b/script_env.txt @@ -1,3 +0,0 @@ -DESTDIR=./destdir # where files get copied to -PREFIX=/versions/package-XXXX # final installation dir? -MAKE_JOBS/NPROCS diff --git a/script_header.txt b/script_header.txt @@ -1,11 +0,0 @@ -package dependency -file hash -git commit hash - ---- -#!/usr/bin/env pthbs-build -#+package-name -#@git:commit-hash:dirname -#@sha256:file-hash:filename -<mandatory empty line> -script start