bootloader.scm 18 KB

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