From 9d447f48e72b0962b5f7653e6f677cff9ebfd876 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 2 Apr 2017 18:41:59 +0200 Subject: [PATCH] Add tix-upgrade(8). --- Makefile | 6 + build-aux/ports.conf | 4 +- build-aux/version.mak | 4 + etc/signify/sortix-1.1.pub | 2 + etc/signify/sortix-1.1dev.pub | 2 + etc/signify/sortix-1.2.pub | 2 + etc/signify/sortix-1.2dev.pub | 2 + share/man/man5/upgrade.conf.5 | 91 ++++++- sysinstall/conf.c | 37 ++- sysinstall/conf.h | 5 + sysinstall/hooks.c | 2 + sysinstall/sysinstall.c | 15 ++ sysinstall/sysmerge.c | 4 + sysinstall/sysupgrade.c | 1 + tix/Makefile | 3 + tix/tix-clean | 68 +++++ tix/tix-fetch | 451 ++++++++++++++++++++++++++++++++++ tix/tix-upgrade | 273 ++++++++++++++++++++ tix/tix.c | 45 +++- 19 files changed, 1004 insertions(+), 13 deletions(-) create mode 100644 etc/signify/sortix-1.1.pub create mode 100644 etc/signify/sortix-1.1dev.pub create mode 100644 etc/signify/sortix-1.2.pub create mode 100644 etc/signify/sortix-1.2dev.pub create mode 100755 tix/tix-clean create mode 100755 tix/tix-fetch create mode 100755 tix/tix-upgrade diff --git a/Makefile b/Makefile index 3844f08b..2dc4746e 100644 --- a/Makefile +++ b/Makefile @@ -231,6 +231,8 @@ sysroot-system: sysroot-fsh sysroot-base-headers echo /etc/sortix-release >> "$(SYSROOT)/tix/manifest/system" ln -sf sortix-release "$(SYSROOT)/etc/os-release" echo /etc/os-release >> "$(SYSROOT)/tix/manifest/system" + find etc | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system" + cp -RT etc "$(SYSROOT)/etc" find share | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system" cp -RT share "$(SYSROOT)/share" export SYSROOT="$(SYSROOT)" && \ @@ -269,6 +271,7 @@ else ifneq ($(SORTIX_INCLUDE_SOURCE),no) cp Makefile -t "$(SYSROOT)/src" cp README -t "$(SYSROOT)/src" cp -RT build-aux "$(SYSROOT)/src/build-aux" + cp -RT etc "$(SYSROOT)/src/etc" cp -RT share "$(SYSROOT)/src/share" (for D in $(MODULES); do (cp -R $$D -t "$(SYSROOT)/src" && $(MAKE) -C "$(SYSROOT)/src/$$D" clean) || exit $$?; done) endif @@ -452,6 +455,9 @@ $(LIVE_INITRD): sysroot echo "include /etc/default/passwd.d/*" >> $(LIVE_INITRD).d/etc/passwd echo "root::0:root" > $(LIVE_INITRD).d/etc/group echo "include /etc/default/group.d/*" >> $(LIVE_INITRD).d/etc/group + (echo 'channel = $(CHANNEL)' && \ + echo 'release_key = $(RELEASE_KEY)' && \ + echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(LIVE_INITRD).d/etc/upgrade.conf mkdir -p $(LIVE_INITRD).d/root -m 700 cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root (echo "You can view the documentation for new users by typing:" && \ diff --git a/build-aux/ports.conf b/build-aux/ports.conf index 47ff54fb..d27b3a99 100644 --- a/build-aux/ports.conf +++ b/build-aux/ports.conf @@ -1,3 +1,3 @@ -set_minimal="cut dash e2fsprogs grep grub mdocml sed xargs" -set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip irssi libcurl libcurses libssl libstdc++ m4 make nano ntpd patch perl pkg-config python ssh tar texinfo vim wget xz xorriso" +set_minimal="cut dash e2fsprogs grep grub libssl mdocml sed signify tar wget xargs xz" +set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip irssi libcurl libcurses libstdc++ m4 make nano ntpd patch perl pkg-config python ssh texinfo vim xorriso" sets="basic minimal" diff --git a/build-aux/version.mak b/build-aux/version.mak index 880070d0..b5ca00e9 100644 --- a/build-aux/version.mak +++ b/build-aux/version.mak @@ -1,2 +1,6 @@ VERSION=1.1dev +CHANNEL?=nightly RELEASE?=$(VERSION) +RELEASE_MASTER?=https://sortix.org/release +RELEASE_KEY=/etc/signify/sortix-$(VERSION).pub +RELEASE_SIG_URL?=$(RELEASE_MASTER)/$(CHANNEL)/$(HOST_MACHINE).sh.sig diff --git a/etc/signify/sortix-1.1.pub b/etc/signify/sortix-1.1.pub new file mode 100644 index 00000000..70bbd3ef --- /dev/null +++ b/etc/signify/sortix-1.1.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQiTQbFzyZJVobf/pn53Jp3njhRB9DgwkMaNakCpDE9RaTABMjlbz9W diff --git a/etc/signify/sortix-1.1dev.pub b/etc/signify/sortix-1.1dev.pub new file mode 100644 index 00000000..19eeb69a --- /dev/null +++ b/etc/signify/sortix-1.1dev.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQnkSm9lj1YIZYpt1Y3mHYzFsaky82gQF6CrW4lme9OoEYzSIl2ZsIC diff --git a/etc/signify/sortix-1.2.pub b/etc/signify/sortix-1.2.pub new file mode 100644 index 00000000..2d0c5ccc --- /dev/null +++ b/etc/signify/sortix-1.2.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWTGrBXmGvl2zUpCa47ui5EyPsnitKLjsCZ2YZphNY8F3b33t6QWYDs1 diff --git a/etc/signify/sortix-1.2dev.pub b/etc/signify/sortix-1.2dev.pub new file mode 100644 index 00000000..c3034752 --- /dev/null +++ b/etc/signify/sortix-1.2dev.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWRTbLQ+3+a9I5yche2BEVP03TRtumGO4Vgq1AQ/5bRj8JAJ1R0+vpxE diff --git a/share/man/man5/upgrade.conf.5 b/share/man/man5/upgrade.conf.5 index bc5405cf..b8e27a08 100644 --- a/share/man/man5/upgrade.conf.5 +++ b/share/man/man5/upgrade.conf.5 @@ -22,6 +22,10 @@ as part of .Xr installation 7 to match what was installed. .Pp +The file also controls the actions of +.Xr tix 8 +when upgrading releases and installing ports. +.Pp Developers may wish to customize what happens to .Pa /src on a system upgrade. @@ -44,7 +48,32 @@ Lines are supposed to contain assignments to variables. An assignment is the name of the variable, whitespace, an equal character, whitespace, the value, whitespace, and then the end of the line. .Bl -tag -width "12345678" -.It Sy grub Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy channel Ns "=" Ns Ar channel +If the current release has an upgrade path named +.Ar channel +to a new release, +then system upgrades will upgrade to that new release. +If no such release path exists or if this variable is not set, upgrades will +continue to upgrade to the current release series. +Depending on the current release, the offically supported values are +.Sy stable +for stable releases and +.Sy nightly +for development releases. +Downgrading releases is not supported. For instance, if the current system is +a development release, specifying +.Sy stable +will not downgrade the system to the previous stable release, as no such upgrade +path exists. +Instead upgrades will upgrade to the next stable release when it becomes +available. +.It Sy force_mirror Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) +Use the preferred mirror set with +.Sy mirror +even if the file specified by +.Sy release_sig_url +does not list it. +.It Sy grub Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) States GRUB is used as the bootloader. If either the .Sy system @@ -56,7 +85,18 @@ then the bootloader is reinstalled .Xr ( grub-install 8 ) and updated .Xr ( update-grub 8 ) . -.It Sy newsrc Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy mirror Ns "=" Ns Ar mirror +Download releases and ports from this preferred +.Ar mirror , +a URL to the top level directory of a mirror. +The mirror is only used if the file specified by +.Sy release_sig_url +lists this mirror, unless +.Sy force_mirror +is set to +.Sy yes . +If no mirror is set, a default mirror is used. +.It Sy newsrc Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) Place the new source code in .Pa /newsrc and move any existing @@ -68,17 +108,39 @@ This preserves the current directory. This takes precedence over and disables the behavior described under .Sy src . -.It Sy ports Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) . +.It Sy ports Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) Install the new ports. Ports that don't exist anymore will be removed. -.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy release_key Ns "=" Ns Ar release_key +Verify the file specified by +.Sy release_sig_url +with the +.Xr signify 1 +public key file at the path +.Ar release_key . +This variable is updated during system upgrades and there is no need to change +this variable manually. +.It Sy release_sig_url Ns "=" Ns Ar release_sig_url +Download the meta-information about the current release from the URL +.Ar release_sig_url . +This file is verified with the +.Xr signify 1 +public key in the +.Sy release_key +variable. +The file describes the current release, provides checksums of all the published +files, lists all the supported mirrors, provides instructions on how to upgrade +to this release, and lists all the supported upgrade paths to new releases. +This variable is updated during system upgrades and there is no need to change +this variable manually. +.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) Place the new source code in .Pa /src and move any existing .Pa /src into .Pa /oldsrc . -.It Sy system Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) . +.It Sy system Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) Install the new system. The upgrade hooks are run if needed as described in .Xr following-development 7 . @@ -95,6 +157,18 @@ then regenerate The defaults will be used if .Pa /etc/upgrade.conf is missing. +If +.Sy release_key +or +.Sy release_sig_url +are absent, +.Xr tix 8 +will not be able to upgrade the current system nor install ports. +If +.Sy channel +is absent, +.Xr tix 8 +will not upgrade to new releases. .Sh FILES .Bl -tag -width "/etc/upgrade.conf" -compact .It Pa /etc/upgrade.conf @@ -102,13 +176,14 @@ Upgrade configuration. .El .Sh EXAMPLES .Bd -literal -system = yes +grub = yes ports = yes src = no -grub = yes +system = yes .Ed .Sh SEE ALSO .Xr autoinstall.conf 5 , .Xr autoupgrade.conf 5 , .Xr upgrade 7 , -.Xr sysupgrade 8 +.Xr sysupgrade 8 , +.Xr tix 8 diff --git a/sysinstall/conf.c b/sysinstall/conf.c index 1876398c..592c3ec1 100644 --- a/sysinstall/conf.c +++ b/sysinstall/conf.c @@ -37,6 +37,10 @@ void conf_init(struct conf* conf) void conf_free(struct conf* conf) { + free(conf->channel); + free(conf->mirror); + free(conf->release_key); + free(conf->release_sig_url); conf_init(conf); } @@ -60,12 +64,43 @@ static bool conf_assign(struct conf* conf, const char* path, off_t line_number) { - if ( !strcmp(name, "grub") ) + char* new_value; + if ( !strcmp(name, "channel") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->channel); + conf->channel = new_value; + } + else if ( !strcmp(name, "force_mirror") ) + conf->force_mirror = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "grub") ) conf->grub = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "mirror") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->mirror); + conf->mirror = new_value; + } else if ( !strcmp(name, "newsrc") ) conf->newsrc = conf_boolean(name, value, path, line_number); else if ( !strcmp(name, "ports") ) conf->ports = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "release_key") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->release_key); + conf->release_key = new_value; + } + else if ( !strcmp(name, "release_sig_url") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->release_sig_url); + conf->release_sig_url = new_value; + } else if ( !strcmp(name, "src") ) conf->src = conf_boolean(name, value, path, line_number); else if ( !strcmp(name, "system") ) diff --git a/sysinstall/conf.h b/sysinstall/conf.h index 761df306..47c3f3a8 100644 --- a/sysinstall/conf.h +++ b/sysinstall/conf.h @@ -22,9 +22,14 @@ struct conf { + char* channel; + bool force_mirror; bool grub; + char* mirror; bool newsrc; bool ports; + char* release_key; + char* release_sig_url; bool src; bool system; }; diff --git a/sysinstall/hooks.c b/sysinstall/hooks.c index 8a7bcad4..923c9521 100644 --- a/sysinstall/hooks.c +++ b/sysinstall/hooks.c @@ -485,6 +485,8 @@ void upgrade_prepare(const struct release* old_release, } free(path); } + + // TODO: Add upstream mirror to /etc/upgrade.conf. } void upgrade_finalize(const struct release* old_release, diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index 4972005e..f38c12bd 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -630,6 +630,21 @@ int main(void) // TODO: You can leave this program by pressing ^C but it can leave your // system in an inconsistent state. + if ( conf.channel ) + install_configurationf("upgrade.conf", "a", "channel = %s\n", + conf.channel); + if ( conf.force_mirror != false ) + install_configurationf("upgrade.conf", "a", "force_mirror = %s\n", + conf.force_mirror ? "yes" : "no"); + if ( conf.mirror ) + install_configurationf("upgrade.conf", "a", "mirror = %s\n", + conf.mirror); + if ( conf.release_key ) + install_configurationf("upgrade.conf", "a", "release_key = %s\n", + conf.release_key); + if ( conf.release_sig_url ) + install_configurationf("upgrade.conf", "a", "release_sig_url = %s\n", + conf.release_sig_url); install_configurationf("upgrade.conf", "a", "src = yes\n"); bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0) || diff --git a/sysinstall/sysmerge.c b/sysinstall/sysmerge.c index a2e7c96b..dde4e4f3 100644 --- a/sysinstall/sysmerge.c +++ b/sysinstall/sysmerge.c @@ -403,6 +403,10 @@ int main(int argc, char* argv[]) if ( copy_files ) { + // TODO: Update /etc/upgrade.conf with new release values. + // TODO: What about native upgrades using make sysmerge? Should those + // values be updated then? Should there be an option to control + // this behavior? const char* sysmerge = target; if ( wait ) { diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c index fcf71f79..3bb31b26 100644 --- a/sysinstall/sysupgrade.c +++ b/sysinstall/sysupgrade.c @@ -904,6 +904,7 @@ int main(void) } if ( conf.system ) upgrade_finalize(target_release, &new_release, "", "."); + // TODO: Update /etc/upgrade.conf with new release values. if ( conf.system ) { printf(" - Creating initrd...\n"); diff --git a/tix/Makefile b/tix/Makefile index 38138d65..f0f0862d 100644 --- a/tix/Makefile +++ b/tix/Makefile @@ -26,12 +26,15 @@ tix-vars \ PROGRAMS:=\ $(BINARIES) \ +tix-clean \ tix-eradicate-libtool-la \ +tix-fetch \ tix-iso-add \ tix-iso-bootconfig \ tix-iso-liveconfig \ tix-port \ tix-repository \ +tix-upgrade \ MANPAGES8=\ tix-build.8 \ diff --git a/tix/tix-clean b/tix/tix-clean new file mode 100755 index 00000000..2b1a81fc --- /dev/null +++ b/tix/tix-clean @@ -0,0 +1,68 @@ +#!/bin/sh +# Copyright (c) 2017 Jonas 'Sortie' Termansen. +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# tix-clean +# Remove temporary tix files. + +set -e + +collection="" +sysroot="" + +dashdash= +previous_option= +for argument do + if test -n "$previous_option"; then + eval $previous_option=\$argument + previous_option= + continue + fi + + case $argument in + *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; + *=) parameter= ;; + *) parameter=yes ;; + esac + + case $dashdash$argument in + --) dashdash=yes ;; + -o) previous_option=output ;; + --collection=*) collection=$parameter ;; + --collection) previous_option=collection ;; + --sysroot=*) sysroot=$parameter ;; + --sysroot) previous_option=sysroot ;; + -*) echo "$0: unrecognized option $argument" >&2 + exit 1 ;; + *) + if [ $operand = 1 ]; then + input="$argument" + operand=2 + elif [ $operand = 2 ]; then + directory="$argument" + operand=3 + else + echo "$0: unexpected extra operand $argument" >&2 + exit 1 + fi + ;; + esac +done + +if test -n "$previous_option"; then + echo "$0: option '$argument' requires an argument" >&2 + exit 1 +fi + +rm -rf "$collection/var/cache/tix" diff --git a/tix/tix-fetch b/tix/tix-fetch new file mode 100755 index 00000000..4432efbd --- /dev/null +++ b/tix/tix-fetch @@ -0,0 +1,451 @@ +#!/bin/sh +# Copyright (c) 2017, 2021, 2023 Jonas 'Sortie' Termansen. +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# tix-fetch +# Download operating system files. + +set -e + +boot=false +collection="" +continue="" +execpatch=false +initrd=false +insecure_downgrade_to_http=false +insecure_no_check_certificate=false +input_release_file= +input_release_sig_file= +input_sha256sum= +normalize=false +output="" +outputdir="" +output_release_file= # TODO: A better term for this? +output_release_sig_file= # TODO: A better term for this? +output_sha256sum= +patch=false +package=false +release=false +repository_metadata=false +sha256=false +sha256sum=false +source=false +source_full=false +sysroot="" +toolchain=false +url=false +url_main=false +url_main_release=false +url_mirror=false +url_mirror_release=false +url_release_sig=false +url_sha256sum=false +upgrade=false +# TODO: Option to select this default: +# TODO: This hides errors. Fix wget so it has a quiet, but errors, mode. +wget_options="-q --show-progress" + +# TODO: Ability to get source code easily for gcc/binutils/libstdc++. + +dashdash= +previous_option= +for argument do + if test -n "$previous_option"; then + eval $previous_option=\$argument + previous_option= + shift + continue + fi + + case $argument in + *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; + *=) parameter= ;; + *) parameter=yes ;; + esac + + case $dashdash$argument in + --) dashdash=yes ;; + -c) continue="-c" ;; + # TODO: Support -ofoo + -o) previous_option=output ;; + -O) previous_option=outputdir ;; + -q) wget_options="-q" ;; + -v) wget_options="-v" ;; + --boot) boot=true ;; + --collection=*) collection=$parameter ;; + --collection) previous_option=collection ;; + --continue) continue="--continue" ;; + --download-non-verbose) wget_options="-nv" ;; + --download-non-verbose) wget_options="-v" ;; + --download-quiet) wget_options="-q" ;; + --download-verbose) wget_options="-v" ;; + --execpatch) execpatch=true ;; + --initrd) initrd=true ;; + --input-release-file=*) input_release_file=$parameter ;; + --insecure-downgrade-to-http) insecure_downgrade_to_http=true ;; + --insecure-no-check-certificate) insecure_no_check_certificate=true ;; + --input-release-file) previous_option=input_release_file ;; + --input-release-sig-file=*) input_release_sig_file=$parameter ;; + --input-release-sig-file) previous_option=input_release_sig_file ;; + --input-sha256sum=*) input_sha256sum=$parameter ;; + --input-sha256sum) previous_option=input_sha256sum ;; + --normalize) normalize=true ;; + --nv) wget_options="-nv" ;; + --outputdir=*) outputdir=$parameter ;; + --outputdir) previous_option=outputdir ;; + --output=*) output=$parameter ;; + --output) previous_option=output ;; + --output-release-file=*) output_release_file=$parameter ;; + --output-release-file) previous_option=output_release_file ;; + --output-release-sig-file=*) output_release_sig_file=$parameter ;; + --output-release-sig-file) previous_option=output_release_sig_file ;; + --output-sha256sum=*) output_sha256sum=$parameter ;; + --output-sha256sum) previous_option=output_sha256sum ;; + --output-upgrade-file=*) output_upgrade_file=$parameter ;; + --output-upgrade-file) previous_option=output_upgrade_file ;; + --package) package=true ;; + --patch) patch=true ;; + --repository-metadata) repository_metadata=true ;; + --sha256) sha256=true ;; + --sha256sum) sha256sum=true ;; + --source-full) source_full=true ;; + --source) source=true ;; + --sysroot) previous_option=sysroot ;; + --sysroot=*) sysroot=$parameter ;; + --toolchain) toolchain=true ;; + --upgrade) upgrade=true ;; + --url) url=true ;; + --url-main) url_main=true ;; + --url-mirror) url_mirror=true ;; + --url-main-release) url_main_release=true ;; + --url-mirror-release) url_mirror_release=true ;; + --url-release-sig) url_release_sig=true ;; + --url-sha256sum) url_sha256sum=true ;; + --wget-options) previous_option=wget_options ;; + --wget-options=*) wget_options=$parameter ;; + -*) echo "$0: unrecognized option $argument" >&2 + exit 1 ;; + *) break ;; + esac + + shift +done + +if test -n "$previous_option"; then + echo "$0: option '$argument' requires an argument" >&2 + exit 1 +fi + +# TODO: Mutually incompatible options. + +conf() { + sed -E -e 's/([a-zA-Z0-9_]+) *? *= */\U\1=/' \ + -e 's/=yes$/=true/' -e 's/no$/=false/' "$3" | \ + tix-vars -d "$2" - "$4" +} + +tmpdir=$(mktemp -dt tix-fetch.XXXXXX) +trap 'rm -rf -- "$tmpdir"' EXIT HUP INT QUIT TERM + +upgrade_conf="${collection%/}/etc/upgrade.conf" +CHANNEL=$(conf -d '' "$upgrade_conf" CHANNEL) +RELEASE_KEY=$(conf -d '' "$upgrade_conf" RELEASE_KEY) +RELEASE_SIG_URL=$(conf -d '' "$upgrade_conf" RELEASE_SIG_URL) +PREFERRED_MIRROR=$(conf -d '' "$upgrade_conf" PREFERRED_MIRROR) +FORCE_MIRROR=$(conf -d '' "$upgrade_conf" FORCE_MIRROR) +USER_AGENT="$(uname -s)/$(uname -r) ($(uname -m); $(uname -v))" + +if $insecure_no_check_certificate; then + echo "$0: warning: insecurely not checking https certificates" >&2 + wget_options="$wget_options --no-check-certificate" +fi + +if $insecure_downgrade_to_http; then + echo "$0: warning: insecurely downloading without https" >&2 + RELEASE_SIG_URL="$(echo "$RELEASE_SIG_URL" | sed -E 's,^https:,http:,')" +fi + +if $url_release_sig; then + printf "%s\n" "$RELEASE_SIG_URL" + exit +fi + +# HACK: Provide more useful errors when wget is silent: +do_wget() { + (set +e + wget "$@" + status=$? + set -e + what= + case $status in + 0) exit 0 ;; + 1) what="Generic error" ;; + 2) what="Parse error" ;; + 3) what="File I/O error" ;; + 4) what="Network I/O error" ;; + 5) what="Transport Layer Security verification failure" ;; + 6) what="Username/password failure" ;; + 7) what="Protocol error" ;; + 8) what="Error response" ;; + *) what="Exit code $status" ;; + esac + echo "$0: $what when running: wget $@" >&2 + exit $status) +} + +# Fetch signed release description. +download_release_sh() { + (cd "$tmpdir" && + do_wget -U "$USER_AGENT" $wget_options -O release.sh.sig \ + -- "$RELEASE_SIG_URL") + signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh" +} + +true > "$tmpdir/upgrade.sh" + +if [ -z "$input_release_file" -a -z "$input_release_sig_file" ]; then + download_release_sh + # TODO: tix-vars's output is not quoted so it can be input again. + tix-vars "$tmpdir/release.sh" | \ + grep -E '^UPGRADE_=' | \ + cat > "$tmpdir/upgrade.sh" + UPGRADE_SIG_URL=$(tix-vars -d '' "$tmpdir/upgrade.sh" UPGRADE_SIG_URL) + if $upgrade && [ -n "$UPGRADE_SIG_URL" ]; then + RELEASE_SIG_URL="$UPGRADE_SIG_URL" + RELEASE_KEY=$(tix-vars "$tmpdir/upgrade.sh" UPGRADE_KEY) + download_release_sh + fi +fi + +if [ -n "$input_release_file" ]; then + cp -T -- "$input_release_file" "$tmpdir/release.sh" +elif [ -n "$input_release_sig_file" ]; then + signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh" +fi + +# Store the signed release file if requested. +if [ -n "$output_release_sig_file" ]; then + cp -T -- "$tmpdir/release.sh.sig" "$output_release_sig_file" +fi + +# Store the release file (without signature) if requested. +if [ -n "$output_release_file" ]; then + cp -T -- "$tmpdir/release.sh" "$output_release_file" +fi + +# Store the upgrade file if requested. +if [ -n "$output_upgrade_file" ]; then + cp -T -- "$tmpdir/upgrade.sh" "$output_upgrade_file" +fi + +# Load the release description. +# TODO: SECURITY: Protect against responding with older release.sh. + +# TODO: DO NOT SUBMIT: Temporary compatibility. +MAIN=$(tix-vars -d '' "$tmpdir/release.sh" MAIN) +MASTER=$(tix-vars -d '' "$tmpdir/release.sh" MASTER) +if [ -z "$MAIN" ]; then + MAIN="$MASTER" +fi + +RELEASE=$(tix-vars "$tmpdir/release.sh" RELEASE) +MACHINE=$(tix-vars "$tmpdir/release.sh" MACHINE) +SHA256SUM_FILE=$(tix-vars -d sha256sum "$tmpdir/release.sh" SHA256SUM_FILE) +SHA256SUM_SHA256SUM=$(tix-vars "$tmpdir/release.sh" SHA256SUM_SHA256SUM) +MIRRORS=$(tix-vars -d '' "$tmpdir/release.sh" MIRRORS) + +if $url_main; then + printf "%s\n" "$MAIN" + exit +elif $url_main_release; then + printf "%s\n" "$MAIN/$RELEASE" + exit +fi + +# Default to the main mirror but switch to the preferred mirror if the release +# description knows about the mirror and believes it to be trustworthy. +MIRROR="$MAIN" +for POTENTIAL_MIRROR in $MIRRORS; do + if [ "$POTENTIAL_MIRROR" = "$PREFERRED_MIRROR" ]; then + MIRROR="$PREFERRED_MIRROR" + fi +done +if [ -n "$PREFERRED_MIRROR" ] && [ "$MIRROR" != "$PREFERRED_MIRROR" ]; then + if [ "$FORCE_MIRROR" = true ]; then + MIRROR="$PREFERRED_MIRROR" + else + echo "$0: warning: ignoring unsupported mirror $PREFERRED_MIRROR" >&2 + fi +fi + +# TODO: Make sure the distant future http downgrade is supported. +if $insecure_downgrade_to_http; then + MIRROR="$(echo "$MIRROR" | sed -E 's,^https:,http:,')" +fi + +if $url_mirror; then + printf "%s\n" "$MIRROR" + exit +elif $url_mirror_release; then + printf "%s\n" "$MIRROR/$RELEASE" + exit +fi + +RELEASE_URL="$MIRROR/$RELEASE" + +# Fetch sha256sum file and check its SHA256 hash with the release description. +if $url_sha256sum; then + printf "%s\n" "$RELEASE_URL/$SHA256SUM_FILE" + exit +fi +if [ -z "$input_sha256sum" ]; then + # TODO: If the mirror doesn't work, try the main. + (cd "$tmpdir" && + do_wget -U "$USER_AGENT" $wget_options -O sha256sum \ + -- "$RELEASE_URL/$SHA256SUM_FILE") +else + cp -T -- "$input_sha256sum" "$tmpdir/sha256sum" +fi +# TODO: Check if upstream release description changed, if so, start over. +echo "$SHA256SUM_SHA256SUM $tmpdir/sha256sum" | sha256sum -cq + +# Store the sha256sum file if requested. +if [ -n "$output_sha256sum" ]; then + cp -T -- "$tmpdir/sha256sum" "$output_sha256sum" +fi + +escape_extended_regex() { + printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}]/\\\0/g' +} + +request() { + REQUEST="$1" + REQUESTDIR="$2" + REQUESTFINAL="${3-$1}" + FULLREQUEST="$REQUESTDIR$REQUEST" + + if $url; then + printf '%s\n' "$RELEASE_URL/$FULLREQUEST" + return + fi + + if $sha256 || $sha256sum; then + set +e # Don't fail if grep exits 1 (no match). + # TODO: Should this be a checksum(1) feature to look up a hash? + grep -E "^[0-9a-fA-F]{64} $(escape_extended_regex "$FULLREQUEST")$" \ + "$tmpdir/sha256sum" > "$tmpdir/match" + EXITCODE=$? + set -e + if [ 2 -le "$EXITCODE" ]; then (exit $EXITCODE); fi + if $sha256 && [ -s "$tmpdir/match" ]; then + grep -Eo '^[0-9a-fA-F]{64}' "$tmpdir/match" + fi + if $sha256sum && [ -s "$tmpdir/match" ]; then + cat "$tmpdir/match" + fi + return + fi + + # Decide the final location the file will end up. + if [ -n "$output" ]; then + FINAL="$output" + OUTPUTDIR=$(dirname -- "$output") + elif [ -n "$outputdir" ]; then + FINAL="$outputdir/$REQUESTFINAL" + OUTPUTDIR="$outputdir" + else + FINAL="$REQUESTFINAL" + OUTPUTDIR=. + fi + + # If a resumable download, store the file directly to the destination path. + # Otherwise download to a temporary directory and move only to the final + # location if the cryptographic check is passed. + if [ -n "$continue" ]; then + DOWNLOADDIR="$OUTPUTDIR" + OUTPUT="$FINAL" + else + DOWNLOADDIR="$tmpdir/download" + OUTPUT="$DOWNLOADDIR/$REQUEST" + mkdir -p -- "$DOWNLOADDIR" + fi + + # Fetch the file. + # TODO: If the mirror doesn't work, try the main. + (cd "$DOWNLOADDIR" && + mkdir -p -- "$(dirname -- "$REQUEST")" && + do_wget -U "$USER_AGENT" $wget_options $continue -O "$REQUEST" \ + -- "$RELEASE_URL/$FULLREQUEST") + + # Verify the cryptographic integrity of the fetched file. + ABSOLUTE_OUTPUT=$(realpath -- "$OUTPUT") + mkdir -p -- "$tmpdir/check" + (cd "$tmpdir/check" && + mkdir -p -- "$(dirname -- "$FULLREQUEST")" + ln -s -- "$ABSOLUTE_OUTPUT" "$FULLREQUEST" + if ! sha256sum -q -C "$tmpdir/sha256sum" -- "$FULLREQUEST"; then + # Don't leave behind a file that didn't pass a cryptographic check. + if [ -n "$continue" ]; then + # TODO: Check if upstream release description changed, if so, start over. + echo "error: Deleting corrupted output file: $OUTPUT" 2>&1 + rm -f -- "$OUTPUT" + fi + exit 1 + fi) + rm -rf -- "$tmpdir/check" + + # Move the file to the final destination if not already. + if [ -z "$continue" ]; then + if [ -z "$output" ]; then + (cd "$OUTPUTDIR" && mkdir -p -- "$(dirname -- "$REQUESTFINAL")") + fi + cp -T -- "$OUTPUT" "$FINAL" + rm -rf -- "$tmpdir/download" + fi +} + +if $release; then + MIRRORS=$(tix-vars "$tmpdir/release.sh" BUILD_FILE) + request "$BUILD_FILE" "" "$(basename -- "$BUILD_FILE")" +fi + +# TODO: --source, --source-full +# TODO: --binutils, --gcc, --libstdc++ + +# Fetch each of the specified signed files from the mirror. +for REQUEST; do + if $package; then + REQUEST="$REQUEST.tix.tar.xz" + REQUESTDIR="repository/$MACHINE-sortix/" + elif $repository_metadata; then + REQUESTDIR="repository/$MACHINE-sortix/" + elif $boot; then + REQUEST="$REQUEST" + REQUESTDIR="$MACHINE/boot/" + elif $initrd; then + REQUEST="$REQUEST.tar.xz" + REQUESTDIR="$MACHINE/boot/" + elif $patch; then + REQUEST="$REQUEST.patch" + REQUESTDIR="patches/" + elif $normalize; then + REQUEST="$REQUEST.normalize" + REQUESTDIR="patches/" + elif $toolchain; then + REQUESTDIR="toolchain/" + else + REQUESTDIR="" + fi + request "$REQUEST" "$REQUESTDIR" +done diff --git a/tix/tix-upgrade b/tix/tix-upgrade new file mode 100755 index 00000000..1a715934 --- /dev/null +++ b/tix/tix-upgrade @@ -0,0 +1,273 @@ +#!/bin/sh +# Copyright (c) 2017, 2021, 2023, 2024 Jonas 'Sortie' Termansen. +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# tix-upgrade +# Upgrade operating system and ports. + +set -e + +cachedir="" +cancel=false +clean=false +collection=/ +download_only=false +fetch_options= +sysroot="" +upgrade=--upgrade +upgrade_ports=false +upgrade_system=false +wait="" + +dashdash= +previous_option= +for argument do + if test -n "$previous_option"; then + eval $previous_option=\$argument + previous_option= + shift + continue + fi + + case $argument in + *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; + *=) parameter= ;; + *) parameter=yes ;; + esac + + case $dashdash$argument in + --) dashdash=yes ;; + -w) wait=-w ;; + --cachedir=*) cachedir=$parameter ;; + --cachedir) previous_option=cachedir ;; + --cancel) cancel=true ;; + --clean) clean=true ;; + --collection=*) collection=$parameter ;; + --collection) previous_option=collection ;; + --download-only) download_only=true ;; + --fetch-options=*) fetch_options="$parameter" ;; + --fetch-options) previous_option=fetch_options ;; + --insecure-downgrade-to-http) fetch_options="$fetch_options $argument" ;; + --insecure-no-check-certificate) fetch_options="$fetch_options $argument" ;; + --no-upgrade) upgrade= ;; + --ports) upgrade_ports=true ;; + --system) upgrade_system=true ;; + --sysroot) previous_option=sysroot ;; + --sysroot=*) sysroot=$parameter ;; + --wait) wait=--wait ;; + -*) echo "$0: unrecognized option $argument" >&2 + exit 1 ;; + *) break ;; + esac + + shift +done + +if test -n "$previous_option"; then + echo "$0: option '$argument' requires an argument" >&2 + exit 1 +fi + +if [ 0 -lt $# ]; then + echo "$0: Unexpected extra operand: $1" >&2 + exit 1 +fi + +conf() { + sed -E -e 's/([a-zA-Z0-9_]+) *? *= */\U\1=/' \ + -e 's/=yes$/=true/' -e 's/no$/=false/' "$3" | \ + tix-vars -d "$2" - "$4" +} + +escape_extended_regex_sed() { + printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}'"$2"']/\\\0/g' +} + +collection=$(cd "$collection" && pwd) + +if ! $upgrade_ports && ! $upgrade_system; then + upgrade_ports=true + upgrade_system=true + if [ -e "$collection/etc/upgrade.conf" ]; then + upgrade_ports=$(conf -d true "$collection/etc/upgrade.conf" PORTS) + upgrade_system=$(conf -d true "$collection/etc/upgrade.conf" SYSTEM) + fi +fi + +# If this isn't a system installation, only upgrade the ports. +if [ ! -e "$collection/tix/manifest/system" ]; then + upgrade_system=false +fi + +case "$upgrade_system$upgrade_ports" in +truefalse) what_to_upgrade=--system;; +falsetrue) what_to_upgrade=--ports;; +*) what_to_upgrade=;; +esac + +if [ -z "$cachedir" ]; then + cachedir="${collection%/}/var/cache/tix" +fi + +if $cancel || $clean; then + echo "Removing cache directory: $cachedir" + rm -rf -- "$cachedir" +fi + +if $cancel; then + sysmerge -t "$collection" --cancel + exit +fi +mkdir -p -- "$cachedir" +mkdir -p -- "$cachedir/new" + +# Fetch the latest official signed release.sh and its matching sha256sum file. +tix-fetch $fetch_options \ + --collection="$collection" \ + --output-release-file="$cachedir/new/release.sh" \ + --output-sha256sum="$cachedir/new/sha256sum" \ + --output-upgrade-file="$cachedir/new/upgrade.sh" \ + $upgrade + +# If release.sh or sha256sum changed, clean the cache directory of downloads +# that were currently in progress as they might not have the right checksums. +if [ ! -e "$cachedir/release.sh" ] || + [ ! -e "$cachedir/sha256sum" ] || + [ ! -e "$cachedir/upgrade.sh" ] || + ! (cd "$cachedir/new" && sha256sum release.sh sha256sum upgrade.sh) | + (cd "$cachedir" && sha256sum -cs); then + rm -rf -- "$cachedir/boot" + rm -rf -- "$cachedir/repository" + rm -rf -- "$cachedir/sysroot" +fi + +# Store the new release.sh and sha256sum files so we can resume the download +# if cancelled and these files still match. +mv -- "$cachedir/new/release.sh" "$cachedir/release.sh" +mv -- "$cachedir/new/sha256sum" "$cachedir/sha256sum" +mv -- "$cachedir/new/upgrade.sh" "$cachedir/upgrade.sh" +rm -rf -- "$cachedir/new" + +# Check if we're upgrading to a new release. +UPGRADE_SIG_URL=$(tix-vars -d '' "$cachedir/upgrade.sh" UPGRADE_SIG_URL) +if [ -n "$UPGRADE_SIG_URL" ]; then + UPGRADE_CHANNEL=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_CHANNEL) + UPGRADE_KEY=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_KEY) + UPGRADE_NAME=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_NAME) + if [ -n "$upgrade" ]; then + echo "Upgrading to $UPGRADE_NAME." + else + echo "Ignoring available upgrade to $UPGRADE_NAME." + fi +fi + +# Decide what binary packages to upgrade. +installed_packages=$(LC_ALL=C ls -- "$collection/tix/tixinfo") +if $upgrade_system && $upgrade_ports; then + upgrade_packages="$installed_packages" +else + upgrade_packages= + for package in $installed_packages; do + is_system=$(tix-vars -d false "$collection/tix/tixinfo/$package" SYSTEM) + if ($upgrade_system && [ "$is_system" = true ]) || + ($upgrade_ports && [ "$is_system" = false ]); then + upgrade_packages="$upgrade_packages $package" + fi + done +fi + +# TODO: Tracking sets like minimal/basic/full with new mandatory or recommended ports. + +mkdir -p -- "$cachedir/repository" + +tix-fetch $fetch_options \ + --collection="$collection" \ + --input-release-file="$cachedir/release.sh" \ + --input-sha256sum="$cachedir/sha256sum" \ + --repository-metadata -O "$cachedir/repository" renames.list + +# Follow RENAMES recursively to handle renames, splits, and deletions. +rename() { + if grep -Eq "^$(escape_extended_regex_sed "$1"):" \ + "$cachedir/repository/renames.list"; then + for new in $(grep -E "^$(escape_extended_regex_sed "$1"):" \ + "$cachedir/repository/renames.list" | + grep -Eo '[^:]*$'); do + rename "$new" + done + else + echo "$1" | grep -Eo '^[^@]*' + fi +} + +# Determine the final package list after the renames. +packages="" +for package in $(LC_ALL=C ls -- "$collection/tix/tixinfo"); do + edition=$(tix-vars -d 1 "$collection/tix/tixinfo/$package" EDITION) + packages="$packages $(rename "$package@$edition")" +done + +# Sort and deduplicate the package list and check for existence. +packages=$(for package in $packages; do + # The package exists upstream if it has a hash. + if [ -n "$(tix-fetch $fetch_options \ + --collection="$collection" \ + --input-release-file="$cachedir/release.sh" \ + --input-sha256sum="$cachedir/sha256sum" \ + --sha256 --package -- $package)" ]; then + echo $package + fi + done | LC_ALL=C sort -u) + +# Fetch each binary package from the mirror. +for package in $packages; do + tix-fetch $fetch_options \ + --collection="$collection" \ + --input-release-file="$cachedir/release.sh" \ + --input-sha256sum="$cachedir/sha256sum" \ + -c --package -O "$cachedir/repository" -- $package +done + +# Stop if only downloading. +if $download_only; then + exit +fi + +rm -rf -- "$cachedir/sysroot" +mkdir -p -- "$cachedir/sysroot" + +# Forward the upgrade metadata. +UPGRADE_SIG_URL=$(tix-vars -d '' "$cachedir/upgrade.sh" UPGRADE_SIG_URL) +if [ -n $upgrade ] && [ -n "$UPGRADE_SIG_URL" ]; then + mkdir -p -- "$cachedir/etc" + # TODO: More flexible and simple model. + cat > "$cachedir/etc/upgrade.conf" << EOF +channel = $UPGRADE_CHANNEL +release_key = $UPGRADE_KEY +release_sig_url = $UPGRADE_SIG_URL +EOF +fi + +# Extract the binary packages into the sysroot. +for package in $packages; do + echo "Extracting $package.tix.tar.xz..." + tar -C "$cachedir/sysroot" -xJf "$cachedir/repository/$package.tix.tar.xz" + rm -f "$cachedir/repository/$package.tix.tar.xz" +done + +# Merge the new sysroot onto the installation. +sysmerge -t "$collection" --full $what_to_upgrade $wait "$cachedir/sysroot" + +rm -rf -- "$cachedir/repository" +rm -rf -- "$cachedir/sysroot" diff --git a/tix/tix.c b/tix/tix.c index 33067aab..b3cd94e5 100644 --- a/tix/tix.c +++ b/tix/tix.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, 2016, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2015, 2016, 2017, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -186,6 +186,8 @@ static void version(FILE* fp, const char* argv0) int main(int argc, char* argv[]) { + const char* tmp = getenv_def("TMPDIR", "/tmp"); + params_t params; memset(¶ms, 0, sizeof(params)); params.collection = NULL; @@ -260,7 +262,7 @@ int main(int argc, char* argv[]) errx(1, "error: no command specified."); const char* cmd = argv[1]; - if ( !strcmp(cmd, "install") ) + if ( !strcmp(cmd, "old-install") ) { if ( argc == 2 ) errx(1, "expected list of packages to install after `install'"); @@ -298,6 +300,45 @@ int main(int argc, char* argv[]) return 0; } + else if ( !strcmp(cmd, "install") ) + { + initialize_tmp(tmp, "tix-install"); + if ( chdir(tmp_root) < 0 ) + err(1, "%s", tmp_root); + + if ( fork_and_wait_or_death() ) + { + char** fetch_argv = malloc(sizeof(char*) * (5 + (argc-2) + 1)); + fetch_argv[0] = (char*) "tix-fetch"; + fetch_argv[1] = (char*) "--collection"; + fetch_argv[2] = params.collection; + fetch_argv[3] = (char*) "--package"; + fetch_argv[4] = (char*) "--"; + int offset = 5; + for ( int i = 0; i < argc-2; i++ ) + fetch_argv[offset + i] = argv[2 + i]; + fetch_argv[offset + argc-2] = NULL; + execvp(fetch_argv[0], (char* const*) fetch_argv); + err(127, "`%s'", fetch_argv[0]); + } + for ( int i = 2; i < argc; i++ ) + { + const char* pkg_name = argv[i]; + char* pkg_path = print_string("%s%s", pkg_name, ".tix.tar.xz"); + if ( fork_and_wait_or_death() ) + { + const char* install_argv[] = + { + "tix-install", + "--collection", params.collection, + "--", pkg_path, + NULL + }; + execvp(install_argv[0], (char* const*) install_argv); + err(127, "`%s'", install_argv[0]); + } + } + } else { fprintf(stderr, "%s: unknown command: `%s'\n", argv0, cmd);