image.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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, evalute 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. (format (current-error-port)
  114. "Unsupported partition type~%.")))))
  115. (define (convert-disk-image image format output)
  116. "Convert IMAGE to OUTPUT according to the given FORMAT."
  117. (case format
  118. ((compressed-qcow2)
  119. (begin
  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. (deduplicate? #t) (reset-timestamps? #t)
  133. (schema (sql-schema))
  134. (wal-mode? #t))
  135. "Register CLOSURE in PREFIX, where PREFIX is the directory name of the
  136. target store and CLOSURE is the name of a file containing a reference graph as
  137. produced by #:references-graphs.. As a side effect, if RESET-TIMESTAMPS? is
  138. true, reset timestamps on store files and, if DEDUPLICATE? is true,
  139. deduplicates files common to CLOSURE and the rest of PREFIX. Pass WAL-MODE?
  140. to call-with-database."
  141. (let ((items (call-with-input-file closure read-reference-graph)))
  142. (parameterize ((sql-schema schema))
  143. (with-database (store-database-file #:prefix prefix) db
  144. #:wal-mode? wal-mode?
  145. (register-items db items
  146. #:prefix prefix
  147. #:deduplicate? deduplicate?
  148. #:reset-timestamps? reset-timestamps?
  149. #:registration-time %epoch)))))
  150. (define* (initialize-efi-partition root
  151. #:key
  152. grub-efi
  153. #:allow-other-keys)
  154. "Install in ROOT directory, an EFI loader using GRUB-EFI."
  155. (install-efi-loader grub-efi root))
  156. (define* (initialize-root-partition root
  157. #:key
  158. bootcfg
  159. bootcfg-location
  160. bootloader-package
  161. bootloader-installer
  162. (deduplicate? #t)
  163. references-graphs
  164. (register-closures? #t)
  165. system-directory
  166. make-device-nodes
  167. (wal-mode? #t)
  168. #:allow-other-keys)
  169. "Initialize the given ROOT directory. Use BOOTCFG and BOOTCFG-LOCATION to
  170. install the bootloader configuration.
  171. If REGISTER-CLOSURES? is true, register REFERENCES-GRAPHS in the store. If
  172. DEDUPLICATE? is true, then also deduplicate files common to CLOSURES and the
  173. rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
  174. of the directory of the 'system' derivation. Pass WAL-MODE? to
  175. register-closure."
  176. (populate-root-file-system system-directory root)
  177. (populate-store references-graphs root)
  178. ;; Populate /dev.
  179. (when make-device-nodes
  180. (make-device-nodes root))
  181. (when register-closures?
  182. (for-each (lambda (closure)
  183. (register-closure root
  184. closure
  185. #:reset-timestamps? #t
  186. #:deduplicate? deduplicate?
  187. #:wal-mode? wal-mode?))
  188. references-graphs))
  189. (when bootloader-installer
  190. (display "installing bootloader...\n")
  191. (bootloader-installer bootloader-package #f root))
  192. (when bootcfg
  193. (install-boot-config bootcfg bootcfg-location root)))
  194. (define* (make-iso9660-image xorriso grub-mkrescue-environment
  195. grub bootcfg system-directory root target
  196. #:key (volume-id "Guix_image") (volume-uuid #f)
  197. register-closures? (references-graphs '())
  198. (compression? #t))
  199. "Given a GRUB package, creates an iso image as TARGET, using BOOTCFG as
  200. GRUB configuration and OS-DRV as the stuff in it."
  201. (define grub-mkrescue
  202. (string-append grub "/bin/grub-mkrescue"))
  203. (define grub-mkrescue-sed.sh
  204. (string-append (getcwd) "/" "grub-mkrescue-sed.sh"))
  205. ;; Use a modified version of grub-mkrescue-sed.sh, see below.
  206. (copy-file (string-append xorriso
  207. "/bin/grub-mkrescue-sed.sh")
  208. grub-mkrescue-sed.sh)
  209. ;; Force grub-mkrescue-sed.sh to use the build directory instead of /tmp
  210. ;; that is read-only inside the build container.
  211. (substitute* grub-mkrescue-sed.sh
  212. (("/tmp/") (string-append (getcwd) "/"))
  213. (("MKRESCUE_SED_XORRISO_ARGS \\$x")
  214. (format #f "MKRESCUE_SED_XORRISO_ARGS $(echo $x | sed \"s|/tmp|~a|\")"
  215. (getcwd))))
  216. ;; 'grub-mkrescue' calls out to mtools programs to create 'efi.img', a FAT
  217. ;; file system image, and mtools honors SOURCE_DATE_EPOCH for the mtime of
  218. ;; those files. The epoch for FAT is Jan. 1st 1980, not 1970, so choose
  219. ;; that.
  220. (setenv "SOURCE_DATE_EPOCH"
  221. (number->string
  222. (time-second
  223. (date->time-utc (make-date 0 0 0 0 1 1 1980 0)))))
  224. ;; Our patched 'grub-mkrescue' honors this environment variable and passes
  225. ;; it to 'mformat', which makes it the serial number of 'efi.img'. This
  226. ;; allows for deterministic builds.
  227. (setenv "GRUB_FAT_SERIAL_NUMBER"
  228. (number->string (if volume-uuid
  229. ;; On 32-bit systems the 2nd argument must be
  230. ;; lower than 2^32.
  231. (string-hash (iso9660-uuid->string volume-uuid)
  232. (- (expt 2 32) 1))
  233. #x77777777)
  234. 16))
  235. (setenv "MKRESCUE_SED_MODE" "original")
  236. (setenv "MKRESCUE_SED_XORRISO" (string-append xorriso "/bin/xorriso"))
  237. (setenv "MKRESCUE_SED_IN_EFI_NO_PT" "yes")
  238. (for-each (match-lambda
  239. ((name . value) (setenv name value)))
  240. grub-mkrescue-environment)
  241. (apply invoke grub-mkrescue
  242. (string-append "--xorriso=" grub-mkrescue-sed.sh)
  243. "-o" target
  244. (string-append "boot/grub/grub.cfg=" bootcfg)
  245. root
  246. "--"
  247. ;; Set all timestamps to 1.
  248. "-volume_date" "all_file_dates" "=1"
  249. `(,@(if compression?
  250. '(;; ‘zisofs’ compression reduces the total image size by
  251. ;; ~60%.
  252. "-zisofs" "level=9:block_size=128k" ; highest compression
  253. ;; It's transparent to our Linux-Libre kernel but not to
  254. ;; GRUB. Don't compress the kernel, initrd, and other
  255. ;; files read by grub.cfg, as well as common
  256. ;; already-compressed file names.
  257. "-find" "/" "-type" "f"
  258. ;; XXX Even after "--" above, and despite documentation
  259. ;; claiming otherwise, "-or" is stolen by grub-mkrescue
  260. ;; which then chokes on it (as ‘-o …’) and dies. Don't use
  261. ;; "-or".
  262. "-not" "-wholename" "/boot/*"
  263. "-not" "-wholename" "/System/*"
  264. "-not" "-name" "unicode.pf2"
  265. "-not" "-name" "bzImage"
  266. "-not" "-name" "*.gz" ; initrd & all man pages
  267. "-not" "-name" "*.png" ; includes grub-image.png
  268. "-exec" "set_filter" "--zisofs"
  269. "--")
  270. '())
  271. "-volid" ,(string-upcase volume-id)
  272. ,@(if volume-uuid
  273. `("-volume_date" "uuid"
  274. ,(string-filter (lambda (value)
  275. (not (char=? #\- value)))
  276. (iso9660-uuid->string
  277. volume-uuid)))
  278. '()))))