Package: dpkg Version: 1.18.3 Severity: wishlist Tags: patch User: helm...@debian.org Usertags: rebootstrap
Hi Guillem, Thank you very much for discussing the idea of DPKG_ROOT and recording some results at https://wiki.debian.org/Teams/Dpkg/Spec/InstallBootstrap already. By now, I am convinced that this idea is worth exploring and have thus prepared a small patch set implementing some of the first steps. a) dpkg should export DPKG_ROOT. DPKG_ROOT should be a string that should be prepended to "/" to arrive at the current installation root (instdir). Notably, when dpkg invokes chroot(), DPKG_ROOT becomes empty. At the moment, DPKG_ROOT is always empty, but this should not be relied upon. This is 0001-export-a-variable-DPKG_ROOT.patch. b) Packages that do not "set -u" (nounset), can now prepend $DPKG_ROOT to any file they operate on. With old versions $DPKG_ROOT will be unset and with change a) $DPKG_ROOT will be empty. Thus this change is backwards-compatible. c) dpkg should gain a new force flag. I call it --force-remote-configure for now. It is supposed to force dpkg into running maintainer scripts without chroot even when the package in question did not declare that its maintainer scripts support this mode of operation. Note that we currently have no way to express whether a package supports running maintainer scripts without chroot. The flag is being added by 0002-add-force-remote-scripts.patch and the behavior is implemented by 0003-inhibit-chroot-when-force-remote-scripts.patch. Packages can only reasonably support this mode after implementing b). d) Once a) is accepted and b) starts getting implemented, we need to think about a way for packages to tell that they support "remote scripts". One way to do so would be to add a header "Remote-Scripts: yes" to the binary package stanza. Packages thus marked would be required to honour DPKG_ROOT in all maintainer scripts. This flag makes no provisions yet on what programs can be assumed to be installed outside the chroot that is operated on. e) Once a) is accepted and b) starts getting implemented, we need to think about what programs maintainer scripts can assume to be available outside the chroot. Some ways to handle that: * Packages may only assume "common unix functionality". Such a set would have to be defined somehow and roughly equates what debootstrap requires. * Packages may only assume essential packages to be available. * A new set of headers Maint-{Depends,Conflicts,...} is added to request tools to be installed. These new relations would be checked outside the chroot (if any). A full Debian release or two needs to pass before such headers can be used in the archive. This also poses the problem that a user can remove packages required for removing other packages and thus revoking the ability to remove certain packages. It is not clear how the absence of Maint-Depends is supposed to be handled. It is not clear whether dpkg needs to lock the dpkg database outside the chroot. f) Once a), b) and d) are implemented and some version of e) is agreed upon, debootstrap can be changed to prefer configuring packages that support "remote scripts" to break dependency cycles. g) At the same time as f), tools like multistrap can start making use of this new functionality. With the above patches, I verified that I can install a package with a maintainer script into a chroot that has an empty database (in particular no essential package is unpacked in the chroot). The maintainer script is run without chroot and has DPKG_ROOT set up properly. bash's preinst will become a problem with this scheme. It is a binary due to earlier breakage when it was a script, but the "remote scripts" approach cannot work with binaries, because we cannot know whether the cpu supports executing such binaries. I don't have a plan for bash.preinst. I understand that what I propose herein is a steep change with wide ranging implications. It needs sincere thought to avoid creating a situation that is hard to fix up. Yet it seems pretty round to me already. Despite there being open questions, we can handle at least a) today. Helmut
>From b10ba1186394ae880a5c1421ddb07afa1f3c9d20 Mon Sep 17 00:00:00 2001 From: Helmut Grohne <hel...@subdivi.de> Date: Mon, 9 Nov 2015 22:07:52 +0100 Subject: [PATCH 1/3] export a variable DPKG_ROOT This variable holds the value of instdir. It is supposed to be used in maintainer scripts. It should be prepended to all paths that are operated on. Currently, dpkg chroots to the instdir before invoking maintainer scripts, so when it does that DPKG_ROOT is set to the empty string. Thus currently, DPKG_ROOT is always empty. --- src/main.c | 2 ++ src/script.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main.c b/src/main.c index f16dc0a..4ac701c 100644 --- a/src/main.c +++ b/src/main.c @@ -885,6 +885,8 @@ int main(int argc, const char *const *argv) { /* Always set environment, to avoid possible security risks. */ if (setenv("DPKG_ADMINDIR", admindir, 1) < 0) ohshite(_("unable to setenv for subprocesses")); + if (setenv("DPKG_ROOT", instdir, 1) < 0) + ohshite(_("unable to setenv for subprocesses")); if (!f_triggers) f_triggers = (cipaction->arg_int == act_triggers && *argv) ? -1 : 1; diff --git a/src/script.c b/src/script.c index a958145..ac79444 100644 --- a/src/script.c +++ b/src/script.c @@ -104,6 +104,8 @@ maintscript_pre_exec(struct command *cmd) ohshit(_("admindir must be inside instdir for dpkg to work properly")); if (setenv("DPKG_ADMINDIR", admindir + instdirl, 1) < 0) ohshite(_("unable to setenv for subprocesses")); + if (setenv("DPKG_ROOT", "", 1) < 0) + ohshite(_("unable to setenv for subprocesses")); if (chroot(instdir)) ohshite(_("failed to chroot to '%.250s'"), instdir); -- 2.4.6
>From 8b7ef663a42b2f88fb91c5e72006362b30d33f85 Mon Sep 17 00:00:00 2001 From: Helmut Grohne <hel...@subdivi.de> Date: Mon, 9 Nov 2015 22:16:10 +0100 Subject: [PATCH 2/3] add --force-remote-scripts Currently, dpkg chroots to the instdir before invoking maintainer scripts. The new force flag is supposed to inhibit the chroot call. The user is supposed to know that the packages he is operating on do support this new mode of operation. Thus the force flag is marked as dangerous. --- src/main.c | 3 +++ src/main.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/main.c b/src/main.c index 4ac701c..9dd5308 100644 --- a/src/main.c +++ b/src/main.c @@ -196,6 +196,7 @@ int fc_conff_ask = 0; int fc_unsafe_io = 0; int fc_badverify = 0; int fc_badversion = 0; +int fc_remote_scripts = 0; int errabort = 50; static const char *admindir = ADMINDIR; @@ -275,6 +276,8 @@ static const struct forceinfo { '!', N_("Remove packages which require installation") }, { "remove-essential", &fc_removeessential, '!', N_("Remove an essential package") }, + { "remote-scripts", &fc_remote_scripts, + '!', N_("Allow running maintainer scripts remotely") }, { NULL } }; diff --git a/src/main.h b/src/main.h index c85c2cb..73a13cb 100644 --- a/src/main.h +++ b/src/main.h @@ -142,6 +142,7 @@ extern int fc_conff_ask; extern int fc_badverify; extern int fc_badversion; extern int fc_unsafe_io; +extern int fc_remote_scripts; extern bool abort_processing; extern int errabort; -- 2.4.6
>From 2a0d657eded51651a8aec658a4c84a607ad51988 Mon Sep 17 00:00:00 2001 From: Helmut Grohne <hel...@subdivi.de> Date: Mon, 9 Nov 2015 22:20:57 +0100 Subject: [PATCH 3/3] inhibit chroot when --force-remote-scripts --- src/script.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/script.c b/src/script.c index ac79444..00075b9 100644 --- a/src/script.c +++ b/src/script.c @@ -99,7 +99,7 @@ maintscript_pre_exec(struct command *cmd) const char *admindir = dpkg_db_get_dir(); size_t instdirl = strlen(instdir); - if (*instdir) { + if (*instdir && !fc_remote_scripts) { if (strncmp(admindir, instdir, instdirl) != 0) ohshit(_("admindir must be inside instdir for dpkg to work properly")); if (setenv("DPKG_ADMINDIR", admindir + instdirl, 1) < 0) @@ -112,8 +112,8 @@ maintscript_pre_exec(struct command *cmd) } /* Switch to a known good directory to give the maintainer script * a saner environment, also needed after the chroot(). */ - if (chdir("/")) - ohshite(_("failed to chdir to '%.255s'"), "/"); + if (chdir(fc_remote_scripts ? instdir : "/")) + ohshite(_("failed to chdir to '%.255s'"), fc_remote_scripts ? instdir : "/"); if (debug_has_flag(dbg_scripts)) { struct varbuf args = VARBUF_INIT; const char **argv = cmd->argv; @@ -127,7 +127,7 @@ maintscript_pre_exec(struct command *cmd) args.buf); varbuf_destroy(&args); } - if (!instdirl) + if (fc_remote_scripts || !instdirl) return cmd->filename; assert(strlen(cmd->filename) >= instdirl); -- 2.4.6