grub

package module
v0.0.0-...-50c2f0b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2026 License: BSD-3-Clause Imports: 15 Imported by: 0

README

grub

ci Go Reference

Pure-Go (CGO=0) GRUB administration toolkit for disk images. It is a production consumer of the storage / firmware / boot stack:

  • go-volumes/gpt locates the EFI System Partition (and, for inspection, the Linux /boot partition) inside a GPT disk image.
  • go-filesystems/detect + go-filesystems/fat32 mount the ESP (FAT32) read/write behind the common filesystem.Filesystem API — so grub.cfg is edited as a real file, not by raw byte surgery on the image.
  • go-filesystems/uefi registers a GRUB UEFI boot entry (Boot#### / BootOrder).
  • go-tpm2/efitcg2 measures the grub.cfg and the kernels/initrds it references into the conventional TPM PCRs for measured boot — optional, behind an injected firmware Caller, so the tool stays TPM-free when no TPM is present.

Module

github.com/go-bootloaders/grub

What it does

Capability Entry point
Open + mount ESP (R/W) OpenImage(path) (*Image, error)
Read / locate grub.cfg (*Image).ReadGrubCfg, LocateGrubCfg
De-quiet / add consoles (*Image).PatchQuiet, PatchQuietImage
Generate a grub.cfg (*Image).MkConfig, GenerateConfig
Discover kernels/initrds DiscoverKernels
UEFI boot entry BuildBootEntry, RegisterBootEntry
Measured boot (TPM) NewMeasurer, (*Image).MeasureBoot
BIOS boot-code install InstallToSectors, InstallMBR

All grub.cfg editing is performed through the mounted filesystem's ReadFile / WriteFile. There is no same-length raw-byte patching of the disk image; the previous quiet→spaces hack is gone. (RawReplaceAll survives only as a documented, deliberately-fenced low-level fallback for raw, filesystem-less regions such as a string baked into core.img.)

Usage

Strip quiet/splash, add serial consoles
changed, err := grub.PatchQuietImage("disk.img")
// removes quiet/splash and ensures console=tty0 console=hvc0 on every
// linux line of the ESP's grub.cfg, persisting through the FAT32 driver.
Generate a fresh grub.cfg from on-disk kernels
im, err := grub.OpenImage("disk.img")
defer im.Close()

cfgPath, n, err := im.MkConfig(grub.MkConfigOptions{
    Distributor: "Debian GNU/Linux",
    Cmdline:     "ro console=tty0 console=hvc0",
})
// scans /boot (and the ESP) for vmlinuz*/initrd*, emits proper
// set/insmod headers + one menuentry{linux,initrd} block per kernel,
// newest first, and writes it to the ESP.
Register a UEFI boot entry
store, _ := uefi.Open("OVMF_VARS.fd")
defer store.Close()

lo := grub.BuildBootEntry("GRUB", 1, partGUID, esp, grub.DefaultGrubLoaderPath)
num, err := grub.RegisterBootEntry(store, lo) // appends to BootOrder
Measure boot into the TPM (optional)
m := grub.NewMeasurer(firmwareCaller) // efitcg2.Caller; omit when no TPM
count, err := im.MeasureBoot(m)
// grub.cfg -> PCR 8, each kernel/initrd -> PCR 9 (GRUB TCG conventions).

Architecture support

Validated on all six 64-bit Go targets: amd64, arm64, riscv64, loong64, ppc64le, and big-endian s390x (the byte-order canary for the GPT / FAT32 / UEFI decoders). Native runners run the full race + coverage suite; the four non-native arches run the cross-compiled test binaries under QEMU.

Known limitation: ext4 /boot

The ESP/FAT32 path is fully supported and resolves cleanly from the public Go proxy. Mounting an ext4 Linux /boot partition is not yet wired in: go-filesystems/ext4 currently pulls in the go-diskimages v0.0.0 + replace tangle that does not resolve in an isolated downstream build. BootPartition() locates the /boot partition's geometry today; mounting it is a documented follow-up, gated on the org-wide pseudo-version migration. This is by design — no faking, no vendor hacks.

License

BSD-3-Clause — see LICENSE. Copyright (c) 2026, the go-bootloaders/grub authors.

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 an optional Linux /boot partition) inside a GPT disk image.
  • go-filesystems/detect (+ its fat32reg adapter) mounts the partition's filesystem 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.)

Index

Constants

