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