Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ event-listener = "5.4.1"
tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread"] }
sd-notify = { version = "0.4.5", optional = true }
getrandom = "0.3.3"
natord = "1.0"

[features]
default = ["dbus", "hooks"]
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

* Mounting an already-mounted boot environment is now a no-op.

* An experimental `beadm load` subcommand enables fast rebooting via `kexec(8)`.

# beadm v0.2.1

* D-Bus errors are now more informative.
Expand Down
21 changes: 21 additions & 0 deletions doc/beadm.8.scd
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ beadm - Boot Environment Administration
*beadm* *rename* _name_ _new-name_ ++
*beadm* *describe* { _name_ | _name@snapshot_ } _desc_ ++
*beadm* *rollback* _name_ _snapshot_ ++
*beadm* *load* ++
*beadm* *init* _pool_ ++
*beadm* *daemon*

Expand Down Expand Up @@ -189,6 +190,14 @@ same installation) on a single system.
_name_
The boot environment to query.

*load*

Load the activated boot environment's kernel and initramfs into *kexec(8)*,
enabling fast reboots on supported systems.

The kernel, initramfs, and command line are determined automatically using the
same techniques used by *zfsbootmenu(7)*.

*init* _pool_

Create the ZFS dataset layout for boot environments.
Expand Down Expand Up @@ -281,6 +290,18 @@ Set a description for a snapshot:
Roll back to a previous snapshot:
*beadm rollback current-be@yesterday*

Reboot the activated boot environment via the systemd *kexec(8)* integration:

*beadm load* ++
*systemctl kexec*

Reboot into a temporarily activated boot environment via *kexec(8)* (on non-systemd
systems):

*beadm activate -t alt* ++
*beadm load* ++
*kexec -e*

# EXIT STATUS

*0*
Expand Down
48 changes: 48 additions & 0 deletions src/be/kexec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MPL-2.0

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::fs;
use std::process::Command;

use crate::{Error, be::scan};

/// Check if `kexec(8)` can be used to load a kernel and initramfs pair.
pub fn has_kexec() -> Result<(), Error> {
if let Err(e) = Command::new("kexec").arg("--version").output() {
return Err(Error::KexecNotAvailable(e));
}

// Check if kexec has been disabled at the kernel level.
if let Ok(contents) = fs::read_to_string("/proc/sys/kernel/kexec_load_disabled") {
if contents.trim() != "0" {
return Err(Error::KexecNotAvailable(std::io::Error::other(
"kexec syscalls disabled for this kernel",
)));
}
}

Ok(())
}

/// Load a kernel and initramfs into kexec.
pub fn kexec_load(kernel: &scan::KernelPair, cmdline: &str) -> Result<(), Error> {
// TODO: Device tree support.
let mut cmd = Command::new("kexec");
cmd.arg("-l")
.arg(kernel.path.as_os_str())
.arg("--initrd")
.arg(kernel.initrd.as_os_str())
.arg("--command-line")
.arg(cmdline);

let output = cmd.output().map_err(|e| Error::KexecFailed(e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(Error::KexecFailed(std::io::Error::other(stderr.trim())));
}

Ok(())
}
16 changes: 16 additions & 0 deletions src/be/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,22 @@ impl Client for EmulatorClient {
fn active_root(&self) -> Option<&Root> {
Some(&self.active_root)
}

fn load(&self, root: Option<&Root>) -> Result<(), Error> {
// Validate that we have a boot environment to load, but otherwise do
// nothing.
let root = self.effective_root(root);
match self
.bes
.read()
.unwrap()
.iter()
.find(|be| (be.next_boot || be.boot_once) && be.root == *root)
{
Some(_) => Ok(()),
None => Err(Error::NoActiveBootEnvironment),
}
}
}

fn sample_boot_environments() -> Vec<BootEnvironment> {
Expand Down
21 changes: 21 additions & 0 deletions src/be/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use thiserror::Error as ThisError;
#[cfg(feature = "dbus")]
use zvariant::{DeserializeDict, SerializeDict, Type};

mod kexec;
mod mock;
pub(crate) mod scan;
mod validation;
Expand Down Expand Up @@ -58,6 +59,15 @@ pub enum Error {
#[error("Invalid boot environment root: '{name}'")]
InvalidBootEnvironmentRoot { name: String },

#[error("No matching kernel and initramfs pair found in /boot")]
KernelNotFound,

#[error("kexec command not found, disabled, or otherwise unavailable: {0}")]
KexecNotAvailable(std::io::Error),

#[error("Failed to execute kexec: {0}")]
KexecFailed(std::io::Error),

#[error(transparent)]
LibzfsError(#[from] zfs::LibzfsError),

Expand Down Expand Up @@ -87,6 +97,9 @@ impl From<Error> for zbus::fdo::Error {
Error::InvalidBootEnvironmentRoot { .. } => {
zbus::fdo::Error::InvalidArgs(err.to_string())
}
Error::KernelNotFound => zbus::fdo::Error::NotSupported(err.to_string()),
Error::KexecNotAvailable(_) => zbus::fdo::Error::NotSupported(err.to_string()),
Error::KexecFailed(_) => zbus::fdo::Error::Failed(err.to_string()),
Error::Io(err) => {
let e: zbus::Error = From::from(err);
e.into()
Expand Down Expand Up @@ -395,6 +408,14 @@ pub trait Client: Send + Sync {

/// Get the active boot environment root, if any.
fn active_root(&self) -> Option<&Root>;

/// Load the activated boot environment's kernel and initramfs into
/// `kexec(8)`. This enables faster reboots on some systems.
///
/// After calling this method, the user can execute the loaded kernel by
/// running `kexec -e` or, ideally, `reboot` or `systemctl kexec` with
/// systemd.
fn load(&self, root: Option<&Root>) -> Result<(), Error>;
}

/// Generate a snapshot name based on the current time.
Expand Down
Loading