From mboxrd@z Thu Jan 1 00:00:00 1970 From: ludovic.courtes@inria.fr (Ludovic =?utf-8?Q?Court=C3=A8s?=) Subject: Generating wrappers for execution in non-root non-Guix contexts Date: Wed, 25 Apr 2018 11:14:05 +0200 Message-ID: <87zi1rwsv6.fsf@inria.fr> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:33613) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fBGVB-00022s-Nz for guix-devel@gnu.org; Wed, 25 Apr 2018 05:14:15 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fBGV8-0004ut-US for guix-devel@gnu.org; Wed, 25 Apr 2018 05:14:13 -0400 Received: from mail2-relais-roc.national.inria.fr ([192.134.164.83]:15156) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1fBGV8-0004tI-Bq for guix-devel@gnu.org; Wed, 25 Apr 2018 05:14:10 -0400 List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-devel-bounces+gcggd-guix-devel=m.gmane.org@gnu.org Sender: "Guix-devel" To: guix-devel --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello Guix! The hack below allows =E2=80=98guix pack=E2=80=99 to produce wrappers that = allow, through user namespaces, programs to automatically relocate themselves when you run them unprivileged on a machine that lacks Guix. In the example below, I run =E2=80=98sed=E2=80=99 from a pack on a machine that la= cks Guix: --8<---------------cut here---------------start------------->8--- ludo@fencepost:~/tmp$ tar xf ../pack.tgz ludo@fencepost:~/tmp$ echo hello > foo ludo@fencepost:~/tmp$ gnu/store/ffdzkyi23n8xh3n6vfqpa1lzg3xx9jpj-sed-4.4/bi= n/sed -i foo -es/hello/bye/g ludo@fencepost:~/tmp$ cat foo bye ludo@fencepost:~/tmp$ ls /gnu/store ls: cannot access '/gnu/store': No such file or directory --8<---------------cut here---------------end--------------->8--- Pretty cool no? What I imagine is that we could make this an option of =E2=80=98guix pack= =E2=80=99, such that =E2=80=98guix pack -w=E2=80=99 would produce such binaries. This relies on the same approach as =E2=80=98call-with-container=E2=80=99= =E2=80=A6 except it=E2=80=99s written in C and statically-linked to avoid bootstrapping issues. Doing that in Scheme would be a bit involved because a shebang like #!/gnu/store/=E2=80=A6-guile/bin/guile wouldn=E2=80=99t work; the wrappers = have to be statically-linked executables. There are (minor) issues to be solved: symlinks created by =E2=80=98guix pa= ck -S=E2=80=99 should be relative instead of absolute, and same for symlinks i= n the profile. This would allow users to directly type ./bin/sed instead of having to find out which directory is the right one as in the example above. We could also have wrappers fall back to PRoot when unshare(2) fails. What do people think? Cheers, Ludo=E2=80=99. --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable diff --git a/gnu/packages/commencement.scm b/gnu/packages/commencement.scm index fe9fbebcc..1026ee892 100644 --- a/gnu/packages/commencement.scm +++ b/gnu/packages/commencement.scm @@ -1026,7 +1026,10 @@ COREUTILS-FINAL vs. COREUTILS, etc." =20 (union-build (assoc-ref %outputs "debug") (list (assoc-ref %build-inputs - "libc-debug"))))))) + "libc-debug"))) + (union-build (assoc-ref %outputs "static") + (list (assoc-ref %build-inputs + "libc-static"))))))) =20 (native-search-paths (package-native-search-paths gcc)) (search-paths (package-search-paths gcc)) @@ -1038,7 +1041,7 @@ COREUTILS-FINAL vs. COREUTILS, etc." be installed in user profiles. This includes GCC, as well as libc (headers and binaries, plus debugging symbols in the 'debug' output), and Binutils.= ") (home-page "https://gcc.gnu.org/") - (outputs '("out" "debug")) + (outputs '("out" "debug" "static")) =20 ;; The main raison d'=C3=AAtre of this "meta-package" is (1) to conven= iently ;; install everything that we need, and (2) to make sure ld-wrapper co= mes @@ -1047,7 +1050,8 @@ and binaries, plus debugging symbols in the 'debug' o= utput), and Binutils.") ("ld-wrapper" ,(car (assoc-ref %final-inputs "ld-wrapper"))) ("binutils" ,binutils-final) ("libc" ,glibc-final) - ("libc-debug" ,glibc-final "debug"))))) + ("libc-debug" ,glibc-final "debug") + ("libc-static" ,glibc-final "static"))))) =20 (define-public gcc-toolchain-4.8 (make-gcc-toolchain gcc-4.8)) diff --git a/guix/profiles.scm b/guix/profiles.scm index 95dc9746b..507a441f5 100644 --- a/guix/profiles.scm +++ b/guix/profiles.scm @@ -318,7 +318,7 @@ denoting a specific output of a package." (propagated-inputs #$(map entry->gexp deps)) (search-paths #$(map search-path-specification->sexp search-paths)))) - (($ name version output (? package? package) + (($ name version output package (deps ...) (search-paths ...)) #~(#$name #$version #$output (ungexp package (or output "out")) @@ -671,7 +671,9 @@ if not found." (return (find-among-inputs inputs))))) ((? string? item) (mlet %store-monad ((refs (references* item))) - (return (find-among-store-items refs))))))) + (return (find-among-store-items refs)))) + (item + (return #f))))) =20 (anym %store-monad entry-lookup-package (manifest-entries manifest))) diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 488638adc..f2c3d4729 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright =C2=A9 2015, 2017 Ludovic Court=C3=A8s +;;; Copyright =C2=A9 2015, 2017, 2018 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2017 Efraim Flashner ;;; Copyright =C2=A9 2017 Ricardo Wurmus ;;; Copyright =C2=A9 2018 Konrad Hinsen @@ -216,11 +216,13 @@ the image." (('gnu rest ...) #t) (rest #f))) =20 + (define defmod 'define-module) ;trick Geiser + (define config ;; (guix config) module for consumption by (guix gcrypt). (scheme-file "gcrypt-config.scm" #~(begin - (define-module (guix config) + (#$defmod (guix config) #:export (%libgcrypt)) =20 ;; XXX: Work around . @@ -265,6 +267,63 @@ the image." #:references-graphs `(("profile" ,profile)))) =20 +;;; +;;; Wrapped package. +;;; + +(define (wrapped-package package) + (define runner + (local-file + (search-path %load-path "gnu/packages/aux-files/run-in-namespace.c"))) + + (define toolchain + (specification->package "gcc-toolchain")) + + (define build + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils) + (ice-9 match)) + + (define (strip-store-prefix file) + ;; Given a file name like "/gnu/store/=E2=80=A6-foo-1.2/bin/fo= o", return + ;; "/bin/foo". + (let* ((len (string-length (%store-directory))) + (base (string-drop file (+ 1 len)))) + (match (string-index base #\/) + (#f base) + (index (string-drop base index))))) + + (define (build-wrapper program) + ;; Build a user-namespace wrapper for PROGRAM. + (format #t "building wrapper for '~a'...~%" program) + (copy-file #$runner "run.c") + + (substitute* "run.c" + (("@WRAPPED_PROGRAM@") program) + (("@STORE_DIRECTORY@") (%store-directory))) + + (let* ((base (strip-store-prefix program)) + (result (string-append #$output "/" base))) + (mkdir-p (dirname result)) + (invoke "gcc" "-static" "-Os" "-g0" "run.c" + "-o" result) + (delete-file "run.c"))) + + (setvbuf (current-output-port) 'line) + (setenv "PATH" #+(file-append toolchain "/bin")) + (setenv "LIBRARY_PATH" + (string-append #+toolchain "/lib:" + #+toolchain:static "/lib")) + (setenv "CPATH" #+(file-append toolchain "/include")) + (for-each build-wrapper + (append (find-files #$(file-append package "/bin")) + (find-files #$(file-append package "/sbin")) + (find-files #$(file-append package "/libexec")= )))))) + + (computed-file (package-full-name package) build)) + + ;;; ;;; Command-line options. ;;; @@ -408,9 +467,18 @@ Create a bundle of PACKAGE.\n")) (load* manifest-file user-module))) (else (packages->manifest packages))))) =20 + (define (map-manifest-entries proc manifest) + (make-manifest + (map (lambda (entry) + (manifest-entry + (inherit entry) + (item (proc (manifest-entry-item entry))))) + (manifest-entries manifest)))) + (with-error-handling (let* ((dry-run? (assoc-ref opts 'dry-run?)) - (manifest (manifest-from-args opts)) + (manifest (map-manifest-entries wrapped-package + (manifest-from-args opts))) (pack-format (assoc-ref opts 'format)) (name (string-append (symbol->string pack-format) "-pack")) --=-=-= Content-Type: text/plain This file has to be dropped as gnu/packages/aux-files/run-in-namespace.c: --=-=-= Content-Type: text/x-csrc; charset=utf-8 Content-Disposition: inline; filename=run-in-namespace.c Content-Transfer-Encoding: quoted-printable Content-Description: the wrapper code #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include static void mkdir_p (const char *directory) { if (strcmp (directory, "/") !=3D 0) { char *parent =3D dirname (strdupa (directory)); mkdir_p (parent); int err =3D mkdir (directory, 0700); if (err < 0 && errno !=3D EEXIST) assert_perror (errno); } } static char * concat (const char *directory, const char *file) { char *result =3D malloc (strlen (directory) + 2 + strlen (file)); assert (result !=3D NULL); strcpy (result, directory); strcat (result, "/"); strcat (result, file); return result; } /* Bind mount all the top-level entries in SOURCE to TARGET. */ static void bind_mount (const char *source, const char *target) { DIR *stream =3D opendir (source); for (struct dirent *entry =3D readdir (stream); entry !=3D NULL; entry =3D readdir (stream)) { if (strcmp (entry->d_name, ".") =3D=3D 0 || strcmp (entry->d_name, "..") =3D=3D 0 || entry->d_type !=3D DT_DIR) continue; char *new_entry =3D concat (target, entry->d_name); if (entry->d_type =3D=3D DT_DIR) { /* Create the mount point. */ int err =3D mkdir (new_entry, 0700); if (err !=3D 0) assert_perror (errno); } char *abs_source =3D concat (source, entry->d_name); int err =3D mount (abs_source, new_entry, "none", MS_BIND | MS_REC | MS_RDONLY, NULL); if (err !=3D 0) assert_perror (errno); free (new_entry); free (abs_source); } closedir (stream); } int main (int argc, char *argv[]) { ssize_t size; char self[PATH_MAX]; size =3D readlink ("/proc/self/exe", self, sizeof self - 1); assert (size > 0); /* SELF is something like "/home/ludo/.local/gnu/store/=E2=80=A6-foo/bin/= ls" and we want to extract "/home/ludo/.local/gnu/store". */ size_t index =3D strlen (self) - strlen ("@WRAPPED_PROGRAM@") + strlen ("@STORE_DIRECTORY@"); char *store =3D strdup (self); store[index] =3D '\0'; if (1) //(strcmp (store, "@STORE_DIRECTORY@") !=3D 0) { /* Spawn @WRAPPED_PROGRAM@ in a separate namespace where STORE is bind-mounted in the right place. */ int err; err =3D unshare (CLONE_NEWNS | CLONE_NEWUSER); if (err < 0) assert_perror (errno); char *new_root =3D mkdtemp (strdup ("/tmp/guix-exec-XXXXXX")); bind_mount ("/", new_root); char *new_store =3D concat (new_root, "@STORE_DIRECTORY@"); mkdir_p (new_store); err =3D mount (store, new_store, "none", MS_BIND | MS_REC | MS_RDONLY, NULL); if (err < 0) assert_perror (errno); err =3D chroot (new_root); if (err < 0) assert_perror (errno); pid_t child =3D fork (); switch (child) { case 0: break; case -1: assert_perror (errno); break; default: { int status; waitpid (child, &status, 0); /* TODO: rm -rf NEW_ROOT */ exit (status); } } } int err =3D execv ("@WRAPPED_PROGRAM@", argv); if (err < 0) assert_perror (errno); } --=-=-=--