Subject: [PATCH v2 1/4] pack: Wrapper honors 'GUIX_EXECUTION_ENGINE' environment variable.

From: Ludovic Courtès <[email-masked]>
Date: Wed, 13 May 2020 14:52:12 +0200

* gnu/packages/aux-files/run-in-namespace.c (struct engine): New type.
(exec_default): New function.
(engines): New variable.
(execution_engine): New function.
(main): Use it instead of calling 'exec_in_user_namespace' and
'exec_with_proot' directly.
* tests/guix-pack-relocatable.sh: Add test with 'GUIX_EXECUTION_ENGINE'.
* doc/guix.texi (Invoking guix pack): Document 'GUIX_EXECUTION_ENGINE'.
---
 doc/guix.texi                             | 30 +++++++--
 gnu/packages/aux-files/run-in-namespace.c | 78 ++++++++++++++++++++---
 tests/guix-pack-relocatable.sh            | 17 +++++
 3 files changed, 110 insertions(+), 15 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 0cba0ee1ec..958ed9ceec 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -5185,9 +5185,9 @@ When this option is passed once, the resulting binaries require support for
 @dfn{user namespaces} in the kernel Linux; when passed
 @emph{twice}@footnote{Here's a trick to memorize it: @code{-RR}, which adds
 PRoot support, can be thought of as the abbreviation of ``Really
-Relocatable''.  Neat, isn't it?}, relocatable binaries fall to back to PRoot
-if user namespaces are unavailable, and essentially work anywhere---see below
-for the implications.
+Relocatable''.  Neat, isn't it?}, relocatable binaries fall to back to
+other techniques if user namespaces are unavailable, and essentially
+work anywhere---see below for the implications.
 
 For example, if you create a pack containing Bash with:
 
@@ -5219,14 +5219,32 @@ turn it off.
 
 To produce relocatable binaries that work even in the absence of user
 namespaces, pass @option{--relocatable} or @option{-R} @emph{twice}.  In that
-case, binaries will try user namespace support and fall back to PRoot if user
-namespaces are not supported.
+case, binaries will try user namespace support and fall back to another
+@dfn{execution engine} if user namespaces are not supported.  The
+following execution engines are supported:
 
-The @uref{https://proot-me.github.io/, PRoot} program provides the necessary
+@table @code
+@item default
+Try user namespaces and fall back to PRoot if user namespaces are not
+supported (see below).
+
+@item userns
+Run the program through user namespaces and abort if they are not
+supported.
+
+@item proot
+Run through PRoot.  The @uref{https://proot-me.github.io/, PRoot} program
+provides the necessary
 support for file system virtualization.  It achieves that by using the
 @code{ptrace} system call on the running program.  This approach has the
 advantage to work without requiring special kernel support, but it incurs
 run-time overhead every time a system call is made.
+@end table
+
+@vindex GUIX_EXECUTION_ENGINE
+When running a wrapped program, you can explicitly request one of the
+execution engines listed above by setting the
+@code{GUIX_EXECUTION_ENGINE} environment variable accordingly.
 @end quotation
 
 @cindex entry point, for Docker images
diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c
index 23e7875173..6beac7fd53 100644
--- a/gnu/packages/aux-files/run-in-namespace.c
+++ b/gnu/packages/aux-files/run-in-namespace.c
@@ -336,6 +336,71 @@ exec_with_proot (const char *store, int argc, char *argv[])
 
 #endif
 
+
+/* Execution engines.  */
+
+struct engine
+{
+  const char *name;
+  void (* exec) (const char *, int, char **);
+};
+
+static void
+buffer_stderr (void)
+{
+  static char stderr_buffer[4096];
+  setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer);
+}
+
+/* The default engine.  */
+static void
+exec_default (const char *store, int argc, char *argv[])
+{
+  /* Buffer stderr so that nothing's displayed if 'exec_in_user_namespace'
+     fails but 'exec_with_proot' works.  */
+  buffer_stderr ();
+
+  exec_in_user_namespace (store, argc, argv);
+#ifdef PROOT_PROGRAM
+  exec_with_proot (store, argc, argv);
+#endif
+}
+
+/* List of supported engines.  */
+static const struct engine engines[] =
+  {
+   { "default", exec_default },
+   { "userns", exec_in_user_namespace },
+#ifdef PROOT_PROGRAM
+   { "proot", exec_with_proot },
+#endif
+   { NULL, NULL }
+  };
+
+/* Return the "execution engine" to use.  */
+static const struct engine *
+execution_engine (void)
+{
+  const char *str = getenv ("GUIX_EXECUTION_ENGINE");
+
+  if (str == NULL)
+    str = "default";
+
+ try:
+  for (const struct engine *engine = engines;
+       engine->name != NULL;
+       engine++)
+    {
+      if (strcmp (engine->name, str) == 0)
+	return engine;
+    }
+
+  fprintf (stderr, "%s: unsupported Guix execution engine; ignoring\n",
+	   str);
+  str = "default";
+  goto try;
+}
+
 
 int
 main (int argc, char *argv[])
@@ -362,22 +427,17 @@ main (int argc, char *argv[])
   if (strcmp (store, "@STORE_DIRECTORY@") != 0
       && lstat ("@WRAPPED_PROGRAM@", &statbuf) != 0)
     {
-      /* Buffer stderr so that nothing's displayed if 'exec_in_user_namespace'
-	 fails but 'exec_with_proot' works.  */
-      static char stderr_buffer[4096];
-      setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer);
+      const struct engine *engine = execution_engine ();
+      engine->exec (store, argc, argv);
 
-      exec_in_user_namespace (store, argc, argv);
-#ifdef PROOT_PROGRAM
-      exec_with_proot (store, argc, argv);
-#else
+      /* If we reach this point, that's because ENGINE failed to do the
+	 job.  */
       fprintf (stderr, "\
 This may be because \"user namespaces\" are not supported on this system.\n\
 Consequently, we cannot run '@WRAPPED_PROGRAM@',\n\
 unless you move it to the '@STORE_DIRECTORY@' directory.\n\
 \n\
 Please refer to the 'guix pack' documentation for more information.\n");
-#endif
       return EXIT_FAILURE;
     }
 
diff --git a/tests/guix-pack-relocatable.sh b/tests/guix-pack-relocatable.sh
index a3fd45623c..cb56815fed 100644
--- a/tests/guix-pack-relocatable.sh
+++ b/tests/guix-pack-relocatable.sh
@@ -84,6 +84,23 @@ fi
 grep 'GNU sed' "$test_directory/output"
 chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
 
+case "`uname -m`" in
+    x86_64|i?86)
+	# Try '-RR' and PRoot.
+	tarball="`guix pack -RR -S /Bin=bin sed`"
+	tar tvf "$tarball" | grep /bin/proot
+	(cd "$test_directory"; tar xvf "$tarball")
+	GUIX_EXECUTION_ENGINE="proot"
+	export GUIX_EXECUTION_ENGINE
+	"$test_directory/Bin/sed" --version > "$test_directory/output"
+	grep 'GNU sed' "$test_directory/output"
+	chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
+	;;
+    *)
+	echo "skipping PRoot test" >&2
+	;;
+esac
+
 # Ensure '-R' works with outputs other than "out".
 tarball="`guix pack -R -S /share=share groff:doc`"
 (cd "$test_directory"; tar xvf "$tarball")
-- 
2.26.2