image.scm 14 KB

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