summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2015-07-20 04:30:16 +0200
committerLudovic Courtès <ludo@gnu.org>2016-11-16 18:19:47 +0100
commit94d92c7796a3dd50c27d532315f7d497ac99f08e (patch)
tree505902f5583fe528ceceacfd15d1f43828c4ad79
parent17ab08bcf0ae27ec6a1f07766080ebfbea8837d9 (diff)
daemon: Add "builtin:download" derivation builder.
This ensures that 1) the derivation doesn't change when Guix changes; 2) the derivation closure doesn't contain Guix and its dependencies; 3) we don't have to rely on ugly chroot hacks. Adapted from Nix commit 0a2bee307b20411f5b0dda0c662b1f9bb9e0e131. * nix/libstore/build.cc (DerivationGoal::runChild): Add special case for 'isBuiltin(drv)'. Disable chroot when 'isBuiltin(drv)'. * nix/libstore/builtins.cc, nix/libstore/builtins.hh, nix/scripts/download.in, guix/scripts/perform-download.scm: New files. * guix/ui.scm (show-guix-help)[internal?]: Add 'perform-download'. * nix/local.mk (libstore_a_SOURCES): Add builtins.cc. (libstore_headers): Add builtins.hh. (nodist_pkglibexec_SCRIPTS): Add 'scripts/download'. * config-daemon.ac: Emit 'scripts/download'. * Makefile.am (MODULES): Add 'guix/scripts/perform-download.scm'. * tests/derivations.scm ("unknown built-in builder") ("'download' built-in builder") ("'download' built-in builder, invalid hash") ("'download' built-in builder, not found") ("'download' built-in builder, not fixed-output"): New tests. Co-authored-by: Eelco Dolstra <eelco.dolstra@logicblox.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am1
-rw-r--r--config-daemon.ac2
-rw-r--r--guix/scripts/perform-download.scm113
-rw-r--r--guix/ui.scm3
-rw-r--r--nix/libstore/build.cc36
-rw-r--r--nix/libstore/builtins.cc69
-rw-r--r--nix/libstore/builtins.hh41
-rw-r--r--nix/local.mk5
-rw-r--r--nix/scripts/download.in11
-rw-r--r--tests/derivations.scm70
11 files changed, 343 insertions, 9 deletions
diff --git a/.gitignore b/.gitignore
index 6e892ca6875..329d489713f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,3 +125,4 @@ config.cache
125stamp-h[0-9] 125stamp-h[0-9]
126tmp 126tmp
127/doc/os-config-lightweight-desktop.texi 127/doc/os-config-lightweight-desktop.texi
128/nix/scripts/download
diff --git a/Makefile.am b/Makefile.am
index 5d3639747f2..9d62f480241 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -123,6 +123,7 @@ MODULES = \
123 guix/import/elpa.scm \ 123 guix/import/elpa.scm \
124 guix/scripts.scm \ 124 guix/scripts.scm \
125 guix/scripts/download.scm \ 125 guix/scripts/download.scm \
126 guix/scripts/perform-download.scm \
126 guix/scripts/build.scm \ 127 guix/scripts/build.scm \
127 guix/scripts/archive.scm \ 128 guix/scripts/archive.scm \
128 guix/scripts/import.scm \ 129 guix/scripts/import.scm \
diff --git a/config-daemon.ac b/config-daemon.ac
index f66f31269df..8a3e6d8b601 100644
--- a/config-daemon.ac
+++ b/config-daemon.ac
@@ -144,6 +144,8 @@ if test "x$guix_build_daemon" = "xyes"; then
144 144
145 AC_CONFIG_FILES([nix/scripts/list-runtime-roots], 145 AC_CONFIG_FILES([nix/scripts/list-runtime-roots],
146 [chmod +x nix/scripts/list-runtime-roots]) 146 [chmod +x nix/scripts/list-runtime-roots])
147 AC_CONFIG_FILES([nix/scripts/download],
148 [chmod +x nix/scripts/download])
147 AC_CONFIG_FILES([nix/scripts/substitute], 149 AC_CONFIG_FILES([nix/scripts/substitute],
148 [chmod +x nix/scripts/substitute]) 150 [chmod +x nix/scripts/substitute])
149 AC_CONFIG_FILES([nix/scripts/guix-authenticate], 151 AC_CONFIG_FILES([nix/scripts/guix-authenticate],
diff --git a/guix/scripts/perform-download.scm b/guix/scripts/perform-download.scm
new file mode 100644
index 00000000000..0d2e7089aab
--- /dev/null
+++ b/guix/scripts/perform-download.scm
@@ -0,0 +1,113 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2016 Ludovic Courtès <ludo@gnu.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (guix scripts perform-download)
20 #:use-module (guix ui)
21 #:use-module (guix derivations)
22 #:use-module ((guix store) #:select (derivation-path?))
23 #:use-module (guix build download)
24 #:use-module (ice-9 match)
25 #:export (guix-perform-download))
26
27;; This program is a helper for the daemon's 'download' built-in builder.
28
29(define-syntax derivation-let
30 (syntax-rules ()
31 ((_ drv ((id name) rest ...) body ...)
32 (let ((id (assoc-ref (derivation-builder-environment-vars drv)
33 name)))
34 (derivation-let drv (rest ...) body ...)))
35 ((_ drv () body ...)
36 (begin body ...))))
37
38(define %user-module
39 ;; Module in which content-address mirror procedures are evaluated.
40 (let ((module (make-fresh-user-module)))
41 (module-use! module (resolve-interface '(guix base32)))
42 module))
43
44(define (perform-download drv)
45 "Perform the download described by DRV, a fixed-output derivation."
46 (derivation-let drv ((url "url")
47 (output "out")
48 (executable "executable")
49 (mirrors "mirrors")
50 (content-addressed-mirrors "content-addressed-mirrors"))
51 (unless url
52 (leave (_ "~a: missing URL~%") (derivation-file-name drv)))
53
54 (let* ((url (call-with-input-string url read))
55 (drv-output (assoc-ref (derivation-outputs drv) "out"))
56 (algo (derivation-output-hash-algo drv-output))
57 (hash (derivation-output-hash drv-output)))
58 (unless (and algo hash)
59 (leave (_ "~a is not a fixed-output derivation~%")
60 (derivation-file-name drv)))
61
62 ;; We're invoked by the daemon, which gives us write access to OUTPUT.
63 (when (url-fetch url output
64 #:mirrors (if mirrors
65 (call-with-input-file mirrors read)
66 '())
67 #:content-addressed-mirrors
68 (if content-addressed-mirrors
69 (call-with-input-file content-addressed-mirrors
70 (lambda (port)
71 (eval (read port) %user-module)))
72 '())
73 #:hashes `((,algo . ,hash))
74
75 ;; Since DRV's output hash is known, X.509 certificate
76 ;; validation is pointless.
77 #:verify-certificate? #f)
78 (when (and executable (string=? executable "1"))
79 (chmod output #o755))))))
80
81(define (assert-low-privileges)
82 (when (zero? (getuid))
83 (leave (_ "refusing to run with elevated privileges (UID ~a)~%")
84 (getuid))))
85
86(define (guix-perform-download . args)
87 "Perform the download described by the given fixed-output derivation.
88
89This is an \"out-of-band\" download in that this code is executed directly by
90the daemon and not explicitly described as an input of the derivation. This
91allows us to sidestep bootstrapping problems, such downloading the source code
92of GnuTLS over HTTPS, before we have built GnuTLS. See
93<http://bugs.gnu.org/22774>."
94 (with-error-handling
95 (match args
96 (((? derivation-path? drv))
97 ;; This program must be invoked by guix-daemon under an unprivileged
98 ;; UID to prevent things downloading from 'file:///etc/shadow' or
99 ;; arbitrary code execution via the content-addressed mirror
100 ;; procedures. (That means we exclude users who did not pass
101 ;; '--build-users-group'.)
102 (assert-low-privileges)
103 (perform-download (call-with-input-file drv read-derivation)))
104 (("--version")
105 (show-version-and-exit))
106 (x
107 (leave (_ "fixed-output derivation name expected~%"))))))
108
109;; Local Variables:
110;; eval: (put 'derivation-let 'scheme-indent-function 2)
111;; End:
112
113;; perform-download.scm ends here
diff --git a/guix/ui.scm b/guix/ui.scm
index 9af86482115..b9fbbfd0e33 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -1184,7 +1184,8 @@ optionally contain a version number and an output name, as in these examples:
1184 1184
1185(define (show-guix-help) 1185(define (show-guix-help)
1186 (define (internal? command) 1186 (define (internal? command)
1187 (member command '("substitute" "authenticate" "offload"))) 1187 (member command '("substitute" "authenticate" "offload"
1188 "perform-download")))
1188 1189
1189 (format #t (_ "Usage: guix COMMAND ARGS... 1190 (format #t (_ "Usage: guix COMMAND ARGS...
1190Run COMMAND with ARGS.\n")) 1191Run COMMAND with ARGS.\n"))
diff --git a/nix/libstore/build.cc b/nix/libstore/build.cc
index ae78e651992..889ee3d2bd2 100644
--- a/nix/libstore/build.cc
+++ b/nix/libstore/build.cc
@@ -8,6 +8,7 @@
8#include "util.hh" 8#include "util.hh"
9#include "archive.hh" 9#include "archive.hh"
10#include "affinity.hh" 10#include "affinity.hh"
11#include "builtins.hh"
11 12
12#include <map> 13#include <map>
13#include <sstream> 14#include <sstream>
@@ -2047,7 +2048,12 @@ void DerivationGoal::runChild()
2047 commonChildInit(builderOut); 2048 commonChildInit(builderOut);
2048 2049
2049#if CHROOT_ENABLED 2050#if CHROOT_ENABLED
2050 if (useChroot) { 2051 /* Note: built-in builders are *not* running in a chroot environment
2052 so that we can easily implement them in Guile without having it as
2053 a derivation input (they are running under a separate build user,
2054 though). */
2055
2056 if (useChroot && !isBuiltin(drv)) {
2051 /* Initialise the loopback interface. */ 2057 /* Initialise the loopback interface. */
2052 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)); 2058 AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
2053 if (fd == -1) throw SysError("cannot open IP socket"); 2059 if (fd == -1) throw SysError("cannot open IP socket");
@@ -2255,6 +2261,28 @@ void DerivationGoal::runChild()
2255 throw SysError("setuid failed"); 2261 throw SysError("setuid failed");
2256 } 2262 }
2257 2263
2264 restoreSIGPIPE();
2265
2266 /* Indicate that we managed to set up the build environment. */
2267 writeFull(STDERR_FILENO, "\n");
2268
2269 /* Execute the program. This should not return. */
2270 if (isBuiltin(drv)) {
2271 try {
2272 logType = ltFlat;
2273
2274 auto buildDrv = lookupBuiltinBuilder(drv.builder);
2275 if (buildDrv != NULL)
2276 buildDrv(drv, drvPath);
2277 else
2278 throw Error(format("unsupported builtin function '%1%'") % string(drv.builder, 8));
2279 _exit(0);
2280 } catch (std::exception & e) {
2281 writeFull(STDERR_FILENO, "error: " + string(e.what()) + "\n");
2282 _exit(1);
2283 }
2284 }
2285
2258 /* Fill in the arguments. */ 2286 /* Fill in the arguments. */
2259 Strings args; 2287 Strings args;
2260 string builderBasename = baseNameOf(drv.builder); 2288 string builderBasename = baseNameOf(drv.builder);
@@ -2262,12 +2290,6 @@ void DerivationGoal::runChild()
2262 foreach (Strings::iterator, i, drv.args) 2290 foreach (Strings::iterator, i, drv.args)
2263 args.push_back(rewriteHashes(*i, rewritesToTmp)); 2291 args.push_back(rewriteHashes(*i, rewritesToTmp));
2264 2292
2265 restoreSIGPIPE();
2266
2267 /* Indicate that we managed to set up the build environment. */
2268 writeFull(STDERR_FILENO, "\n");
2269
2270 /* Execute the program. This should not return. */
2271 execve(drv.builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data()); 2293 execve(drv.builder.c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(envStrs).data());
2272 2294
2273 throw SysError(format("executing `%1%'") % drv.builder); 2295 throw SysError(format("executing `%1%'") % drv.builder);
diff --git a/nix/libstore/builtins.cc b/nix/libstore/builtins.cc
new file mode 100644
index 00000000000..605e44079a1
--- /dev/null
+++ b/nix/libstore/builtins.cc
@@ -0,0 +1,69 @@
1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2016 Ludovic Courtès <ludo@gnu.org>
3
4 This file is part of GNU Guix.
5
6 GNU Guix is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Guix is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. */
18
19#include <builtins.hh>
20#include <util.hh>
21#include <globals.hh>
22
23#include <unistd.h>
24
25namespace nix {
26
27static void builtinDownload(const Derivation &drv,
28 const std::string &drvPath)
29{
30 /* Invoke 'guix perform-download'. */
31 Strings args;
32 args.push_back("perform-download");
33 args.push_back(drvPath);
34
35 /* Close all other file descriptors. */
36 closeMostFDs(set<int>());
37
38 const char *const argv[] = { "download", drvPath.c_str(), NULL };
39
40 /* XXX: Hack our way to use the 'download' script from 'LIBEXECDIR/guix'
41 or just 'LIBEXECDIR', depending on whether we're running uninstalled or
42 not. */
43 const string subdir = getenv("GUIX_UNINSTALLED") != NULL
44 ? "" : "/guix";
45
46 const string program = settings.nixLibexecDir + subdir + "/download";
47 execv(program.c_str(), (char *const *) argv);
48
49 throw SysError(format("failed to run download program '%1%'") % program);
50}
51
52static const std::map<std::string, derivationBuilder> builtins =
53{
54 { "download", builtinDownload }
55};
56
57derivationBuilder lookupBuiltinBuilder(const std::string & name)
58{
59 if (name.substr(0, 8) == "builtin:")
60 {
61 auto realName = name.substr(8);
62 auto builder = builtins.find(realName);
63 return builder == builtins.end() ? NULL : builder->second;
64 }
65 else
66 return NULL;
67}
68
69}
diff --git a/nix/libstore/builtins.hh b/nix/libstore/builtins.hh
new file mode 100644
index 00000000000..0c6db651ab1
--- /dev/null
+++ b/nix/libstore/builtins.hh
@@ -0,0 +1,41 @@
1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2016 Ludovic Courtès <ludo@gnu.org>
3
4 This file is part of GNU Guix.
5
6 GNU Guix is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Guix is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. */
18
19/* Interface to built-in derivation builders. */
20
21#pragma once
22
23#include <derivations.hh>
24#include <map>
25#include <string>
26
27namespace nix {
28
29 inline bool isBuiltin(const Derivation & drv)
30 {
31 return string(drv.builder, 0, 8) == "builtin:";
32 }
33
34 /* Build DRV, which lives at DRVPATH. */
35 typedef void (*derivationBuilder) (const Derivation &drv,
36 const std::string &drvPath);
37
38 /* Return the built-in builder called BUILDER, or NULL if none was
39 found. */
40 derivationBuilder lookupBuiltinBuilder(const std::string &builder);
41}
diff --git a/nix/local.mk b/nix/local.mk
index c666edd0332..86ef7695492 100644
--- a/nix/local.mk
+++ b/nix/local.mk
@@ -87,6 +87,7 @@ libstore_a_SOURCES = \
87 %D%/libstore/build.cc \ 87 %D%/libstore/build.cc \
88 %D%/libstore/pathlocks.cc \ 88 %D%/libstore/pathlocks.cc \
89 %D%/libstore/derivations.cc \ 89 %D%/libstore/derivations.cc \
90 %D%/libstore/builtins.cc \
90 %D%/libstore/sqlite.cc 91 %D%/libstore/sqlite.cc
91 92
92libstore_headers = \ 93libstore_headers = \
@@ -98,6 +99,7 @@ libstore_headers = \
98 %D%/libstore/misc.hh \ 99 %D%/libstore/misc.hh \
99 %D%/libstore/local-store.hh \ 100 %D%/libstore/local-store.hh \
100 %D%/libstore/sqlite.hh \ 101 %D%/libstore/sqlite.hh \
102 %D%/libstore/builtins.hh \
101 %D%/libstore/store-api.hh 103 %D%/libstore/store-api.hh
102 104
103libstore_a_CPPFLAGS = \ 105libstore_a_CPPFLAGS = \
@@ -166,7 +168,8 @@ noinst_HEADERS = \
166 168
167nodist_pkglibexec_SCRIPTS = \ 169nodist_pkglibexec_SCRIPTS = \
168 %D%/scripts/list-runtime-roots \ 170 %D%/scripts/list-runtime-roots \
169 %D%/scripts/substitute 171 %D%/scripts/substitute \
172 %D%/scripts/download
170 173
171if BUILD_DAEMON_OFFLOAD 174if BUILD_DAEMON_OFFLOAD
172 175
diff --git a/nix/scripts/download.in b/nix/scripts/download.in
new file mode 100644
index 00000000000..4d7088a9933
--- /dev/null
+++ b/nix/scripts/download.in
@@ -0,0 +1,11 @@
1#!@SHELL@
2# A shorthand for "guix perform-download", for use by the daemon.
3
4if test "x$GUIX_UNINSTALLED" = "x"
5then
6 prefix="@prefix@"
7 exec_prefix="@exec_prefix@"
8 exec "@bindir@/guix" perform-download "$@"
9else
10 exec guix perform-download "$@"
11fi
diff --git a/tests/derivations.scm b/tests/derivations.scm
index d8553b223eb..449fb47832d 100644
--- a/tests/derivations.scm
+++ b/tests/derivations.scm
@@ -16,6 +16,8 @@
16;;; You should have received a copy of the GNU General Public License 16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. 17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18 18
19(unsetenv "http_proxy")
20
19(define-module (test-derivations) 21(define-module (test-derivations)
20 #:use-module (guix derivations) 22 #:use-module (guix derivations)
21 #:use-module (guix grafts) 23 #:use-module (guix grafts)
@@ -24,6 +26,7 @@
24 #:use-module (guix hash) 26 #:use-module (guix hash)
25 #:use-module (guix base32) 27 #:use-module (guix base32)
26 #:use-module (guix tests) 28 #:use-module (guix tests)
29 #:use-module (guix tests http)
27 #:use-module ((guix packages) #:select (package-derivation base32)) 30 #:use-module ((guix packages) #:select (package-derivation base32))
28 #:use-module ((guix build utils) #:select (executable-file?)) 31 #:use-module ((guix build utils) #:select (executable-file?))
29 #:use-module ((gnu packages) #:select (search-bootstrap-binary)) 32 #:use-module ((gnu packages) #:select (search-bootstrap-binary))
@@ -75,6 +78,9 @@
75 (lambda (e1 e2) 78 (lambda (e1 e2)
76 (string<? (car e1) (car e2))))) 79 (string<? (car e1) (car e2)))))
77 80
81;; Avoid collisions with other tests.
82(%http-server-port 10500)
83
78 84
79(test-begin "derivations") 85(test-begin "derivations")
80 86
@@ -205,6 +211,70 @@
205 (= (stat:ino (lstat file1)) 211 (= (stat:ino (lstat file1))
206 (stat:ino (lstat file2)))))))) 212 (stat:ino (lstat file2))))))))
207 213
214(test-assert "unknown built-in builder"
215 (let ((drv (derivation %store "ohoh" "builtin:does-not-exist" '())))
216 (guard (c ((nix-protocol-error? c)
217 (string-contains (nix-protocol-error-message c) "failed")))
218 (build-derivations %store (list drv))
219 #f)))
220
221(unless (force %http-server-socket)
222 (test-skip 1))
223(test-assert "'download' built-in builder"
224 (let ((text (random-text)))
225 (with-http-server 200 text
226 (let* ((drv (derivation %store "world"
227 "builtin:download" '()
228 #:env-vars `(("url"
229 . ,(object->string (%local-url))))
230 #:hash-algo 'sha256
231 #:hash (sha256 (string->utf8 text)))))
232 (and (build-derivations %store (list drv))
233 (string=? (call-with-input-file (derivation->output-path drv)
234 get-string-all)
235 text))))))
236
237(unless (force %http-server-socket)
238 (test-skip 1))
239(test-assert "'download' built-in builder, invalid hash"
240 (with-http-server 200 "hello, world!"
241 (let* ((drv (derivation %store "world"
242 "builtin:download" '()
243 #:env-vars `(("url"
244 . ,(object->string (%local-url))))
245 #:hash-algo 'sha256
246 #:hash (sha256 (random-bytevector 100))))) ;wrong
247 (guard (c ((nix-protocol-error? c)
248 (string-contains (nix-protocol-error-message c) "failed")))
249 (build-derivations %store (list drv))
250 #f))))
251
252(unless (force %http-server-socket)
253 (test-skip 1))
254(test-assert "'download' built-in builder, not found"
255 (with-http-server 404 "not found"
256 (let* ((drv (derivation %store "will-never-be-found"
257 "builtin:download" '()
258 #:env-vars `(("url"
259 . ,(object->string (%local-url))))
260 #:hash-algo 'sha256
261 #:hash (sha256 (random-bytevector 100)))))
262 (guard (c ((nix-protocol-error? c)
263 (string-contains (nix-protocol-error-message (pk c)) "failed")))
264 (build-derivations %store (list drv))
265 #f))))
266
267(test-assert "'download' built-in builder, not fixed-output"
268 (let* ((source (add-text-to-store %store "hello" "hi!"))
269 (url (string-append "file://" source))
270 (drv (derivation %store "world"
271 "builtin:download" '()
272 #:env-vars `(("url" . ,(object->string url))))))
273 (guard (c ((nix-protocol-error? c)
274 (string-contains (nix-protocol-error-message c) "failed")))
275 (build-derivations %store (list drv))
276 #f)))
277
208(test-equal "derivation-name" 278(test-equal "derivation-name"
209 "foo-0.0" 279 "foo-0.0"
210 (let ((drv (derivation %store "foo-0.0" %bash '()))) 280 (let ((drv (derivation %store "foo-0.0" %bash '())))