(define-library (zilch file) (import (scheme base) (scheme case-lambda) (zilch magic) (zilch nix binproto) (zilch nix daemon) (zilch nix drv) (zilch zexpr) (chicken base) (chicken format) (srfi 128) (srfi 132) (srfi 146) (srfi 151)) (export zfile zsymlink zdir zfile->store) (begin (define-record-type (make-z-file contents executable cache) z-file? (contents z-file-contents) (executable z-file-executable) (cache z-file-cache z-file-set-cache)) (define-record-printer ( file out) (if (z-file-executable file) (fprintf out "#") (fprintf out "#"))) (define-record-type (make-z-directory contents cache) z-directory? (contents z-directory-contents) (cache z-directory-cache z-directory-set-cache)) (define-record-printer ( dir out) (fprintf out "# ~S" (car kv) (cdr kv))) (z-directory-contents dir)) (fprintf out ">")) (define-record-type (make-z-symlink target cache) z-symlink? (target z-symlink-target) (cache z-symlink-cache z-symlink-set-cache)) (define-record-printer ( symlink out) (fprintf out "# ~S>" (z-symlink-target symlink))) (define (env-pair` object with given contents and optional `executable` flag. ;; The contents may either be a string or a ``. (define zfile (case-lambda ((contents) (make-z-file contents #f #f)) ((contents executable) (make-z-file contents executable #f)))) ;; Create a `` record. The target may be any string, *or* a `` containing one. (define (zsymlink target) (make-z-symlink target #f)) ;; Create a `` record. The contents is an alist of file name -> zfile/zsymlink/zdir. ;; For simplicity, one can also write e.g. `(zdir "key" value "key2" value)`. (define zdir (case-lambda ((contents) (make-z-directory (list-sort env-pairinteger #\a))) '(0 157)) (bytevector-copy! baseline-ustar-header 257 (string->utf8 "ustar\x00;00")) ; We subtract 32*21 here to exclude the mode, size, and typeflag fields from the baseline checksum. (define baseline-ustar-header-checksum (do ((i 0 (+ i 1)) (csum 0 csum)) ((= i 512) (- csum (* 32 21))) (set! csum (+ csum (bytevector-u8-ref baseline-ustar-header i))))) ;; Write a tar header with specified mode, content length, and type byte. ;; For simplicity, all paths and link paths must be defined in PAX attributes. (define (make-ustar-header mode size type) (define output (bytevector-copy baseline-ustar-header)) (define octal-mode (number->string mode 8)) (bytevector-copy! output 100 (string->utf8 octal-mode)) (define octal-size (number->string size 8)) (bytevector-copy! output 124 (string->utf8 octal-size)) (bytevector-u8-set! output 156 (char->integer type)) (define csum (+ baseline-ustar-header-checksum (char->integer type))) (do ((i 100 (+ i 1))) ((= i 108) #f) (set! csum (+ csum (bytevector-u8-ref output i)))) (do ((i 124 (+ i 1))) ((= i 136) #f) (set! csum (+ csum (bytevector-u8-ref output i)))) (define octal-csum (number->string csum 8)) (bytevector-copy! output 148 (string->utf8 octal-csum)) output) ;; PAX extended attributes contain the length of the entire line, including the length bytes. ;; Calculate the length of " {key}={value}\n" and the length of that length in decimal. ;; If adding the length of the length would make the length overflow, add one more byte; then ;; return the full serialized key-value pair as a string. (define make-pax-extended-header (case-lambda ((key value) (make-pax-extended-header key value (string-length value))) ((key value value-length) (define kv-length (+ (string-length key) value-length 3)) (define length-length (string-length (number->string kv-length))) (unless (eqv? length-length (string-length (number->string (+ length-length kv-length)))) (set! length-length (+ 1 length-length))) (string-append (number->string (+ length-length kv-length)) " " key "=" value "\n")))) (define padding-block (make-bytevector 512 0)) (define (write-padding len) (define rem (bitwise-and len 511)) (unless (= rem 0) (write-bytevector padding-block (current-output-port) 0 (- 512 rem)))) (define (unwrap-zexp-to-placeholder zexp) (define contents-zexp (zexp-unwrap zexp)) (define contents (zexp-evaluation-value contents-zexp)) (zexp-context-register-items (zexp-evaluation-drvs contents-zexp) (zexp-evaluation-srcs contents-zexp)) (when (string? contents) (set! contents (string->utf8 contents))) (define placeholder-mapping (mapping (make-default-comparator))) (define has-mapping #f) (for-each (lambda (drv) (for-each (lambda (output) (define output-obj (cdr (assoc output (derivation-outputs (car drv))))) (when (derivation-output-placeholder? output-obj) (set! placeholder-mapping (mapping-set! placeholder-mapping (derivation-output-path output-obj) (derivation-output-path-length (car drv) output))) (set! has-mapping #t))) (cdr drv))) (zexp-evaluation-drvs contents-zexp)) (define contents-length (bytevector-length contents)) (define sliced (make-bytevector 53)) (when has-mapping (do ((i 0 (+ 1 i)) (ref #f #f)) ((> i (- (bytevector-length contents) 53)) #f) (when (= (bytevector-u8-ref contents i) #x2F) (bytevector-copy! sliced 0 contents i (+ i 53)) (set! ref (mapping-ref/default placeholder-mapping (utf8->string sliced) #f)) (when ref (set! contents-length (+ (- contents-length 53) ref)))))) (values contents contents-length)) (define (write-pax-directory path) (define header (string->utf8 (make-pax-extended-header "path" path))) (write-bytevector (make-ustar-header 0 (bytevector-length header) #\x)) (write-bytevector header) (write-padding (bytevector-length header)) (write-bytevector (make-ustar-header #o777 0 #\5))) (define (write-pax-file path executable contents-zexp) (define-values (contents contents-length) (unwrap-zexp-to-placeholder contents-zexp)) (define header (string->utf8 (make-pax-extended-header "path" path))) (write-bytevector (make-ustar-header 0 (bytevector-length header) #\x)) (write-bytevector header) (write-padding (bytevector-length header)) (write-bytevector (make-ustar-header (if executable #o555 #o444) contents-length #\0)) (if (bytevector? contents) (write-bytevector contents) (write-string contents)) (write-padding contents-length)) (define (write-pax-symlink path linkpath-zexp) (define-values (linkpath linkpath-length) (unwrap-zexp-to-placeholder linkpath-zexp)) (define header (string->utf8 (string-append (make-pax-extended-header "path" path) (make-pax-extended-header "linkpath" (utf8->string linkpath) linkpath-length)))) (define total-len (+ (bytevector-length header) (- linkpath-length (bytevector-length linkpath)))) (write-bytevector (make-ustar-header 0 total-len #\x)) (write-bytevector header) (write-padding total-len) (write-bytevector (make-ustar-header #o777 0 #\2))) ;; Serialize the specified structure as a ustar-style (with pax extensions) tape archive to the standard output port. The filename is specified by `name`. (define (serialize-as-tar f name) (cond ((z-file? f) (write-pax-file name (z-file-executable f) (z-file-contents f))) ((z-directory? f) (write-pax-directory name) (map (lambda (entry) (serialize-as-tar (cdr entry) (string-append name "/" (car entry)))) (z-directory-contents f))) ((z-symlink? f) (write-pax-symlink name (z-symlink-target f))))) ;; Serialize a file-like (`zfile`, `zsymlink`, `zdir`) to a ``. ;; This function should not depend on the system of the builder. ;; ;; TODO(puck): due to limitations, whatever you pass in ends up at `/-` instead. (define (zfile->store val) (define cached (cond ((z-file? val) (z-file-cache val)) ((z-directory? val) (z-directory-cache val)) ((z-symlink? val) (z-symlink-cache val)))) (unless cached (set! cached (let* ((bvport (open-output-bytevector)) (serialized (zexp-with-context (lambda () (parameterize ((current-output-port bvport)) (serialize-as-tar val "-"))))) (bv (get-output-bytevector bvport)) (intermediate #f) (drv #f)) (close-port bvport) ;(set! intermediate ; (zexp-with-injected-context ; (zexp ; (zexp-unquote (store-path-for-text "file" bv))) ; (cadr serialized) ; (car (cddr serialized)))) (set! drv (store-path-for-ca-drv* "zilchfile" "builtin" (zexp ("builtin:unpack-channel")) (zexp (("src" . ".attr-1s42g1c76fxb77skzq0b4wdhcrg8jmzb54czmxvh1qm7psgsbcni") ("contents" . (zexp-unquote (call-with-port (open-output-bytevector) (lambda (port) (parameterize ((current-output-port port)) (serialize-as-tar val "-")) (get-output-bytevector port))))) ("passAsFile" . "contents") ("allowSubstitutes" . "") ("channelName" . "-"))) '("out"))) (cdar drv)))) (cond ((z-file? val) (z-file-set-cache val cached)) ((z-directory? val) (z-directory-set-cache val cached)) ((z-symlink? val) (z-symlink-set-cache val cached))) cached) (zexp-add-unquote-handler (lambda (val) (if (or (z-file? val) (z-symlink? val) (z-directory? val)) (string-append (zexp-unquote (zfile->store val)) "/-") #f)))))