bootloader.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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-with-syntax-properties (warn-target-field-deprecation
  168. (value properties))
  169. (when value
  170. (warning (source-properties->location properties)
  171. (G_ "the 'target' field is deprecated, please use 'targets' \
  172. instead~%")))
  173. value)
  174. (define-record-type* <bootloader-configuration>
  175. bootloader-configuration make-bootloader-configuration
  176. bootloader-configuration?
  177. (bootloader bootloader-configuration-bootloader) ;<bootloader>
  178. (targets %bootloader-configuration-targets ;list of strings
  179. (default #f))
  180. (target %bootloader-configuration-target ;deprecated
  181. (default #f) (sanitize warn-target-field-deprecation))
  182. (menu-entries bootloader-configuration-menu-entries ;list of <menu-entry>
  183. (default '()))
  184. (default-entry bootloader-configuration-default-entry ;integer
  185. (default 0))
  186. (timeout bootloader-configuration-timeout ;seconds as integer
  187. (default 5))
  188. (keyboard-layout bootloader-configuration-keyboard-layout ;<keyboard-layout> | #f
  189. (default #f))
  190. (theme bootloader-configuration-theme ;bootloader-specific theme
  191. (default #f))
  192. (terminal-outputs bootloader-configuration-terminal-outputs ;list of symbols
  193. (default '(gfxterm)))
  194. (terminal-inputs bootloader-configuration-terminal-inputs ;list of symbols
  195. (default '()))
  196. (serial-unit bootloader-configuration-serial-unit ;integer | #f
  197. (default #f))
  198. (serial-speed bootloader-configuration-serial-speed ;integer | #f
  199. (default #f)))
  200. (define-deprecated (bootloader-configuration-target config)
  201. bootloader-configuration-targets
  202. (%bootloader-configuration-target config))
  203. (define (bootloader-configuration-targets config)
  204. (or (%bootloader-configuration-targets config)
  205. ;; TODO: Remove after the deprecated 'target' field is removed.
  206. (list (%bootloader-configuration-target config))
  207. ;; XXX: At least the GRUB installer (see (gnu bootloader grub)) has this
  208. ;; peculiar behavior of installing fonts and GRUB modules when DEVICE is #f,
  209. ;; hence the default value of '(#f) rather than '().
  210. (list #f)))
  211. ;;;
  212. ;;; Bootloaders.
  213. ;;;
  214. (define (bootloader-modules)
  215. "Return the list of bootloader modules."
  216. (all-modules (map (lambda (entry)
  217. `(,entry . "gnu/bootloader"))
  218. %load-path)
  219. #:warn warn-about-load-error))
  220. (define %bootloaders
  221. ;; The list of publically-known bootloaders.
  222. (delay (fold-module-public-variables (lambda (obj result)
  223. (if (bootloader? obj)
  224. (cons obj result)
  225. result))
  226. '()
  227. (bootloader-modules))))
  228. (define (lookup-bootloader-by-name name)
  229. "Return the bootloader called NAME."
  230. (or (find (lambda (bootloader)
  231. (eq? name (bootloader-name bootloader)))
  232. (force %bootloaders))
  233. (leave (G_ "~a: no such bootloader~%") name)))
  234. (define (efi-bootloader-profile files bootloader-package hooks)
  235. "Creates a profile with BOOTLOADER-PACKAGE and a directory collection/ with
  236. links to additional FILES from the store. This collection is meant to be used
  237. by the bootloader installer.
  238. FILES is a list of file or directory names from the store, which will be
  239. symlinked into the collection/ directory. If a directory name ends with '/',
  240. then the directory content instead of the directory itself will be symlinked
  241. into the collection/ directory.
  242. FILES may contain file like objects produced by functions like plain-file,
  243. local-file, etc., or package contents produced with file-append.
  244. HOOKS lists additional hook functions to modify the profile."
  245. (define (bootloader-collection manifest)
  246. (define build
  247. (with-imported-modules '((guix build utils)
  248. (ice-9 ftw)
  249. (srfi srfi-1)
  250. (srfi srfi-26))
  251. #~(begin
  252. (use-modules ((guix build utils)
  253. #:select (mkdir-p strip-store-file-name))
  254. ((ice-9 ftw)
  255. #:select (scandir))
  256. ((srfi srfi-1)
  257. #:select (append-map every remove))
  258. ((srfi srfi-26)
  259. #:select (cut)))
  260. (define (symlink-to file directory transform)
  261. "Creates a symlink to FILE named (TRANSFORM FILE) in DIRECTORY."
  262. (symlink file (string-append directory "/" (transform file))))
  263. (define (directory-content directory)
  264. "Creates a list of absolute path names inside DIRECTORY."
  265. (map (lambda (name)
  266. (string-append directory name))
  267. (or (scandir directory (lambda (name)
  268. (not (member name '("." "..")))))
  269. '())))
  270. (define name-ends-with-/? (cut string-suffix? "/" <>))
  271. (define (name-is-store-entry? name)
  272. "Return #t if NAME is a direct store entry and nothing inside."
  273. (not (string-index (strip-store-file-name name) #\/)))
  274. (let* ((collection (string-append #$output "/collection"))
  275. (files '#$files)
  276. (directories (filter name-ends-with-/? files))
  277. (names-from-directories
  278. (append-map (lambda (directory)
  279. (directory-content directory))
  280. directories))
  281. (names (append names-from-directories
  282. (remove name-ends-with-/? files))))
  283. (mkdir-p collection)
  284. (if (every file-exists? names)
  285. (begin
  286. (for-each (lambda (name)
  287. (symlink-to name collection
  288. (if (name-is-store-entry? name)
  289. strip-store-file-name
  290. basename)))
  291. names)
  292. #t)
  293. #f)))))
  294. (gexp->derivation "bootloader-collection"
  295. build
  296. #:local-build? #t
  297. #:substitutable? #f
  298. #:properties
  299. `((type . profile-hook)
  300. (hook . bootloader-collection))))
  301. (profile (content (packages->manifest (list bootloader-package)))
  302. (name "bootloader-profile")
  303. (hooks (append (list bootloader-collection) hooks))
  304. (locales? #f)
  305. (allow-collisions? #f)
  306. (relative-symlinks? #f)))
  307. (define* (efi-bootloader-chain files
  308. final-bootloader
  309. #:key
  310. (hooks '())
  311. installer)
  312. "Define a bootloader chain with FINAL-BOOTLOADER as the final bootloader and
  313. certain directories and files from the store given in the list of FILES.
  314. FILES may contain file like objects produced by functions like plain-file,
  315. local-file, etc., or package contents produced with file-append. They will be
  316. collected inside a directory collection/ inside a generated bootloader profile,
  317. which will be passed to the INSTALLER.
  318. If a directory name in FILES ends with '/', then the directory content instead
  319. of the directory itself will be symlinked into the collection/ directory.
  320. The procedures in the HOOKS list can be used to further modify the bootloader
  321. profile. It is possible to pass a single function instead of a list.
  322. If the INSTALLER argument is used, then this function will be called to install
  323. the bootloader. Otherwise the installer of the FINAL-BOOTLOADER will be called."
  324. (let* ((final-installer (or installer
  325. (bootloader-installer final-bootloader)))
  326. (profile (efi-bootloader-profile files
  327. (bootloader-package final-bootloader)
  328. (if (list? hooks)
  329. hooks
  330. (list hooks)))))
  331. (bootloader
  332. (inherit final-bootloader)
  333. (package profile)
  334. (installer
  335. #~(lambda (bootloader target mount-point)
  336. (#$final-installer bootloader target mount-point)
  337. (copy-recursively
  338. (string-append bootloader "/collection")
  339. (string-append mount-point target)
  340. #:follow-symlinks? #t
  341. #:log (%make-void-port "w")))))))