pack: "-RR" produces PRoot-enabled relocatable binaries.

* gnu/packages/aux-files/run-in-namespace.c (exec_with_proot): New
function.
(main): When 'clone' fails, call 'rm_rf'.
[PROOT_PROGRAM]: When 'clone' fails, call 'exec_with_proot'.
* guix/scripts/pack.scm (wrapped-package): Add #:proot?.
[proot]: New procedure.
[build]: Compile with -DPROOT_PROGRAM when PROOT? is true.
* guix/scripts/pack.scm (%options): Set the 'relocatable?' value to
'proot when "-R" is passed several times.
(guix-pack): Pass #:proot? to 'wrapped-package'.
* tests/guix-pack-relocatable.sh: Use "-RR" on Intel systems that lack
user namespace support.
* doc/guix.texi (Invoking guix pack): Document -RR.
This commit is contained in:
Ludovic Courtès 2019-03-14 17:02:53 +01:00 committed by Ludovic Courtès
parent c9b3a72b67
commit 99aec37a78
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
4 changed files with 119 additions and 21 deletions

View file

@ -4760,14 +4760,24 @@ symlinks, as well as empty mount points for virtual file systems like
procfs.
@end table
@cindex relocatable binaries
@item --relocatable
@itemx -R
Produce @dfn{relocatable binaries}---i.e., binaries that can be placed
anywhere in the file system hierarchy and run from there. For example,
if you create a pack containing Bash with:
anywhere in the file system hierarchy and run from there.
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.
For example, if you create a pack containing Bash with:
@example
guix pack -R -S /mybin=bin bash
guix pack -RR -S /mybin=bin bash
@end example
@noindent
@ -4786,12 +4796,23 @@ In that shell, if you type @code{ls /gnu/store}, you'll notice that
altogether! That is probably the simplest way to deploy Guix-built
software on a non-Guix machine.
There's a gotcha though: this technique relies on the @dfn{user
namespace} feature of the kernel Linux, which allows unprivileged users
to mount or change root. Old versions of Linux did not support it, and
some GNU/Linux distributions turn it off; on these systems, programs
from the pack @emph{will fail to run}, unless they are unpacked in the
root file system.
@quotation Note
By default, relocatable binaries rely on the @dfn{user namespace} feature of
the kernel Linux, which allows unprivileged users to mount or change root.
Old versions of Linux did not support it, and some GNU/Linux distributions
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.
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 quotation
@item --expression=@var{expr}
@itemx -e @var{expr}

View file

