summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gnu/system/vm.scm163
-rw-r--r--guix/build/vm.scm139
2 files changed, 152 insertions, 150 deletions
diff --git a/gnu/system/vm.scm b/gnu/system/vm.scm
index b0fd3f5710..069ac3093a 100644
--- a/gnu/system/vm.scm
+++ b/gnu/system/vm.scm
@@ -217,154 +217,21 @@ such as /etc files."
    (expression->derivation-in-linux-vm
     "qemu-image"
     `(let ()
-       (use-modules (ice-9 rdelim)
-                    (srfi srfi-1)
-                    (guix build utils)
-                    (guix build linux-initrd))
-
-       (let ((parted  (string-append (assoc-ref %build-inputs "parted")
-                                     "/sbin/parted"))
-             (mkfs    (string-append (assoc-ref %build-inputs "e2fsprogs")
-                                     "/sbin/mkfs.ext3"))
-             (grub    (string-append (assoc-ref %build-inputs "grub")
-                                     "/sbin/grub-install"))
-             (umount  (string-append (assoc-ref %build-inputs "util-linux")
-                                     "/bin/umount")) ; XXX: add to Guile
-             (grub.cfg ,grub-configuration))
-
-         (define (read-reference-graph port)
-           ;; Return a list of store paths from the reference graph at PORT.
-           ;; The data at PORT is the format produced by #:references-graphs.
-           (let loop ((line   (read-line port))
-                      (result '()))
-             (cond ((eof-object? line)
-                    (delete-duplicates result))
-                   ((string-prefix? "/" line)
-                    (loop (read-line port)
-                          (cons line result)))
-                   (else
-                    (loop (read-line port)
-                          result)))))
-
-         (define (things-to-copy)
-           ;; Return the list of store files to copy to the image.
-           (define (graph-from-file file)
-             (call-with-input-file file
-               read-reference-graph))
-
-           ,(match inputs-to-copy
-              (((graph-files . _) ...)
-               `(let* ((graph-files ',(map (cut string-append "/xchg/" <>)
-                                           graph-files))
-                       (paths       (append-map graph-from-file graph-files)))
-                  (delete-duplicates paths)))
-              (#f ''())))
-
-         ;; GRUB is full of shell scripts.
-         (setenv "PATH"
-                 (string-append (dirname grub) ":"
-                                (assoc-ref %build-inputs "coreutils") "/bin:"
-                                (assoc-ref %build-inputs "findutils") "/bin:"
-                                (assoc-ref %build-inputs "sed") "/bin:"
-                                (assoc-ref %build-inputs "grep") "/bin:"
-                                (assoc-ref %build-inputs "gawk") "/bin"))
-
-         (display "creating partition table...\n")
-         (and (zero? (system* parted "/dev/sda" "mklabel" "msdos"
-                              "mkpart" "primary" "ext2" "1MiB"
-                              ,(format #f "~aB"
-                                       (- disk-image-size
-                                          (* 5 (expt 2 20))))))
-              (begin
-                (display "creating ext3 partition...\n")
-                (and (zero? (system* mkfs "-F" "/dev/sda1"))
-                     (let ((store (string-append "/fs" ,(%store-prefix))))
-                       (display "mounting partition...\n")
-                       (mkdir "/fs")
-                       (mount "/dev/sda1" "/fs" "ext3")
-                       (mkdir-p "/fs/boot/grub")
-                       (symlink grub.cfg "/fs/boot/grub/grub.cfg")
-
-                       ;; Populate the image's store.
-                       (mkdir-p store)
-                       (chmod store #o1775)
-                       (for-each (lambda (thing)
-                                   (copy-recursively thing
-                                                     (string-append "/fs"
-                                                                    thing)))
-                                 (things-to-copy))
-
-                       ;; Populate /dev.
-                       (make-essential-device-nodes #:root "/fs")
-
-                       ;; Optionally, register the inputs in the image's store.
-                       (let* ((guix     (assoc-ref %build-inputs "guix"))
-                              (register (and guix
-                                             (string-append guix
-                                                            "/sbin/guix-register"))))
-                         ,@(if initialize-store?
-                               (match inputs-to-copy
-                                 (((graph-files . _) ...)
-                                  (map (lambda (closure)
-                                         `(system* register "--prefix" "/fs"
-                                                   ,(string-append "/xchg/"
-                                                                   closure)))
-                                       graph-files)))
-                               '(#f)))
-
-                       ;; Evaluate the POPULATE directives.
-                       ,@(let loop ((directives populate)
-                                    (statements '()))
-                           (match directives
-                             (()
-                              (reverse statements))
-                             ((('directory name) rest ...)
-                              (loop rest
-                                    (cons `(mkdir-p ,(string-append "/fs" name))
-                                          statements)))
-                             ((('directory name uid gid) rest ...)
-                              (let ((dir (string-append "/fs" name)))
-                                (loop rest
-                                      (cons* `(chown ,dir ,uid ,gid)
-                                             `(mkdir-p ,dir)
-                                             statements))))
-                             (((new '-> old) rest ...)
-                              (loop rest
-                                    (cons `(symlink ,old
-                                                    ,(string-append "/fs" new))
-                                          statements)))))
-
-                       (and=> (assoc-ref %build-inputs "populate")
-                              (lambda (populate)
-                                (chdir "/fs")
-                                (primitive-load populate)
-                                (chdir "/")))
-
-                       (display "clearing file timestamps...\n")
-                       (for-each (lambda (file)
-                                   (let ((s (lstat file)))
-                                     ;; XXX: Guile uses libc's 'utime' function
-                                     ;; (not 'futime'), so the timestamp of
-                                     ;; symlinks cannot be changed, and there
-                                     ;; are symlinks here pointing to
-                                     ;; /gnu/store, which is the host,
-                                     ;; read-only store.
-                                     (unless (eq? (stat:type s) 'symlink)
-                                       (utime file 0 0 0 0))))
-                                 (find-files "/fs" ".*"))
-
-                       (and (zero?
-                             (system* grub "--no-floppy"
-                                      "--boot-directory" "/fs/boot"
-                                      "/dev/sda"))
-                            (begin
-                              (when (file-exists? "/fs/dev/pts")
-                                ;; Unmount devpts so /fs itself can be
-                                ;; unmounted (failing to do that leads to
-                                ;; EBUSY.)
-                                (system* umount "/fs/dev/pts"))
-                              (zero? (system* umount "/fs")))
-                            (reboot))))))))
+       (use-modules (guix build vm)
+                    (guix build utils))
+
+       (set-path-environment-variable "PATH" '("bin" "sbin")
+                                      (map cdr %build-inputs))
+
+       (let ((graphs ',(match inputs-to-copy
+                         (((names . _) ...)
+                          names))))
+         (initialize-hard-disk #:grub.cfg ,grub-configuration
+                               #:closures-to-copy graphs
+                               #:disk-image-size ,disk-image-size
+                               #:initialize-store? ,initialize-store?
+                               #:directives ',populate)
+         (reboot)))
     #:system system
     #:inputs `(("parted" ,parted)
                ("grub" ,grub)
diff --git a/guix/build/vm.scm b/guix/build/vm.scm
index 725ede4e1f..33c898d968 100644
--- a/guix/build/vm.scm
+++ b/guix/build/vm.scm
@@ -17,9 +17,14 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (guix build vm)
-  #:use-module (ice-9 match)
   #:use-module (guix build utils)
-  #:export (load-in-linux-vm))
+  #:use-module (guix build linux-initrd)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:export (load-in-linux-vm
+            initialize-hard-disk))
 
 ;;; Commentary:
 ;;;
@@ -94,4 +99,134 @@ the #:references-graphs parameter of 'derivation'."
         (mkdir output)
         (copy-recursively "xchg" output))))
 
+(define (read-reference-graph port)
+  "Return a list of store paths from the reference graph at PORT.
+The data at PORT is the format produced by #:references-graphs."
+  (let loop ((line   (read-line port))
+             (result '()))
+    (cond ((eof-object? line)
+           (delete-duplicates result))
+          ((string-prefix? "/" line)
+           (loop (read-line port)
+                 (cons line result)))
+          (else
+           (loop (read-line port)
+                 result)))))
+
+(define* (initialize-partition-table device
+                                     #:key
+                                     (label-type "msdos")
+                                     partition-size)
+  "Create on DEVICE a partition table of type LABEL-TYPE, with a single
+partition of PARTITION-SIZE MiB.  Return #t on success."
+  (display "creating partition table...\n")
+  (zero? (system* "parted" "/dev/sda" "mklabel" label-type
+                  "mkpart" "primary" "ext2" "1MiB"
+                  (format #f "~aB" partition-size))))
+
+(define* (install-grub grub.cfg device mount-point)
+  "Install GRUB with GRUB.CFG on DEVICE, which is assumed to be mounted on
+MOUNT-POINT.  Return #t on success."
+  (mkdir-p (string-append mount-point "/boot/grub"))
+  (symlink grub.cfg (string-append mount-point "/boot/grub/grub.cfg"))
+  (zero? (system* "grub-install" "--no-floppy"
+                  "--boot-directory" (string-append mount-point "/boot")
+                  device)))
+
+(define* (populate-store reference-graphs target)
+  "Populate the store under directory TARGET with the items specified in
+REFERENCE-GRAPHS, a list of reference-graph files."
+  (define store
+    (string-append target (%store-directory)))
+
+  (define (things-to-copy)
+    ;; Return the list of store files to copy to the image.
+    (define (graph-from-file file)
+      (call-with-input-file file read-reference-graph))
+
+    (delete-duplicates (append-map graph-from-file reference-graphs)))
+
+  (mkdir-p store)
+  (chmod store #o1775)
+  (for-each (lambda (thing)
+              (copy-recursively thing
+                                (string-append target thing)))
+            (things-to-copy)))
+
+(define (evaluate-populate-directive directive target)
+  "Evaluate DIRECTIVE, an sexp describing a file or directory to create under
+directory TARGET."
+  (match directive
+    (('directory name)
+     (mkdir-p (string-append target name)))
+    (('directory name uid gid)
+     (let ((dir (string-append target name)))
+       (mkdir-p dir)
+       (chown dir uid gid)))
+    ((new '-> old)
+     (symlink old (string-append target new)))))
+
+(define (reset-timestamps directory)
+  "Reset the timestamps of all the files under DIRECTORY, so that they appear
+as created and modified at the Epoch."
+  (display "clearing file timestamps...\n")
+  (for-each (lambda (file)
+              (let ((s (lstat file)))
+                ;; XXX: Guile uses libc's 'utime' function (not 'futime'), so
+                ;; the timestamp of symlinks cannot be changed, and there are
+                ;; symlinks here pointing to /gnu/store, which is the host,
+                ;; read-only store.
+                (unless (eq? (stat:type s) 'symlink)
+                  (utime file 0 0 0 0))))
+            (find-files directory "")))
+
+(define* (initialize-hard-disk #:key
+                               grub.cfg
+                               disk-image-size
+                               (mkfs "mkfs.ext3")
+                               initialize-store?
+                               (closures-to-copy '())
+                               (directives '()))
+  (unless (initialize-partition-table "/dev/sda"
+                                      #:partition-size
+                                      (- disk-image-size (* 5 (expt 2 20))))
+    (error "failed to create partition table"))
+
+  (display "creating ext3 partition...\n")
+  (unless (zero? (system* mkfs "-F" "/dev/sda1"))
+    (error "failed to create partition"))
+
+  (display "mounting partition...\n")
+  (mkdir "/fs")
+  (mount "/dev/sda1" "/fs" "ext3")
+
+  (when (pair? closures-to-copy)
+    ;; Populate the store.
+    (populate-store (map (cut string-append "/xchg/" <>)
+                         closures-to-copy)
+                    "/fs"))
+
+  ;; Populate /dev.
+  (make-essential-device-nodes #:root "/fs")
+
+  ;; Optionally, register the inputs in the image's store.
+  (when initialize-store?
+    (for-each (lambda (closure)
+                (let ((status (system* "guix-register" "--prefix" "/fs"
+                                       (string-append "/xchg/" closure))))
+                  (unless (zero? status)
+                    (error "failed to register store items" closure))))
+              closures-to-copy))
+
+  ;; Evaluate the POPULATE directives.
+  (for-each (cut evaluate-populate-directive <> "/fs")
+            directives)
+
+  (unless (install-grub grub.cfg "/dev/sda" "/fs")
+    (error "failed to install GRUB"))
+
+  (reset-timestamps "/fs")
+
+  (zero? (system* "umount" "/fs")))
+
 ;;; vm.scm ends here