pull: Set '%nix-instantiate' to a sensible value.
[jackhill/guix/guix.git] / gnu / build / vm.scm
CommitLineData
e1a87b90 1;;; GNU Guix --- Functional package management for GNU
b1dd6ac5 2;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
944d2b17
CAW
3;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
4;;; Copyright © 2016 Leo Famulari <leo@famulari.name>
e1a87b90
LC
5;;;
6;;; This file is part of GNU Guix.
7;;;
8;;; GNU Guix is free software; you can redistribute it and/or modify it
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
13;;; GNU Guix is distributed in the hope that it will be useful, but
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
548f7a8f 21(define-module (gnu build vm)
e1a87b90 22 #:use-module (guix build utils)
6fd1a796 23 #:use-module (guix build store-copy)
8a9e21d1 24 #:use-module (gnu build linux-boot)
548f7a8f 25 #:use-module (gnu build install)
72b891e5 26 #:use-module (guix records)
55651ff2 27 #:use-module (ice-9 match)
66670cf3 28 #:use-module (ice-9 regex)
72b891e5
LC
29 #:use-module (srfi srfi-1)
30 #:use-module (srfi srfi-9)
55651ff2 31 #:use-module (srfi srfi-26)
66670cf3
LC
32 #:export (qemu-command
33 load-in-linux-vm
641f9a2a 34 format-partition
72b891e5
LC
35
36 partition
37 partition?
38 partition-device
39 partition-size
40 partition-file-system
41 partition-label
42 partition-bootable?
43 partition-initializer
44
45 root-partition-initializer
641f9a2a 46 initialize-partition-table
55651ff2 47 initialize-hard-disk))
e1a87b90
LC
48
49;;; Commentary:
50;;;
51;;; This module provides supporting code to run virtual machines and build
52;;; virtual machine images using QEMU.
53;;;
54;;; Code:
55
66670cf3
LC
56(define* (qemu-command #:optional (system %host-type))
57 "Return the default name of the QEMU command for SYSTEM."
b1dd6ac5
LC
58 (let ((cpu (substring system 0
59 (string-index system #\-))))
66670cf3
LC
60 (string-append "qemu-system-"
61 (if (string-match "^i[3456]86$" cpu)
62 "i386"
63 cpu))))
e1a87b90
LC
64
65(define* (load-in-linux-vm builder
66 #:key
67 output
68 (qemu (qemu-command)) (memory-size 512)
69 linux initrd
70 make-disk-image? (disk-image-size 100)
c4a74364 71 (disk-image-format "qcow2")
e1a87b90
LC
72 (references-graphs '()))
73 "Run BUILDER, a Scheme file, into a VM running LINUX with INITRD, and copy
74the result to OUTPUT.
75
76When MAKE-DISK-IMAGE? is true, OUTPUT will contain a VM image of
77DISK-IMAGE-SIZE MiB resulting from the execution of BUILDER, which may access
78it via /dev/hda.
79
80REFERENCES-GRAPHS can specify a list of reference-graph files as produced by
81the #:references-graphs parameter of 'derivation'."
c4a74364
LC
82 (define image-file
83 (string-append "image." disk-image-format))
e1a87b90
LC
84
85 (when make-disk-image?
c4a74364
LC
86 (unless (zero? (system* "qemu-img" "create" "-f" disk-image-format
87 image-file
e1a87b90
LC
88 (number->string disk-image-size)))
89 (error "qemu-img failed")))
90
91 (mkdir "xchg")
92
93 (match references-graphs
94 ((graph-files ...)
95 ;; Copy the reference-graph files under xchg/ so EXP can access it.
96 (map (lambda (file)
97 (copy-file file (string-append "xchg/" file)))
98 graph-files))
99 (_ #f))
100
101 (unless (zero?
944d2b17 102 (apply system* qemu "-nographic" "-no-reboot"
e1a87b90
LC
103 "-m" (number->string memory-size)
104 "-net" "nic,model=virtio"
105 "-virtfs"
106 (string-append "local,id=store_dev,path="
107 (%store-directory)
108 ",security_model=none,mount_tag=store")
109 "-virtfs"
110 (string-append "local,id=xchg_dev,path=xchg"
111 ",security_model=none,mount_tag=xchg")
112 "-kernel" linux
113 "-initrd" initrd
114 "-append" (string-append "console=ttyS0 --load="
115 builder)
944d2b17
CAW
116 (append
117 (if make-disk-image?
118 `("-drive" ,(string-append "file=" image-file
119 ",if=virtio"))
120 '())
121 ;; Only enable kvm if we see /dev/kvm exists.
122 ;; This allows users without hardware virtualization to still
123 ;; use these commands.
124 (if (file-exists? "/dev/kvm")
125 '("-enable-kvm")
126 '()))))
e1a87b90
LC
127 (error "qemu failed" qemu))
128
129 (if make-disk-image?
c4a74364 130 (copy-file image-file output)
e1a87b90
LC
131 (begin
132 (mkdir output)
133 (copy-recursively "xchg" output))))
134
72b891e5
LC
135\f
136;;;
137;;; Partitions.
138;;;
139
140(define-record-type* <partition> partition make-partition
141 partition?
142 (device partition-device (default #f))
143 (size partition-size)
144 (file-system partition-file-system (default "ext4"))
145 (label partition-label (default #f))
146 (bootable? partition-bootable? (default #f))
147 (initializer partition-initializer (default (const #t))))
148
149(define (fold2 proc seed1 seed2 lst) ;TODO: factorize
150 "Like `fold', but with a single list and two seeds."
151 (let loop ((result1 seed1)
152 (result2 seed2)
153 (lst lst))
154 (if (null? lst)
155 (values result1 result2)
156 (call-with-values
157 (lambda () (proc (car lst) result1 result2))
158 (lambda (result1 result2)
159 (loop result1 result2 (cdr lst)))))))
160
161(define* (initialize-partition-table device partitions
55651ff2
LC
162 #:key
163 (label-type "msdos")
641f9a2a 164 (offset (expt 2 20)))
72b891e5
LC
165 "Create on DEVICE a partition table of type LABEL-TYPE, containing the given
166PARTITIONS (a list of <partition> objects), starting at OFFSET bytes. On
167success, return PARTITIONS with their 'device' field changed to reflect their
168actual /dev name based on DEVICE."
169 (define (partition-options part offset index)
170 (cons* "mkpart" "primary" "ext2"
171 (format #f "~aB" offset)
172 (format #f "~aB" (+ offset (partition-size part)))
173 (if (partition-bootable? part)
174 `("set" ,(number->string index) "boot" "on")
175 '())))
176
177 (define (options partitions offset)
178 (let loop ((partitions partitions)
179 (offset offset)
180 (index 1)
181 (result '()))
182 (match partitions
183 (()
184 (concatenate (reverse result)))
185 ((head tail ...)
186 (loop tail
187 ;; Leave one sector (512B) between partitions to placate
188 ;; Parted.
189 (+ offset 512 (partition-size head))
190 (+ 1 index)
191 (cons (partition-options head offset index)
192 result))))))
193
194 (format #t "creating partition table with ~a partitions...\n"
195 (length partitions))
196 (unless (zero? (apply system* "parted" "--script"
197 device "mklabel" label-type
198 (options partitions offset)))
199 (error "failed to create partition table"))
200
201 ;; Set the 'device' field of each partition.
202 (reverse
203 (fold2 (lambda (part result index)
204 (values (cons (partition
205 (inherit part)
206 (device (string-append device
207 (number->string index))))
208 result)
209 (+ 1 index)))
210 '()
211 1
212 partitions)))
55651ff2 213
150e20dd
LC
214(define MS_BIND 4096) ; <sys/mounts.h> again!
215
ef9fc40d
LC
216(define* (format-partition partition type
217 #:key label)
218 "Create a file system TYPE on PARTITION. If LABEL is true, use that as the
219volume name."
641f9a2a 220 (format #t "creating ~a partition...\n" type)
ef9fc40d
LC
221 (unless (zero? (apply system* (string-append "mkfs." type)
222 "-F" partition
223 (if label
224 `("-L" ,label)
225 '())))
641f9a2a 226 (error "failed to create partition")))
150e20dd 227
72b891e5
LC
228(define (initialize-partition partition)
229 "Format PARTITION, a <partition> object with a non-#f 'device' field, mount
230it, run its initializer, and unmount it."
231 (let ((target "/fs"))
232 (format-partition (partition-device partition)
233 (partition-file-system partition)
234 #:label (partition-label partition))
235 (mkdir-p target)
236 (mount (partition-device partition) target
237 (partition-file-system partition))
238
239 ((partition-initializer partition) target)
240
241 (umount target)
242 partition))
243
244(define* (root-partition-initializer #:key (closures '())
245 copy-closures?
246 (register-closures? #t)
247 system-directory)
248 "Return a procedure to initialize a root partition.
249
250If REGISTER-CLOSURES? is true, register all of CLOSURES is the partition's
251store. If COPY-CLOSURES? is true, copy all of CLOSURES to the partition.
252SYSTEM-DIRECTORY is the name of the directory of the 'system' derivation."
253 (lambda (target)
254 (define target-store
255 (string-append target (%store-directory)))
256
257 (when copy-closures?
258 ;; Populate the store.
259 (populate-store (map (cut string-append "/xchg/" <>) closures)
260 target))
261
262 ;; Populate /dev.
263 (make-essential-device-nodes #:root target)
264
265 ;; Optionally, register the inputs in the image's store.
266 (when register-closures?
267 (unless copy-closures?
268 ;; XXX: 'guix-register' wants to palpate the things it registers, so
269 ;; bind-mount the store on the target.
270 (mkdir-p target-store)
271 (mount (%store-directory) target-store "" MS_BIND))
272
273 (display "registering closures...\n")
274 (for-each (lambda (closure)
275 (register-closure target
276 (string-append "/xchg/" closure)))
277 closures)
278 (unless copy-closures?
279 (umount target-store)))
280
281 ;; Add the non-store directories and files.
282 (display "populating...\n")
283 (populate-root-file-system system-directory target)
284
285 ;; 'guix-register' resets timestamps and everything, so no need to do it
286 ;; once more in that case.
287 (unless register-closures?
288 (reset-timestamps target))))
641f9a2a 289
6412e58a
LC
290(define (register-grub.cfg-root target grub.cfg)
291 "On file system TARGET, register GRUB.CFG as a GC root."
292 (let ((directory (string-append target "/var/guix/gcroots")))
39d1f82b 293 (mkdir-p directory)
6412e58a 294 (symlink grub.cfg (string-append directory "/grub.cfg"))))
39d1f82b 295
641f9a2a
LC
296(define* (initialize-hard-disk device
297 #:key
298 grub.cfg
72b891e5
LC
299 (partitions '()))
300 "Initialize DEVICE as a disk containing all the <partition> objects listed
301in PARTITIONS, and using GRUB.CFG as its bootloader configuration file.
641f9a2a 302
72b891e5
LC
303Each partition is initialized by calling its 'initializer' procedure,
304passing it a directory name where it is mounted."
305 (let* ((partitions (initialize-partition-table device partitions))
306 (root (find partition-bootable? partitions))
307 (target "/fs"))
308 (unless root
309 (error "no bootable partition specified" partitions))
55651ff2 310
72b891e5 311 (for-each initialize-partition partitions)
55651ff2 312
72b891e5
LC
313 (display "mounting root partition...\n")
314 (mkdir-p target)
315 (mount (partition-device root) target (partition-file-system root))
316 (install-grub grub.cfg device target)
39d1f82b 317
72b891e5
LC
318 ;; Register GRUB.CFG as a GC root.
319 (register-grub.cfg-root target grub.cfg)
55651ff2 320
72b891e5 321 (umount target)))
55651ff2 322
e1a87b90 323;;; vm.scm ends here