From patchwork Mon May 11 17:11:33 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: 22001 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 0E4D427BBE1; Mon, 11 May 2020 18:12: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 73B5B27BBE1 for ; Mon, 11 May 2020 18:12:09 +0100 (BST) Received: from localhost ([::1]:57336 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYByK-0004wh-W5 for patchwork@mira.cbaines.net; Mon, 11 May 2020 13:12:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60372) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYByE-0004v5-Ea for guix-patches@gnu.org; Mon, 11 May 2020 13:12:02 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:41588) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYByE-0001Ux-4S for guix-patches@gnu.org; Mon, 11 May 2020 13:12:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYByD-0001d4-VS for guix-patches@gnu.org; Mon, 11 May 2020 13:12:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH 1/3] pack: Wrapper honors 'GUIX_EXECUTION_ENGINE' environment variable. References: <20200511170554.22916-1-ludo@gnu.org> In-Reply-To: <20200511170554.22916-1-ludo@gnu.org> Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 11 May 2020 17:12:01 +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.15892171126236 (code B ref 41189); Mon, 11 May 2020 17:12:01 +0000 Received: (at 41189) by debbugs.gnu.org; 11 May 2020 17:11:52 +0000 Received: from localhost ([127.0.0.1]:53129 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYBy4-0001cV-AI for submit@debbugs.gnu.org; Mon, 11 May 2020 13:11:52 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57330) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYBy1-0001cA-TN for 41189@debbugs.gnu.org; Mon, 11 May 2020 13:11:50 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:41566) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYBxw-0001TU-HO; Mon, 11 May 2020 13:11:44 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=38172 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYBxv-0005Cj-Sf; Mon, 11 May 2020 13:11:44 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Mon, 11 May 2020 19:11:33 +0200 Message-Id: <20200511171135.23157-1-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 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 Mon May 11 17:11:34 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: 22002 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 480FA27BBE1; Mon, 11 May 2020 18:12: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 C86D927BBE3 for ; Mon, 11 May 2020 18:12:09 +0100 (BST) Received: from localhost ([::1]:57348 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYByL-0004x6-Ca for patchwork@mira.cbaines.net; Mon, 11 May 2020 13:12:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60374) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYByE-0004vT-Pf for guix-patches@gnu.org; Mon, 11 May 2020 13:12:02 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:41589) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYByE-0001V1-FR for guix-patches@gnu.org; Mon, 11 May 2020 13:12:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYByE-0001dC-Bd for guix-patches@gnu.org; Mon, 11 May 2020 13:12:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH 2/3] gnu: Add fakechroot. Resent-From: Ludovic =?utf-8?q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 11 May 2020 17:12: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.15892171136243 (code B ref 41189); Mon, 11 May 2020 17:12:02 +0000 Received: (at 41189) by debbugs.gnu.org; 11 May 2020 17:11:53 +0000 Received: from localhost ([127.0.0.1]:53131 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYBy4-0001cX-PD for submit@debbugs.gnu.org; Mon, 11 May 2020 13:11:53 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57338) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYBy3-0001cC-0N for 41189@debbugs.gnu.org; Mon, 11 May 2020 13:11:51 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:41568) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYBxx-0001Tf-QM; Mon, 11 May 2020 13:11:45 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=38172 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYBxw-0005Cj-Rb; Mon, 11 May 2020 13:11:45 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Mon, 11 May 2020 19:11:34 +0200 Message-Id: <20200511171135.23157-2-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200511171135.23157-1-ludo@gnu.org> References: <20200511171135.23157-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 Mon May 11 17:11:35 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: 22003 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 8A39A27BBE3; Mon, 11 May 2020 18:13:35 +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 CBC2C27BBE1 for ; Mon, 11 May 2020 18:13:34 +0100 (BST) Received: from localhost ([::1]:33770 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYBzi-0006y1-CK for patchwork@mira.cbaines.net; Mon, 11 May 2020 13:13:34 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60616) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jYBzC-0006JT-8M for guix-patches@gnu.org; Mon, 11 May 2020 13:13:03 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:41590) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jYBzB-0001mr-QY for guix-patches@gnu.org; Mon, 11 May 2020 13:13:01 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1jYBzB-0001eh-MQ for guix-patches@gnu.org; Mon, 11 May 2020 13:13:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#41189] [PATCH 3/3] 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: Mon, 11 May 2020 17:13:01 +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.15892171346308 (code B ref 41189); Mon, 11 May 2020 17:13:01 +0000 Received: (at 41189) by debbugs.gnu.org; 11 May 2020 17:12:14 +0000 Received: from localhost ([127.0.0.1]:53134 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYByB-0001cv-6f for submit@debbugs.gnu.org; Mon, 11 May 2020 13:12:14 -0400 Received: from eggs.gnu.org ([209.51.188.92]:57352) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jYBy4-0001cF-Rj for 41189@debbugs.gnu.org; Mon, 11 May 2020 13:11:57 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:41569) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jYBxz-0001Tt-3c; Mon, 11 May 2020 13:11:47 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=38172 helo=gnu.org) by fencepost.gnu.org with esmtpsa (TLS1.2:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jYBxy-0005Cj-4S; Mon, 11 May 2020 13:11:46 -0400 From: Ludovic =?utf-8?q?Court=C3=A8s?= Date: Mon, 11 May 2020 19:11:35 +0200 Message-Id: <20200511171135.23157-3-ludo@gnu.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200511171135.23157-1-ludo@gnu.org> References: <20200511171135.23157-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]: New procedures. [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. --- doc/guix.texi | 13 ++ gnu/packages/aux-files/run-in-namespace.c | 174 ++++++++++++++++++++-- guix/scripts/pack.scm | 65 +++++++- tests/guix-pack-relocatable.sh | 6 + 4 files changed, 237 insertions(+), 21 deletions(-) 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/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index 6beac7fd53..ed72a169f2 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 PROGRAM_RUNPATH) + + /* Like 'malloc', but abort if 'malloc' returns NULL. */ static void * xmalloc (size_t size) @@ -113,9 +118,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); @@ -150,17 +188,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. @@ -244,7 +272,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); @@ -336,6 +364,106 @@ exec_with_proot (const char *store, int argc, char *argv[]) #endif + +#if HAVE_EXEC_WITH_LOADER + +static void +exec_with_loader (const char *store, int argc, char *argv[]) +{ + static const char *runpath[] = PROGRAM_RUNPATH; + char *library_path; + size_t size = 0; + + for (size_t i = 0; runpath[i] != NULL; i++) + size += strlen (store) + strlen (runpath[i]) + 1; /* upper bound */ + + library_path = xmalloc (size + 1); + library_path[0] = '\0'; + + for (size_t i = 0; runpath[i] != NULL; i++) + { + if (strncmp (runpath[i], "@STORE_DIRECTORY@", + sizeof "@STORE_DIRECTORY@" - 1) == 0) + { + strcat (library_path, store); + strcat (library_path, runpath[i] + sizeof "@STORE_DIRECTORY@"); + } + else + strcat (library_path, runpath[i]); /* possibly $ORIGIN */ + + strcat (library_path, ":"); + } + + library_path[strlen (library_path) - 1] = '\0'; /* Remove trailing colon. */ + + char *loader = concat (store, + PROGRAM_INTERPRETER + sizeof "@STORE_DIRECTORY@"); + 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] = "--library-path"; + loader_argv[2] = library_path; + loader_argv[3] = "--preload"; + loader_argv[4] = concat (store, + FAKECHROOT_LIBRARY + sizeof "@STORE_DIRECTORY@"); + loader_argv[5] = concat (store, + "@WRAPPED_PROGRAM@" + sizeof "@STORE_DIRECTORY@"); + + 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, "@STORE_DIRECTORY@"); + char *new_store_parent = dirname (strdup (new_store)); + mkdir_p (new_store_parent); + symlink (store, new_store); + + 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. */ @@ -352,7 +480,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[]) { @@ -366,13 +494,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..3b72496a34 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm @@ -684,15 +684,26 @@ last resort for relocation." (define (proot) (specification->package "proot-static")) + (define (fakechroot-library) + (file-append (specification->package "fakechroot") + "/lib/fakechroot/libfakechroot.so")) + (define build (with-imported-modules (source-module-closure '((guix build utils) - (guix build union))) + (guix build union) + (guix build gremlin) + (guix elf))) #~(begin (use-modules (guix build utils) ((guix build union) #:select (relative-file-name)) + (guix build gremlin) + (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 +722,47 @@ 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))) + (match (elf-dynamic-info elf) + (#f '()) + (dyninfo + (let ((runpath (elf-dynamic-info-runpath dyninfo)) + (interp (elf-interpreter elf))) + (if interp + (list (string-append "-DPROGRAM_INTERPRETER=\"" + interp "\"") + (string-append "-DPROGRAM_RUNPATH={ " + (string-join + (map object->string + runpath) + ", ") + ", NULL }") + (string-append "-DFAKECHROOT_LIBRARY=\"" + #$(fakechroot-library) "\"")) + '()))))) + '())) + (define (build-wrapper program) ;; Build a user-namespace wrapper for PROGRAM. (format #t "building wrapper for '~a'...~%" program) @@ -730,10 +782,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"/* ;; *)