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