image.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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. ;;;
  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. (define-module (gnu build image)
  24. #:use-module (guix build store-copy)
  25. #:use-module (guix build syscalls)
  26. #:use-module (guix build utils)
  27. #:use-module (guix store database)
  28. #:use-module (gnu build bootloader)
  29. #:use-module (gnu build install)
  30. #:use-module (gnu build linux-boot)
  31. #:use-module (gnu image)
  32. #:use-module (gnu system uuid)
  33. #:use-module (ice-9 ftw)
  34. #:use-module (ice-9 match)
  35. #:use-module (srfi srfi-19)
  36. #:use-module (srfi srfi-34)
  37. #:use-module (srfi srfi-35)
  38. #:export (make-partition-image
  39. convert-disk-image
  40. genimage
  41. initialize-efi-partition
  42. initialize-root-partition
  43. make-iso9660-image))
  44. (define (sexp->partition sexp)
  45. "Take SEXP, a tuple as returned by 'partition->gexp', and turn it into a
  46. <partition> record."
  47. (match sexp
  48. ((size file-system file-system-options label uuid)
  49. (partition (size size)
  50. (file-system file-system)
  51. (file-system-options file-system-options)
  52. (label label)
  53. (uuid uuid)))))
  54. (define (size-in-kib size)
  55. "Convert SIZE expressed in bytes, to kilobytes and return it as a string."
  56. (number->string
  57. (inexact->exact (ceiling (/ size 1024)))))
  58. (define (estimate-partition-size root)
  59. "Given the ROOT directory, evaluate and return its size. As this doesn't
  60. take the partition metadata size into account, take a 25% margin. As this in
  61. turn doesn't take any constant overhead into account, force a 1-MiB minimum."
  62. (max (ash 1 20)
  63. (* 1.25 (file-size root))))
  64. (define* (make-ext-image partition target root
  65. #:key
  66. (owner-uid 0)
  67. (owner-gid 0))
  68. "Handle the creation of EXT2/3/4 partition images. See
  69. 'make-partition-image'."
  70. (let ((size (partition-size partition))
  71. (fs (partition-file-system partition))
  72. (fs-options (partition-file-system-options partition))
  73. (label (partition-label partition))
  74. (uuid (partition-uuid partition))
  75. (journal-options "lazy_itable_init=1,lazy_journal_init=1"))
  76. (apply invoke
  77. `("fakeroot" "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 journal-options)
  81. ,@fs-options
  82. ,target
  83. ,(format #f "~ak"
  84. (size-in-kib
  85. (if (eq? size 'guess)
  86. (estimate-partition-size root)
  87. size)))))))
  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 "fakeroot" "mkdosfs" "-n" label "-C" target
  93. "-F" "16" "-S" "1024"
  94. (size-in-kib
  95. (if (eq? size 'guess)
  96. (estimate-partition-size root)
  97. size)))
  98. (for-each (lambda (file)
  99. (unless (member file '("." ".."))
  100. (invoke "mcopy" "-bsp" "-i" target
  101. (string-append root "/" file)
  102. (string-append "::" file))))
  103. (scandir root))))
  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. (raise (condition
  116. (&message
  117. (message "unsupported partition type"))))))))
  118. (define (convert-disk-image image format output)
  119. "Convert IMAGE to OUTPUT according to the given FORMAT."
  120. (case format
  121. ((compressed-qcow2)
  122. (invoke "qemu-img" "convert" "-c" "-f" "raw"
  123. "-O" "qcow2" image output))
  124. (else
  125. (copy-file image output))))
  126. (define* (genimage config)
  127. "Use genimage to generate in TARGET directory, the image described in the
  128. given CONFIG file."
  129. ;; genimage needs a 'root' directory.
  130. (mkdir "root")
  131. (invoke "genimage" "--config" config))
  132. (define* (register-closure prefix closure
  133. #:key
  134. (schema (sql-schema))
  135. (wal-mode? #t))
  136. "Register CLOSURE in PREFIX, where PREFIX is the directory name of the
  137. target store and CLOSURE is the name of a file containing a reference graph as
  138. produced by #:references-graphs. Pass WAL-MODE? to call-with-database."
  139. (let ((items (call-with-input-file closure read-reference-graph)))
  140. (parameterize ((sql-schema schema))
  141. (with-database (store-database-file #:prefix prefix) db
  142. #:wal-mode? wal-mode?
  143. (register-items db items
  144. #:prefix prefix
  145. #:registration-time %epoch)))))
  146. (define* (initialize-efi-partition root
  147. #:key
  148. grub-efi
  149. #:allow-other-keys)
  150. "Install in ROOT directory, an EFI loader using GRUB-EFI."
  151. (install-efi-loader grub-efi root))
  152. (define* (initialize-root-partition root
  153. #:key
  154. bootcfg
  155. bootcfg-location
  156. bootloader-package
  157. bootloader-installer
  158. (copy-closures? #t)
  159. (deduplicate? #t)
  160. references-graphs
  161. (register-closures? #t)
  162. system-directory
  163. make-device-nodes
  164. (wal-mode? #t)
  165. #:allow-other-keys)
  166. "Initialize the given ROOT directory. Use BOOTCFG and BOOTCFG-LOCATION to
  167. install the bootloader configuration.
  168. If COPY-CLOSURES? is true, copy all of REFERENCES-GRAPHS to the partition. If
  169. REGISTER-CLOSURES? is true, register REFERENCES-GRAPHS in the store. If
  170. DEDUPLICATE? is true, then also deduplicate files common to CLOSURES and the
  171. rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
  172. of the directory of the 'system' derivation. Pass WAL-MODE? to
  173. register-closure."
  174. (define root-store
  175. (string-append root (%store-directory)))
  176. (define tmp-store ".tmp-store")
  177. (populate-root-file-system system-directory root)
  178. (when copy-closures?
  179. (populate-store references-graphs root
  180. #:deduplicate? deduplicate?))
  181. ;; Populate /dev.
  182. (when make-device-nodes
  183. (make-device-nodes root))
  184. (when register-closures?
  185. (unless copy-closures?
  186. ;; XXX: 'register-closure' wants to palpate the things it registers, so
  187. ;; create a symlink to the store.
  188. (rename-file root-store tmp-store)
  189. (symlink (%store-directory) root-store))
  190. (for-each (lambda (closure)
  191. (register-closure root closure
  192. #:wal-mode? wal-mode?))
  193. references-graphs)
  194. (unless copy-closures?
  195. (delete-file root-store)
  196. (rename-file tmp-store root-store)))
  197. ;; There's no point installing a bootloader if we do not populate the store.
  198. (when copy-closures?
  199. (when bootloader-installer
  200. (display "installing bootloader...\n")
  201. (bootloader-installer bootloader-package #f root))
  202. (when bootcfg
  203. (install-boot-config bootcfg bootcfg-location root))))
  204. (define* (make-iso9660-image xorriso grub-mkrescue-environment
  205. grub bootcfg system-directory root target
  206. #:key (volume-id "Guix_image") (volume-uuid #f)
  207. register-closures? (references-graphs '())
  208. (compression? #t))
  209. "Given a GRUB package, creates an iso image as TARGET, using BOOTCFG as
  210. GRUB configuration and OS-DRV as the stuff in it."
  211. (define grub-mkrescue
  212. (string-append grub "/bin/grub-mkrescue"))
  213. (define grub-mkrescue-sed.sh
  214. (string-append (getcwd) "/" "grub-mkrescue-sed.sh"))
  215. ;; Use a modified version of grub-mkrescue-sed.sh, see below.
  216. (copy-file (string-append xorriso
  217. "/bin/grub-mkrescue-sed.sh")
  218. grub-mkrescue-sed.sh)
  219. ;; Force grub-mkrescue-sed.sh to use the build directory instead of /tmp
  220. ;; that is read-only inside the build container.
  221. (substitute* grub-mkrescue-sed.sh
  222. (("/tmp/") (string-append (getcwd) "/"))
  223. (("MKRESCUE_SED_XORRISO_ARGS \\$x")
  224. (format #f "MKRESCUE_SED_XORRISO_ARGS $(echo $x | sed \"s|/tmp|~a|\")"
  225. (getcwd))))
  226. ;; 'grub-mkrescue' calls out to mtools programs to create 'efi.img', a FAT
  227. ;; file system image, and mtools honors SOURCE_DATE_EPOCH for the mtime of
  228. ;; those files. The epoch for FAT is Jan. 1st 1980, not 1970, so choose
  229. ;; that.
  230. (setenv "SOURCE_DATE_EPOCH"
  231. (number->string
  232. (time-second
  233. (date->time-utc (make-date 0 0 0 0 1 1 1980 0)))))
  234. ;; Our patched 'grub-mkrescue' honors this environment variable and passes
  235. ;; it to 'mformat', which makes it the serial number of 'efi.img'. This
  236. ;; allows for deterministic builds.
  237. (setenv "GRUB_FAT_SERIAL_NUMBER"
  238. (number->string (if volume-uuid
  239. ;; On 32-bit systems the 2nd argument must be
  240. ;; lower than 2^32.
  241. (string-hash (iso9660-uuid->string volume-uuid)
  242. (- (expt 2 32) 1))
  243. #x77777777)
  244. 16))
  245. (setenv "MKRESCUE_SED_MODE" "original")
  246. (setenv "MKRESCUE_SED_XORRISO" (string-append xorriso "/bin/xorriso"))
  247. (setenv "MKRESCUE_SED_IN_EFI_NO_PT" "yes")
  248. (for-each (match-lambda
  249. ((name . value) (setenv name value)))
  250. grub-mkrescue-environment)
  251. (apply invoke grub-mkrescue
  252. (string-append "--xorriso=" grub-mkrescue-sed.sh)
  253. "-o" target
  254. (string-append "boot/grub/grub.cfg=" bootcfg)
  255. root
  256. "--"
  257. ;; Set all timestamps to 1.
  258. "-volume_date" "all_file_dates" "=1"
  259. `(,@(if compression?
  260. '(;; ‘zisofs’ compression reduces the total image size by
  261. ;; ~60%.
  262. "-zisofs" "level=9:block_size=128k" ; highest compression
  263. ;; It's transparent to our Linux-Libre kernel but not to
  264. ;; GRUB. Don't compress the kernel, initrd, and other
  265. ;; files read by grub.cfg, as well as common
  266. ;; already-compressed file names.
  267. "-find" "/" "-type" "f"
  268. ;; XXX Even after "--" above, and despite documentation
  269. ;; claiming otherwise, "-or" is stolen by grub-mkrescue
  270. ;; which then chokes on it (as ‘-o …’) and dies. Don't use
  271. ;; "-or".
  272. "-not" "-wholename" "/boot/*"
  273. "-not" "-wholename" "/System/*"
  274. "-not" "-name" "unicode.pf2"
  275. "-not" "-name" "bzImage"
  276. "-not" "-name" "*.gz" ; initrd & all man pages
  277. "-not" "-name" "*.png" ; includes grub-image.png
  278. "-exec" "set_filter" "--zisofs"
  279. "--")
  280. '())
  281. "-volid" ,(string-upcase volume-id)
  282. ,@(if volume-uuid
  283. `("-volume_date" "uuid"
  284. ,(string-filter (lambda (value)
  285. (not (char=? #\- value)))
  286. (iso9660-uuid->string
  287. volume-uuid)))
  288. '()))))