mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 19:00:18 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
527ce4b1b4
|
|||
|
ebd3c07bf5
|
|||
|
e8b7b967fa
|
|||
|
2bf4013938
|
|||
| 6819e55e23 | |||
|
|
3ffda86544 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -116,7 +116,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "edera-sprout"
|
||||
version = "0.0.12"
|
||||
version = "0.0.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"image",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "edera-sprout"
|
||||
description = "Modern UEFI bootloader"
|
||||
license = "Apache-2.0"
|
||||
version = "0.0.12"
|
||||
version = "0.0.13"
|
||||
homepage = "https://sprout.edera.dev"
|
||||
repository = "https://github.com/edera-dev/sprout"
|
||||
edition = "2024"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
ARG RUST_PROFILE=release
|
||||
ARG RUST_TARGET_SUBDIR=release
|
||||
|
||||
FROM --platform=$BUILDPLATFORM rustlang/rust:nightly-alpine@sha256:141e9a7f13f77237dd4d462364c3a1b21cb8a6791d8924c409573e77b788af5e AS build
|
||||
FROM --platform=$BUILDPLATFORM rustlang/rust:nightly-alpine@sha256:7cba2edabb6ba0e92cd806cd1e0acae99d50f63e5b9c9ad842766d13c896d68c AS build
|
||||
RUN apk --no-cache add musl-dev busybox-static
|
||||
ARG RUST_PROFILE
|
||||
RUN adduser -S -s /bin/sh build
|
||||
|
||||
@@ -33,15 +33,10 @@ Write the following file to `X:\sprout.toml`:
|
||||
# sprout configuration: version 1
|
||||
version = 1
|
||||
|
||||
# add a boot entry for booting Windows
|
||||
# which will run the boot-windows action.
|
||||
[entries.windows]
|
||||
title = "Windows"
|
||||
actions = ["boot-windows"]
|
||||
|
||||
# use the chainload action to boot the Windows bootloader.
|
||||
[actions.boot-windows]
|
||||
chainload.path = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi"
|
||||
# global options.
|
||||
[options]
|
||||
# enable autoconfiguration to detect Windows.
|
||||
autoconfigure = true
|
||||
```
|
||||
|
||||
## Step 4: Configure EFI Firmware to boot Sprout
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::utils;
|
||||
use crate::utils::media_loader::MediaLoaderHandle;
|
||||
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use log::{error, info};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::rc::Rc;
|
||||
use uefi::CString16;
|
||||
@@ -69,8 +69,6 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
||||
.context("unable to convert chainloader options to CString16")?,
|
||||
);
|
||||
|
||||
info!("options: {}", options);
|
||||
|
||||
if options.num_bytes() > u32::MAX as usize {
|
||||
bail!("chainloader options too large");
|
||||
}
|
||||
@@ -85,14 +83,17 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
||||
options_holder = Some(options);
|
||||
}
|
||||
|
||||
// Stamp the initrd path, if provided.
|
||||
let initrd = configuration
|
||||
.linux_initrd
|
||||
.as_ref()
|
||||
.map(|item| context.stamp(item));
|
||||
// The initrd can be None or empty, so we need to collapse that into a single Option.
|
||||
let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref());
|
||||
let initrd = utils::empty_is_none(initrd);
|
||||
|
||||
// If an initrd is provided, register it with the EFI stack.
|
||||
let mut initrd_handle = None;
|
||||
if let Some(linux_initrd) = initrd {
|
||||
// Stamp the path to the initrd.
|
||||
let linux_initrd = context.stamp(linux_initrd);
|
||||
let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd)
|
||||
.context("unable to read linux initrd")?;
|
||||
let handle =
|
||||
|
||||
@@ -12,6 +12,9 @@ pub mod bls;
|
||||
/// on BLS-enabled filesystems as it may make duplicate entries.
|
||||
pub mod linux;
|
||||
|
||||
/// windows: autodetect and configure Windows boot configurations.
|
||||
pub mod windows;
|
||||
|
||||
/// Generate a [RootConfiguration] based on the environment.
|
||||
/// Intakes a `config` to use as the basis of the autoconfiguration.
|
||||
pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
|
||||
@@ -44,6 +47,10 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
|
||||
linux::scan(&mut filesystem, &root, config)
|
||||
.context("unable to scan for linux configurations")?;
|
||||
}
|
||||
|
||||
// Always look for Windows configurations.
|
||||
windows::scan(&mut filesystem, &root, config)
|
||||
.context("unable to scan for windows configurations")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::generators::GeneratorDeclaration;
|
||||
use crate::generators::list::ListConfiguration;
|
||||
use crate::utils;
|
||||
use anyhow::{Context, Result};
|
||||
use log::info;
|
||||
use std::collections::BTreeMap;
|
||||
use uefi::CString16;
|
||||
use uefi::fs::{FileSystem, Path};
|
||||
@@ -23,7 +24,7 @@ const SCAN_LOCATIONS: &[&str] = &["/boot", "/"];
|
||||
const KERNEL_PREFIXES: &[&str] = &["vmlinuz"];
|
||||
|
||||
/// Prefixes of initramfs files to match to.
|
||||
const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd"];
|
||||
const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd", "initrd.img"];
|
||||
|
||||
/// Pair of kernel and initramfs.
|
||||
/// This is what scanning a directory is meant to find.
|
||||
@@ -158,7 +159,7 @@ pub fn scan(
|
||||
// Kernel pairs are detected, generate a list configuration for it.
|
||||
let generator = ListConfiguration {
|
||||
entry: EntryDeclaration {
|
||||
title: "Boot Linux $kernel".to_string(),
|
||||
title: "Boot Linux $name".to_string(),
|
||||
actions: vec![chainload_action_name.clone()],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -166,8 +167,14 @@ pub fn scan(
|
||||
.into_iter()
|
||||
.map(|pair| {
|
||||
BTreeMap::from_iter(vec![
|
||||
("kernel".to_string(), pair.kernel),
|
||||
("initrd".to_string(), pair.initramfs.unwrap_or_default()),
|
||||
("name".to_string(), pair.kernel.clone()),
|
||||
("kernel".to_string(), format!("{}{}", root, pair.kernel)),
|
||||
(
|
||||
"initrd".to_string(),
|
||||
pair.initramfs
|
||||
.map(|initramfs| format!("{}{}", root, initramfs))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
@@ -194,9 +201,9 @@ pub fn scan(
|
||||
// Note that we don't need an extra \\ in the paths here.
|
||||
// The root already contains a trailing slash.
|
||||
let chainload = ChainloadConfiguration {
|
||||
path: format!("{}$kernel", root),
|
||||
path: "$kernel".to_string(),
|
||||
options: vec!["$linux-options".to_string()],
|
||||
linux_initrd: Some(format!("{}$initrd", root)),
|
||||
linux_initrd: Some("$initrd".to_string()),
|
||||
};
|
||||
|
||||
// Insert the chainload action into the configuration.
|
||||
@@ -208,6 +215,8 @@ pub fn scan(
|
||||
},
|
||||
);
|
||||
|
||||
info!("{:?}", config);
|
||||
|
||||
// We had a Linux kernel, so return true to indicate something was found.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
80
src/autoconfigure/windows.rs
Normal file
80
src/autoconfigure/windows.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::actions::ActionDeclaration;
|
||||
use crate::actions::chainload::ChainloadConfiguration;
|
||||
use crate::config::RootConfiguration;
|
||||
use crate::entries::EntryDeclaration;
|
||||
use crate::utils;
|
||||
use anyhow::{Context, Result};
|
||||
use uefi::CString16;
|
||||
use uefi::fs::{FileSystem, Path};
|
||||
use uefi::proto::device_path::DevicePath;
|
||||
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
||||
|
||||
/// The name prefix of the Windows chainload action that will be used to boot Windows.
|
||||
const WINDOWS_CHAINLOAD_ACTION_PREFIX: &str = "windows-chainload-";
|
||||
|
||||
/// Windows boot manager path.
|
||||
const BOOTMGR_FW_PATH: &str = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi";
|
||||
|
||||
/// Scan the specified `filesystem` for Windows configurations.
|
||||
pub fn scan(
|
||||
filesystem: &mut FileSystem,
|
||||
root: &DevicePath,
|
||||
config: &mut RootConfiguration,
|
||||
) -> Result<bool> {
|
||||
// Convert the boot manager firmware path to a path.
|
||||
let bootmgr_fw_path =
|
||||
CString16::try_from(BOOTMGR_FW_PATH).context("unable to convert path to CString16")?;
|
||||
let bootmgr_fw_path = Path::new(&bootmgr_fw_path);
|
||||
|
||||
// Check if the boot manager firmware path exists, if it doesn't, return false.
|
||||
if !filesystem
|
||||
.try_exists(bootmgr_fw_path)
|
||||
.context("unable to check if bootmgr firmware path exists")?
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Convert the device path root to a string we can use in the configuration.
|
||||
let mut root = root
|
||||
.to_string(DisplayOnly(false), AllowShortcuts(false))
|
||||
.context("unable to convert device root to string")?
|
||||
.to_string();
|
||||
// Add a trailing slash to the root to ensure the path is valid.
|
||||
root.push('/');
|
||||
|
||||
// Generate a unique hash of the root path.
|
||||
let root_unique_hash = utils::unique_hash(&root);
|
||||
|
||||
// Generate a unique name for the Windows chainload action.
|
||||
let chainload_action_name = format!("{}{}", WINDOWS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,);
|
||||
|
||||
// Generate an entry name for Windows.
|
||||
let entry_name = format!("autoconfigure-windows-{}", root_unique_hash,);
|
||||
|
||||
// Create an entry for Windows and insert it into the configuration.
|
||||
let entry = EntryDeclaration {
|
||||
title: "Boot Windows".to_string(),
|
||||
actions: vec![chainload_action_name.clone()],
|
||||
values: Default::default(),
|
||||
};
|
||||
config.entries.insert(entry_name, entry);
|
||||
|
||||
// Generate a chainload configuration for Windows.
|
||||
let chainload = ChainloadConfiguration {
|
||||
path: format!("{}{}", root, bootmgr_fw_path),
|
||||
options: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Insert the chainload action into the configuration.
|
||||
config.actions.insert(
|
||||
chainload_action_name,
|
||||
ActionDeclaration {
|
||||
chainload: Some(chainload),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// We have a Windows boot entry, so return true to indicate something was found.
|
||||
Ok(true)
|
||||
}
|
||||
@@ -118,6 +118,9 @@ fn run() -> Result<()> {
|
||||
|
||||
// Extend the root context with the autoconfigured actions.
|
||||
root.actions_mut().extend(config.actions);
|
||||
|
||||
// Insert any modified root values.
|
||||
context.insert(&config.values);
|
||||
}
|
||||
|
||||
// Refreeze the context to ensure that further operations can share the context.
|
||||
@@ -248,6 +251,8 @@ fn main() -> Result<()> {
|
||||
for (index, stack) in error.chain().enumerate() {
|
||||
error!("[{}]: {}", index, stack);
|
||||
}
|
||||
// Sleep for 10 seconds to allow the user to read the error.
|
||||
uefi::boot::stall(Duration::from_secs(10));
|
||||
}
|
||||
|
||||
// Sprout doesn't necessarily guarantee anything was booted.
|
||||
|
||||
Reference in New Issue
Block a user