image.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. (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."
  61. (* 1.25 (file-size root)))
  62. (define* (make-ext-image partition target root
  63. #:key
  64. (owner-uid 0)
  65. (owner-gid 0))
  66. "Handle the creation of EXT2/3/4 partition images. See
  67. 'make-partition-image'."
  68. (let ((size (partition-size partition))
  69. (fs (partition-file-system partition))
  70. (fs-options (partition-file-system-options partition))
  71. (label (partition-label partition))
  72. (uuid (partition-uuid partition))
  73. (journal-options "lazy_itable_init=1,lazy_journal_init=1"))
  74. (apply invoke
  75. `("fakeroot" "mke2fs" "-t" ,fs "-d" ,root
  76. "-L" ,label "-U" ,(uuid->string uuid)
  77. "-E" ,(format #f "root_owner=~a:~a,~a"
  78. owner-uid owner-gid journal-options)
  79. ,@fs-options
  80. ,target
  81. ,(format #f "~ak"
  82. (size-in-kib
  83. (if (eq? size 'guess)
  84. (estimate-partition-size root)
  85. size)))))))
  86. (define* (make-vfat-image partition target root)
  87. "Handle the creation of VFAT partition images. See 'make-partition-image'."
  88. (let ((size (partition-size partition))
  89. (label (partition-label partition)))
  90. (invoke "fakeroot" "mkdosfs" "-n" label "-C" target
  91. "-F" "16" "-S" "1024"
  92. (size-in-kib
  93. (if (eq? size 'guess)
  94. (estimate-partition-size root)
  95. size)))
  96. (for-each (lambda (file)
  97. (unless (member file '("." ".."))
  98. (invoke "mcopy" "-bsp" "-i" target
  99. (string-append root "/" file)
  100. (string-append "::" file))))
  101. (scandir root))))
  102. (define* (make-partition-image partition-sexp target root)
  103. "Create and return the image of PARTITION-SEXP as TARGET. Use the given
  104. ROOT directory to populate the image."
  105. (let* ((partition (sexp->partition partition-sexp))
  106. (type (partition-file-system partition)))
  107. (cond
  108. ((string-prefix? "ext" type)
  109. (make-ext-image partition target root))
  110. ((string=? type "vfat")
  111. (make-vfat-image partition target root))
  112. (else
  113. (raise (condition
  114. (&message
  115. (message "unsupported partition type"))))))))
  116. (define (convert-disk-image image format output)
  117. "Convert IMAGE to OUTPUT according to the given FORMAT."
  118. (case format
  119. ((compressed-qcow2)
  120. (invoke "qemu-img" "convert" "-c" "-f" "raw"
  121. "-O" "qcow2" image output))
  122. (else
  123. (copy-file image output))))
  124. (define* (genimage config)
  125. "Use genimage to generate in TARGET directory, the image described in the
  126. given CONFIG file."
  127. ;; genimage needs a 'root' directory.
  128. (mkdir "root")
  129. (invoke "genimage" "--config" config))
  130. (define* (register-closure prefix closure
  131. #:key
  132. (schema (sql-schema))
  133. (wal-mode? #t))
  134. "Register CLOSURE in PREFIX, where PREFIX is the directory name of the
  135. target store and CLOSURE is the name of a file containing a reference graph as
  136. produced by #:references-graphs. Pass WAL-MODE? to call-with-database."
  137. (let ((items (call-with-input-file closure read-reference-graph)))
  138. (parameterize ((sql-schema schema))
  139. (with-database (store-database-file #:prefix prefix) db
  140. #:wal-mode? wal-mode?
  141. (register-items db items
  142. #:prefix prefix
  143. #:registration-time %epoch)))))
  144. (define* (initialize-efi-partition root
  145. #:key
  146. grub-efi
  147. #:allow-other-keys)
  148. "Install in ROOT directory, an EFI loader using GRUB-EFI."
  149. (install-efi-loader grub-efi root))
  150. (define* (initialize-root-partition root
  151. #:key
  152. bootcfg
  153. bootcfg-location
  154. bootloader-package
  155. bootloader-installer
  156. (deduplicate? #t)
  157. references-graphs
  158. (register-closures? #t)
  159. system-directory
  160. make-device-nodes
  161. (wal-mode? #t)
  162. #:allow-other-keys)
  163. "Initialize the given ROOT directory. Use BOOTCFG and BOOTCFG-LOCATION to
  164. install the bootloader configuration.
  165. If REGISTER-CLOSURES? is true, register REFERENCES-GRAPHS in the store. If
  166. DEDUPLICATE? is true, then also deduplicate files common to CLOSURES and the
  167. rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
  168. of the directory of the 'system' derivation. Pass WAL-MODE? to
  169. register-closure."
  170. (populate-root-file-system system-directory root)
  171. (populate-store references-graphs root
  172. #:deduplicate? deduplicate?)
  173. ;; Populate /dev.
  174. (when make-device-nodes
  175. (make-device-nodes root))
  176. (when register-closures?
  177. (for-each (lambda (closure)
  178. (register-closure root closure
  179. #:wal-mode? wal-mode?))
  180. references-graphs))
  181. (when bootloader-installer
  182. (display "installing bootloader...\n")
  183. (bootloader-installer bootloader-package #f root))
  184. (when bootcfg
  185. (install-boot-config bootcfg bootcfg-location root)))
  186. (define* (make-iso9660-image xorriso grub-mkrescue-environment
  187. grub bootcfg system-directory root target
  188. #:key (volume-id "Guix_image") (volume-uuid #f)
  189. register-closures? (references-graphs '())
  190. (compression? #t))
  191. "Given a GRUB package, creates an iso image as TARGET, using BOOTCFG as
  192. GRUB configuration and OS-DRV as the stuff in it."
  193. (define grub-mkrescue
  194. (string-append grub "/bin/grub-mkrescue"))
  195. (define grub-mkrescue-sed.sh
  196. (string-append (getcwd) "/" "grub-mkrescue-sed.sh"))
  197. ;; Use a modified version of grub-mkrescue-sed.sh, see below.
  198. (copy-file (string-append xorriso
  199. "/bin/grub-mkrescue-sed.sh")
  200. grub-mkrescue-sed.sh)
  201. ;; Force grub-mkrescue-sed.sh to use the build directory instead of /tmp
  202. ;; that is read-only inside the build container.
  203. (substitute* grub-mkrescue-sed.sh
  204. (("/tmp/") (string-append (getcwd) "/"))
  205. (("MKRESCUE_SED_XORRISO_ARGS \\$x")
  206. (format #f "MKRESCUE_SED_XORRISO_ARGS $(echo $x | sed \"s|/tmp|~a|\")"
  207. (getcwd))))
  208. ;; 'grub-mkrescue' calls out to mtools programs to create 'efi.img', a FAT
  209. ;; file system image, and mtools honors SOURCE_DATE_EPOCH for the mtime of
  210. ;; those files. The epoch for FAT is Jan. 1st 1980, not 1970, so choose
  211. ;; that.
  212. (setenv "SOURCE_DATE_EPOCH"
  213. (number->string
  214. (time-second
  215. (date->time-utc (make-date 0 0 0 0 1 1 1980 0)))))
  216. ;; Our patched 'grub-mkrescue' honors this environment variable and passes
  217. ;; it to 'mformat', which makes it the serial number of 'efi.img'. This
  218. ;; allows for deterministic builds.
  219. (setenv "GRUB_FAT_SERIAL_NUMBER"
  220. (number->string (if volume-uuid
  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. #x77777777)
  226. 16))
  227. (setenv "MKRESCUE_SED_MODE" "original")
  228. (setenv "MKRESCUE_SED_XORRISO" (string-append xorriso "/bin/xorriso"))
  229. (setenv "MKRESCUE_SED_IN_EFI_NO_PT" "yes")
  230. (for-each (match-lambda
  231. ((name . value) (setenv name value)))
  232. grub-mkrescue-environment)
  233. (apply invoke grub-mkrescue
  234. (string-append "--xorriso=" grub-mkrescue-sed.sh)
  235. "-o" target
  236. (string-append "boot/grub/grub.cfg=" bootcfg)
  237. root
  238. "--"
  239. ;; Set all timestamps to 1.
  240. "-volume_date" "all_file_dates" "=1"
  241. `(,@(if compression?
  242. '(;; ‘zisofs’ compression reduces the total image size by
  243. ;; ~60%.
  244. "-zisofs" "level=9:block_size=128k" ; highest compression
  245. ;; It's transparent to our Linux-Libre kernel but not to
  246. ;; GRUB. Don't compress the kernel, initrd, and other
  247. ;; files read by grub.cfg, as well as common
  248. ;; already-compressed file names.
  249. "-find" "/" "-type" "f"
  250. ;; XXX Even after "--" above, and despite documentation
  251. ;; claiming otherwise, "-or" is stolen by grub-mkrescue
  252. ;; which then chokes on it (as ‘-o …’) and dies. Don't use
  253. ;; "-or".
  254. "-not" "-wholename" "/boot/*"
  255. "-not" "-wholename" "/System/*"
  256. "-not" "-name" "unicode.pf2"
  257. "-not" "-name" "bzImage"
  258. "-not" "-name" "*.gz" ; initrd & all man pages
  259. "-not" "-name" "*.png" ; includes grub-image.png
  260. "-exec" "set_filter" "--zisofs"
  261. "--")
  262. '())
  263. "-volid" ,(string-upcase volume-id)
  264. ,@(if volume-uuid
  265. `("-volume_date" "uuid"
  266. ,(string-filter (lambda (value)
  267. (not (char=? #\- value)))
  268. (iso9660-uuid->string
  269. volume-uuid)))
  270. '()))))