Documentation
¶
Overview ¶
Package grub is a pure-Go (CGO=0) GRUB administration toolkit for disk images. It is a production consumer of the go-volumes / go-filesystems / go-tpm2 stack:
- go-volumes/gpt locates the EFI System Partition and the Linux /boot (or root) partition inside a GPT disk image.
- go-filesystems/detect probes each partition's filesystem type; grub then mounts it writably through the matching concrete driver (go-filesystems/fat32 for the ESP, go-filesystems/ext4 or go-filesystems/btrfs for /boot) behind the common filesystem.Filesystem interface.
- go-filesystems/uefi registers a GRUB UEFI boot entry (Boot####/BootOrder).
- go-tpm2/efitcg2 measures the grub.cfg + referenced kernel/initrd into the TPM PCRs for measured boot (optional, behind an injected Caller).
All grub.cfg editing is performed through the mounted filesystem's ReadFile/WriteFile — there is no raw same-length byte patching of the disk image. (RawReplaceAll survives only as a documented, deliberately-fenced low-level fallback; see raw.go.)
Both the ESP and the /boot filesystem are opened in place via each driver's path+partition-index entry point (fat32.Open / ext4.Open / btrfs.Open), so WriteFile changes persist back to the partition's region of the disk image. The drivers expose a real WriteFile, so grub.cfg regeneration (MkConfig) and in-place patching (PatchQuiet) work against the /boot ext4/btrfs filesystem exactly as they do against the FAT32 ESP — no read-only temp-file staging.
Index ¶
- Constants
- Variables
- func BuildBootEntry(description string, partNumber uint32, partGUID [16]byte, part gpt.Partition, ...) *uefi.LoadOption
- func CmdlineRemoveWord(cmdline, word string) string
- func FindBootEntry(store uefi.VariableStore, description string) (num uint16, ok bool, err error)
- func GenerateConfig(kernels []Kernel, opts MkConfigOptions) string
- func InstallMBR(imagePath string, code []byte) error
- func InstallToSectors(imagePath string, code []byte, startSector, numSectors int64) error
- func LocateGrubCfg(fs filesystem.Filesystem) (string, error)
- func PatchGrubCfgContent(input string) string
- func PatchGrubDefaultsContent(input string) string
- func PatchQuietImage(imagePath string) (bool, error)
- func RawReplaceAll(f *os.File, from, to []byte)
- func ReadGrubCfgFS(fs filesystem.Filesystem) (cfgPath string, content string, err error)
- func RegisterBootEntry(store uefi.VariableStore, lo *uefi.LoadOption) (uint16, error)
- type Image
- func (im *Image) Boot() filesystem.Filesystem
- func (im *Image) BootPartition() (gpt.Partition, bool, error)
- func (im *Image) BootType() detect.Type
- func (im *Image) Close() error
- func (im *Image) ESP() filesystem.Filesystem
- func (im *Image) ESPPartition() gpt.Partition
- func (im *Image) HasBoot() bool
- func (im *Image) MeasureBoot(m *Measurer) (int, error)
- func (im *Image) MeasureBootOnBoot(m *Measurer) (int, error)
- func (im *Image) MkConfig(opts MkConfigOptions) (cfgPath string, entries int, err error)
- func (im *Image) MkConfigOnBoot(opts MkConfigOptions) (cfgPath string, entries int, err error)
- func (im *Image) PatchQuiet() (changed bool, err error)
- func (im *Image) PatchQuietOnBoot() (changed bool, err error)
- func (im *Image) Path() string
- func (im *Image) ReadGrubCfg() (cfgPath string, content string, err error)
- func (im *Image) ReadGrubCfgOnBoot() (cfgPath string, content string, err error)
- func (im *Image) Size() int64
- func (im *Image) WriteGrubCfg(cfgPath, content string) error
- func (im *Image) WriteGrubCfgOnBoot(cfgPath, content string) error
- type Kernel
- type Measurer
- type MkConfigOptions
Constants ¶
const ( PCRLoadedImage = 4 PCRGrubConfig = 8 PCRGrubFiles = 9 )
Conventional TPM PCR indices for GRUB measured boot, per the GRUB TCG spec and the EFI boot measurement conventions:
- PCR 4: the loaded boot image (the GRUB EFI binary / kernel image).
- PCR 8: GRUB's configuration and typed commands (grub.cfg contents).
- PCR 9: files GRUB loads on behalf of the OS (kernel, initrd).
const DefaultGrubLoaderPath = `\EFI\debian\grubx64.efi`
DefaultGrubLoaderPath is the conventional ESP path of the GRUB UEFI image.
Variables ¶
var ErrNoBoot = errors.New("grub: no /boot filesystem mounted")
ErrNoBoot is returned by the *OnBoot methods when no Linux /boot filesystem is mounted (the image has no Linux partition).
var ErrNoESP = errors.New("grub: no EFI System Partition found in image")
ErrNoESP is returned when the image has no EFI System Partition.
var ErrNoGrubCfg = errors.New("grub: no grub.cfg found on filesystem")
ErrNoGrubCfg is returned when no grub.cfg can be located on a mounted FS.
var ErrUnsupportedBootFS = errors.New("grub: unsupported /boot filesystem type")
ErrUnsupportedBootFS is returned (wrapped) when the Linux partition exists but holds a filesystem grub cannot mount for /boot administration (i.e. not ext4 or btrfs).
Functions ¶
func BuildBootEntry ¶
func BuildBootEntry(description string, partNumber uint32, partGUID [16]byte, part gpt.Partition, loaderPath string) *uefi.LoadOption
BuildBootEntry constructs the UEFI EFI_LOAD_OPTION for a GRUB loader living on the given GPT partition. The device path is
HD(<partno>,GPT,<part-guid>,<startLBA>,<sizeLBA>)/File(<loaderPath>)
matching what firmware writes for a GRUB install. loaderPath is a backslash-separated EFI path such as DefaultGrubLoaderPath. partNumber is the 1-based GPT partition index; partGUID is the partition's unique GUID (raw 16 bytes, GPT mixed-endian as stored on disk).
func CmdlineRemoveWord ¶
CmdlineRemoveWord removes a single word from a kernel command line. It preserves the order of other tokens and returns the original string unchanged if the word is not present.
func FindBootEntry ¶
FindBootEntry returns the Boot#### number of an existing entry whose description matches description, or ok=false when none is present. It lets callers detect (and avoid duplicating) a previously-registered GRUB entry.
func GenerateConfig ¶
func GenerateConfig(kernels []Kernel, opts MkConfigOptions) string
GenerateConfig renders a grub.cfg from a list of kernels. The output is a complete, valid GRUB configuration: a header (set default/timeout, load the needed modules), one menuentry per kernel with a linux + initrd line, and proper quoting. It does not touch the filesystem.
func InstallMBR ¶
InstallMBR writes a minimal MBR boot sector. `code` must be at most 446 bytes (the MBR boot-code area). The function ensures the boot signature 0x55AA is set at offsets 510-511. Like InstallToSectors this targets a raw, filesystem-free region (sector 0) and is a legitimate boot-code install.
func InstallToSectors ¶
InstallToSectors writes `code` into `numSectors` sectors starting at `startSector` (0-based). This is the legitimate BIOS boot-code install path: GRUB's core.img / boot.img live in the post-MBR gap (the sectors between the MBR and the first partition) on a BIOS/GPT-hybrid layout, which is a raw region with no filesystem. It returns an error if `code` is larger than the provided sector range.
func LocateGrubCfg ¶
func LocateGrubCfg(fs filesystem.Filesystem) (string, error)
LocateGrubCfg returns the path of the grub.cfg on the given filesystem, trying the conventional locations and then any /EFI/<vendor>/grub.cfg. It returns ErrNoGrubCfg if none is present.
func PatchGrubCfgContent ¶
PatchGrubCfgContent edits a single grub.cfg "linux" or "linux16" line to remove quiet/splash tokens and ensure console arguments are present. If no linux line is found the input is returned unchanged. Indentation is preserved and existing console arguments are not duplicated.
func PatchGrubDefaultsContent ¶
PatchGrubDefaultsContent ensures `/etc/default/grub` contains the desired terminal and kernel cmdline settings. Keys are overridden if present or appended if missing.
func PatchQuietImage ¶
PatchQuietImage is the convenience wrapper matching the historical package-level entry point: open the image, FS-patch its grub.cfg, close. It patches the ESP grub.cfg, then the /boot grub.cfg when one is mounted, so a single call covers both the ESP-resident and the Debian-style /boot-resident configuration. It reports changed=true if either was modified. If neither the ESP nor /boot carries a grub.cfg at all it returns ErrNoGrubCfg (matching the historical single-target behaviour); a grub.cfg present on only one side is patched and the missing side is not an error.
func RawReplaceAll ¶
RawReplaceAll scans f in overlapping 1 MiB chunks and replaces every occurrence of from with to (which MUST be the same length) in-place.
DEPRECATED / LOW-LEVEL FALLBACK. This is NOT how grub.cfg should be edited: the production path is OpenImage -> PatchQuiet (filesystem-based ReadFile/ WriteFile through the mounted ESP), which can shorten the kernel command line correctly instead of padding it with spaces. RawReplaceAll is retained only for the rare case of patching a raw, filesystem-less region (e.g. a string baked into core.img) where no driver can mount the bytes. Prefer the FS path for anything residing in a real filesystem.
func ReadGrubCfgFS ¶
func ReadGrubCfgFS(fs filesystem.Filesystem) (cfgPath string, content string, err error)
ReadGrubCfgFS locates and reads the grub.cfg on an arbitrary mounted filesystem (the ESP or /boot). It is the shared core behind ReadGrubCfg and ReadGrubCfgOnBoot.
func RegisterBootEntry ¶
func RegisterBootEntry(store uefi.VariableStore, lo *uefi.LoadOption) (uint16, error)
RegisterBootEntry writes a GRUB boot entry into the given UEFI variable store and appends it to BootOrder, returning the assigned Boot#### number. The store is typically obtained via uefi.Open(<varstore.fd>); the grub package stays decoupled from how the store is materialised (an OVMF NvVar region, a firmware-backed store, etc.). It does not mount or modify the disk image.
Types ¶
type Image ¶
type Image struct {
// contains filtered or unexported fields
}
Image is a mounted GRUB-administration handle over a GPT disk image. It owns the opened backing file, the mounted ESP filesystem, and — when present and of a supported type — the mounted Linux /boot filesystem; call Close to release them all. The /boot filesystem is mounted by OpenImage when a Linux partition is present and its filesystem (ext4 or btrfs) resolves cleanly.
func OpenImage ¶
OpenImage opens a GPT disk image read/write, locates the EFI System Partition, and mounts its FAT32 filesystem for read/write. The returned Image exposes the mounted ESP via ESP(); callers must Close it.
The ESP is mounted writably through the go-filesystems/fat32 driver, which opens the partition in place inside the disk image (partition selected by its GPT index) so WriteFile changes persist to the underlying image. The FS type is first verified as FAT32 via go-filesystems/detect; a non-FAT32 ESP is rejected rather than mis-mounted.
The Linux /boot partition (LinuxFilesystemGUID), when present, is mounted writably too: its filesystem type is probed via go-filesystems/detect and dispatched to the matching in-place driver (ext4.Open or btrfs.Open). A Linux partition holding an unsupported filesystem (neither ext4 nor btrfs) is reported as a hard error rather than silently skipped. An image with no Linux partition at all simply has no /boot mount (Boot() returns nil); this is the normal ESP-only case and not an error.
func (*Image) Boot ¶
func (im *Image) Boot() filesystem.Filesystem
Boot returns the mounted Linux /boot filesystem, or nil when the image has no Linux partition. Use HasBoot to disambiguate a missing /boot from a nil check, and BootType to learn which driver (ext4/btrfs) backs it.
func (*Image) BootPartition ¶
BootPartition returns the GPT geometry of the Linux /boot partition and whether one is present. The partition is already mounted by OpenImage when of a supported type; this exposes its geometry for callers that need it.
func (*Image) BootType ¶
BootType reports the detected filesystem type of the mounted /boot (detect.Ext4 or detect.Btrfs), or the empty Type when none is mounted.
func (*Image) Close ¶
Close releases the mounted ESP and /boot filesystems, flushing any pending writes back to the disk image. Both are closed even if the first errors; the first error encountered is returned.
func (*Image) ESP ¶
func (im *Image) ESP() filesystem.Filesystem
ESP returns the mounted EFI System Partition filesystem.
func (*Image) ESPPartition ¶
ESPPartition returns the GPT geometry of the EFI System Partition.
func (*Image) MeasureBoot ¶
MeasureBoot measures a complete GRUB boot into the conventional PCRs from a mounted Image: the ESP grub.cfg into PCR 8, and every discovered kernel/initrd into PCR 9. It is the convenience entry point that ties OpenImage to the TPM. Returns the number of measurements made.
func (*Image) MeasureBootOnBoot ¶
MeasureBootOnBoot measures the grub.cfg + kernels/initrds resident on the mounted /boot filesystem (ext4/btrfs) into the conventional PCRs. It returns ErrNoBoot when no /boot is mounted.
func (*Image) MkConfig ¶
func (im *Image) MkConfig(opts MkConfigOptions) (cfgPath string, entries int, err error)
MkConfig discovers the kernels on the mounted ESP, generates a grub.cfg, and writes it to the conventional ESP location (/grub/grub.cfg, or the path of an existing grub.cfg if one is already present). It returns the path written and the number of menu entries emitted.
This replaces the former no-op stub: it produces a real, valid configuration from on-disk kernels rather than returning nil.
func (*Image) MkConfigOnBoot ¶
func (im *Image) MkConfigOnBoot(opts MkConfigOptions) (cfgPath string, entries int, err error)
MkConfigOnBoot is MkConfig against the mounted /boot filesystem (ext4/btrfs): it scans /boot for vmlinuz*/initrd* kernels and writes the generated grub.cfg to /boot/grub/grub.cfg (or an existing config path). It returns ErrNoBoot when no /boot is mounted. The ext4/btrfs drivers persist the write to the disk image.
func (*Image) PatchQuiet ¶
PatchQuiet removes the "quiet"/"splash" kernel flags and ensures serial console arguments on every linux line of the grub.cfg, editing the file through the mounted ESP filesystem (NOT raw disk bytes). It locates the grub.cfg, applies PatchGrubCfgContent, and writes it back. If the content is unchanged the file is left untouched.
This replaces the old same-length raw-byte hack: the rewritten cmdline is shorter than the original (quiet/splash removed), which the FAT32 driver handles by re-truncating the file — something raw patching could never do without padding spaces into the kernel command line.
func (*Image) PatchQuietOnBoot ¶
PatchQuietOnBoot is PatchQuiet against the mounted /boot filesystem's grub.cfg (ext4/btrfs). It returns ErrNoBoot when no /boot is mounted. Like PatchQuiet, the rewritten (shorter) cmdline is truncated in place by the driver, and the change persists to the disk image.
func (*Image) ReadGrubCfg ¶
ReadGrubCfg locates and reads the grub.cfg on the ESP.
func (*Image) ReadGrubCfgOnBoot ¶
ReadGrubCfgOnBoot locates and reads the grub.cfg on the mounted /boot filesystem (where Debian/Fedora/etc. keep /boot/grub/grub.cfg or /boot/grub2/grub.cfg). It returns ErrNoBoot when no /boot is mounted.
func (*Image) WriteGrubCfg ¶
WriteGrubCfg writes content to the given grub.cfg path on the ESP.
func (*Image) WriteGrubCfgOnBoot ¶
WriteGrubCfgOnBoot writes content to cfgPath on the mounted /boot filesystem. It returns ErrNoBoot when no /boot is mounted. The ext4/btrfs drivers expose a real WriteFile, so the change persists to the disk image.
type Kernel ¶
type Kernel struct {
// Version is the release string parsed from the vmlinuz name, e.g.
// "6.1.0-18-amd64" from "vmlinuz-6.1.0-18-amd64". Empty for an
// un-versioned "vmlinuz".
Version string
// KernelPath is the absolute path of the kernel image on the FS.
KernelPath string
// InitrdPath is the absolute path of the matching initrd/initramfs, or
// empty when none was found.
InitrdPath string
}
Kernel is a discovered (kernel, initrd) pair on a mounted filesystem.
func DiscoverKernels ¶
func DiscoverKernels(fs filesystem.Filesystem) ([]Kernel, error)
DiscoverKernels scans the conventional directories of a mounted filesystem for kernel images and their matching initrds. Results are sorted by version descending (newest first) so the default entry is the newest kernel.
type Measurer ¶
type Measurer struct {
// contains filtered or unexported fields
}
Measurer performs GRUB measured-boot extensions through an injected efitcg2.Caller (the firmware TPM2 protocol). Constructing it without a real TPM is a no-op-free design: the tool stays entirely TPM-free unless a Caller is supplied, mirroring efitcg2's own injection seam.
func NewMeasurer ¶
NewMeasurer wraps an efitcg2.Caller (the firmware-call mechanism the loader implements; in tests a fake Caller) into a Measurer.
func (*Measurer) MeasureConfig ¶
MeasureConfig extends PCR 8 with the grub.cfg contents (EV_IPL), exactly as GRUB measures its configuration before acting on it.
func (*Measurer) MeasureFile ¶
MeasureFile extends PCR 9 with a file GRUB loads (kernel or initrd), tagging the event with the file's path as its description.
type MkConfigOptions ¶
type MkConfigOptions struct {
// Default is the index of the default menu entry. Defaults to 0.
Default int
// Timeout is the menu timeout in seconds. Defaults to 5.
Timeout int
// Cmdline is appended to every linux line (after the root= argument the
// caller bakes in). Typical value: "ro console=tty0 console=hvc0".
Cmdline string
// Distributor labels the menu entries, e.g. "Debian GNU/Linux".
Distributor string
}
MkConfigOptions tunes generated grub.cfg output.