vm: Support arbitrary partition flags.
[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>
07f812c4 5;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
01cc84da 6;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
e1a87b90
LC
7;;;
8;;; This file is part of GNU Guix.
9;;;
10;;; GNU Guix is free software; you can redistribute it and/or modify it
11;;; under the terms of the GNU General Public License as published by
12;;; the Free Software Foundation; either version 3 of the License, or (at
13;;; your option) any later version.
14;;;
15;;; GNU Guix is distributed in the hope that it will be useful, but
16;;; WITHOUT ANY WARRANTY; without even the implied warranty of
17;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;;; GNU General Public License for more details.
19;;;
20;;; You should have received a copy of the GNU General Public License
21;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
22
548f7a8f 23(define-module (gnu build vm)
e1a87b90 24 #:use-module (guix build utils)
6fd1a796 25 #:use-module (guix build store-copy)
abf0880a 26 #:use-module (guix build syscalls)
8a9e21d1 27 #:use-module (gnu build linux-boot)
548f7a8f 28 #:use-module (gnu build install)
72b891e5 29 #:use-module (guix records)
55651ff2 30 #:use-module (ice-9 match)
66670cf3 31 #:use-module (ice-9 regex)
72b891e5
LC
32 #:use-module (srfi srfi-1)
33 #:use-module (srfi srfi-9)
55651ff2 34 #:use-module (srfi srfi-26)
66670cf3
LC
35 #:export (qemu-command
36 load-in-linux-vm
641f9a2a 37 format-partition
72b891e5
LC
38
39 partition
40 partition?
41 partition-device
42 partition-size
43 partition-file-system
44 partition-label
01cc84da 45 partition-flags
72b891e5
LC
46 partition-initializer
47
48 root-partition-initializer
641f9a2a 49 initialize-partition-table
55651ff2 50 initialize-hard-disk))
e1a87b90
LC
51
52;;; Commentary:
53;;;
54;;; This module provides supporting code to run virtual machines and build
55;;; virtual machine images using QEMU.
56;;;
57;;; Code:
58
66670cf3
LC
59(define* (qemu-command #:optional (system %host-type))
60 "Return the default name of the QEMU command for SYSTEM."
b1dd6ac5
LC
61 (let ((cpu (substring system 0
62 (string-index system #\-))))
66670cf3
LC
63 (string-append "qemu-system-"
64 (if (string-match "^i[3456]86$" cpu)
65 "i386"
66 cpu))))
e1a87b90
LC
67
68(define* (load-in-linux-vm builder
69 #:key
70 output
71 (qemu (qemu-command)) (memory-size 512)
72 linux initrd
73 make-disk-image? (disk-image-size 100)
c4a74364 74 (disk-image-format "qcow2")
e1a87b90
LC
75 (references-graphs '()))
76 "Run BUILDER, a Scheme file, into a VM running LINUX with INITRD, and copy
77the result to OUTPUT.
78
79When MAKE-DISK-IMAGE? is true, OUTPUT will contain a VM image of
80DISK-IMAGE-SIZE MiB resulting from the execution of BUILDER, which may access
81it via /dev/hda.
82
83REFERENCES-GRAPHS can specify a list of reference-graph files as produced by
84the #:references-graphs parameter of 'derivation'."
e1a87b90 85 (when make-disk-image?
c4a74364 86 (unless (zero? (system* "qemu-img" "create" "-f" disk-image-format
d2bcf35e 87 output
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?
d2bcf35e 118 `("-drive" ,(string-append "file=" output
944d2b17
CAW
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
d2bcf35e
LC
129 ;; When MAKE-DISK-IMAGE? is true, the image is in OUTPUT already.
130 (unless make-disk-image?
131 (mkdir output)
132 (copy-recursively "xchg" output)))
e1a87b90 133
72b891e5
LC
134\f
135;;;
136;;; Partitions.
137;;;
138
139(define-record-type* <partition> partition make-partition
140 partition?
141 (device partition-device (default #f))
142 (size partition-size)
143 (file-system partition-file-system (default "ext4"))
144 (label partition-label (default #f))
01cc84da 145 (flags partition-flags (default '()))
72b891e5
LC
146 (initializer partition-initializer (default (const #t))))
147
148(define (fold2 proc seed1 seed2 lst) ;TODO: factorize
149 "Like `fold', but with a single list and two seeds."
150 (let loop ((result1 seed1)
151 (result2 seed2)
152 (lst lst))
153 (if (null? lst)
154 (values result1 result2)
155 (call-with-values
156 (lambda () (proc (car lst) result1 result2))
157 (lambda (result1 result2)
158 (loop result1 result2 (cdr lst)))))))
159
160(define* (initialize-partition-table device partitions
55651ff2
LC
161 #:key
162 (label-type "msdos")
641f9a2a 163 (offset (expt 2 20)))
72b891e5
LC
164 "Create on DEVICE a partition table of type LABEL-TYPE, containing the given
165PARTITIONS (a list of <partition> objects), starting at OFFSET bytes. On
166success, return PARTITIONS with their 'device' field changed to reflect their
167actual /dev name based on DEVICE."
168 (define (partition-options part offset index)
169 (cons* "mkpart" "primary" "ext2"
170 (format #f "~aB" offset)
171 (format #f "~aB" (+ offset (partition-size part)))
01cc84da
MB
172 (append-map (lambda (flag)
173 (list "set" (number->string index)
174 (symbol->string flag) "on"))
175 (partition-flags part))))
72b891e5
LC
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
9121ce55 290(define (register-bootcfg-root target bootcfg)
07f812c4 291 "On file system TARGET, register BOOTCFG as a GC root."
6412e58a 292 (let ((directory (string-append target "/var/guix/gcroots")))
39d1f82b 293 (mkdir-p directory)
9121ce55 294 (symlink bootcfg (string-append directory "/bootcfg"))))
39d1f82b 295
641f9a2a
LC
296(define* (initialize-hard-disk device
297 #:key
9121ce55
MO
298 bootloader-package
299 bootcfg
300 bootcfg-location
301 bootloader-installer
72b891e5
LC
302 (partitions '()))
303 "Initialize DEVICE as a disk containing all the <partition> objects listed
07f812c4 304in PARTITIONS, and using BOOTCFG as its bootloader configuration file.
641f9a2a 305
72b891e5
LC
306Each partition is initialized by calling its 'initializer' procedure,
307passing it a directory name where it is mounted."
01cc84da
MB
308
309 (define (partition-bootable? partition)
310 "Return the first partition found with the boot flag set."
311 (member 'boot (partition-flags partition)))
312
72b891e5
LC
313 (let* ((partitions (initialize-partition-table device partitions))
314 (root (find partition-bootable? partitions))
315 (target "/fs"))
316 (unless root
317 (error "no bootable partition specified" partitions))
55651ff2 318
72b891e5 319 (for-each initialize-partition partitions)
55651ff2 320
72b891e5
LC
321 (display "mounting root partition...\n")
322 (mkdir-p target)
323 (mount (partition-device root) target (partition-file-system root))
9121ce55
MO
324 (install-boot-config bootcfg bootcfg-location target)
325 (when bootloader-installer
326 (bootloader-installer bootloader-package device target))
39d1f82b 327
9121ce55
MO
328 ;; Register BOOTCFG as a GC root.
329 (register-bootcfg-root target bootcfg)
55651ff2 330
72b891e5 331 (umount target)))
55651ff2 332
e1a87b90 333;;; vm.scm ends here