bootloader.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017 David Craven <david@craven.ch>
  3. ;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
  4. ;;; Copyright © 2017 Leo Famulari <leo@famulari.name>
  5. ;;; Copyright © 2019, 2021 Ludovic Courtès <ludo@gnu.org>
  6. ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (gnu bootloader)
  23. #:use-module (guix discovery)
  24. #:use-module (guix gexp)
  25. #:use-module (guix profiles)
  26. #:use-module (guix records)
  27. #:use-module (guix deprecation)
  28. #:use-module ((guix ui) #:select (warn-about-load-error))
  29. #:use-module (guix diagnostics)
  30. #:use-module (guix i18n)
  31. #:use-module (srfi srfi-1)
  32. #:use-module (ice-9 match)
  33. #:export (menu-entry
  34. menu-entry?
  35. menu-entry-label
  36. menu-entry-device
  37. menu-entry-linux
  38. menu-entry-linux-arguments
  39. menu-entry-initrd
  40. menu-entry-device-mount-point
  41. menu-entry-multiboot-kernel
  42. menu-entry-multiboot-arguments
  43. menu-entry-multiboot-modules
  44. menu-entry->sexp
  45. sexp->menu-entry
  46. bootloader
  47. bootloader?
  48. bootloader-name
  49. bootloader-package
  50. bootloader-installer
  51. bootloader-disk-image-installer
  52. bootloader-configuration-file
  53. bootloader-configuration-file-generator
  54. bootloader-configuration
  55. bootloader-configuration?
  56. bootloader-configuration-bootloader
  57. bootloader-configuration-target ;deprecated
  58. bootloader-configuration-targets
  59. bootloader-configuration-menu-entries
  60. bootloader-configuration-default-entry
  61. bootloader-configuration-timeout
  62. bootloader-configuration-keyboard-layout
  63. bootloader-configuration-theme
  64. bootloader-configuration-terminal-outputs
  65. bootloader-configuration-terminal-inputs
  66. bootloader-configuration-serial-unit
  67. bootloader-configuration-serial-speed
  68. bootloader-configuration-additional-configuration
  69. %bootloaders
  70. lookup-bootloader-by-name
  71. efi-bootloader-chain))
  72. ;;;
  73. ;;; Menu-entry record.
  74. ;;;
  75. (define-record-type* <menu-entry>
  76. menu-entry make-menu-entry
  77. menu-entry?
  78. (label menu-entry-label)
  79. (device menu-entry-device ; file system uuid, label, or #f
  80. (default #f))
  81. (device-mount-point menu-entry-device-mount-point
  82. (default #f))
  83. (linux menu-entry-linux
  84. (default #f))
  85. (linux-arguments menu-entry-linux-arguments
  86. (default '())) ; list of string-valued gexps
  87. (initrd menu-entry-initrd ; file name of the initrd as a gexp
  88. (default #f))
  89. (multiboot-kernel menu-entry-multiboot-kernel
  90. (default #f))
  91. (multiboot-arguments menu-entry-multiboot-arguments
  92. (default '())) ; list of string-valued gexps
  93. (multiboot-modules menu-entry-multiboot-modules
  94. (default '()))) ; list of multiboot commands, where
  95. ; a command is a list of <string>
  96. (define (menu-entry->sexp entry)
  97. "Return ENTRY serialized as an sexp."
  98. (match entry
  99. (($ <menu-entry> label device mount-point linux linux-arguments initrd #f
  100. ())
  101. `(menu-entry (version 0)
  102. (label ,label)
  103. (device ,device)
  104. (device-mount-point ,mount-point)
  105. (linux ,linux)
  106. (linux-arguments ,linux-arguments)
  107. (initrd ,initrd)))
  108. (($ <menu-entry> label device mount-point #f () #f
  109. multiboot-kernel multiboot-arguments multiboot-modules)
  110. `(menu-entry (version 0)
  111. (label ,label)
  112. (device ,device)
  113. (device-mount-point ,mount-point)
  114. (multiboot-kernel ,multiboot-kernel)
  115. (multiboot-arguments ,multiboot-arguments)
  116. (multiboot-modules ,multiboot-modules)))))
  117. (define (sexp->menu-entry sexp)
  118. "Turn SEXP, an sexp as returned by 'menu-entry->sexp', into a <menu-entry>
  119. record."
  120. (match sexp
  121. (('menu-entry ('version 0)
  122. ('label label) ('device device)
  123. ('device-mount-point mount-point)
  124. ('linux linux) ('linux-arguments linux-arguments)
  125. ('initrd initrd) _ ...)
  126. (menu-entry
  127. (label label)
  128. (device device)
  129. (device-mount-point mount-point)
  130. (linux linux)
  131. (linux-arguments linux-arguments)
  132. (initrd initrd)))
  133. (('menu-entry ('version 0)
  134. ('label label) ('device device)
  135. ('device-mount-point mount-point)
  136. ('multiboot-kernel multiboot-kernel)
  137. ('multiboot-arguments multiboot-arguments)
  138. ('multiboot-modules multiboot-modules) _ ...)
  139. (menu-entry
  140. (label label)
  141. (device device)
  142. (device-mount-point mount-point)
  143. (multiboot-kernel multiboot-kernel)
  144. (multiboot-arguments multiboot-arguments)
  145. (multiboot-modules multiboot-modules)))))
  146. ;;;
  147. ;;; Bootloader record.
  148. ;;;
  149. ;; The <bootloader> record contains fields expressing how the bootloader
  150. ;; should be installed. Every bootloader in gnu/bootloader/ directory
  151. ;; has to be described by this record.
  152. (define-record-type* <bootloader>
  153. bootloader make-bootloader
  154. bootloader?
  155. (name bootloader-name)
  156. (package bootloader-package)
  157. (installer bootloader-installer)
  158. (disk-image-installer bootloader-disk-image-installer
  159. (default #f))
  160. (configuration-file bootloader-configuration-file)
  161. (configuration-file-generator bootloader-configuration-file-generator))
  162. ;;;
  163. ;;; Bootloader configuration record.
  164. ;;;
  165. ;; The <bootloader-configuration> record contains bootloader independant
  166. ;; configuration used to fill bootloader configuration file.
  167. (define-syntax-rule (warn-target-field-deprecation value)
  168. (%warn-target-field-deprecation value (current-source-location)))
  169. (define-record-type* <bootloader-configuration>
  170. bootloader-configuration make-bootloader-configuration
  171. bootloader-configuration?
  172. (bootloader bootloader-configuration-bootloader) ;<bootloader>
  173. (targets %bootloader-configuration-targets ;list of strings
  174. (default #f))
  175. (target %bootloader-configuration-target ;deprecated
  176. (default #f) (sanitize warn-target-field-deprecation))
  177. (menu-entries bootloader-configuration-menu-entries ;list of <menu-entry>
  178. (default '()))
  179. (default-entry bootloader-configuration-default-entry ;integer
  180. (default 0))
  181. (timeout bootloader-configuration-timeout ;seconds as integer
  182. (default 5))
  183. (keyboard-layout bootloader-configuration-keyboard-layout ;<keyboard-layout> | #f
  184. (default #f))
  185. (theme bootloader-configuration-theme ;bootloader-specific theme
  186. (default #f))
  187. (terminal-outputs bootloader-configuration-terminal-outputs ;list of symbols
  188. (default '(gfxterm)))
  189. (terminal-inputs bootloader-configuration-terminal-inputs ;list of symbols
  190. (default '()))
  191. (serial-unit bootloader-configuration-serial-unit ;integer | #f
  192. (default #f))
  193. (serial-speed bootloader-configuration-serial-speed ;integer | #f
  194. (default #f)))
  195. (define (%warn-target-field-deprecation value location)
  196. (when value
  197. (warning (source-properties->location location)
  198. (G_ "the 'target' field is deprecated, please use 'targets' \
  199. instead~%")))
  200. value)
  201. (define-deprecated (bootloader-configuration-target config)
  202. bootloader-configuration-targets
  203. (%bootloader-configuration-target config))
  204. (define (bootloader-configuration-targets config)
  205. (or (%bootloader-configuration-targets config)
  206. ;; TODO: Remove after the deprecated 'target' field is removed.
  207. (list (%bootloader-configuration-target config))
  208. ;; XXX: At least the GRUB installer (see (gnu bootloader grub)) has this
  209. ;; peculiar behavior of installing fonts and GRUB modules when DEVICE is #f,
  210. ;; hence the default value of '(#f) rather than '().
  211. (list #f)))
  212. ;;;
  213. ;;; Bootloaders.
  214. ;;;
  215. (define (bootloader-modules)
  216. "Return the list of bootloader modules."
  217. (all-modules (map (lambda (entry)
  218. `(,entry . "gnu/bootloader"))
  219. %load-path)
  220. #:warn warn-about-load-error))
  221. (define %bootloaders
  222. ;; The list of publically-known bootloaders.
  223. (delay (fold-module-public-variables (lambda (obj result)
  224. (if (bootloader? obj)
  225. (cons obj result)
  226. result))
  227. '()
  228. (bootloader-modules))))
  229. (define (lookup-bootloader-by-name name)
  230. "Return the bootloader called NAME."
  231. (or (find (lambda (bootloader)
  232. (eq? name (bootloader-name bootloader)))
  233. (force %bootloaders))
  234. (leave (G_ "~a: no such bootloader~%") name)))
  235. (define (efi-bootloader-profile files bootloader-package hooks)
  236. "Creates a profile with BOOTLOADER-PACKAGE and a directory collection/ with
  237. links to additional FILES from the store. This collection is meant to be used
  238. by the bootloader installer.
  239. FILES is a list of file or directory names from the store, which will be
  240. symlinked into the collection/ directory. If a directory name ends with '/',
  241. then the directory content instead of the directory itself will be symlinked
  242. into the collection/ directory.
  243. FILES may contain file like objects produced by functions like plain-file,
  244. local-file, etc., or package contents produced with file-append.
  245. HOOKS lists additional hook functions to modify the profile."
  246. (define (bootloader-collection manifest)
  247. (define build
  248. (with-imported-modules '((guix build utils)
  249. (ice-9 ftw)
  250. (srfi srfi-1)
  251. (srfi srfi-26))
  252. #~(begin
  253. (use-modules ((guix build utils)
  254. #:select (mkdir-p strip-store-file-name))
  255. ((ice-9 ftw)
  256. #:select (scandir))
  257. ((srfi srfi-1)
  258. #:select (append-map every remove))
  259. ((srfi srfi-26)
  260. #:select (cut)))
  261. (define (symlink-to file directory transform)
  262. "Creates a symlink to FILE named (TRANSFORM FILE) in DIRECTORY."
  263. (symlink file (string-append directory "/" (transform file))))
  264. (define (directory-content directory)
  265. "Creates a list of absolute path names inside DIRECTORY."
  266. (map (lambda (name)
  267. (string-append directory name))
  268. (or (scandir directory (lambda (name)
  269. (not (member name '("." "..")))))
  270. '())))
  271. (define name-ends-with-/? (cut string-suffix? "/" <>))
  272. (define (name-is-store-entry? name)
  273. "Return #t if NAME is a direct store entry and nothing inside."
  274. (not (string-index (strip-store-file-name name) #\/)))
  275. (let* ((collection (string-append #$output "/collection"))
  276. (files '#$files)
  277. (directories (filter name-ends-with-/? files))
  278. (names-from-directories
  279. (append-map (lambda (directory)
  280. (directory-content directory))
  281. directories))
  282. (names (append names-from-directories
  283. (remove name-ends-with-/? files))))
  284. (mkdir-p collection)
  285. (if (every file-exists? names)
  286. (begin
  287. (for-each (lambda (name)
  288. (symlink-to name collection
  289. (if (name-is-store-entry? name)
  290. strip-store-file-name
  291. basename)))
  292. names)
  293. #t)
  294. #f)))))
  295. (gexp->derivation "bootloader-collection"
  296. build
  297. #:local-build? #t
  298. #:substitutable? #f
  299. #:properties
  300. `((type . profile-hook)
  301. (hook . bootloader-collection))))
  302. (profile (content (packages->manifest (list bootloader-package)))
  303. (name "bootloader-profile")
  304. (hooks (append (list bootloader-collection) hooks))
  305. (locales? #f)
  306. (allow-collisions? #f)
  307. (relative-symlinks? #f)))
  308. (define* (efi-bootloader-chain files
  309. final-bootloader
  310. #:key
  311. (hooks '())
  312. installer)
  313. "Define a bootloader chain with FINAL-BOOTLOADER as the final bootloader and
  314. certain directories and files from the store given in the list of FILES.
  315. FILES may contain file like objects produced by functions like plain-file,
  316. local-file, etc., or package contents produced with file-append. They will be
  317. collected inside a directory collection/ inside a generated bootloader profile,
  318. which will be passed to the INSTALLER.
  319. If a directory name in FILES ends with '/', then the directory content instead
  320. of the directory itself will be symlinked into the collection/ directory.
  321. The procedures in the HOOKS list can be used to further modify the bootloader
  322. profile. It is possible to pass a single function instead of a list.
  323. If the INSTALLER argument is used, then this function will be called to install
  324. the bootloader. Otherwise the installer of the FINAL-BOOTLOADER will be called."
  325. (let* ((final-installer (or installer
  326. (bootloader-installer final-bootloader)))
  327. (profile (efi-bootloader-profile files
  328. (bootloader-package final-bootloader)
  329. (if (list? hooks)
  330. hooks
  331. (list hooks)))))
  332. (bootloader
  333. (inherit final-bootloader)
  334. (package profile)
  335. (installer
  336. #~(lambda (bootloader target mount-point)
  337. (#$final-installer bootloader target mount-point)
  338. (copy-recursively
  339. (string-append bootloader "/collection")
  340. (string-append mount-point target)
  341. #:follow-symlinks? #t
  342. #:log (%make-void-port "w")))))))