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

Reply via email to