@ -1,5 +1,5 @@
/* GNU Guix --- Functional package management for GNU
Copyright (C) 2018 Ludovic Courtès <ludo@gnu.org>
Copyright (C) 2018, 2019 Ludovic Courtès <ludo@gnu.org>
This file is part of GNU Guix.
@ -211,6 +211,46 @@ disallow_setgroups (pid_t pid)
close (fd);
}
#ifdef PROOT_PROGRAM
/* Execute the wrapped program with PRoot, passing it ARGC and ARGV, and
"bind-mounting" STORE in the right place. */
static void
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], *proot;
char bind_spec[strlen (store) + 1 + sizeof "@STORE_DIRECTORY@"];
strcpy (bind_spec, store);
strcat (bind_spec, ":");
strcat (bind_spec, "@STORE_DIRECTORY@");
proot = concat (store, PROOT_PROGRAM);
proot_argv[0] = proot;
proot_argv[1] = "-b";
proot_argv[2] = bind_spec;
proot_argv[3] = "@WRAPPED_PROGRAM@";
for (int i = 0; i < argc; i++)
proot_argv[i + proot_specific_argc] = argv[i + 1];
proot_argv[proot_argc] = NULL;
/* Seccomp support seems to invariably lead to segfaults; disable it by
default. */
setenv ("PROOT_NO_SECCOMP", "1", 0);
int err = execv (proot, proot_argv);
if (err < 0)
assert_perror (errno);
}
#endif
int
main (int argc, char *argv[])
@ -274,6 +314,10 @@ main (int argc, char *argv[])
break;
case -1:
rm_rf (new_root);
#ifdef PROOT_PROGRAM
exec_with_proot (store, argc, argv);
#else
fprintf (stderr, "%s: error: 'clone' failed: %m\n", argv[0]);
fprintf (stderr, "\
This may be because \"user namespaces\" are not supported on this system.\n\
@ -281,6 +325,7 @@ 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;
default:

View file

@ -517,10 +517,14 @@ (define (output-file args)
;;;
(define* (wrapped-package package
#:optional (compiler (c-compiler)))
#:optional (compiler (c-compiler))
#:key proot?)
(define runner
(local-file (search-auxiliary-file "run-in-namespace.c")))
(define (proot)
(specification->package "proot-static"))
(define build
(with-imported-modules (source-module-closure
'((guix build utils)
@ -550,10 +554,19 @@ (define (build-wrapper program)
(("@STORE_DIRECTORY@") (%store-directory)))
(let* ((base (strip-store-prefix program))
(result (string-append #$output "/" base)))
(result (string-append #$output "/" base))
(proot #$(and proot?
#~(string-drop
#$(file-append (proot) "/bin/proot")
(+ (string-length (%store-directory))
1)))))
(mkdir-p (dirname result))
(invoke #$compiler "-std=gnu99" "-static" "-Os" "-g0" "-Wall"
"run.c" "-o" result)
(apply invoke #$compiler "-std=gnu99" "-static" "-Os" "-g0" "-Wall"
"run.c" "-o" result
(if proot
(list (string-append "-DPROOT_PROGRAM=\""
proot "\""))
'()))
(delete-file "run.c")))
(setvbuf (current-output-port) 'line)
@ -646,7 +659,12 @@ (define %options
(exit 0)))
(option '(#\R "relocatable") #f #f
(lambda (opt name arg result)
(alist-cons 'relocatable? #t result)))
(match (assq-ref result 'relocatable?)
(#f
(alist-cons 'relocatable? #t result))
(_
(alist-cons 'relocatable? 'proot
(alist-delete 'relocatable? result))))))
(option '(#\e "expression") #t #f
(lambda (opt name arg result)
(alist-cons 'expression arg result)))
@ -821,11 +839,14 @@ (define properties
#:graft? (assoc-ref opts 'graft?))))
(let* ((dry-run? (assoc-ref opts 'dry-run?))
(relocatable? (assoc-ref opts 'relocatable?))
(proot? (eq? relocatable? 'proot))
(manifest (let ((manifest (manifest-from-args store opts)))
;; Note: We cannot honor '--bootstrap' here because
;; 'glibc-bootstrap' lacks 'libc.a'.
(if relocatable?
(map-manifest-entries wrapped-package manifest)
(map-manifest-entries
(cut wrapped-package <> #:proot? proot?)
manifest)
manifest)))
(pack-format (assoc-ref opts 'format))
(name (string-append (symbol->string pack-format)

View file

@ -1,5 +1,5 @@
# GNU Guix --- Functional package management for GNU
# Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
# Copyright © 2018, 2019 Ludovic Courtès <ludo@gnu.org>
#
# This file is part of GNU Guix.
#
@ -41,17 +41,28 @@ STORE_PARENT="`dirname $NIX_STORE_DIR`"
export STORE_PARENT
if test "$STORE_PARENT" = "/"; then exit 77; fi
# This test requires user namespaces and associated command-line tools.
if ! unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"'
if unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"'
then
exit 77
# Test the wrapper that relies on user namespaces.
relocatable_option="-R"
else
case "`uname -m`" in
x86_64|i?86)
# Test the wrapper that falls back to PRoot.
relocatable_option="-RR";;
*)
# XXX: Our 'proot' package currently fails tests on non-Intel
# architectures, so skip this by default.
exit 77;;
esac
fi
test_directory="`mktemp -d`"
export test_directory
trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT
tarball="`guix pack -R -S /Bin=bin sed`"
export relocatable_option
tarball="`guix pack $relocatable_option -S /Bin=bin sed`"
(cd "$test_directory"; tar xvf "$tarball")
# Run that relocatable 'sed' in a user namespace where we "erase" the store by