diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2014-01-22 17:09:21 +0100 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2014-01-24 00:01:50 +0100 |
| commit | cd4027fa478e20b59e798dd163a54e7ff9c42c98 (patch) | |
| tree | 5e8345f9800d039432fb98560ebd66a46d9eb024 | |
| parent | ce4a482983abaf7090d098cdda973139cefb56b7 (diff) | |
nar: Add 'restore-file-set', for use by build hooks.
* guix/nar.scm (&nar-invalid-hash-error, &nar-signature-error): New
condition types.
(&nar-error): Add 'file' and 'port' fields.
(&nar-read-error): Remove 'port' and 'file' fields.
(lock-store-file, unlock-store-file, finalize-store-file,
temporary-store-directory, restore-file-set): New procedures.
* tests/nar.scm (%seed): New variable.
(random-text): New procedure.
("restore-file-set (signed, valid)", "restore-file-set (missing
signature)", "restore-file-set (corrupt)"): New tests.
* po/Makevars (XGETTEXT_OPTIONS): Add '--keyword=message'.nar fixes
* po/POTFILES.in: Add guix/nar.scm.
| -rw-r--r-- | guix/nar.scm | 229 | ||||
| -rw-r--r-- | po/Makevars | 13 | ||||
| -rw-r--r-- | po/POTFILES.in | 1 | ||||
| -rw-r--r-- | tests/nar.scm | 103 |
4 files changed, 332 insertions, 14 deletions
diff --git a/guix/nar.scm b/guix/nar.scm index ea119a25fe1..4bc2deb2299 100644 --- a/guix/nar.scm +++ b/guix/nar.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -19,23 +19,40 @@ | |||
| 19 | (define-module (guix nar) | 19 | (define-module (guix nar) |
| 20 | #:use-module (guix utils) | 20 | #:use-module (guix utils) |
| 21 | #:use-module (guix serialization) | 21 | #:use-module (guix serialization) |
| 22 | #:use-module ((guix build utils) #:select (with-directory-excursion)) | 22 | #:use-module ((guix build utils) |
| 23 | #:select (delete-file-recursively with-directory-excursion)) | ||
| 24 | #:use-module (guix store) | ||
| 25 | #:use-module (guix ui) ; for '_' | ||
| 26 | #:use-module (guix hash) | ||
| 27 | #:use-module (guix pki) | ||
| 28 | #:use-module (guix pk-crypto) | ||
| 23 | #:use-module (rnrs bytevectors) | 29 | #:use-module (rnrs bytevectors) |
| 24 | #:use-module (rnrs io ports) | 30 | #:use-module (rnrs io ports) |
| 25 | #:use-module (srfi srfi-1) | 31 | #:use-module (srfi srfi-1) |
| 32 | #:use-module (srfi srfi-11) | ||
| 26 | #:use-module (srfi srfi-26) | 33 | #:use-module (srfi srfi-26) |
| 27 | #:use-module (srfi srfi-34) | 34 | #:use-module (srfi srfi-34) |
| 28 | #:use-module (srfi srfi-35) | 35 | #:use-module (srfi srfi-35) |
| 29 | #:use-module (ice-9 ftw) | 36 | #:use-module (ice-9 ftw) |
| 30 | #:use-module (ice-9 match) | 37 | #:use-module (ice-9 match) |
| 31 | #:export (nar-error? | 38 | #:export (nar-error? |
| 39 | nar-error-port | ||
| 40 | nar-error-file | ||
| 41 | |||
| 32 | nar-read-error? | 42 | nar-read-error? |
| 33 | nar-read-error-file | ||
| 34 | nar-read-error-port | ||
| 35 | nar-read-error-token | 43 | nar-read-error-token |
| 36 | 44 | ||
| 45 | nar-invalid-hash-error? | ||
| 46 | nar-invalid-hash-error-expected | ||
| 47 | nar-invalid-hash-error-actual | ||
| 48 | |||
| 49 | nar-signature-error? | ||
| 50 | nar-signature-error-signature | ||
| 51 | |||
| 37 | write-file | 52 | write-file |
| 38 | restore-file)) | 53 | restore-file |
| 54 | |||
| 55 | restore-file-set)) | ||
| 39 | 56 | ||
| 40 | ;;; Comment: | 57 | ;;; Comment: |
| 41 | ;;; | 58 | ;;; |
| @@ -44,15 +61,24 @@ | |||
| 44 | ;;; Code: | 61 | ;;; Code: |
| 45 | 62 | ||
| 46 | (define-condition-type &nar-error &error ; XXX: inherit from &nix-error ? | 63 | (define-condition-type &nar-error &error ; XXX: inherit from &nix-error ? |
| 47 | nar-error?) | 64 | nar-error? |
| 65 | (file nar-error-file) ; file we were restoring, or #f | ||
| 66 | (port nar-error-port)) ; port from which we read | ||
| 48 | 67 | ||
| 49 | (define-condition-type &nar-read-error &nar-error | 68 | (define-condition-type &nar-read-error &nar-error |
| 50 | nar-read-error? | 69 | nar-read-error? |
| 51 | (port nar-read-error-port) ; port from which we read | ||
| 52 | (file nar-read-error-file) ; file we were restoring, or #f | ||
| 53 | (token nar-read-error-token)) ; faulty token, or #f | 70 | (token nar-read-error-token)) ; faulty token, or #f |
| 54 | 71 | ||
| 72 | (define-condition-type &nar-signature-error &nar-error | ||
| 73 | nar-signature-error? | ||
| 74 | (signature nar-signature-error-signature)) ; faulty signature or #f | ||
| 55 | 75 | ||
| 76 | (define-condition-type &nar-invalid-hash-error &nar-signature-error | ||
| 77 | nar-invalid-hash-error? | ||
| 78 | (expected nar-invalid-hash-error-expected) ; expected hash (a bytevector) | ||
| 79 | (actual nar-invalid-hash-error-actual)) ; actual hash | ||
| 80 | |||
| 81 | |||
| 56 | (define (dump in out size) | 82 | (define (dump in out size) |
| 57 | "Copy SIZE bytes from IN to OUT." | 83 | "Copy SIZE bytes from IN to OUT." |
| 58 | (define buf-size 65536) | 84 | (define buf-size 65536) |
| @@ -239,4 +265,191 @@ Restore it as FILE." | |||
| 239 | (&message (message "unsupported nar entry type")) | 265 | (&message (message "unsupported nar entry type")) |
| 240 | (&nar-read-error (port port) (file file) (token x)))))))) | 266 | (&nar-read-error (port port) (file file) (token x)))))))) |
| 241 | 267 | ||
| 268 | |||
| 269 | ;;; | ||
| 270 | ;;; Restoring a file set into the store. | ||
| 271 | ;;; | ||
| 272 | |||
| 273 | ;; The code below accesses the store directly and is meant to be run from | ||
| 274 | ;; "build hooks", which cannot invoke the daemon's 'import-paths' RPC since | ||
| 275 | ;; (1) the locks on the files to be restored as already held, and (2) the | ||
| 276 | ;; $NIX_HELD_LOCKS hackish environment variable cannot be set. | ||
| 277 | ;; | ||
| 278 | ;; So we're really duplicating that functionality of the daemon (well, until | ||
| 279 | ;; most of the daemon is in Scheme :-)). But note that we do use a couple of | ||
| 280 | ;; RPCs for functionality not available otherwise, like 'valid-path?'. | ||
| 281 | |||
| 282 | (define (lock-store-file file) | ||
| 283 | "Acquire exclusive access to FILE, a store file." | ||
| 284 | (call-with-output-file (string-append file ".lock") | ||
| 285 | (cut fcntl-flock <> 'write-lock))) | ||
| 286 | |||
| 287 | (define (unlock-store-file file) | ||
| 288 | "Release access to FILE." | ||
| 289 | (call-with-input-file (string-append file ".lock") | ||
| 290 | (cut fcntl-flock <> 'unlock))) | ||
| 291 | |||
| 292 | (define* (finalize-store-file source target | ||
| 293 | #:key (references '()) deriver (lock? #t)) | ||
| 294 | "Rename SOURCE to TARGET and register TARGET as a valid store item, with | ||
| 295 | REFERENCES and DERIVER. When LOCK? is true, acquire exclusive locks on TARGET | ||
| 296 | before attempting to register it; otherwise, assume TARGET's locks are already | ||
| 297 | held." | ||
| 298 | |||
| 299 | ;; XXX: Currently we have to call out to the daemon to check whether TARGET | ||
| 300 | ;; is valid. | ||
| 301 | (with-store store | ||
| 302 | (unless (valid-path? store target) | ||
| 303 | (when lock? | ||
| 304 | (lock-store-file target)) | ||
| 305 | |||
| 306 | (unless (valid-path? store target) | ||
| 307 | ;; If FILE already exists, delete it (it's invalid anyway.) | ||
| 308 | (when (file-exists? target) | ||
| 309 | (delete-file-recursively target)) | ||
| 310 | |||
| 311 | ;; Install the new TARGET. | ||
| 312 | (rename-file source target) | ||
| 313 | |||
| 314 | ;; Register TARGET. As a side effect, it resets the timestamps of all | ||
| 315 | ;; its files, recursively. However, it doesn't attempt to deduplicate | ||
| 316 | ;; its files like 'importPaths' does (FIXME). | ||
| 317 | (register-path target | ||
| 318 | #:references references | ||
| 319 | #:deriver deriver)) | ||
| 320 | |||
| 321 | (when lock? | ||
| 322 | (unlock-store-file target))))) | ||
| 323 | |||
| 324 | (define (temporary-store-directory) | ||
| 325 | "Return the file name of a temporary directory created in the store that is | ||
| 326 | protected from garbage collection." | ||
| 327 | (let* ((template (string-append (%store-prefix) "/guix-XXXXXX")) | ||
| 328 | (port (mkstemp! template))) | ||
| 329 | (close-port port) | ||
| 330 | (with-store store | ||
| 331 | (add-temp-root store template)) | ||
| 332 | |||
| 333 | ;; There's a small window during which the GC could delete the file. Try | ||
| 334 | ;; again if that happens. | ||
| 335 | (if (file-exists? template) | ||
| 336 | (begin | ||
| 337 | ;; It's up to the caller to create that file or directory. | ||
| 338 | (delete-file template) | ||
| 339 | template) | ||
| 340 | (temporary-store-directory)))) | ||
| 341 | |||
| 342 | (define* (restore-file-set port | ||
| 343 | #:key (verify-signature? #t) (lock? #t) | ||
| 344 | (log-port (current-error-port))) | ||
| 345 | "Restore the file set read from PORT to the store. The format of the data | ||
| 346 | on PORT must be as created by 'export-paths'---i.e., a series of Nar-formatted | ||
| 347 | archives with interspersed meta-data joining them together, possibly with a | ||
| 348 | digital signature at the end. Log progress to LOG-PORT. Return the list of | ||
| 349 | files restored. | ||
| 350 | |||
| 351 | When LOCK? is #f, assume locks for the files to be restored are already held. | ||
| 352 | This is the case when the daemon calls a build hook. | ||
| 353 | |||
| 354 | Note that this procedure accesses the store directly, so it's only meant to be | ||
| 355 | used by the daemon's build hooks since they cannot call back to the daemon | ||
| 356 | while the locks are held." | ||
| 357 | (define %export-magic | ||
| 358 | ;; Number used to identify genuine file set archives. | ||
| 359 | #x4558494e) | ||
| 360 | |||
| 361 | (define port* | ||
| 362 | ;; Keep that one around, for error conditions. | ||
| 363 | port) | ||
| 364 | |||
| 365 | (define (assert-valid-signature signature hash file) | ||
| 366 | ;; Bail out if SIGNATURE, an sexp, doesn't match HASH, a bytevector | ||
| 367 | ;; containing the expected hash for FILE. | ||
| 368 | (let* ((signature (catch 'gcry-error | ||
| 369 | (lambda () | ||
| 370 | (string->canonical-sexp signature)) | ||
| 371 | (lambda (err . _) | ||
| 372 | (raise (condition | ||
| 373 | (&message | ||
| 374 | (message "signature is not a valid \ | ||
| 375 | s-expression")) | ||
| 376 | (&nar-signature-error | ||
| 377 | (file file) | ||
| 378 | (signature signature) (port port))))))) | ||
| 379 | (subject (signature-subject signature)) | ||
| 380 | (data (signature-signed-data signature))) | ||
| 381 | (if (and data subject) | ||
| 382 | (if (authorized-key? subject) | ||
| 383 | (if (equal? (hash-data->bytevector data) hash) | ||
| 384 | (unless (valid-signature? signature) | ||
| 385 | (raise (condition | ||
| 386 | (&message (message "invalid signature")) | ||
| 387 | (&nar-signature-error | ||
| 388 | (file file) (signature signature) (port port))))) | ||
| 389 | (raise (condition (&message (message "invalid hash")) | ||
| 390 | (&nar-invalid-hash-error | ||
| 391 | (port port) (file file) | ||
| 392 | (signature signature) | ||
| 393 | (expected (hash-data->bytevector data)) | ||
| 394 | (actual hash))))) | ||
| 395 | (raise (condition (&message (message "unauthorized public key")) | ||
| 396 | (&nar-signature-error | ||
| 397 | (signature signature) (file file) (port port))))) | ||
| 398 | (raise (condition | ||
| 399 | (&message (message "corrupt signature data")) | ||
| 400 | (&nar-signature-error | ||
| 401 | (signature signature) (file file) (port port))))))) | ||
| 402 | |||
| 403 | (let loop ((n (read-long-long port)) | ||
| 404 | (files '())) | ||
| 405 | (case n | ||
| 406 | ((0) | ||
| 407 | (reverse files)) | ||
| 408 | ((1) | ||
| 409 | (let-values (((port get-hash) | ||
| 410 | (open-sha256-input-port port))) | ||
| 411 | (let ((temp (temporary-store-directory))) | ||
| 412 | (restore-file port temp) | ||
| 413 | (let ((magic (read-int port))) | ||
| 414 | (unless (= magic %export-magic) | ||
| 415 | (raise (condition | ||
| 416 | (&message (message "corrupt file set archive")) | ||
| 417 | (&nar-read-error | ||
| 418 | (port port*) (file #f) (token #f)))))) | ||
| 419 | |||
| 420 | (let ((file (read-store-path port)) | ||
| 421 | (refs (read-store-path-list port)) | ||
| 422 | (deriver (read-string port)) | ||
| 423 | (hash (get-hash)) | ||
| 424 | (has-sig? (= 1 (read-int port)))) | ||
| 425 | (format log-port | ||
| 426 | (_ "importing file or directory '~a'...~%") | ||
| 427 | file) | ||
| 428 | |||
| 429 | (let ((sig (and has-sig? (read-string port)))) | ||
| 430 | (when verify-signature? | ||
| 431 | (if sig | ||
| 432 | (begin | ||
| 433 | (assert-valid-signature sig hash file) | ||
| 434 | (format log-port | ||
| 435 | (_ "found valid signature for '~a'~%") | ||
| 436 | file) | ||
| 437 | (finalize-store-file temp file | ||
| 438 | #:references refs | ||
| 439 | #:deriver deriver | ||
| 440 | #:lock? lock?) | ||
| 441 | (loop (read-long-long port) | ||
| 442 | (cons file files))) | ||
| 443 | (raise (condition | ||
| 444 | (&message (message "imported file lacks \ | ||
| 445 | a signature")) | ||
| 446 | (&nar-signature-error | ||
| 447 | (port port*) (file file) (signature #f))))))))))) | ||
| 448 | (else | ||
| 449 | ;; Neither 0 nor 1. | ||
| 450 | (raise (condition | ||
| 451 | (&message (message "invalid inter-file archive mark")) | ||
| 452 | (&nar-read-error | ||
| 453 | (port port) (file #f) (token #f)))))))) | ||
| 454 | |||
| 242 | ;;; nar.scm ends here | 455 | ;;; nar.scm ends here |
diff --git a/po/Makevars b/po/Makevars index 81fd53ef2cb..ade615a4523 100644 --- a/po/Makevars +++ b/po/Makevars | |||
| @@ -5,11 +5,14 @@ DOMAIN = $(PACKAGE) | |||
| 5 | subdir = po | 5 | subdir = po |
| 6 | top_builddir = .. | 6 | top_builddir = .. |
| 7 | 7 | ||
| 8 | # These options get passed to xgettext. | 8 | # These options get passed to xgettext. We want to catch standard |
| 9 | XGETTEXT_OPTIONS = \ | 9 | # gettext uses, package synopses and descriptions, and SRFI-34 error |
| 10 | --language=Scheme --from-code=UTF-8 \ | 10 | # condition messages. |
| 11 | --keyword=_ --keyword=N_ \ | 11 | XGETTEXT_OPTIONS = \ |
| 12 | --keyword=synopsis --keyword=description | 12 | --language=Scheme --from-code=UTF-8 \ |
| 13 | --keyword=_ --keyword=N_ \ | ||
| 14 | --keyword=synopsis --keyword=description \ | ||
| 15 | --keyword=message | ||
| 13 | 16 | ||
| 14 | COPYRIGHT_HOLDER = Ludovic Courtès | 17 | COPYRIGHT_HOLDER = Ludovic Courtès |
| 15 | 18 | ||
diff --git a/po/POTFILES.in b/po/POTFILES.in index beefdc901b8..b329f21e927 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in | |||
| @@ -15,3 +15,4 @@ guix/scripts/authenticate.scm | |||
| 15 | guix/gnu-maintenance.scm | 15 | guix/gnu-maintenance.scm |
| 16 | guix/ui.scm | 16 | guix/ui.scm |
| 17 | guix/http-client.scm | 17 | guix/http-client.scm |
| 18 | guix/nar.scm | ||
diff --git a/tests/nar.scm b/tests/nar.scm index 6493d76876f..9f21f990c82 100644 --- a/tests/nar.scm +++ b/tests/nar.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -18,11 +18,17 @@ | |||
| 18 | 18 | ||
| 19 | (define-module (test-nar) | 19 | (define-module (test-nar) |
| 20 | #:use-module (guix nar) | 20 | #:use-module (guix nar) |
| 21 | #:use-module (guix store) | ||
| 22 | #:use-module ((guix hash) #:select (open-sha256-input-port)) | ||
| 21 | #:use-module (rnrs bytevectors) | 23 | #:use-module (rnrs bytevectors) |
| 22 | #:use-module (rnrs io ports) | 24 | #:use-module (rnrs io ports) |
| 25 | #:use-module (srfi srfi-1) | ||
| 23 | #:use-module (srfi srfi-26) | 26 | #:use-module (srfi srfi-26) |
| 27 | #:use-module (srfi srfi-34) | ||
| 28 | #:use-module (srfi srfi-35) | ||
| 24 | #:use-module (srfi srfi-64) | 29 | #:use-module (srfi srfi-64) |
| 25 | #:use-module (ice-9 ftw) | 30 | #:use-module (ice-9 ftw) |
| 31 | #:use-module (ice-9 regex) | ||
| 26 | #:use-module (ice-9 match)) | 32 | #:use-module (ice-9 match)) |
| 27 | 33 | ||
| 28 | ;; Test the (guix nar) module. | 34 | ;; Test the (guix nar) module. |
| @@ -156,6 +162,24 @@ | |||
| 156 | (string-append (dirname (search-path %load-path "pre-inst-env")) | 162 | (string-append (dirname (search-path %load-path "pre-inst-env")) |
| 157 | "/test-nar-" (number->string (getpid)))) | 163 | "/test-nar-" (number->string (getpid)))) |
| 158 | 164 | ||
| 165 | ;; XXX: Factorize. | ||
| 166 | (define %seed | ||
| 167 | (seed->random-state (logxor (getpid) (car (gettimeofday))))) | ||
| 168 | |||
| 169 | (define (random-text) | ||
| 170 | (number->string (random (expt 2 256) %seed) 16)) | ||
| 171 | |||
| 172 | (define-syntax-rule (let/ec k exp...) | ||
| 173 | ;; This one appeared in Guile 2.0.9, so provide a copy here. | ||
| 174 | (let ((tag (make-prompt-tag))) | ||
| 175 | (call-with-prompt tag | ||
| 176 | (lambda () | ||
| 177 | (let ((k (lambda args | ||
| 178 | (apply abort-to-prompt tag args)))) | ||
| 179 | exp...)) | ||
| 180 | (lambda (_ . args) | ||
| 181 | (apply values args))))) | ||
| 182 | |||
| 159 | 183 | ||
| 160 | (test-begin "nar") | 184 | (test-begin "nar") |
| 161 | 185 | ||
| @@ -201,6 +225,83 @@ | |||
| 201 | (lambda () | 225 | (lambda () |
| 202 | (rmdir input))))) | 226 | (rmdir input))))) |
| 203 | 227 | ||
| 228 | ;; 'restore-file-set' depends on 'open-sha256-input-port', which in turn | ||
| 229 | ;; relies on a Guile 2.0.10+ feature. | ||
| 230 | (test-skip (if (false-if-exception | ||
| 231 | (open-sha256-input-port (%make-void-port "r"))) | ||
| 232 | 0 | ||
| 233 | 3)) | ||
| 234 | |||
| 235 | (test-assert "restore-file-set (signed, valid)" | ||
| 236 | (with-store store | ||
| 237 | (let* ((texts (unfold (cut >= <> 10) | ||
| 238 | (lambda _ (random-text)) | ||
| 239 | 1+ | ||
| 240 | 0)) | ||
| 241 | (files (map (cut add-text-to-store store "text" <>) texts)) | ||
| 242 | (dump (call-with-bytevector-output-port | ||
| 243 | (cut export-paths store files <>)))) | ||
| 244 | (delete-paths store files) | ||
| 245 | (and (every (negate file-exists?) files) | ||
| 246 | (let* ((source (open-bytevector-input-port dump)) | ||
| 247 | (imported (restore-file-set source))) | ||
| 248 | (and (equal? imported files) | ||
| 249 | (every (lambda (file) | ||
| 250 | (and (file-exists? file) | ||
| 251 | (valid-path? store file))) | ||
| 252 | files) | ||
| 253 | (equal? texts | ||
| 254 | (map (lambda (file) | ||
| 255 | (call-with-input-file file | ||
| 256 | get-string-all)) | ||
| 257 | files)))))))) | ||
| 258 | |||
| 259 | (test-assert "restore-file-set (missing signature)" | ||
| 260 | (let/ec return | ||
| 261 | (with-store store | ||
| 262 | (let* ((file (add-text-to-store store "foo" "Hello, world!")) | ||
| 263 | (dump (call-with-bytevector-output-port | ||
| 264 | (cute export-paths store (list file) <> | ||
| 265 | #:sign? #f)))) | ||
| 266 | (delete-paths store (list file)) | ||
| 267 | (and (not (file-exists? file)) | ||
| 268 | (let ((source (open-bytevector-input-port dump))) | ||
| 269 | (guard (c ((nar-signature-error? c) | ||
| 270 | (let ((message (condition-message c)) | ||
| 271 | (port (nar-error-port c))) | ||
| 272 | (return | ||
| 273 | (and (string-match "lacks.*signature" message) | ||
| 274 | (string=? file (nar-error-file c)) | ||
| 275 | (eq? source port)))))) | ||
| 276 | (restore-file-set source)) | ||
| 277 | #f)))))) | ||
| 278 | |||
| 279 | (test-assert "restore-file-set (corrupt)" | ||
| 280 | (let/ec return | ||
| 281 | (with-store store | ||
| 282 | (let* ((file (add-text-to-store store "foo" | ||
| 283 | (random-text))) | ||
| 284 | (dump (call-with-bytevector-output-port | ||
| 285 | (cute export-paths store (list file) <>)))) | ||
| 286 | (delete-paths store (list file)) | ||
| 287 | |||
| 288 | ;; Flip a byte in the file contents. | ||
| 289 | (let* ((index 120) | ||
| 290 | (byte (bytevector-u8-ref dump index))) | ||
| 291 | (bytevector-u8-set! dump index (logxor #xff byte))) | ||
| 292 | |||
| 293 | (and (not (file-exists? file)) | ||
| 294 | (let ((source (open-bytevector-input-port dump))) | ||
| 295 | (guard (c ((nar-invalid-hash-error? c) | ||
| 296 | (let ((message (condition-message c)) | ||
| 297 | (port (nar-error-port c))) | ||
| 298 | (return | ||
| 299 | (and (string-contains message "hash") | ||
| 300 | (string=? file (nar-error-file c)) | ||
| 301 | (eq? source port)))))) | ||
| 302 | (restore-file-set source)) | ||
| 303 | #f)))))) | ||
| 304 | |||
| 204 | (test-end "nar") | 305 | (test-end "nar") |
| 205 | 306 | ||
| 206 | 307 | ||
