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