all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
blob 064466bd331f4c9a1c3fb04d779dc456fdeeb045 7123 bytes (raw)
name: gnu/build/bootloader.scm 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
 
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2019 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2022 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
;;; Copyright © 2022 Timothy Sample <samplet@ngyro.com>
;;; Copyright © 2024 Lilah Tascheter <lilah@lunabee.space>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu build bootloader)
  #:autoload   (guix build syscalls) (free-disk-space)
  #:use-module (guix build utils)
  #:use-module (guix diagnostics)
  #:use-module (guix i18n)
  #:use-module (ice-9 format)
  #:use-module (ice-9 match)
  #:use-module (ice-9 popen)
  #:use-module (ice-9 receive)
  #:use-module (ice-9 regex)
  #:use-module (rnrs io ports)
  #:use-module (rnrs io simple)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (srfi srfi-35)
  #:export (atomic-copy
            in-temporary-directory
            write-file-on-device
            install-efi))

\f
;;;
;;; Writing utils.
;;;

(define (atomic-copy from to)
  (let ((pivot (string-append to ".new")))
    (copy-file from pivot)
    (rename-file pivot to)))

(define-syntax-rule (in-temporary-directory blocks ...)
  "Run BLOCKS while chdir'd into a temporary directory."
  ;; Under POSIX.1-2008, mkdtemp must make the dir with 700 perms.
  (let* ((tmp (or (getenv "TMPDIR") "/tmp"))
         (dir (mkdtemp (string-append tmp "/guix-bootloader.XXXXXX")))
         (cwd (getcwd)))
    (dynamic-wind (lambda () (chdir dir))
                  (lambda () blocks ...)
                  (lambda () (chdir cwd) (delete-file-recursively dir)))))

(define (write-file-on-device file size device offset)
  "Write SIZE bytes from FILE to DEVICE starting at OFFSET."
  (call-with-input-file file
    (lambda (input)
      (let ((bv (get-bytevector-n input size)))
        (call-with-port
         ;; Do not use "call-with-output-file" that would truncate the file.
         (open-file-output-port device
                                (file-options no-truncate no-fail)
                                (buffer-mode block)
                                ;; Use the binary-friendly ISO-8859-1
                                ;; encoding.
                                (make-transcoder (latin-1-codec)))
         (lambda (output)
           (seek output offset SEEK_SET)
           (put-bytevector output bv)))))))

\f
;;;
;;; EFI bootloader.
;;;

;; XXX: Parsing efibootmgr output may be kinda jank.  A better way may exist.
(define (efi-bootnums efibootmgr)
  "Returns '(path . bootnum) pairs for each EFI boot entry.  bootnum is
a string, and path is backslash-deliminated and relative to the ESP."
  (let* ((pipe (open-pipe* OPEN_READ efibootmgr))
         (text (get-string-all pipe))
         (status (status:exit-val (close-pipe pipe)))
         (bootnum-pattern
           "^Boot([0-9a-fA-F]+).*[^A-Za-z]File\\(([^)]+)\\)$"))
    (unless (zero? status)
      (raise-exception
        (formatted-message (G_ "efibootmgr exited with error code ~a") status)))
    (fold-matches (make-regexp bootnum-pattern regexp/newline) text '()
                  (lambda (match acc)
                    (let* ((path (match:substring match 2))
                           (bootnum (match:substring match 1)))
                      (cons (cons path bootnum) acc))))))

