Skip to main content

x86/
bootloader.rs

1use crate::error::{Result, X86Error, io_error};
2use crate::image::{Image, ImageKind};
3use std::fs;
4use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7#[derive(Debug, Clone)]
8pub enum Resource {
9    File(PathBuf),
10    Url(String),
11    Bytes { name: String, bytes: Vec<u8> },
12}
13
14impl Resource {
15    pub fn file(path: impl Into<PathBuf>) -> Self {
16        Self::File(path.into())
17    }
18
19    pub fn url(url: impl Into<String>) -> Self {
20        Self::Url(url.into())
21    }
22
23    pub fn bytes(name: impl Into<String>, bytes: impl Into<Vec<u8>>) -> Self {
24        Self::Bytes {
25            name: name.into(),
26            bytes: bytes.into(),
27        }
28    }
29}
30
31#[derive(Debug, Clone)]
32pub struct FetchOptions {
33    pub timeout: Duration,
34    pub user_agent: String,
35}
36
37impl Default for FetchOptions {
38    fn default() -> Self {
39        Self {
40            timeout: Duration::from_secs(60),
41            user_agent: format!("x86-native/{}", env!("CARGO_PKG_VERSION")),
42        }
43    }
44}
45
46#[derive(Debug, Clone)]
47pub struct Bootloader {
48    pub image: Image,
49    pub source: Resource,
50}
51
52impl Bootloader {
53    pub fn load(source: Resource) -> Result<Self> {
54        Self::load_with_options(source, &FetchOptions::default())
55    }
56
57    pub fn load_with_options(source: Resource, options: &FetchOptions) -> Result<Self> {
58        let image = load_resource(&source, ImageKind::Bootloader, options)?;
59        Ok(Self { image, source })
60    }
61
62    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
63        Self::load(Resource::file(path.as_ref().to_path_buf()))
64    }
65
66    #[cfg(feature = "remote")]
67    pub fn from_url(url: impl Into<String>) -> Result<Self> {
68        Self::load(Resource::url(url))
69    }
70}
71
72pub fn load_resource(source: &Resource, kind: ImageKind, options: &FetchOptions) -> Result<Image> {
73    match source {
74        Resource::File(path) => Image::from_file(kind, path),
75        Resource::Bytes { name, bytes } => Ok(Image::from_bytes(kind, name.clone(), bytes.clone())),
76        Resource::Url(url) => load_url(url, kind, options),
77    }
78}
79
80#[cfg(feature = "remote")]
81fn load_url(url: &str, kind: ImageKind, options: &FetchOptions) -> Result<Image> {
82    if !(url.starts_with("http://") || url.starts_with("https://")) {
83        return Err(X86Error::Remote {
84            url: url.to_owned(),
85            message: "only http:// and https:// URLs are supported".to_owned(),
86        });
87    }
88    let agent = ureq::AgentBuilder::new()
89        .timeout(options.timeout)
90        .user_agent(&options.user_agent)
91        .build();
92    let response = agent.get(url).call().map_err(|error| X86Error::Remote {
93        url: url.to_owned(),
94        message: error.to_string(),
95    })?;
96    let mut reader = response.into_reader();
97    let mut bytes = Vec::new();
98    std::io::Read::read_to_end(&mut reader, &mut bytes).map_err(|source| io_error(url, source))?;
99    let name = url
100        .rsplit('/')
101        .next()
102        .filter(|x| !x.is_empty())
103        .unwrap_or("remote-bootloader");
104    Ok(Image::from_bytes(kind, name, bytes))
105}
106
107#[cfg(not(feature = "remote"))]
108fn load_url(url: &str, _kind: ImageKind, _options: &FetchOptions) -> Result<Image> {
109    let _ = url;
110    Err(X86Error::RemoteDisabled)
111}
112
113pub fn copy_resource_to(source: &Resource, destination: impl AsRef<Path>) -> Result<PathBuf> {
114    let destination = destination.as_ref();
115    let image = load_resource(source, ImageKind::Other, &FetchOptions::default())?;
116    fs::write(destination, image.bytes()).map_err(|source| io_error(destination, source))?;
117    Ok(destination.to_path_buf())
118}