build: image: Add support for EXT2 and EXT3 file-systems.
[jackhill/guix/guix.git] / gnu / build / image.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
4 ;;; Copyright © 2016, 2017 Leo Famulari <leo@famulari.name>
5 ;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
6 ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
7 ;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
8 ;;;
9 ;;; This file is part of GNU Guix.
10 ;;;
11 ;;; GNU Guix is free software; you can redistribute it and/or modify it
12 ;;; under the terms of the GNU General Public License as published by
13 ;;; the Free Software Foundation; either version 3 of the License, or (at
14 ;;; your option) any later version.
15 ;;;
16 ;;; GNU Guix is distributed in the hope that it will be useful, but
17 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;;; GNU General Public License for more details.
20 ;;;
21 ;;; You should have received a copy of the GNU General Public License
22 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24 (define-module (gnu build image)
25 #:use-module (guix build store-copy)
26 #:use-module (guix build syscalls)
27 #:use-module (guix build utils)
28 #:use-module (guix store database)
29 #:use-module (gnu build bootloader)
30 #:use-module (gnu build install)
31 #:use-module (gnu build linux-boot)
32 #:use-module (gnu image)
33 #:use-module (gnu system uuid)
34 #:use-module (ice-9 ftw)
35 #:use-module (ice-9 match)
36 #:use-module (srfi srfi-19)
37 #:use-module (srfi srfi-34)
38 #:use-module (srfi srfi-35)
39 #:export (make-partition-image
40 genimage
41 initialize-efi-partition
42 initialize-root-partition
43
44 make-iso9660-image))
45
46 (define (sexp->partition sexp)
47 "Take SEXP, a tuple as returned by 'partition->gexp', and turn it into a
48 <partition> record."
49 (match sexp
50 ((size file-system label uuid)
51 (partition (size size)
52 (file-system file-system)
53 (label label)
54 (uuid uuid)))))
55
56 (define (size-in-kib size)
57 "Convert SIZE expressed in bytes, to kilobytes and return it as a string."
58 (number->string
59 (inexact->exact (ceiling (/ size 1024)))))
60
61 (define (estimate-partition-size root)
62 "Given the ROOT directory, evalute and return its size. As this doesn't
63 take the partition metadata size into account, take a 25% margin."
64 (* 1.25 (file-size root)))
65
66 (define* (make-ext-image partition target root
67 #:key
68 (owner-uid 0)
69 (owner-gid 0))
70 "Handle the creation of EXT2/3/4 partition images. See
71 'make-partition-image'."
72 (let ((size (partition-size partition))
73 (fs (partition-file-system partition))
74 (label (partition-label partition))
75 (uuid (partition-uuid partition))
76 (options "lazy_itable_init=1,lazy_journal_init=1"))
77 (invoke "mke2fs" "-t" fs "-d" root
78 "-L" label "-U" (uuid->string uuid)
79 "-E" (format #f "root_owner=~a:~a,~a"
80 owner-uid owner-gid options)
81 target
82 (format #f "~ak"
83 (size-in-kib
84 (if (eq? size 'guess)
85 (estimate-partition-size root)
86 size))))))
87
88 (define* (make-vfat-image partition target root)
89 "Handle the creation of VFAT partition images. See 'make-partition-image'."
90 (let ((size (partition-size partition))
91 (label (partition-label partition)))
92 (invoke "mkdosfs" "-n" label "-C" target "-F" "16" "-S" "1024"
93 (size-in-kib
94 (if (eq? size 'guess)
95 (estimate-partition-size root)
96 size)))
97 (for-each (lambda (file)
98 (unless (member file '("." ".."))
99 (invoke "mcopy" "-bsp" "-i" target
100 (string-append root "/" file)
101 (string-append "::" file))))
102 (scandir root))))
103
104 (define* (make-partition-image partition-sexp target root)
105 "Create and return the image of PARTITION-SEXP as TARGET. Use the given
106 ROOT directory to populate the image."
107 (let* ((partition (sexp->partition partition-sexp))
108 (type (partition-file-system partition)))
109 (cond
110 ((string-prefix? "ext" type)
111 (make-ext-image partition target root))
112 ((string=? type "vfat")
113 (make-vfat-image partition target root))
114 (else
115 (format (current-error-port)
116 "Unsupported partition type~%.")))))
117
118 (define* (genimage config target)
119 "Use genimage to generate in TARGET directory, the image described in the
120 given CONFIG file."
121 ;; genimage needs a 'root' directory.
122 (mkdir "root")
123 (invoke "genimage" "--config" config
124 "--outputpath" target))
125
126 (define* (register-closure prefix closure
127 #:key
128 (deduplicate? #t) (reset-timestamps? #t)
129 (schema (sql-schema)))
130 "Register CLOSURE in PREFIX, where PREFIX is the directory name of the
131 target store and CLOSURE is the name of a file containing a reference graph as
132 produced by #:references-graphs.. As a side effect, if RESET-TIMESTAMPS? is
133 true, reset timestamps on store files and, if DEDUPLICATE? is true,
134 deduplicates files common to CLOSURE and the rest of PREFIX."
135 (let ((items (call-with-input-file closure read-reference-graph)))
136 (register-items items
137 #:prefix prefix
138 #:deduplicate? deduplicate?
139 #:reset-timestamps? reset-timestamps?
140 #:registration-time %epoch
141 #:schema schema)))
142
143 (define* (initialize-efi-partition root
144 #:key
145 bootloader-package
146 #:allow-other-keys)
147 "Install in ROOT directory, an EFI loader using BOOTLOADER-PACKAGE."
148 (install-efi-loader bootloader-package root))
149
150 (define* (initialize-root-partition root
151 #:key
152 bootcfg
153 bootcfg-location
154 (deduplicate? #t)
155 references-graphs
156 (register-closures? #t)
157 system-directory
158 #:allow-other-keys)
159 "Initialize the given ROOT directory. Use BOOTCFG and BOOTCFG-LOCATION to
160 install the bootloader configuration.
161
162 If REGISTER-CLOSURES? is true, register REFERENCES-GRAPHS in the store. If
163 DEDUPLICATE? is true, then also deduplicate files common to CLOSURES and the
164 rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
165 of the directory of the 'system' derivation."
166 (populate-root-file-system system-directory root)
167 (populate-store references-graphs root)
168
169 (when register-closures?
170 (for-each (lambda (closure)
171 (register-closure root
172 closure
173 #:reset-timestamps? #t
174 #:deduplicate? deduplicate?))
175 references-graphs))
176
177 (when bootcfg
178 (install-boot-config bootcfg bootcfg-location root)))
179
180 (define* (make-iso9660-image xorriso grub-mkrescue-environment
181 grub bootcfg system-directory root target
182 #:key (volume-id "Guix_image") (volume-uuid #f)
183 register-closures? (references-graphs '())
184 (compression? #t))
185 "Given a GRUB package, creates an iso image as TARGET, using BOOTCFG as
186 GRUB configuration and OS-DRV as the stuff in it."
187 (define grub-mkrescue
188 (string-append grub "/bin/grub-mkrescue"))
189
190 (define grub-mkrescue-sed.sh
191 (string-append (getcwd) "/" "grub-mkrescue-sed.sh"))
192
193 ;; Use a modified version of grub-mkrescue-sed.sh, see below.
194 (copy-file (string-append xorriso
195 "/bin/grub-mkrescue-sed.sh")
196 grub-mkrescue-sed.sh)
197
198 ;; Force grub-mkrescue-sed.sh to use the build directory instead of /tmp
199 ;; that is read-only inside the build container.
200 (substitute* grub-mkrescue-sed.sh
201 (("/tmp/") (string-append (getcwd) "/"))
202 (("MKRESCUE_SED_XORRISO_ARGS \\$x")
203 (format #f "MKRESCUE_SED_XORRISO_ARGS $(echo $x | sed \"s|/tmp|~a|\")"
204 (getcwd))))
205
206 ;; 'grub-mkrescue' calls out to mtools programs to create 'efi.img', a FAT
207 ;; file system image, and mtools honors SOURCE_DATE_EPOCH for the mtime of
208 ;; those files. The epoch for FAT is Jan. 1st 1980, not 1970, so choose
209 ;; that.
210 (setenv "SOURCE_DATE_EPOCH"
211 (number->string
212 (time-second
213 (date->time-utc (make-date 0 0 0 0 1 1 1980 0)))))
214
215 ;; Our patched 'grub-mkrescue' honors this environment variable and passes
216 ;; it to 'mformat', which makes it the serial number of 'efi.img'. This
217 ;; allows for deterministic builds.
218 (setenv "GRUB_FAT_SERIAL_NUMBER"
219 (number->string (if volume-uuid
220
221 ;; On 32-bit systems the 2nd argument must be
222 ;; lower than 2^32.
223 (string-hash (iso9660-uuid->string volume-uuid)
224 (- (expt 2 32) 1))
225
226 #x77777777)
227 16))
228
229 (setenv "MKRESCUE_SED_MODE" "original")
230 (setenv "MKRESCUE_SED_XORRISO" (string-append xorriso "/bin/xorriso"))
231 (setenv "MKRESCUE_SED_IN_EFI_NO_PT" "yes")
232
233 (for-each (match-lambda
234 ((name . value) (setenv name value)))
235 grub-mkrescue-environment)
236
237 (apply invoke grub-mkrescue
238 (string-append "--xorriso=" grub-mkrescue-sed.sh)
239 "-o" target
240 (string-append "boot/grub/grub.cfg=" bootcfg)
241 root
242 "--"
243 ;; Set all timestamps to 1.
244 "-volume_date" "all_file_dates" "=1"
245
246 `(,@(if compression?
247 '(;; ‘zisofs’ compression reduces the total image size by
248 ;; ~60%.
249 "-zisofs" "level=9:block_size=128k" ; highest compression
250 ;; It's transparent to our Linux-Libre kernel but not to
251 ;; GRUB. Don't compress the kernel, initrd, and other
252 ;; files read by grub.cfg, as well as common
253 ;; already-compressed file names.
254 "-find" "/" "-type" "f"
255 ;; XXX Even after "--" above, and despite documentation
256 ;; claiming otherwise, "-or" is stolen by grub-mkrescue
257 ;; which then chokes on it (as ‘-o …’) and dies. Don't use
258 ;; "-or".
259 "-not" "-wholename" "/boot/*"
260 "-not" "-wholename" "/System/*"
261 "-not" "-name" "unicode.pf2"
262 "-not" "-name" "bzImage"
263 "-not" "-name" "*.gz" ; initrd & all man pages
264 "-not" "-name" "*.png" ; includes grub-image.png
265 "-exec" "set_filter" "--zisofs"
266 "--")
267 '())
268 "-volid" ,(string-upcase volume-id)
269 ,@(if volume-uuid
270 `("-volume_date" "uuid"
271 ,(string-filter (lambda (value)
272 (not (char=? #\- value)))
273 (iso9660-uuid->string
274 volume-uuid)))
275 '()))))