(define (install-efi efibootmgr vendir loader* disk plan)
  "See also install-efi in (gnu bootloader)."
  (let* ((loader (string-map (match-lambda (#\/ #\\) (x x)) loader*))
         (bootnums (filter (compose (cut string-prefix? loader <>) car)
                     (efi-bootnums efibootmgr)))
         (plan-files (map cadr plan)))
    (define (size file) (if (file-exists? file) (stat:size (stat file)) 0))
    (define (vendirof file) (string-append vendir "/" file))
    (define (loaderof file) (string-append loader "\\" file))
    (define (delete-boot num file)
      (invoke efibootmgr "--quiet" "--bootnum" num "--delete-bootnum")
      (when (file-exists? file) (delete-file file)))

    (mkdir-p vendir)
    ;; Delete old entries first, to clear up space.
    (for-each (lambda (spec) ; '(path . bootnum)
                (let* ((s (substring (car spec) (string-length loader)))
                       (file (substring s (if (string-prefix? "\\" s) 1 0))))
                  (unless (member file plan-files)
                    (delete-boot (cdr spec) (vendirof file)))))
      bootnums)
    ;; New and updated entries.
    (in-temporary-directory
      (for-each
        (lambda (spec)
          (let* ((builder (car spec)) (name (cadr spec))
                 (dest (vendirof name)) (loadest (loaderof name))
                 (rest (reverse (cdr (member name plan-files)))))
            ;; Build to a temporary file so we can check its size.
            (builder name)
            ;; Disk space is usually limited on ESPs.
            ;; Try to clear space as we install new bootloaders.
            (if (while (> (- (size name) (size dest)) (free-disk-space vendir))
                  (let ((del (find (compose file-exists? vendirof) rest)))
                    (if del (delete-file (vendirof del)) (break #t))))
                (begin
                  (and=> (assoc-ref bootnums loadest) (cut delete-boot <> dest))
                  (warning (G_ "ESP too small for bootloader ~a!~%") name))
                ;; The ESP is too small for atomic copy.
                (begin
                  (copy-file name dest)
                  (unless (assoc loadest bootnums)
                    (invoke
                      efibootmgr "--quiet" "--create-only" "--label"
                      (cddr spec) "--disk" disk "--loader" loadest))))
            (delete-file name)))
        plan))
    ;; Verify that at least the first entry was installed.
    (unless (file-exists? (vendirof (cadr (car plan))))
      ;; Extremely fatal error so we use leave instead of raise.
      (leave (G_ "not enough space in ESP to install bootloader!
 SYSTEM WILL NOT BOOT UNLESS THIS IS FIXED!~%")))
    ;; Some UEFI systems will refuse to acknowledge the existence of boot
    ;; entries unless they're in bootorder, so just shove everything in there.
    (invoke
      efibootmgr "--quiet" "--bootorder"
      ;; Recall efi-bootnums to get a fresh list with new installs.
      (let ((num (cute assoc-ref (efi-bootnums efibootmgr) <>))) ; cute is eager
        (string-join (filter-map (compose num loaderof) plan-files) ",")))))

debug log:

solving 064466bd33 ...
found 064466bd33 in https://yhetil.org/guix/52e31df0e5a3a0d0c4b015d135d5eb0ce3e4829e.1726827025.git.herman@rimm.ee/ ||
	https://yhetil.org/guix/e5a67b99cf8792af7870903e8517bc42b60c9528.1727345067.git.herman@rimm.ee/
found 3934e03aee in https://yhetil.org/guix/3063da96c7b9dc3db63dbc96d2238f6ed742b857.1727345067.git.herman@rimm.ee/ ||
	https://yhetil.org/guix/5bb21b9075822392a90e3e5aeb4e5daa2fcfff82.1726827025.git.herman@rimm.ee/
found af6063a884 in https://git.savannah.gnu.org/cgit/guix.git
preparing index
index prepared:
100644 af6063a884a81787f6d210e733948d596684d2ea	gnu/build/bootloader.scm

applying [1/2] https://yhetil.org/guix/3063da96c7b9dc3db63dbc96d2238f6ed742b857.1727345067.git.herman@rimm.ee/
diff --git a/gnu/build/bootloader.scm b/gnu/build/bootloader.scm
index af6063a884..3934e03aee 100644

Checking patch gnu/build/bootloader.scm...
Applied patch gnu/build/bootloader.scm cleanly.

skipping https://yhetil.org/guix/5bb21b9075822392a90e3e5aeb4e5daa2fcfff82.1726827025.git.herman@rimm.ee/ for 3934e03aee
index at:
100644 3934e03aeeff6329a8130d007acd24705b071dad	gnu/build/bootloader.scm

applying [2/2] https://yhetil.org/guix/52e31df0e5a3a0d0c4b015d135d5eb0ce3e4829e.1726827025.git.herman@rimm.ee/
diff --git a/gnu/build/bootloader.scm b/gnu/build/bootloader.scm
index 3934e03aee..064466bd33 100644

Checking patch gnu/build/bootloader.scm...
Applied patch gnu/build/bootloader.scm cleanly.

skipping https://yhetil.org/guix/e5a67b99cf8792af7870903e8517bc42b60c9528.1727345067.git.herman@rimm.ee/ for 064466bd33
index at:
100644 064466bd331f4c9a1c3fb04d779dc456fdeeb045	gnu/build/bootloader.scm

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.