bootloader.scm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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 files bootloader-package hooks)
  297. "Creates a profile with BOOTLOADER-PACKAGE and a directory collection/ with
  298. links to additional FILES from the store. This collection is meant to be used
  299. by the bootloader installer.
  300. FILES is a list of file or directory names from the store, which will be
  301. symlinked into the collection/ directory. If a directory name ends with '/',
  302. then the directory content instead of the directory itself will be symlinked
  303. into the collection/ directory.
  304. FILES may contain file like objects produced by functions like plain-file,
  305. local-file, etc., or package contents produced with file-append.
  306. HOOKS lists additional hook functions to modify the profile."
  307. (define (bootloader-collection manifest)
  308. (define build
  309. (with-imported-modules '((guix build utils)
  310. (ice-9 ftw)
  311. (srfi srfi-1)
  312. (srfi srfi-26))
  313. #~(begin
  314. (use-modules ((guix build utils)
  315. #:select (mkdir-p strip-store-file-name))
  316. ((ice-9 ftw)
  317. #:select (scandir))
  318. ((srfi srfi-1)
  319. #:select (append-map every remove))
  320. ((srfi srfi-26)
  321. #:select (cut)))
  322. (define (symlink-to file directory transform)
  323. "Creates a symlink to FILE named (TRANSFORM FILE) in DIRECTORY."
  324. (symlink file (string-append directory "/" (transform file))))
  325. (define (directory-content directory)
  326. "Creates a list of absolute path names inside DIRECTORY."
  327. (map (lambda (name)
  328. (string-append directory name))
  329. (or (scandir directory (lambda (name)
  330. (not (member name '("." "..")))))
  331. '())))
  332. (define name-ends-with-/? (cut string-suffix? "/" <>))
  333. (define (name-is-store-entry? name)
  334. "Return #t if NAME is a direct store entry and nothing inside."
  335. (not (string-index (strip-store-file-name name) #\/)))
  336. (let* ((collection (string-append #$output "/collection"))
  337. (files '#$files)
  338. (directories (filter name-ends-with-/? files))
  339. (names-from-directories
  340. (append-map (lambda (directory)
  341. (directory-content directory))
  342. directories))
  343. (names (append names-from-directories
  344. (remove name-ends-with-/? files))))
  345. (mkdir-p collection)
  346. (if (every file-exists? names)
  347. (begin
  348. (for-each (lambda (name)
  349. (symlink-to name collection
  350. (if (name-is-store-entry? name)
  351. strip-store-file-name
  352. basename)))
  353. names)
  354. #t)
  355. #f)))))
  356. (gexp->derivation "bootloader-collection"
  357. build
  358. #:local-build? #t
  359. #:substitutable? #f
  360. #:properties
  361. `((type . profile-hook)
  362. (hook . bootloader-collection))))
  363. (profile (content (packages->manifest (list bootloader-package)))
  364. (name "bootloader-profile")
  365. (hooks (append (list bootloader-collection) hooks))
  366. (locales? #f)
  367. (allow-collisions? #f)
  368. (relative-symlinks? #f)))
  369. (define* (efi-bootloader-chain files
  370. final-bootloader
  371. #:key
  372. (hooks '())
  373. installer)
  374. "Define a bootloader chain with FINAL-BOOTLOADER as the final bootloader and
  375. certain directories and files from the store given in the list of FILES.
  376. FILES may contain file like objects produced by functions like plain-file,
  377. local-file, etc., or package contents produced with file-append. They will be
  378. collected inside a directory collection/ inside a generated bootloader profile,
  379. which will be passed to the INSTALLER.
  380. If a directory name in FILES ends with '/', then the directory content instead
  381. of the directory itself will be symlinked into the collection/ directory.
  382. The procedures in the HOOKS list can be used to further modify the bootloader
  383. profile. It is possible to pass a single function instead of a list.
  384. If the INSTALLER argument is used, then this function will be called to install
  385. the bootloader. Otherwise the installer of the FINAL-BOOTLOADER will be called."
  386. (let* ((final-installer (or installer
  387. (bootloader-installer final-bootloader)))
  388. (profile (efi-bootloader-profile files
  389. (bootloader-package final-bootloader)
  390. (if (list? hooks)
  391. hooks
  392. (list hooks)))))
  393. (bootloader
  394. (inherit final-bootloader)
  395. (package profile)
  396. (installer
  397. #~(lambda (bootloader target mount-point)
  398. (#$final-installer bootloader target mount-point)
  399. (copy-recursively
  400. (string-append bootloader "/collection")
  401. (string-append mount-point target)
  402. #:follow-symlinks? #t
  403. #:log (%make-void-port "w")))))))