From patchwork Wed May 13 12:52:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Ludovic_Court=C3=A8s?= X-Patchwork-Id: 22053 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id D48EB27BBE1; Wed, 13 May 2020 13:53:10 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_MSPIKE_H2,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTP id 33D6827BBE5 for ; Wed, 13 May 2020 13:53:09 +0100 (BST) Received: from localhost ([::1]:40828 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqsm-0001UU-Lt for patchwork@mira.cbaines.net; Wed, 13 May 2020 08:53:08 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40772) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYqsh-0001U2-6g for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:46094) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYqsg-0001S7-U4 for guix-patches@gnu.org; Wed, 13 May 2020 08:53:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYqsg-0007xz-Sm for guix-patches@gnu.org; Wed, 13 May 2020 08:53:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH v2 1/4] pack: Wrapper honors 'GUIX_EXECUTION_ENGINE' environment variable. Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 13 May 2020 12:53:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 41189 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 41189@debbugs.gnu.org Cc: Ludovic =?utf-8?q?Court=C3=A8s?= Received: via spool by 41189-submit@debbugs.gnu.org id=B41189.158937435130546 (code B ref 41189); Wed, 13 May 2020 12:53:02 +0000 Received: (at 41189) by debbugs.gnu.org; 13 May 2020 12:52:31 +0000 Received: from localhost ([127.0.0.1]:57630 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsA-0007wZ-Su for submit@debbugs.gnu.org; Wed, 13 May 2020 08:52:31 -0400 Received: from eggs.gnu.org ([209.51.188.92]:37718) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqs8-0007vi-GR for 41189@debbugs.gnu.org; Wed, 13 May 2020 08:52:29 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:35568) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqs3-0001Kl-83; Wed, 13 May 2020 08:52:23 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=44970 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYqs2-0006Fm-GG; Wed, 13 May 2020 08:52:22 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Wed, 13 May 2020 14:52:12 +0200 Message-Id: <20200513125215.27740-2-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200513125215.27740-1-ludo@gnu.org> References: <87tv0l9njp.fsf@inria.fr> <20200513125215.27740-1-ludo@gnu.org> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches * 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") From patchwork Wed May 13 12:52:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Ludovic_Court=C3=A8s?= X-Patchwork-Id: 22055 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 49DB127BBE3; Wed, 13 May 2020 13:53:13 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_MSPIKE_H2 autolearn=unavailable autolearn_force=no version=3.4.2 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTP id 1495627BBE1 for ; Wed, 13 May 2020 13:53:13 +0100 (BST) Received: from localhost ([::1]:41130 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqsq-0001dC-Ll for patchwork@mira.cbaines.net; Wed, 13 May 2020 08:53:12 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40774) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYqsh-0001U8-Jb for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:46095) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYqsh-0001SK-Ak for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYqsh-0007y7-9F for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH v2 2/4] pack: Factorize store references in wrapper. Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 13 May 2020 12:53:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 41189 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 41189@debbugs.gnu.org Cc: Ludovic =?utf-8?q?Court=C3=A8s?= Received: via spool by 41189-submit@debbugs.gnu.org id=B41189.158937435130552 (code B ref 41189); Wed, 13 May 2020 12:53:03 +0000 Received: (at 41189) by debbugs.gnu.org; 13 May 2020 12:52:31 +0000 Received: from localhost ([127.0.0.1]:57632 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsB-0007wd-A9 for submit@debbugs.gnu.org; Wed, 13 May 2020 08:52:31 -0400 Received: from eggs.gnu.org ([209.51.188.92]:37724) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsA-0007vk-2z for 41189@debbugs.gnu.org; Wed, 13 May 2020 08:52:30 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:35571) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqs4-0001Ku-FZ; Wed, 13 May 2020 08:52:24 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=44970 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYqs3-0006Fm-Ir; Wed, 13 May 2020 08:52:24 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Wed, 13 May 2020 14:52:13 +0200 Message-Id: <20200513125215.27740-3-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200513125215.27740-1-ludo@gnu.org> References: <87tv0l9njp.fsf@inria.fr> <20200513125215.27740-1-ludo@gnu.org> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches * gnu/packages/aux-files/run-in-namespace.c (original_store): New variable. (exec_in_user_namespace, exec_with_proot, main): Use it instead of the literal "@STORE_DIRECTORY@". --- gnu/packages/aux-files/run-in-namespace.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index 6beac7fd53..6e97359078 100644 --- a/gnu/packages/aux-files/run-in-namespace.c +++ b/gnu/packages/aux-files/run-in-namespace.c @@ -42,6 +42,10 @@ #include #include +/* The original store, "/gnu/store" by default. */ +static const char original_store[] = "@STORE_DIRECTORY@"; + + /* Like 'malloc', but abort if 'malloc' returns NULL. */ static void * xmalloc (size_t size) @@ -228,7 +232,7 @@ exec_in_user_namespace (const char *store, int argc, char *argv[]) bind-mounted in the right place. */ int err; char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX")); - char *new_store = concat (new_root, "@STORE_DIRECTORY@"); + char *new_store = concat (new_root, original_store); char *cwd = get_current_dir_name (); /* Create a child with separate namespaces and set up bind-mounts from @@ -307,11 +311,11 @@ exec_with_proot (const char *store, int argc, char *argv[]) int proot_specific_argc = 4; int proot_argc = argc + proot_specific_argc; char *proot_argv[proot_argc + 1], *proot; - char bind_spec[strlen (store) + 1 + sizeof "@STORE_DIRECTORY@"]; + char bind_spec[strlen (store) + 1 + sizeof original_store]; strcpy (bind_spec, store); strcat (bind_spec, ":"); - strcat (bind_spec, "@STORE_DIRECTORY@"); + strcat (bind_spec, original_store); proot = concat (store, PROOT_PROGRAM); @@ -413,8 +417,7 @@ main (int argc, char *argv[]) /* SELF is something like "/home/ludo/.local/gnu/store/…-foo/bin/ls" and we want to extract "/home/ludo/.local/gnu/store". */ size_t index = strlen (self) - - strlen ("@WRAPPED_PROGRAM@") - + strlen ("@STORE_DIRECTORY@"); + - strlen ("@WRAPPED_PROGRAM@") + strlen (original_store); char *store = strdup (self); store[index] = '\0'; @@ -424,7 +427,7 @@ main (int argc, char *argv[]) @WRAPPED_PROGRAM@ right away. This is not just an optimization: it's needed when running one of these wrappers from within an unshare'd namespace, because 'unshare' fails with EPERM in that context. */ - if (strcmp (store, "@STORE_DIRECTORY@") != 0 + if (strcmp (store, original_store) != 0 && lstat ("@WRAPPED_PROGRAM@", &statbuf) != 0) { const struct engine *engine = execution_engine (); From patchwork Wed May 13 12:52:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Ludovic_Court=C3=A8s?= X-Patchwork-Id: 22054 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id F364827BBE1; Wed, 13 May 2020 13:53:10 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_MSPIKE_H2,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTP id 2602627BBE3 for ; Wed, 13 May 2020 13:53:10 +0100 (BST) Received: from localhost ([::1]:40886 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqsn-0001WG-My for patchwork@mira.cbaines.net; Wed, 13 May 2020 08:53:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40776) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYqsh-0001US-Vx for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:46096) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYqsh-0001SU-Mq for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYqsh-0007yF-LY for guix-patches@gnu.org; Wed, 13 May 2020 08:53:03 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH v2 3/4] gnu: Add fakechroot. Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 13 May 2020 12:53:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 41189 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 41189@debbugs.gnu.org Cc: Ludovic =?utf-8?q?Court=C3=A8s?= Received: via spool by 41189-submit@debbugs.gnu.org id=B41189.158937435430562 (code B ref 41189); Wed, 13 May 2020 12:53:03 +0000 Received: (at 41189) by debbugs.gnu.org; 13 May 2020 12:52:34 +0000 Received: from localhost ([127.0.0.1]:57635 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsE-0007ws-JH for submit@debbugs.gnu.org; Wed, 13 May 2020 08:52:34 -0400 Received: from eggs.gnu.org ([209.51.188.92]:37728) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsA-0007vs-GE for 41189@debbugs.gnu.org; Wed, 13 May 2020 08:52:30 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:35572) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqs5-0001Kz-7w; Wed, 13 May 2020 08:52:25 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=44970 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYqs4-0006Fm-Qy; Wed, 13 May 2020 08:52:25 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Wed, 13 May 2020 14:52:14 +0200 Message-Id: <20200513125215.27740-4-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200513125215.27740-1-ludo@gnu.org> References: <87tv0l9njp.fsf@inria.fr> <20200513125215.27740-1-ludo@gnu.org> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches * gnu/packages/linux.scm (fakechroot): New variable. --- gnu/packages/linux.scm | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm index 7cf7521e24..35526b3513 100644 --- a/gnu/packages/linux.scm +++ b/gnu/packages/linux.scm @@ -6793,6 +6793,36 @@ have to construct the archives directly, without using the archiver.") (home-page "http://freshmeat.sourceforge.net/projects/fakeroot") (license license:gpl3+))) +(define-public fakechroot + (package + (name "fakechroot") + (version "2.20.1") + (source (origin + (method url-fetch) + (uri (string-append + "https://github.com/dex4er/fakechroot/releases/download/" + version "/fakechroot-" version ".tar.gz")) + (sha256 + (base32 + "1aijkd0b45wav25v01qhw8zxwa3pl0nnp9fabmmy1nlx7hr09gas")))) + (build-system gnu-build-system) + (arguments + ;; XXX: The tests heavily assume they run on an FHS system so for now + ;; skip them. + '(#:tests? #f + #:configure-flags '("--disable-static"))) + (synopsis "Emulate @code{chroot} by overriding file system calls") + (description + "@command{fakechroot} runs a command in an environment were is additional +possibility to use @code{chroot} command without root privileges. This is +useful for allowing users to create own chrooted environment with possibility +to install another packages without need for root privileges. + +It works by providing @file{libfakechroot.so}, a shared library meant to be +set as @code{LD_PRELOAD} to override the C library file system functions.") + (home-page "https://github.com/dex4er/fakechroot/") + (license license:lgpl2.1+))) + (define-public inputattach (package (name "inputattach") From patchwork Wed May 13 12:52:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Ludovic_Court=C3=A8s?= X-Patchwork-Id: 22056 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 6FC4227BBE1; Wed, 13 May 2020 13:53:17 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_MSPIKE_H2,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTP id 1AFFE27BBE3 for ; Wed, 13 May 2020 13:53:16 +0100 (BST) Received: from localhost ([::1]:41528 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqst-0001n7-Mw for patchwork@mira.cbaines.net; Wed, 13 May 2020 08:53:15 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40778) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYqsi-0001V6-DR for guix-patches@gnu.org; Wed, 13 May 2020 08:53:04 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:46097) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYqsi-0001SY-3i for guix-patches@gnu.org; Wed, 13 May 2020 08:53:04 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYqsi-0007yM-28 for guix-patches@gnu.org; Wed, 13 May 2020 08:53:04 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH v2 4/4] pack: Add relocation via ld.so and fakechroot. Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 13 May 2020 12:53:04 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 41189 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 41189@debbugs.gnu.org Cc: Ludovic =?utf-8?q?Court=C3=A8s?= Received: via spool by 41189-submit@debbugs.gnu.org id=B41189.158937436430585 (code B ref 41189); Wed, 13 May 2020 12:53:04 +0000 Received: (at 41189) by debbugs.gnu.org; 13 May 2020 12:52:44 +0000 Received: from localhost ([127.0.0.1]:57637 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsI-0007x8-TU for submit@debbugs.gnu.org; Wed, 13 May 2020 08:52:44 -0400 Received: from eggs.gnu.org ([209.51.188.92]:37734) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYqsC-0007w3-PO for 41189@debbugs.gnu.org; Wed, 13 May 2020 08:52:37 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:35573) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYqs6-0001LC-HF; Wed, 13 May 2020 08:52:26 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=44970 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYqs5-0006Fm-JX; Wed, 13 May 2020 08:52:25 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Wed, 13 May 2020 14:52:15 +0200 Message-Id: <20200513125215.27740-5-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200513125215.27740-1-ludo@gnu.org> References: <87tv0l9njp.fsf@inria.fr> <20200513125215.27740-1-ludo@gnu.org> MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches From: Ludovic Courtès * gnu/packages/aux-files/run-in-namespace.c (HAVE_EXEC_WITH_LOADER): New macro. (bind_mount): Rename to... (mirror_directory): ... this. Add 'firmlink' argument and use it instead of calling mkdir/open/close/mount directly. (bind_mount, make_symlink): New functions. (exec_in_user_namespace): Adjust accordingly. (exec_with_loader) [HAVE_EXEC_WITH_LOADER]: New function. (exec_performance): New function. (engines): Add them. * guix/scripts/pack.scm (wrapped-package)[fakechroot-library] [audit-module]: New procedures. [audit-source]: New variable. [build](elf-interpreter, elf-loader-compile-flags): New procedures. (build-wrapper): Use them. * tests/guix-pack-relocatable.sh: Test with 'GUIX_EXECUTION_ENGINE=fakechroot'. * doc/guix.texi (Invoking guix pack): Document the 'performance' and 'fakechroot' engines. * gnu/packages/aux-files/pack-audit.c: New file. * Makefile.am (AUX_FILES): Add it. --- Makefile.am | 1 + doc/guix.texi | 13 ++ gnu/packages/aux-files/pack-audit.c | 85 ++++++++++++ gnu/packages/aux-files/run-in-namespace.c | 160 ++++++++++++++++++++-- guix/scripts/pack.scm | 87 +++++++++++- tests/guix-pack-relocatable.sh | 6 + 6 files changed, 331 insertions(+), 21 deletions(-) create mode 100644 gnu/packages/aux-files/pack-audit.c diff --git a/Makefile.am b/Makefile.am index 6cd6e79cab..f65bf5f900 100644 --- a/Makefile.am +++ b/Makefile.am @@ -338,6 +338,7 @@ AUX_FILES = \ gnu/packages/aux-files/linux-libre/4.9-x86_64.conf \ gnu/packages/aux-files/linux-libre/4.4-i686.conf \ gnu/packages/aux-files/linux-libre/4.4-x86_64.conf \ + gnu/packages/aux-files/pack-audit.c \ gnu/packages/aux-files/run-in-namespace.c # Templates, examples. diff --git a/doc/guix.texi b/doc/guix.texi index 958ed9ceec..a70a058afb 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -5228,6 +5228,10 @@ following execution engines are supported: Try user namespaces and fall back to PRoot if user namespaces are not supported (see below). +@item performance +Try user namespaces and fall back to Fakechroot if user namespaces are +not supported (see below). + @item userns Run the program through user namespaces and abort if they are not supported. @@ -5239,6 +5243,15 @@ 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. + +@item fakechroot +Run through Fakechroot. @uref{https://github.com/dex4er/fakechroot/, +Fakechroot} virtualizes file system accesses by intercepting calls to C +library functions such as @code{open}, @code{stat}, @code{exec}, and so +on. Unlike PRoot, it incurs very little overhead. However, it does not +always work: for example, some file system accesses made from within the +C library are not intercepted, and file system accesses made @i{via} +direct syscalls are not intercepted either, leading to erratic behavior. @end table @vindex GUIX_EXECUTION_ENGINE diff --git a/gnu/packages/aux-files/pack-audit.c b/gnu/packages/aux-files/pack-audit.c new file mode 100644 index 0000000000..374787e8b9 --- /dev/null +++ b/gnu/packages/aux-files/pack-audit.c @@ -0,0 +1,85 @@ +/* GNU Guix --- Functional package management for GNU + Copyright (C) 2020 Ludovic Courtès + + This file is part of GNU Guix. + + GNU Guix is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or (at + your option) any later version. + + GNU Guix is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Guix. If not, see . */ + +/* This file implements part of the GNU ld.so audit interface. It is used by + the "fakechroot" engine of the 'guix pack -RR' wrappers to make sure the + loader looks for shared objects under the "fake" root directory. */ + +#define _GNU_SOURCE 1 + +#include + +#include +#include +#include +#include + +/* The pseudo root directory and store that we are relocating to. */ +static const char *root_directory; +static char *store; + +/* The original store, "/gnu/store" by default. */ +static const char original_store[] = "@STORE_DIRECTORY@"; + +/* Like 'malloc', but abort if 'malloc' returns NULL. */ +static void * +xmalloc (size_t size) +{ + void *result = malloc (size); + assert (result != NULL); + return result; +} + +unsigned int +la_version (unsigned int v) +{ + if (v != LAV_CURRENT) + error (1, 0, "cannot handle interface version %u", v); + + root_directory = getenv ("FAKECHROOT_BASE"); + if (root_directory == NULL) + error (1, 0, "'FAKECHROOT_BASE' is not set"); + + store = xmalloc (strlen (root_directory) + sizeof original_store); + strcpy (store, root_directory); + strcat (store, original_store); + + return v; +} + +/* Return NAME, a shared object file name, relocated under STORE. This + function is called by the loader whenever it looks for a shared object. */ +char * +la_objsearch (const char *name, uintptr_t *cookie, unsigned int flag) +{ + char *result; + + if (strncmp (name, original_store, + sizeof original_store - 1) == 0) + { + size_t len = strlen (name) - sizeof original_store + + strlen (store) + 1; + result = xmalloc (len); + strcpy (result, store); + strcat (result, name + sizeof original_store - 1); + } + else + result = strdup (name); + + return result; +} diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index 6e97359078..5a6b932b87 100644 --- a/gnu/packages/aux-files/run-in-namespace.c +++ b/gnu/packages/aux-files/run-in-namespace.c @@ -42,6 +42,11 @@ #include #include +/* Whether we're building the ld.so/libfakechroot wrapper. */ +#define HAVE_EXEC_WITH_LOADER \ + (defined PROGRAM_INTERPRETER) && (defined LOADER_AUDIT_MODULE) \ + && (defined FAKECHROOT_LIBRARY) + /* The original store, "/gnu/store" by default. */ static const char original_store[] = "@STORE_DIRECTORY@"; @@ -117,9 +122,42 @@ rm_rf (const char *directory) assert_perror (errno); } -/* Bind mount all the top-level entries in SOURCE to TARGET. */ +/* Make TARGET a bind-mount of SOURCE. Take into account ENTRY's type, which + corresponds to SOURCE. */ +static int +bind_mount (const char *source, const struct dirent *entry, + const char *target) +{ + if (entry->d_type == DT_DIR) + { + int err = mkdir (target, 0700); + if (err != 0) + return err; + } + else + close (open (target, O_WRONLY | O_CREAT)); + + return mount (source, target, "none", + MS_BIND | MS_REC | MS_RDONLY, NULL); +} + +#if HAVE_EXEC_WITH_LOADER + +/* Make TARGET a symlink to SOURCE. */ +static int +make_symlink (const char *source, const struct dirent *entry, + const char *target) +{ + return symlink (source, target); +} + +#endif + +/* Mirror with FIRMLINK all the top-level entries in SOURCE to TARGET. */ static void -bind_mount (const char *source, const char *target) +mirror_directory (const char *source, const char *target, + int (* firmlink) (const char *, const struct dirent *, + const char *)) { DIR *stream = opendir (source); @@ -154,17 +192,7 @@ bind_mount (const char *source, const char *target) else { /* Create the mount point. */ - if (entry->d_type == DT_DIR) - { - int err = mkdir (new_entry, 0700); - if (err != 0) - assert_perror (errno); - } - else - close (open (new_entry, O_WRONLY | O_CREAT)); - - int err = mount (abs_source, new_entry, "none", - MS_BIND | MS_REC | MS_RDONLY, NULL); + int err = firmlink (abs_source, entry, new_entry); /* It used to be that only directories could be bind-mounted. Thus, keep going if we fail to bind-mount a non-directory entry. @@ -248,7 +276,7 @@ exec_in_user_namespace (const char *store, int argc, char *argv[]) /* Note: Due to we cannot make NEW_ROOT a tmpfs (which would have saved the need for 'rm_rf'.) */ - bind_mount ("/", new_root); + mirror_directory ("/", new_root, bind_mount); mkdir_p (new_store); err = mount (store, new_store, "none", MS_BIND | MS_REC | MS_RDONLY, NULL); @@ -340,6 +368,92 @@ exec_with_proot (const char *store, int argc, char *argv[]) #endif + +#if HAVE_EXEC_WITH_LOADER + +/* Execute the wrapped program by invoking the loader (ld.so) directly, + passing it the audit module and preloading libfakechroot.so. */ +static void +exec_with_loader (const char *store, int argc, char *argv[]) +{ + char *loader = concat (store, + PROGRAM_INTERPRETER + sizeof original_store); + size_t loader_specific_argc = 6; + size_t loader_argc = argc + loader_specific_argc; + char *loader_argv[loader_argc + 1]; + loader_argv[0] = argv[0]; + loader_argv[1] = "--audit"; + loader_argv[2] = concat (store, + LOADER_AUDIT_MODULE + sizeof original_store); + loader_argv[3] = "--preload"; + loader_argv[4] = concat (store, + FAKECHROOT_LIBRARY + sizeof original_store); + loader_argv[5] = concat (store, + "@WRAPPED_PROGRAM@" + sizeof original_store); + + for (size_t i = 0; i < argc; i++) + loader_argv[i + loader_specific_argc] = argv[i + 1]; + + loader_argv[loader_argc] = NULL; + + /* Set up the root directory. */ + int err; + char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX")); + mirror_directory ("/", new_root, make_symlink); + + char *new_store = concat (new_root, original_store); + char *new_store_parent = dirname (strdup (new_store)); + mkdir_p (new_store_parent); + symlink (store, new_store); + +#ifdef GCONV_DIRECTORY + /* Tell libc where to find its gconv modules. This is necessary because + gconv uses non-interposable 'open' calls. */ + char *gconv_path = concat (store, + GCONV_DIRECTORY + sizeof original_store); + setenv ("GCONV_PATH", gconv_path, 1); + free (gconv_path); +#endif + + setenv ("FAKECHROOT_BASE", new_root, 1); + + pid_t child = fork (); + switch (child) + { + case 0: + err = execv (loader, loader_argv); + if (err < 0) + assert_perror (errno); + exit (EXIT_FAILURE); + break; + + case -1: + assert_perror (errno); + exit (EXIT_FAILURE); + break; + + default: + { + int status; + waitpid (child, &status, 0); + chdir ("/"); /* avoid EBUSY */ + rm_rf (new_root); + free (new_root); + + close (2); /* flushing stderr should be silent */ + + if (WIFEXITED (status)) + exit (WEXITSTATUS (status)); + else + /* Abnormal termination cannot really be reproduced, so exit + with 255. */ + exit (255); + } + } +} + +#endif + /* Execution engines. */ @@ -356,7 +470,7 @@ buffer_stderr (void) setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer); } -/* The default engine. */ +/* The default engine: choose a robust method. */ static void exec_default (const char *store, int argc, char *argv[]) { @@ -370,13 +484,29 @@ exec_default (const char *store, int argc, char *argv[]) #endif } +/* The "performance" engine: choose performance over robustness. */ +static void +exec_performance (const char *store, int argc, char *argv[]) +{ + buffer_stderr (); + + exec_in_user_namespace (store, argc, argv); +#if HAVE_EXEC_WITH_LOADER + exec_with_loader (store, argc, argv); +#endif +} + /* List of supported engines. */ static const struct engine engines[] = { { "default", exec_default }, + { "performance", exec_performance }, { "userns", exec_in_user_namespace }, #ifdef PROOT_PROGRAM { "proot", exec_with_proot }, +#endif +#if HAVE_EXEC_WITH_LOADER + { "fakechroot", exec_with_loader }, #endif { NULL, NULL } }; diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 580f696b41..23aab01701 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm @@ -681,18 +681,50 @@ last resort for relocation." (define runner (local-file (search-auxiliary-file "run-in-namespace.c"))) + (define audit-source + (local-file (search-auxiliary-file "pack-audit.c"))) + (define (proot) (specification->package "proot-static")) + (define (fakechroot-library) + (computed-file "libfakechroot.so" + #~(copy-file #$(file-append + (specification->package "fakechroot") + "/lib/fakechroot/libfakechroot.so") + #$output))) + + (define (audit-module) + ;; Return an ld.so audit module for use by the 'fakechroot' execution + ;; engine that translates file names of all the files ld.so loads. + (computed-file "pack-audit.so" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + + (copy-file #$audit-source "audit.c") + (substitute* "audit.c" + (("@STORE_DIRECTORY@") + (%store-directory))) + + (invoke #$compiler "-std=gnu99" + "-shared" "-fPIC" "-Os" "-g0" + "-Wall" "audit.c" "-o" #$output))))) + (define build (with-imported-modules (source-module-closure '((guix build utils) - (guix build union))) + (guix build union) + (guix elf))) #~(begin (use-modules (guix build utils) ((guix build union) #:select (relative-file-name)) + (guix elf) + (ice-9 binary-ports) (ice-9 ftw) - (ice-9 match)) + (ice-9 match) + (srfi srfi-1) + (rnrs bytevectors)) (define input ;; The OUTPUT* output of PACKAGE. @@ -711,6 +743,48 @@ last resort for relocation." (#f base) (index (string-drop base index))))) + (define (elf-interpreter elf) + ;; Return the interpreter of ELF as a string, or #f if ELF has no + ;; interpreter segment. + (match (find (lambda (segment) + (= (elf-segment-type segment) PT_INTERP)) + (elf-segments elf)) + (#f #f) ;maybe a .so + (segment + (let ((bv (make-bytevector (- (elf-segment-memsz segment) 1)))) + (bytevector-copy! (elf-bytes elf) + (elf-segment-offset segment) + bv 0 (bytevector-length bv)) + (utf8->string bv))))) + + (define (elf-loader-compile-flags program) + ;; Return the cpp flags defining macros for the ld.so/fakechroot + ;; wrapper of PROGRAM. + + ;; TODO: Handle scripts by wrapping their interpreter. + (if (elf-file? program) + (let* ((bv (call-with-input-file program + get-bytevector-all)) + (elf (parse-elf bv)) + (interp (elf-interpreter elf)) + (gconv (and interp + (string-append (dirname interp) + "/gconv")))) + (if interp + (list (string-append "-DPROGRAM_INTERPRETER=\"" + interp "\"") + (string-append "-DFAKECHROOT_LIBRARY=\"" + #$(fakechroot-library) "\"") + + (string-append "-DLOADER_AUDIT_MODULE=\"" + #$(audit-module) "\"") + (if gconv + (string-append "-DGCONV_DIRECTORY=\"" + gconv "\"") + "-UGCONV_DIRECTORY")) + '())) + '())) + (define (build-wrapper program) ;; Build a user-namespace wrapper for PROGRAM. (format #t "building wrapper for '~a'...~%" program) @@ -730,10 +804,11 @@ last resort for relocation." (mkdir-p (dirname result)) (apply invoke #$compiler "-std=gnu99" "-static" "-Os" "-g0" "-Wall" "run.c" "-o" result - (if proot - (list (string-append "-DPROOT_PROGRAM=\"" - proot "\"")) - '())) + (append (if proot + (list (string-append "-DPROOT_PROGRAM=\"" + proot "\"")) + '()) + (elf-loader-compile-flags program))) (delete-file "run.c"))) (setvbuf (current-output-port) 'line) diff --git a/tests/guix-pack-relocatable.sh b/tests/guix-pack-relocatable.sh index cb56815fed..358cac5b26 100644 --- a/tests/guix-pack-relocatable.sh +++ b/tests/guix-pack-relocatable.sh @@ -94,6 +94,12 @@ case "`uname -m`" in export GUIX_EXECUTION_ENGINE "$test_directory/Bin/sed" --version > "$test_directory/output" grep 'GNU sed' "$test_directory/output" + + # Now with fakechroot. + GUIX_EXECUTION_ENGINE="fakechroot" + "$test_directory/Bin/sed" --version > "$test_directory/output" + grep 'GNU sed' "$test_directory/output" + chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* ;; *)