pull: '--url', '--commit', and '--branch' apply to the 'guix' channel.
[jackhill/guix/guix.git] / guix / scripts / substitute.scm
CommitLineData
f65cf81a 1;;; GNU Guix --- Functional package management for GNU
76832d34 2;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
e9c6c584 3;;; Copyright © 2014 Nikita Karetnikov <nikita@karetnikov.org>
7ede577a 4;;; Copyright © 2018 Kyle Meyer <kyle@kyleam.com>
f65cf81a
LC
5;;;
6;;; This file is part of GNU Guix.
7;;;
8;;; GNU Guix is free software; you can redistribute it and/or modify it
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
13;;; GNU Guix is distributed in the hope that it will be useful, but
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
2c74fde0 21(define-module (guix scripts substitute)
f65cf81a 22 #:use-module (guix ui)
b879b3e8 23 #:use-module ((guix store) #:hide (close-connection))
f65cf81a 24 #:use-module (guix utils)
958dd3ce 25 #:use-module (guix combinators)
fe0cff14 26 #:use-module (guix config)
c0cd1b3e 27 #:use-module (guix records)
2535635f 28 #:use-module ((guix serialization) #:select (restore-file))
ca719424 29 #:use-module (gcrypt hash)
895d1eda 30 #:use-module (guix base32)
e9c6c584 31 #:use-module (guix base64)
2ea2aac6 32 #:use-module (guix cache)
ca719424 33 #:use-module (gcrypt pk-crypto)
e9c6c584 34 #:use-module (guix pki)
d3a65203 35 #:use-module ((guix build utils) #:select (mkdir-p dump-port))
a85060ef 36 #:use-module ((guix build download)
8c348825 37 #:select (uri-abbreviation nar-uri-abbreviation
4fd06a4d
LC
38 (open-connection-for-uri
39 . guix:open-connection-for-uri)
b879b3e8 40 close-connection
a8be7b9a 41 store-path-abbreviation byte-count->string))
8c348825 42 #:use-module (guix progress)
8902d0f2
LC
43 #:use-module ((guix build syscalls)
44 #:select (set-thread-name))
f65cf81a
LC
45 #:use-module (ice-9 rdelim)
46 #:use-module (ice-9 regex)
47 #:use-module (ice-9 match)
fe0cff14 48 #:use-module (ice-9 format)
4c7cacf1 49 #:use-module (ice-9 ftw)
a85060ef 50 #:use-module (ice-9 binary-ports)
3d3e93b3 51 #:use-module (ice-9 vlist)
e9c6c584 52 #:use-module (rnrs bytevectors)
f65cf81a
LC
53 #:use-module (srfi srfi-1)
54 #:use-module (srfi srfi-9)
55 #:use-module (srfi srfi-11)
eba783b7 56 #:use-module (srfi srfi-19)
f65cf81a 57 #:use-module (srfi srfi-26)
706e9e57 58 #:use-module (srfi srfi-34)
e9c6c584 59 #:use-module (srfi srfi-35)
f65cf81a 60 #:use-module (web uri)
9b7bd1b1 61 #:use-module (web http)
d3a65203
LC
62 #:use-module (web request)
63 #:use-module (web response)
3b8258c5 64 #:use-module (guix http-client)
e9c6c584 65 #:export (narinfo-signature->canonical-sexp
ea0c6e05
LC
66
67 narinfo?
68 narinfo-path
69 narinfo-uri
70 narinfo-uri-base
71 narinfo-compression
72 narinfo-file-hash
73 narinfo-file-size
74 narinfo-hash
75 narinfo-size
76 narinfo-references
77 narinfo-deriver
78 narinfo-system
79 narinfo-signature
80
81 narinfo-hash->sha256
ea0c6e05
LC
82
83 lookup-narinfos
55b2fc18 84 lookup-narinfos/diverse
e9c6c584
NK
85 read-narinfo
86 write-narinfo
218f6ecc
LC
87
88 substitute-urls
2c74fde0 89 guix-substitute))
f65cf81a
LC
90
91;;; Comment:
92;;;
93;;; This is the "binary substituter". It is invoked by the daemon do check
94;;; for the existence of available "substitutes" (pre-built binaries), and to
95;;; actually use them as a substitute to building things locally.
96;;;
97;;; If possible, substitute a binary for the requested store path, using a Nix
98;;; "binary cache". This program implements the Nix "substituter" protocol.
99;;;
100;;; Code:
101
b547349d
LC
102(cond-expand
103 (guile-2.2
104 ;; Guile 2.2.2 has a bug whereby 'time-monotonic' objects have seconds and
105 ;; nanoseconds swapped (fixed in Guile commit 886ac3e). Work around it.
106 (define time-monotonic time-tai))
107 (else #t))
108
eba783b7 109(define %narinfo-cache-directory
f10dcbf1
LC
110 ;; A local cache of narinfos, to avoid going to the network. Most of the
111 ;; time, 'guix substitute' is called by guix-daemon as root and stores its
112 ;; cached data in /var/guix/…. However, when invoked from 'guix challenge'
113 ;; as a user, it stores its cache in ~/.cache.
114 (if (zero? (getuid))
115 (or (and=> (getenv "XDG_CACHE_HOME")
116 (cut string-append <> "/guix/substitute"))
117 (string-append %state-directory "/substitute/cache"))
f0e492f0 118 (string-append (cache-directory #:ensure? #f) "/substitute")))
eba783b7 119
e9c6c584
NK
120(define %allow-unauthenticated-substitutes?
121 ;; Whether to allow unchecked substitutes. This is useful for testing
122 ;; purposes, and should be avoided otherwise.
123 (and (and=> (getenv "GUIX_ALLOW_UNAUTHENTICATED_SUBSTITUTES")
124 (cut string-ci=? <> "yes"))
125 (begin
69daee23 126 (warning (G_ "authentication and authorization of substitutes \
e9c6c584
NK
127disabled!~%"))
128 #t)))
129
eba783b7
LC
130(define %narinfo-ttl
131 ;; Number of seconds during which cached narinfo lookups are considered
23d60ba6
LC
132 ;; valid for substitute servers that do not advertise a TTL via the
133 ;; 'Cache-Control' response header.
5e6039a4 134 (* 36 3600))
eba783b7
LC
135
136(define %narinfo-negative-ttl
958fb14c 137 ;; Likewise, but for negative lookups---i.e., cached lookup failures (404).
eba783b7
LC
138 (* 3 3600))
139
958fb14c
LC
140(define %narinfo-transient-error-ttl
141 ;; Likewise, but for transient errors such as 504 ("Gateway timeout").
142 (* 10 60))
143
4c7cacf1
LC
144(define %narinfo-expired-cache-entry-removal-delay
145 ;; How often we want to remove files corresponding to expired cache entries.
146 (* 7 24 3600))
147
ce689ccf
LC
148(define fields->alist
149 ;; The narinfo format is really just like recutils.
150 recutils->alist)
f65cf81a 151
2207f731
LC
152(define %fetch-timeout
153 ;; Number of seconds after which networking is considered "slow".
8b79e2e6 154 5)
2207f731 155
bb7dcaea
LC
156(define %random-state
157 (seed->random-state (+ (ash (cdr (gettimeofday)) 32) (getpid))))
158
2207f731
LC
159(define-syntax-rule (with-timeout duration handler body ...)
160 "Run BODY; when DURATION seconds have expired, call HANDLER, and run BODY
161again."
162 (begin
163 (sigaction SIGALRM
164 (lambda (signum)
165 (sigaction SIGALRM SIG_DFL)
166 handler))
167 (alarm duration)
168 (call-with-values
169 (lambda ()
170 (let try ()
171 (catch 'system-error
172 (lambda ()
173 body ...)
174 (lambda args
c509bf8c
LC
175 ;; Before Guile v2.0.9-39-gfe51c7b, the SIGALRM triggers EINTR
176 ;; because of the bug at
bb7dcaea
LC
177 ;; <http://lists.gnu.org/archive/html/guile-devel/2013-06/msg00050.html>.
178 ;; When that happens, try again. Note: SA_RESTART cannot be
179 ;; used because of <http://bugs.gnu.org/14640>.
2207f731 180 (if (= EINTR (system-error-errno args))
bb7dcaea
LC
181 (begin
182 ;; Wait a little to avoid bursts.
183 (usleep (random 3000000 %random-state))
184 (try))
2207f731
LC
185 (apply throw args))))))
186 (lambda result
187 (alarm 0)
188 (sigaction SIGALRM SIG_DFL)
189 (apply values result)))))
190
cc27dbcf 191(define* (fetch uri #:key (buffered? #t) (timeout? #t))
fe0cff14 192 "Return a binary input port to URI and the number of bytes it's expected to
cc27dbcf 193provide."
f65cf81a
LC
194 (case (uri-scheme uri)
195 ((file)
b6952cad
LC
196 (let ((port (open-file (uri-path uri)
197 (if buffered? "rb" "r0b"))))
fe0cff14 198 (values port (stat:size (stat port)))))
9b7bd1b1 199 ((http https)
706e9e57 200 (guard (c ((http-get-error? c)
69daee23 201 (leave (G_ "download from '~a' failed: ~a, ~s~%")
cc27dbcf
LC
202 (uri->string (http-get-error-uri c))
203 (http-get-error-code c)
204 (http-get-error-reason c))))
706e9e57
LC
205 ;; Test this with:
206 ;; sudo tc qdisc add dev eth0 root netem delay 1500ms
207 ;; and then cancel with:
208 ;; sudo tc qdisc del dev eth0 root
209 (let ((port #f))
09d809db 210 (with-timeout (if timeout?
706e9e57
LC
211 %fetch-timeout
212 0)
213 (begin
69daee23 214 (warning (G_ "while fetching ~a: server is somewhat slow~%")
706e9e57 215 (uri->string uri))
1d84d7bf 216 (warning (G_ "try `--no-substitutes' if the problem persists~%")))
706e9e57
LC
217 (begin
218 (when (or (not port) (port-closed? port))
4fd06a4d
LC
219 (set! port (guix:open-connection-for-uri
220 uri #:verify-certificate? #f))
9b7bd1b1 221 (unless (or buffered? (not (file-port? port)))
76832d34 222 (setvbuf port 'none)))
166ba5b1
LC
223 (http-fetch uri #:text? #f #:port port
224 #:verify-certificate? #f))))))
204d34ff 225 (else
69daee23 226 (leave (G_ "unsupported substitute URI scheme: ~a~%")
204d34ff 227 (uri->string uri)))))
f65cf81a 228
074efd63
LC
229(define-record-type <cache-info>
230 (%make-cache-info url store-directory wants-mass-query?)
231 cache-info?
232 (url cache-info-url)
233 (store-directory cache-info-store-directory)
234 (wants-mass-query? cache-info-wants-mass-query?))
235
236(define (download-cache-info url)
026ca50f
LC
237 "Download the information for the cache at URL. On success, return a
238<cache-info> object and a port on which to send further HTTP requests. On
239failure, return #f and #f."
240 (define uri
241 (string->uri (string-append url "/nix-cache-info")))
242
243 (define (read-cache-info port)
244 (alist->record (fields->alist port)
245 (cut %make-cache-info url <...>)
246 '("StoreDir" "WantMassQuery")))
247
248 (catch #t
249 (lambda ()
250 (case (uri-scheme uri)
251 ((file)
252 (values (call-with-input-file (uri-path uri)
253 read-cache-info)
254 #f))
255 ((http https)
4fd06a4d
LC
256 (let ((port (guix:open-connection-for-uri
257 uri
258 #:verify-certificate? #f
259 #:timeout %fetch-timeout)))
026ca50f 260 (guard (c ((http-get-error? c)
69daee23 261 (warning (G_ "while fetching '~a': ~a (~s)~%")
026ca50f
LC
262 (uri->string (http-get-error-uri c))
263 (http-get-error-code c)
264 (http-get-error-reason c))
b879b3e8 265 (close-connection port)
69daee23 266 (warning (G_ "ignoring substitute server at '~s'~%") url)
026ca50f
LC
267 (values #f #f)))
268 (values (read-cache-info (http-fetch uri
166ba5b1 269 #:verify-certificate? #f
026ca50f
LC
270 #:port port
271 #:keep-alive? #t))
272 port))))))
273 (lambda (key . args)
274 (case key
275 ((getaddrinfo-error system-error)
276 ;; Silently ignore the error: probably due to lack of network access.
277 (values #f #f))
278 (else
279 (apply throw key args))))))
f65cf81a 280
074efd63 281\f
f65cf81a 282(define-record-type <narinfo>
00230df1 283 (%make-narinfo path uri uri-base compression file-hash file-size nar-hash nar-size
e9c6c584 284 references deriver system signature contents)
f65cf81a
LC
285 narinfo?
286 (path narinfo-path)
fe0cff14 287 (uri narinfo-uri)
00230df1 288 (uri-base narinfo-uri-base) ; URI of the cache it originates from
f65cf81a
LC
289 (compression narinfo-compression)
290 (file-hash narinfo-file-hash)
291 (file-size narinfo-file-size)
292 (nar-hash narinfo-hash)
293 (nar-size narinfo-size)
294 (references narinfo-references)
295 (deriver narinfo-deriver)
e9c6c584
NK
296 (system narinfo-system)
297 (signature narinfo-signature) ; canonical sexp
298 ;; The original contents of a narinfo file. This field is needed because we
299 ;; want to preserve the exact textual representation for verification purposes.
300 ;; See <https://lists.gnu.org/archive/html/guix-devel/2014-02/msg00340.html>
301 ;; for more information.
302 (contents narinfo-contents))
303
ea0c6e05
LC
304(define (narinfo-hash->sha256 hash)
305 "If the string HASH denotes a sha256 hash, return it as a bytevector.
306Otherwise return #f."
307 (and (string-prefix? "sha256:" hash)
308 (nix-base32-string->bytevector (string-drop hash 7))))
309
e9c6c584
NK
310(define (narinfo-signature->canonical-sexp str)
311 "Return the value of a narinfo's 'Signature' field as a canonical sexp."
312 (match (string-split str #\;)
e465d9e1 313 ((version host-name sig)
e9c6c584
NK
314 (let ((maybe-number (string->number version)))
315 (cond ((not (number? maybe-number))
69daee23 316 (leave (G_ "signature version must be a number: ~s~%")
e9c6c584
NK
317 version))
318 ;; Currently, there are no other versions.
319 ((not (= 1 maybe-number))
69daee23 320 (leave (G_ "unsupported signature version: ~a~%")
e9c6c584 321 maybe-number))
cdea30e0
LC
322 (else
323 (let ((signature (utf8->string (base64-decode sig))))
324 (catch 'gcry-error
325 (lambda ()
326 (string->canonical-sexp signature))
6ef3644e 327 (lambda (key proc err)
69daee23 328 (leave (G_ "signature is not a valid \
e4687a5e
LC
329s-expression: ~s~%")
330 signature))))))))
e9c6c584 331 (x
69daee23 332 (leave (G_ "invalid format of the signature field: ~a~%") x))))
f65cf81a 333
e9c6c584
NK
334(define (narinfo-maker str cache-url)
335 "Return a narinfo constructor for narinfos originating from CACHE-URL. STR
336must contain the original contents of a narinfo file."
fe0cff14 337 (lambda (path url compression file-hash file-size nar-hash nar-size
e9c6c584 338 references deriver system signature)
fe0cff14
LC
339 "Return a new <narinfo> object."
340 (%make-narinfo path
fe0cff14
LC
341 ;; Handle the case where URL is a relative URL.
342 (or (string->uri url)
343 (string->uri (string-append cache-url "/" url)))
00230df1 344 cache-url
fe0cff14
LC
345
346 compression file-hash
347 (and=> file-size string->number)
348 nar-hash
349 (and=> nar-size string->number)
350 (string-tokenize references)
351 (match deriver
352 ((or #f "") #f)
353 (_ deriver))
e9c6c584 354 system
cdea30e0
LC
355 (false-if-exception
356 (and=> signature narinfo-signature->canonical-sexp))
e9c6c584 357 str)))
f65cf81a 358
e4687a5e 359(define* (assert-valid-signature narinfo signature hash
e9c6c584 360 #:optional (acl (current-acl)))
e4687a5e
LC
361 "Bail out if SIGNATURE, a canonical sexp representing the signature of
362NARINFO, doesn't match HASH, a bytevector containing the hash of NARINFO."
363 (let ((uri (uri->string (narinfo-uri narinfo))))
364 (signature-case (signature hash acl)
365 (valid-signature #t)
366 (invalid-signature
69daee23 367 (leave (G_ "invalid signature for '~a'~%") uri))
e4687a5e 368 (hash-mismatch
69daee23 369 (leave (G_ "hash mismatch for '~a'~%") uri))
e4687a5e 370 (unauthorized-key
69daee23 371 (leave (G_ "'~a' is signed with an unauthorized key~%") uri))
e4687a5e 372 (corrupt-signature
69daee23 373 (leave (G_ "signature on '~a' is corrupt~%") uri)))))
e9c6c584 374
0561e9ae
LC
375(define* (read-narinfo port #:optional url
376 #:key size)
e9c6c584 377 "Read a narinfo from PORT. If URL is true, it must be a string used to
0561e9ae
LC
378build full URIs from relative URIs found while reading PORT. When SIZE is
379true, read at most SIZE bytes from PORT; otherwise, read as much as possible.
cdea30e0
LC
380
381No authentication and authorization checks are performed here!"
0561e9ae
LC
382 (let ((str (utf8->string (if size
383 (get-bytevector-n port size)
384 (get-bytevector-all port)))))
cdea30e0
LC
385 (alist->record (call-with-input-string str fields->alist)
386 (narinfo-maker str url)
387 '("StorePath" "URL" "Compression"
388 "FileHash" "FileSize" "NarHash" "NarSize"
389 "References" "Deriver" "System"
390 "Signature"))))
391
e4687a5e
LC
392(define (narinfo-sha256 narinfo)
393 "Return the sha256 hash of NARINFO as a bytevector, or #f if NARINFO lacks a
394'Signature' field."
60b04024
LC
395 (define %mandatory-fields
396 ;; List of fields that must be signed. If they are not signed, the
397 ;; narinfo is considered unsigned.
398 '("StorePath" "NarHash" "References"))
399
e4687a5e 400 (let ((contents (narinfo-contents narinfo)))
8234fcf2 401 (match (string-contains contents "Signature:")
e4687a5e 402 (#f #f)
8234fcf2 403 (index
60b04024
LC
404 (let* ((above-signature (string-take contents index))
405 (signed-fields (match (call-with-input-string above-signature
406 fields->alist)
407 (((fields . values) ...) fields))))
408 (and (every (cut member <> signed-fields) %mandatory-fields)
409 (sha256 (string->utf8 above-signature))))))))
e4687a5e 410
a9468b42
LC
411(define* (valid-narinfo? narinfo #:optional (acl (current-acl))
412 #:key verbose?)
cdea30e0 413 "Return #t if NARINFO's signature is not valid."
e4687a5e
LC
414 (or %allow-unauthenticated-substitutes?
415 (let ((hash (narinfo-sha256 narinfo))
a9468b42
LC
416 (signature (narinfo-signature narinfo))
417 (uri (uri->string (narinfo-uri narinfo))))
e4687a5e
LC
418 (and hash signature
419 (signature-case (signature hash acl)
420 (valid-signature #t)
a9468b42
LC
421 (invalid-signature
422 (when verbose?
423 (format (current-error-port)
424 "invalid signature for substitute at '~a'~%"
425 uri))
426 #f)
427 (hash-mismatch
428 (when verbose?
429 (format (current-error-port)
430 "hash mismatch for substitute at '~a'~%"
431 uri))
432 #f)
433 (unauthorized-key
434 (when verbose?
435 (format (current-error-port)
436 "substitute at '~a' is signed by an \
437unauthorized party~%"
438 uri))
439 #f)
440 (corrupt-signature
441 (when verbose?
442 (format (current-error-port)
443 "corrupt signature for substitute at '~a'~%"
444 uri))
445 #f))))))
eba783b7
LC
446
447(define (write-narinfo narinfo port)
448 "Write NARINFO to PORT."
e9c6c584 449 (put-bytevector port (string->utf8 (narinfo-contents narinfo))))
eba783b7
LC
450
451(define (narinfo->string narinfo)
452 "Return the external representation of NARINFO."
453 (call-with-output-string (cut write-narinfo narinfo <>)))
454
00230df1 455(define (string->narinfo str cache-uri)
cdea30e0
LC
456 "Return the narinfo represented by STR. Assume CACHE-URI as the base URI of
457the cache STR originates form."
00230df1 458 (call-with-input-string str (cut read-narinfo <> cache-uri)))
eba783b7 459
895d1eda
LC
460(define (narinfo-cache-file cache-url path)
461 "Return the name of the local file that contains an entry for PATH. The
462entry is stored in a sub-directory specific to CACHE-URL."
30d4bc04
LC
463 ;; The daemon does not sanitize its input, so PATH could be something like
464 ;; "/gnu/store/foo". Gracefully handle that.
465 (match (store-path-hash-part path)
466 (#f
69daee23 467 (leave (G_ "'~a' does not name a store item~%") path))
30d4bc04
LC
468 ((? string? hash-part)
469 (string-append %narinfo-cache-directory "/"
470 (bytevector->base32-string (sha256 (string->utf8 cache-url)))
471 "/" hash-part))))
895d1eda
LC
472
473(define (cached-narinfo cache-url path)
474 "Check locally if we have valid info about PATH coming from CACHE-URL.
475Return two values: a Boolean indicating whether we have valid cached info, and
476that info, which may be either #f (when PATH is unavailable) or the narinfo
477for PATH."
eba783b7
LC
478 (define now
479 (current-time time-monotonic))
480
eba783b7 481 (define cache-file
895d1eda 482 (narinfo-cache-file cache-url path))
d3a65203
LC
483
484 (catch 'system-error
485 (lambda ()
486 (call-with-input-file cache-file
487 (lambda (p)
488 (match (read p)
1cf7e318 489 (('narinfo ('version 2)
d3a65203 490 ('cache-uri cache-uri)
5db5dff5 491 ('date date) ('ttl ttl) ('value #f))
d3a65203 492 ;; A cached negative lookup.
5db5dff5 493 (if (obsolete? date now ttl)
d3a65203
LC
494 (values #f #f)
495 (values #t #f)))
1cf7e318 496 (('narinfo ('version 2)
d3a65203 497 ('cache-uri cache-uri)
1cf7e318 498 ('date date) ('ttl ttl) ('value value))
d3a65203 499 ;; A cached positive lookup
1cf7e318 500 (if (obsolete? date now ttl)
d3a65203
LC
501 (values #f #f)
502 (values #t (string->narinfo value cache-uri))))
503 (('narinfo ('version v) _ ...)
504 (values #f #f))))))
505 (lambda _
506 (values #f #f))))
507
23d60ba6
LC
508(define (cache-narinfo! cache-url path narinfo ttl)
509 "Cache locally NARNIFO for PATH, which originates from CACHE-URL, with the
510given TTL (a number of seconds or #f). NARINFO may be #f, in which case it
511indicates that PATH is unavailable at CACHE-URL."
d3a65203
LC
512 (define now
513 (current-time time-monotonic))
eba783b7 514
cdea30e0 515 (define (cache-entry cache-uri narinfo)
1cf7e318 516 `(narinfo (version 2)
cdea30e0 517 (cache-uri ,cache-uri)
eba783b7 518 (date ,(time-second now))
23d60ba6
LC
519 (ttl ,(or ttl
520 (if narinfo %narinfo-ttl %narinfo-negative-ttl)))
eba783b7
LC
521 (value ,(and=> narinfo narinfo->string))))
522
895d1eda 523 (let ((file (narinfo-cache-file cache-url path)))
f10dcbf1
LC
524 (mkdir-p (dirname file))
525 (with-atomic-file-output file
526 (lambda (out)
527 (write (cache-entry cache-url narinfo) out))))
895d1eda 528
d3a65203
LC
529 narinfo)
530
531(define (narinfo-request cache-url path)
532 "Return an HTTP request for the narinfo of PATH at CACHE-URL."
533 (let ((url (string-append cache-url "/" (store-path-hash-part path)
f264e838
TGR
534 ".narinfo"))
535 (headers '((User-Agent . "GNU Guile"))))
536 (build-request (string->uri url) #:method 'GET #:headers headers)))
d3a65203 537
d213cc8c
LC
538(define (at-most max-length lst)
539 "If LST is shorter than MAX-LENGTH, return it; otherwise return its
540MAX-LENGTH first elements."
541 (let loop ((len 0)
542 (lst lst)
543 (result '()))
544 (match lst
545 (()
546 (reverse result))
547 ((head . tail)
548 (if (>= len max-length)
549 (reverse result)
550 (loop (+ 1 len) tail (cons head result)))))))
551
026ca50f 552(define* (http-multiple-get base-uri proc seed requests
166ba5b1 553 #:key port (verify-certificate? #t))
9b7bd1b1 554 "Send all of REQUESTS to the server at BASE-URI. Call PROC for each
f151298f
LC
555response, passing it the request object, the response, a port from which to
556read the response body, and the previous result, starting with SEED, à la
026ca50f
LC
557'fold'. Return the final result. When PORT is specified, use it as the
558initial connection on which HTTP requests are sent."
559 (let connect ((port port)
560 (requests requests)
f151298f 561 (result seed))
d3a65203
LC
562 ;; (format (current-error-port) "connecting (~a requests left)..."
563 ;; (length requests))
4fd06a4d
LC
564 (let ((p (or port (guix:open-connection-for-uri
565 base-uri
566 #:verify-certificate?
567 verify-certificate?))))
9b7bd1b1
LC
568 ;; For HTTPS, P is not a file port and does not support 'setvbuf'.
569 (when (file-port? p)
76832d34 570 (setvbuf p 'block (expt 2 16)))
9b7bd1b1 571
d213cc8c 572 ;; Send REQUESTS, up to a certain number, in a row.
ec278439
LC
573 ;; XXX: Do our own caching to work around inefficiencies when
574 ;; communicating over TLS: <http://bugs.gnu.org/22966>.
575 (let-values (((buffer get) (open-bytevector-output-port)))
1d84d7bf
LC
576 ;; Inherit the HTTP proxying property from P.
577 (set-http-proxy-port?! buffer (http-proxy-port? p))
ec278439 578
d213cc8c
LC
579 (for-each (cut write-request <> buffer)
580 (at-most 1000 requests))
ec278439
LC
581 (put-bytevector p (get))
582 (force-output p))
d3a65203
LC
583
584 ;; Now start processing responses.
585 (let loop ((requests requests)
586 (result result))
587 (match requests
588 (()
589 (reverse result))
590 ((head tail ...)
075d99f1
AP
591 (let* ((resp (read-response p))
592 (body (response-body-port resp))
f151298f 593 (result (proc head resp body result)))
d3a65203
LC
594 ;; The server can choose to stop responding at any time, in which
595 ;; case we have to try again. Check whether that is the case.
075d99f1 596 ;; Note that even upon "Connection: close", we can read from BODY.
d3a65203
LC
597 (match (assq 'connection (response-headers resp))
598 (('connection 'close)
b879b3e8 599 (close-connection p)
026ca50f 600 (connect #f tail result)) ;try again
d3a65203 601 (_
075d99f1 602 (loop tail result)))))))))) ;keep going
d3a65203
LC
603
604(define (read-to-eof port)
605 "Read from PORT until EOF is reached. The data are discarded."
606 (dump-port port (%make-void-port "w")))
607
608(define (narinfo-from-file file url)
609 "Attempt to read a narinfo from FILE, using URL as the cache URL. Return #f
610if file doesn't exist, and the narinfo otherwise."
611 (catch 'system-error
612 (lambda ()
613 (call-with-input-file file
614 (cut read-narinfo <> url)))
615 (lambda args
616 (if (= ENOENT (system-error-errno args))
617 #f
618 (apply throw args)))))
619
074efd63
LC
620(define (fetch-narinfos url paths)
621 "Retrieve all the narinfos for PATHS from the cache at URL and return them."
d3a65203 622 (define update-progress!
75a4d86f
LC
623 (let ((done 0)
624 (total (length paths)))
d3a65203 625 (lambda ()
4c97a368 626 (display "\r\x1b[K" (current-error-port)) ;erase current line
d3a65203
LC
627 (force-output (current-error-port))
628 (format (current-error-port)
2bf9351e 629 (G_ "updating substitutes from '~a'... ~5,1f%")
75a4d86f 630 url (* 100. (/ done total)))
d3a65203
LC
631 (set! done (+ 1 done)))))
632
3d3e93b3
LC
633 (define hash-part->path
634 (let ((mapping (fold (lambda (path result)
635 (vhash-cons (store-path-hash-part path) path
636 result))
637 vlist-null
638 paths)))
639 (lambda (hash)
640 (match (vhash-assoc hash mapping)
641 (#f #f)
642 ((_ . path) path)))))
643
f151298f 644 (define (handle-narinfo-response request response port result)
958fb14c
LC
645 (let* ((code (response-code response))
646 (len (response-content-length response))
23d60ba6
LC
647 (cache (response-cache-control response))
648 (ttl (and cache (assoc-ref cache 'max-age))))
d3a65203
LC
649 ;; Make sure to read no more than LEN bytes since subsequent bytes may
650 ;; belong to the next response.
958fb14c
LC
651 (if (= code 200) ; hit
652 (let ((narinfo (read-narinfo port url #:size len)))
653 (cache-narinfo! url (narinfo-path narinfo) narinfo ttl)
654 (update-progress!)
655 (cons narinfo result))
656 (let* ((path (uri-path (request-uri request)))
a7a3b390
LC
657 (hash-part (basename
658 (string-drop-right path 8)))) ;drop ".narinfo"
958fb14c
LC
659 (if len
660 (get-bytevector-n port len)
661 (read-to-eof port))
3d3e93b3 662 (cache-narinfo! url (hash-part->path hash-part) #f
958fb14c
LC
663 (if (= 404 code)
664 ttl
665 %narinfo-transient-error-ttl))
666 (update-progress!)
667 result))))
d3a65203 668
026ca50f 669 (define (do-fetch uri port)
ae4427e3 670 (case (and=> uri uri-scheme)
9b7bd1b1 671 ((http https)
ae4427e3
LC
672 (let ((requests (map (cut narinfo-request url <>) paths)))
673 (update-progress!)
166ba5b1
LC
674
675 ;; Note: Do not check HTTPS server certificates to avoid depending on
676 ;; the X.509 PKI. We can do it because we authenticate narinfos,
677 ;; which provides a much stronger guarantee.
9b7bd1b1 678 (let ((result (http-multiple-get uri
ae4427e3 679 handle-narinfo-response '()
026ca50f 680 requests
166ba5b1 681 #:verify-certificate? #f
026ca50f 682 #:port port)))
b879b3e8 683 (close-connection port)
ae4427e3
LC
684 (newline (current-error-port))
685 result)))
686 ((file #f)
687 (let* ((base (string-append (uri-path uri) "/"))
688 (files (map (compose (cut string-append base <> ".narinfo")
689 store-path-hash-part)
690 paths)))
691 (filter-map (cut narinfo-from-file <> url) files)))
692 (else
69daee23 693 (leave (G_ "~s: unsupported server URI scheme~%")
ae4427e3
LC
694 (if uri (uri-scheme uri) url)))))
695
026ca50f
LC
696 (let-values (((cache-info port)
697 (download-cache-info url)))
698 (and cache-info
699 (if (string=? (cache-info-store-directory cache-info)
700 (%store-prefix))
701 (do-fetch (string->uri url) port) ;reuse PORT
702 (begin
69daee23 703 (warning (G_ "'~a' uses different store '~a'; ignoring it~%")
026ca50f 704 url (cache-info-store-directory cache-info))
b879b3e8 705 (close-connection port)
026ca50f 706 #f)))))
d3a65203
LC
707
708(define (lookup-narinfos cache paths)
709 "Return the narinfos for PATHS, invoking the server at CACHE when no
710information is available locally."
711 (let-values (((cached missing)
712 (fold2 (lambda (path cached missing)
713 (let-values (((valid? value)
895d1eda 714 (cached-narinfo cache path)))
d3a65203 715 (if valid?
a89dde1e
LC
716 (if value
717 (values (cons value cached) missing)
718 (values cached missing))
d3a65203
LC
719 (values cached (cons path missing)))))
720 '()
721 '()
722 paths)))
723 (if (null? missing)
724 cached
074efd63
LC
725 (let ((missing (fetch-narinfos cache missing)))
726 (append cached (or missing '()))))))
d3a65203 727
a9468b42
LC
728(define (equivalent-narinfo? narinfo1 narinfo2)
729 "Return true if NARINFO1 and NARINFO2 are equivalent--i.e., if they describe
730the same store item. This ignores unnecessary metadata such as the Nar URL."
731 (and (string=? (narinfo-hash narinfo1)
732 (narinfo-hash narinfo2))
733
734 ;; The following is not needed if all we want is to download a valid
735 ;; nar, but it's necessary if we want valid narinfo.
736 (string=? (narinfo-path narinfo1)
737 (narinfo-path narinfo2))
738 (equal? (narinfo-references narinfo1)
739 (narinfo-references narinfo2))
740
741 (= (narinfo-size narinfo1)
742 (narinfo-size narinfo2))))
743
744(define (lookup-narinfos/diverse caches paths authorized?)
55b2fc18 745 "Look up narinfos for PATHS on all of CACHES, a list of URLS, in that order.
a9468b42
LC
746That is, when a cache lacks an AUTHORIZED? narinfo, look it up in the next
747cache, and so on.
748
749Return a list of narinfos for PATHS or a subset thereof. The returned
750narinfos are either AUTHORIZED?, or they claim a hash that matches an
751AUTHORIZED? narinfo."
752 (define (select-hit result)
753 (lambda (path)
754 (match (vhash-fold* cons '() path result)
755 ((one)
756 one)
757 ((several ..1)
758 (let ((authorized (find authorized? (reverse several))))
759 (and authorized
760 (find (cut equivalent-narinfo? <> authorized)
761 several)))))))
762
55b2fc18
LC
763 (let loop ((caches caches)
764 (paths paths)
a9468b42
LC
765 (result vlist-null) ;path->narinfo vhash
766 (hits '())) ;paths
55b2fc18
LC
767 (match paths
768 (() ;we're done
a9468b42
LC
769 ;; Now iterate on all the HITS, and return exactly one match for each
770 ;; hit: the first narinfo that is authorized, or that has the same hash
771 ;; as an authorized narinfo, in the order of CACHES.
772 (filter-map (select-hit result) hits))
55b2fc18
LC
773 (_
774 (match caches
775 ((cache rest ...)
776 (let* ((narinfos (lookup-narinfos cache paths))
a9468b42
LC
777 (definite (map narinfo-path (filter authorized? narinfos)))
778 (missing (lset-difference string=? paths definite))) ;XXX: perf
779 (loop rest missing
780 (fold vhash-cons result
781 (map narinfo-path narinfos) narinfos)
782 (append definite hits))))
55b2fc18 783 (() ;that's it
a9468b42 784 (filter-map (select-hit result) hits)))))))
55b2fc18 785
a9468b42 786(define (lookup-narinfo caches path authorized?)
55b2fc18
LC
787 "Return the narinfo for PATH in CACHES, or #f when no substitute for PATH
788was found."
a9468b42 789 (match (lookup-narinfos/diverse caches (list path) authorized?)
55b2fc18
LC
790 ((answer) answer)
791 (_ #f)))
f65cf81a 792
2ea2aac6
LC
793(define (cached-narinfo-expiration-time file)
794 "Return the expiration time for FILE, which is a cached narinfo."
795 (catch 'system-error
796 (lambda ()
797 (call-with-input-file file
798 (lambda (port)
799 (match (read port)
800 (('narinfo ('version 2) ('cache-uri uri)
801 ('date date) ('ttl ttl) ('value #f))
5db5dff5 802 (+ date ttl))
2ea2aac6
LC
803 (('narinfo ('version 2) ('cache-uri uri)
804 ('date date) ('ttl ttl) ('value value))
805 (+ date ttl))
806 (x
807 0)))))
808 (lambda args
809 ;; FILE may have been deleted.
810 0)))
4c7cacf1 811
2ea2aac6 812(define (narinfo-cache-directories directory)
895d1eda 813 "Return the list of narinfo cache directories (one per cache URL.)"
2ea2aac6 814 (map (cut string-append directory "/" <>)
895d1eda
LC
815 (scandir %narinfo-cache-directory
816 (lambda (item)
817 (and (not (member item '("." "..")))
818 (file-is-directory?
819 (string-append %narinfo-cache-directory
820 "/" item)))))))
821
2ea2aac6
LC
822(define* (cached-narinfo-files #:optional
823 (directory %narinfo-cache-directory))
824 "Return the list of cached narinfo files under DIRECTORY."
825 (append-map (lambda (directory)
826 (map (cut string-append directory "/" <>)
827 (scandir directory
828 (lambda (file)
829 (= (string-length file) 32)))))
830 (narinfo-cache-directories directory)))
4c7cacf1 831
79864851
SB
832(define (progress-report-port reporter port)
833 "Return a port that continuously reports the bytes read from PORT using
834REPORTER, which should be a <progress-reporter> object."
835 (match reporter
836 (($ <progress-reporter> start report stop)
837 (let* ((total 0)
838 (read! (lambda (bv start count)
839 (let ((n (match (get-bytevector-n! port bv start count)
840 ((? eof-object?) 0)
841 (x x))))
842 (set! total (+ total n))
843 (report total)
844 n))))
845 (start)
846 (make-custom-binary-input-port "progress-port-proc"
847 read! #f #f
848 (lambda ()
dc0f74e5
LC
849 ;; XXX: Kludge! When used through
850 ;; 'decompressed-port', this port ends
851 ;; up being closed twice: once in a
852 ;; child process early on, and at the
853 ;; end in the parent process. Ignore
854 ;; the early close so we don't output
855 ;; a spurious "download-succeeded"
856 ;; trace.
857 (unless (zero? total)
858 (stop))
f85dbc4f 859 (close-port port)))))))
a85060ef 860
cf5d2ca3
LC
861(define-syntax with-networking
862 (syntax-rules ()
8c321299 863 "Catch DNS lookup errors and TLS errors and gracefully exit."
cf5d2ca3
LC
864 ;; Note: no attempt is made to catch other networking errors, because DNS
865 ;; lookup errors are typically the first one, and because other errors are
866 ;; a subset of `system-error', which is harder to filter.
867 ((_ exp ...)
8c321299 868 (catch #t
cf5d2ca3 869 (lambda () exp ...)
8c321299
LC
870 (match-lambda*
871 (('getaddrinfo-error error)
69daee23 872 (leave (G_ "host name lookup error: ~a~%")
8c321299
LC
873 (gai-strerror error)))
874 (('gnutls-error error proc . rest)
875 (let ((error->string (module-ref (resolve-interface '(gnutls))
876 'error->string)))
69daee23 877 (leave (G_ "TLS error in procedure '~a': ~a~%")
8c321299
LC
878 proc (error->string error))))
879 (args
880 (apply throw args)))))))
cf5d2ca3 881
f65cf81a 882\f
29479de5
LC
883;;;
884;;; Help.
885;;;
886
887(define (show-help)
69daee23 888 (display (G_ "Usage: guix substitute [OPTION]...
29479de5 889Internal tool to substitute a pre-built binary to a local build.\n"))
69daee23 890 (display (G_ "
29479de5
LC
891 --query report on the availability of substitutes for the
892 store file names passed on the standard input"))
69daee23 893 (display (G_ "
29479de5
LC
894 --substitute STORE-FILE DESTINATION
895 download STORE-FILE and store it as a Nar in file
896 DESTINATION"))
897 (newline)
69daee23 898 (display (G_ "
29479de5 899 -h, --help display this help and exit"))
69daee23 900 (display (G_ "
29479de5
LC
901 -V, --version display version information and exit"))
902 (newline)
903 (show-bug-report-information))
904
905
906\f
ef8f910f
LC
907;;;
908;;; Daemon/substituter protocol.
909;;;
910
911(define (display-narinfo-data narinfo)
9d2f48df 912 "Write to the current output port the contents of NARINFO in the format
ef8f910f
LC
913expected by the daemon."
914 (format #t "~a\n~a\n~a\n"
915 (narinfo-path narinfo)
916 (or (and=> (narinfo-deriver narinfo)
917 (cute string-append (%store-prefix) "/" <>))
918 "")
919 (length (narinfo-references narinfo)))
920 (for-each (cute format #t "~a/~a~%" (%store-prefix) <>)
921 (narinfo-references narinfo))
922 (format #t "~a\n~a\n"
923 (or (narinfo-file-size narinfo) 0)
924 (or (narinfo-size narinfo) 0)))
925
926(define* (process-query command
55b2fc18 927 #:key cache-urls acl)
ef8f910f
LC
928 "Reply to COMMAND, a query as written by the daemon to this process's
929standard input. Use ACL as the access-control list against which to check
930authorized substitutes."
931 (define (valid? obj)
55b2fc18 932 (valid-narinfo? obj acl))
ef8f910f
LC
933
934 (match (string-tokenize command)
935 (("have" paths ..1)
55b2fc18 936 ;; Return the subset of PATHS available in CACHE-URLS.
a9468b42 937 (let ((substitutable (lookup-narinfos/diverse cache-urls paths valid?)))
ef8f910f
LC
938 (for-each (lambda (narinfo)
939 (format #t "~a~%" (narinfo-path narinfo)))
a9468b42 940 substitutable)
ef8f910f
LC
941 (newline)))
942 (("info" paths ..1)
55b2fc18 943 ;; Reply info about PATHS if it's in CACHE-URLS.
a9468b42
LC
944 (let ((substitutable (lookup-narinfos/diverse cache-urls paths valid?)))
945 (for-each display-narinfo-data substitutable)
ef8f910f
LC
946 (newline)))
947 (wtf
948 (error "unknown `--query' command" wtf))))
949
950(define* (process-substitution store-item destination
dc0f74e5 951 #:key cache-urls acl print-build-trace?)
55b2fc18 952 "Substitute STORE-ITEM (a store file name) from CACHE-URLS, and write it to
ef8f910f 953DESTINATION as a nar file. Verify the substitute against ACL."
a9468b42
LC
954 (let* ((narinfo (lookup-narinfo cache-urls store-item
955 (cut valid-narinfo? <> acl)))
956 (uri (and=> narinfo narinfo-uri)))
957 (unless uri
958 (leave (G_ "no valid substitute for '~a'~%")
959 store-item))
ef8f910f
LC
960
961 ;; Tell the daemon what the expected hash of the Nar itself is.
962 (format #t "~a~%" (narinfo-hash narinfo))
963
dc0f74e5
LC
964 (unless print-build-trace?
965 (format (current-error-port)
966 (G_ "Downloading ~a...~%") (uri->string uri)))
967
ef8f910f
LC
968 (let*-values (((raw download-size)
969 ;; Note that Hydra currently generates Nars on the fly
970 ;; and doesn't specify a Content-Length, so
971 ;; DOWNLOAD-SIZE is #f in practice.
972 (fetch uri #:buffered? #f #:timeout? #f))
973 ((progress)
974 (let* ((comp (narinfo-compression narinfo))
975 (dl-size (or download-size
976 (and (equal? comp "none")
977 (narinfo-size narinfo))))
dc0f74e5
LC
978 (reporter (if print-build-trace?
979 (progress-reporter/trace
980 destination
981 (uri->string uri) dl-size
982 (current-error-port))
983 (progress-reporter/file
984 (uri->string uri) dl-size
985 (current-error-port)
986 #:abbreviation nar-uri-abbreviation))))
79864851 987 (progress-report-port reporter raw)))
ef8f910f 988 ((input pids)
5efa0e4d
SB
989 ;; NOTE: This 'progress' port of current process will be
990 ;; closed here, while the child process doing the
991 ;; reporting will close it upon exit.
ef8f910f
LC
992 (decompressed-port (and=> (narinfo-compression narinfo)
993 string->symbol)
994 progress)))
995 ;; Unpack the Nar at INPUT into DESTINATION.
996 (restore-file input destination)
4220514b 997 (close-port input)
5efa0e4d
SB
998
999 ;; Wait for the reporter to finish.
1000 (every (compose zero? cdr waitpid) pids)
ef8f910f 1001
79864851
SB
1002 ;; Skip a line after what 'progress-reporter/file' printed, and another
1003 ;; one to visually separate substitutions.
5efa0e4d 1004 (display "\n\n" (current-error-port)))))
ef8f910f
LC
1005
1006\f
f65cf81a
LC
1007;;;
1008;;; Entry point.
1009;;;
1010
cdea30e0
LC
1011(define (check-acl-initialized)
1012 "Warn if the ACL is uninitialized."
1013 (define (singleton? acl)
1014 ;; True if ACL contains just the user's public key.
1015 (and (file-exists? %public-key-file)
1016 (let ((key (call-with-input-file %public-key-file
1017 (compose string->canonical-sexp
2535635f 1018 read-string))))
00fe9333
LC
1019 (match acl
1020 ((thing)
1021 (equal? (canonical-sexp->string thing)
1022 (canonical-sexp->string key)))
1023 (_
1024 #f)))))
1025
1026 (let ((acl (acl->public-keys (current-acl))))
cdea30e0 1027 (when (or (null? acl) (singleton? acl))
69daee23 1028 (warning (G_ "ACL for archive imports seems to be uninitialized, \
cdea30e0
LC
1029substitutes may be unavailable\n")))))
1030
9176607e
LC
1031(define (daemon-options)
1032 "Return a list of name/value pairs denoting build daemon options."
1033 (define %not-newline
1034 (char-set-complement (char-set #\newline)))
1035
1036 (match (getenv "_NIX_OPTIONS")
1037 (#f ;should not happen when called by the daemon
1038 '())
1039 (newline-separated
1040 ;; Here we get something of the form "OPTION1=VALUE1\nOPTION2=VALUE2\n".
1041 (filter-map (lambda (option=value)
1042 (match (string-index option=value #\=)
1043 (#f ;invalid option setting
1044 #f)
1045 (equal-sign
1046 (cons (string-take option=value equal-sign)
1047 (string-drop option=value (+ 1 equal-sign))))))
1048 (string-tokenize newline-separated %not-newline)))))
1049
1050(define (find-daemon-option option)
1051 "Return the value of build daemon option OPTION, or #f if it could not be
1052found."
1053 (assoc-ref (daemon-options) option))
1054
218f6ecc 1055(define %default-substitute-urls
71e2065a
LC
1056 (match (and=> (or (find-daemon-option "untrusted-substitute-urls") ;client
1057 (find-daemon-option "substitute-urls")) ;admin
4938b0ee 1058 string-tokenize)
55b2fc18
LC
1059 ((urls ...)
1060 urls)
4938b0ee
LC
1061 (#f
1062 ;; This can only happen when this script is not invoked by the
1063 ;; daemon.
0a5fa004 1064 '("http://ci.guix.info"))))
9176607e 1065
218f6ecc
LC
1066(define substitute-urls
1067 ;; List of substitute URLs.
1068 (make-parameter %default-substitute-urls))
1069
b0a6a971
LC
1070(define (client-terminal-columns)
1071 "Return the number of columns in the client's terminal, if it is known, or a
1072default value."
1073 (or (and=> (or (find-daemon-option "untrusted-terminal-columns")
1074 (find-daemon-option "terminal-columns"))
85fc958d
LC
1075 (lambda (str)
1076 (let ((number (string->number str)))
1077 (and number (max 20 (- number 1))))))
b0a6a971
LC
1078 80))
1079
8a210507
LC
1080(define (validate-uri uri)
1081 (unless (string->uri uri)
69daee23 1082 (leave (G_ "~a: invalid URI~%") uri)))
8a210507 1083
2c74fde0 1084(define (guix-substitute . args)
f65cf81a 1085 "Implement the build daemon's substituter protocol."
dc0f74e5
LC
1086 (define print-build-trace?
1087 (match (or (find-daemon-option "untrusted-print-extended-build-trace")
1088 (find-daemon-option "print-extended-build-trace"))
1089 (#f #f)
1090 ((= string->number number) (> number 0))
1091 (_ #f)))
1092
eba783b7 1093 (mkdir-p %narinfo-cache-directory)
2ea2aac6
LC
1094 (maybe-remove-expired-cache-entries %narinfo-cache-directory
1095 cached-narinfo-files
1096 #:entry-expiration
1097 cached-narinfo-expiration-time
1098 #:cleanup-period
1099 %narinfo-expired-cache-entry-removal-delay)
00fe9333 1100 (check-acl-initialized)
d43eb499
LC
1101
1102 ;; Starting from commit 22144afa in Nix, we are allowed to bail out directly
1103 ;; when we know we cannot substitute, but we must emit a newline on stdout
1104 ;; when everything is alright.
218f6ecc 1105 (when (null? (substitute-urls))
55b2fc18 1106 (exit 0))
d43eb499
LC
1107
1108 ;; Say hello (see above.)
1109 (newline)
1110 (force-output (current-output-port))
1111
218f6ecc
LC
1112 ;; Sanity-check SUBSTITUTE-URLS so we can provide a meaningful error message.
1113 (for-each validate-uri (substitute-urls))
8a210507 1114
38f50f49
LC
1115 ;; Attempt to install the client's locale, mostly so that messages are
1116 ;; suitably translated.
1117 (match (or (find-daemon-option "untrusted-locale")
1118 (find-daemon-option "locale"))
1119 (#f #f)
1120 (locale (false-if-exception (setlocale LC_ALL locale))))
1121
7d058688
LC
1122 (catch 'system-error
1123 (lambda ()
1124 (set-thread-name "guix substitute"))
1125 (const #t)) ;GNU/Hurd lacks 'prctl'
8902d0f2 1126
cf5d2ca3 1127 (with-networking
cdea30e0
LC
1128 (with-error-handling ; for signature errors
1129 (match args
1130 (("--query")
ef8f910f 1131 (let ((acl (current-acl)))
cdea30e0
LC
1132 (let loop ((command (read-line)))
1133 (or (eof-object? command)
1134 (begin
ef8f910f 1135 (process-query command
218f6ecc 1136 #:cache-urls (substitute-urls)
ef8f910f 1137 #:acl acl)
cdea30e0
LC
1138 (loop (read-line)))))))
1139 (("--substitute" store-path destination)
1140 ;; Download STORE-PATH and add store it as a Nar in file DESTINATION.
b0a6a971
LC
1141 ;; Specify the number of columns of the terminal so the progress
1142 ;; report displays nicely.
1143 (parameterize ((current-terminal-columns (client-terminal-columns)))
1144 (process-substitution store-path destination
218f6ecc 1145 #:cache-urls (substitute-urls)
dc0f74e5
LC
1146 #:acl (current-acl)
1147 #:print-build-trace? print-build-trace?)))
7ede577a 1148 ((or ("-V") ("--version"))
2c74fde0 1149 (show-version-and-exit "guix substitute"))
cdea30e0
LC
1150 (("--help")
1151 (show-help))
1152 (opts
69daee23 1153 (leave (G_ "~a: unrecognized options~%") opts))))))
f65cf81a 1154
bb7dcaea 1155;;; Local Variables:
2207f731 1156;;; eval: (put 'with-timeout 'scheme-indent-function 1)
ae3b6bb0
LC
1157;;; End:
1158
2c74fde0 1159;;; substitute.scm ends here