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