View Source
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).
View Source
const DefaultGrubLoaderPath = `\EFI\debian\grubx64.efi`

DefaultGrubLoaderPath is the conventional ESP path of the GRUB UEFI image.

Variables

View Source
var ErrNoESP = errors.New("grub: no EFI System Partition found in image")

ErrNoESP is returned when the image has no EFI System Partition.

View Source
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.

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

func CmdlineRemoveWord(cmdline, word string) string

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

func FindBootEntry(store uefi.VariableStore, description string) (num uint16, ok bool, err error)

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

func InstallMBR(imagePath string, code []byte) error

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

func InstallToSectors(imagePath string, code []byte, startSector, numSectors int64) error

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

func PatchGrubCfgContent(input string) string

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

func PatchGrubDefaultsContent(input string) string

PatchGrubDefaultsContent ensures `/etc/default/grub` contains the desired terminal and kernel cmdline settings. Keys are overridden if present or appended if missing.

func PatchQuietImage

func PatchQuietImage(imagePath string) (bool, error)

PatchQuietImage is the convenience wrapper matching the historical package-level entry point: open the image, FS-patch its grub.cfg, close. Unlike the removed raw-byte PatchQuiet, this performs real filesystem I/O and returns a meaningful error.

func RawReplaceAll

func RawReplaceAll(f *os.File, from, to []byte)

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 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 and the mounted ESP filesystem; call Close to release both. The optional Linux /boot filesystem is mounted lazily and only when it resolves cleanly (see OpenImage).

func OpenImage

func OpenImage(imagePath string) (*Image, error)

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) is intentionally NOT mounted here: that partition is typically ext4, whose driver module currently drags in the go-diskimages v0.0.0+replace tangle that does not resolve in an isolated downstream build. The ESP/FAT32 path is fully supported and clean; mounting /boot-ext4 is a documented follow-up gated on the org-wide pseudo-version migration. Use BootPartition() to inspect (not mount) it.

func (*Image) BootPartition

func (im *Image) BootPartition() (gpt.Partition, bool, error)

BootPartition locates (without mounting) the Linux /boot partition, if any. Mounting it is a documented follow-up (see OpenImage); this lets callers inspect its geometry today.

func (*Image) Close

func (im *Image) Close() error

Close releases the mounted ESP filesystem, flushing any pending writes back to the disk image.

func (*Image) ESP

func (im *Image) ESP() filesystem.Filesystem

ESP returns the mounted EFI System Partition filesystem.

func (*Image) ESPPartition

func (im *Image) ESPPartition() gpt.Partition

ESPPartition returns the GPT geometry of the EFI System Partition.

func (*Image) MeasureBoot

func (im *Image) MeasureBoot(m *Measurer) (int, error)

MeasureBoot measures a complete GRUB boot into the conventional PCRs from a mounted Image: the 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) 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) PatchQuiet

func (im *Image) PatchQuiet() (changed bool, err error)

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) Path

func (im *Image) Path() string

Path reports the backing image path.

func (*Image) ReadGrubCfg

func (im *Image) ReadGrubCfg() (cfgPath string, content string, err error)

ReadGrubCfg locates and reads the grub.cfg on the ESP.

func (*Image) Size

func (im *Image) Size() int64

Size reports the backing image size in bytes.

func (*Image) WriteGrubCfg

func (im *Image) WriteGrubCfg(cfgPath, content string) error

WriteGrubCfg writes content to the given grub.cfg path on the ESP.

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

func NewMeasurer(caller efitcg2.Caller) *Measurer

NewMeasurer wraps an efitcg2.Caller (the firmware-call mechanism the loader implements; in tests a fake Caller) into a Measurer.

func (*Measurer) MeasureConfig

func (m *Measurer) MeasureConfig(cfg []byte) error

MeasureConfig extends PCR 8 with the grub.cfg contents (EV_IPL), exactly as GRUB measures its configuration before acting on it.

func (*Measurer) MeasureFile

func (m *Measurer) MeasureFile(path string, data []byte) error

MeasureFile extends PCR 9 with a file GRUB loads (kernel or initrd), tagging the event with the file's path as its description.

func (*Measurer) MeasureLoadedImage

func (m *Measurer) MeasureLoadedImage(desc string, image []byte) error

MeasureLoadedImage extends PCR 4 with a loaded boot image (the kernel or the GRUB EFI binary itself), using the EFI boot-services-application event type.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL