1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19 (define-module (gnu build vm)
20 #:use-module (guix build utils)
21 #:use-module (guix build store-copy)
22 #:use-module (gnu build linux-boot)
23 #:use-module (gnu build install)
24 #:use-module (guix records)
25 #:use-module (ice-9 match)
26 #:use-module (ice-9 regex)
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-9)
29 #:use-module (srfi srfi-26)
30 #:export (qemu-command
43 root-partition-initializer
44 initialize-partition-table
45 initialize-hard-disk))
49 ;;; This module provides supporting code to run virtual machines and build
50 ;;; virtual machine images using QEMU.
54 (define* (qemu-command #:optional (system %host-type))
55 "Return the default name of the QEMU command for SYSTEM."
56 (let ((cpu (substring %host-type 0
57 (string-index %host-type #\-))))
58 (string-append "qemu-system-"
59 (if (string-match "^i[3456]86$" cpu)
63 (define* (load-in-linux-vm builder
66 (qemu (qemu-command)) (memory-size 512)
68 make-disk-image? (disk-image-size 100)
69 (disk-image-format "qcow2")
70 (references-graphs '()))
71 "Run BUILDER, a Scheme file, into a VM running LINUX with INITRD, and copy
74 When MAKE-DISK-IMAGE? is true, OUTPUT will contain a VM image of
75 DISK-IMAGE-SIZE MiB resulting from the execution of BUILDER, which may access
78 REFERENCES-GRAPHS can specify a list of reference-graph files as produced by
79 the #:references-graphs parameter of 'derivation'."
81 (string-append "image." disk-image-format))
83 (when make-disk-image?
84 (unless (zero? (system* "qemu-img" "create" "-f" disk-image-format
86 (number->string disk-image-size)))
87 (error "qemu-img failed")))
91 (match references-graphs
93 ;; Copy the reference-graph files under xchg/ so EXP can access it.
95 (copy-file file (string-append "xchg/" file)))
100 (apply system* qemu "-enable-kvm" "-nographic" "-no-reboot"
101 "-m" (number->string memory-size)
102 "-net" "nic,model=virtio"
104 (string-append "local,id=store_dev,path="
106 ",security_model=none,mount_tag=store")
108 (string-append "local,id=xchg_dev,path=xchg"
109 ",security_model=none,mount_tag=xchg")
112 "-append" (string-append "console=ttyS0 --load="
115 `("-drive" ,(string-append "file=" image-file
118 (error "qemu failed" qemu))
121 (copy-file image-file output)
124 (copy-recursively "xchg" output))))
131 (define-record-type* <partition> partition make-partition
133 (device partition-device (default #f))
134 (size partition-size)
135 (file-system partition-file-system (default "ext4"))
136 (label partition-label (default #f))
137 (bootable? partition-bootable? (default #f))
138 (initializer partition-initializer (default (const #t))))
140 (define (fold2 proc seed1 seed2 lst) ;TODO: factorize
141 "Like `fold', but with a single list and two seeds."
142 (let loop ((result1 seed1)
146 (values result1 result2)
148 (lambda () (proc (car lst) result1 result2))
149 (lambda (result1 result2)
150 (loop result1 result2 (cdr lst)))))))
152 (define* (initialize-partition-table device partitions
155 (offset (expt 2 20)))
156 "Create on DEVICE a partition table of type LABEL-TYPE, containing the given
157 PARTITIONS (a list of <partition> objects), starting at OFFSET bytes. On
158 success, return PARTITIONS with their 'device' field changed to reflect their
159 actual /dev name based on DEVICE."
160 (define (partition-options part offset index)
161 (cons* "mkpart" "primary" "ext2"
162 (format #f "~aB" offset)
163 (format #f "~aB" (+ offset (partition-size part)))
164 (if (partition-bootable? part)
165 `("set" ,(number->string index) "boot" "on")
168 (define (options partitions offset)
169 (let loop ((partitions partitions)
175 (concatenate (reverse result)))
178 ;; Leave one sector (512B) between partitions to placate
180 (+ offset 512 (partition-size head))
182 (cons (partition-options head offset index)
185 (format #t "creating partition table with ~a partitions...\n"
187 (unless (zero? (apply system* "parted" "--script"
188 device "mklabel" label-type
189 (options partitions offset)))
190 (error "failed to create partition table"))
192 ;; Set the 'device' field of each partition.
194 (fold2 (lambda (part result index)
195 (values (cons (partition
197 (device (string-append device
198 (number->string index))))
205 (define MS_BIND 4096) ; <sys/mounts.h> again!
207 (define* (format-partition partition type
209 "Create a file system TYPE on PARTITION. If LABEL is true, use that as the
211 (format #t "creating ~a partition...\n" type)
212 (unless (zero? (apply system* (string-append "mkfs." type)
217 (error "failed to create partition")))
219 (define (initialize-partition partition)
220 "Format PARTITION, a <partition> object with a non-#f 'device' field, mount
221 it, run its initializer, and unmount it."
222 (let ((target "/fs"))
223 (format-partition (partition-device partition)
224 (partition-file-system partition)
225 #:label (partition-label partition))
227 (mount (partition-device partition) target
228 (partition-file-system partition))
230 ((partition-initializer partition) target)
235 (define* (root-partition-initializer #:key (closures '())
237 (register-closures? #t)
239 "Return a procedure to initialize a root partition.
241 If REGISTER-CLOSURES? is true, register all of CLOSURES is the partition's
242 store. If COPY-CLOSURES? is true, copy all of CLOSURES to the partition.
243 SYSTEM-DIRECTORY is the name of the directory of the 'system' derivation."
246 (string-append target (%store-directory)))
249 ;; Populate the store.
250 (populate-store (map (cut string-append "/xchg/" <>) closures)
254 (make-essential-device-nodes #:root target)
256 ;; Optionally, register the inputs in the image's store.
257 (when register-closures?
258 (unless copy-closures?
259 ;; XXX: 'guix-register' wants to palpate the things it registers, so
260 ;; bind-mount the store on the target.
261 (mkdir-p target-store)
262 (mount (%store-directory) target-store "" MS_BIND))
264 (display "registering closures...\n")
265 (for-each (lambda (closure)
266 (register-closure target
267 (string-append "/xchg/" closure)))
269 (unless copy-closures?
270 (umount target-store)))
272 ;; Add the non-store directories and files.
273 (display "populating...\n")
274 (populate-root-file-system system-directory target)
276 ;; 'guix-register' resets timestamps and everything, so no need to do it
277 ;; once more in that case.
278 (unless register-closures?
279 (reset-timestamps target))))
281 (define (register-grub.cfg-root target grub.cfg)
282 "On file system TARGET, register GRUB.CFG as a GC root."
283 (let ((directory (string-append target "/var/guix/gcroots")))
285 (symlink grub.cfg (string-append directory "/grub.cfg"))))
287 (define* (initialize-hard-disk device
291 "Initialize DEVICE as a disk containing all the <partition> objects listed
292 in PARTITIONS, and using GRUB.CFG as its bootloader configuration file.
294 Each partition is initialized by calling its 'initializer' procedure,
295 passing it a directory name where it is mounted."
296 (let* ((partitions (initialize-partition-table device partitions))
297 (root (find partition-bootable? partitions))
300 (error "no bootable partition specified" partitions))
302 (for-each initialize-partition partitions)
304 (display "mounting root partition...\n")
306 (mount (partition-device root) target (partition-file-system root))
307 (install-grub grub.cfg device target)
309 ;; Register GRUB.CFG as a GC root.
310 (register-grub.cfg-root target grub.cfg)