#[macro_use]
extern crate thiserror;
pub mod entry;
pub mod loader;
use self::entry::*;
use self::loader::*;
use once_cell::sync::OnceCell;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufWriter};
use std::path::{Path, PathBuf};
#[derive(Debug, Error)]
pub enum Error {
#[error("error reading loader enrties directory")]
EntriesDir(#[source] io::Error),
#[error("error parsing entry at {:?}", path)]
Entry { path: PathBuf, source: EntryError },
#[error("error writing entry file")]
EntryWrite(#[source] io::Error),
#[error("error reading entry in loader entries directory")]
FileEntry(#[source] io::Error),
#[error("error parsing loader conf at {:?}", path)]
Loader { path: PathBuf, source: LoaderError },
#[error("error writing loader file")]
LoaderWrite(#[source] io::Error),
#[error("entry not found in data structure")]
NotFound,
}
#[derive(Debug, Clone)]
pub struct SystemdBootConf {
pub efi_mount: Box<Path>,
pub entries_path: Box<Path>,
pub loader_path: Box<Path>,
pub entries: Vec<Entry>,
pub loader_conf: LoaderConf,
}
impl SystemdBootConf {
pub fn new<P: Into<PathBuf>>(efi_mount: P) -> Result<Self, Error> {
let efi_mount = efi_mount.into();
let entries_path = efi_mount.join("loader/entries").into();
let loader_path = efi_mount.join("loader/loader.conf").into();
let mut manager = Self {
efi_mount: efi_mount.into(),
entries_path,
loader_path,
entries: Vec::default(),
loader_conf: LoaderConf::default(),
};
manager.load_conf()?;
manager.load_entries()?;
Ok(manager)
}
pub fn current_entry(&self) -> Option<&Entry> {
self.entries.iter().find(|e| e.is_current())
}
pub fn default_entry_exists(&self) -> DefaultState {
match self.loader_conf.default {
Some(ref default) => {
if self.entry_exists(default) {
DefaultState::Exists
} else {
DefaultState::DoesNotExist
}
}
None => DefaultState::NotDefined,
}
}
pub fn entry_exists(&self, entry: &str) -> bool {
self.entries.iter().any(|e| e.id.as_ref() == entry)
}
pub fn get(&self, entry: &str) -> Option<&Entry> {
self.entries.iter().find(|e| e.id.as_ref() == entry)
}
pub fn get_mut(&mut self, entry: &str) -> Option<&mut Entry> {
self.entries.iter_mut().find(|e| e.id.as_ref() == entry)
}
pub fn load_conf(&mut self) -> Result<(), Error> {
let &mut SystemdBootConf {
ref mut loader_conf,
ref loader_path,
..
} = self;
*loader_conf = LoaderConf::from_path(loader_path).map_err(move |source| Error::Loader {
path: loader_path.to_path_buf(),
source,
})?;
Ok(())
}
pub fn load_entries(&mut self) -> Result<(), Error> {
let &mut SystemdBootConf {
ref mut entries,
ref entries_path,
..
} = self;
let dir_entries = fs::read_dir(entries_path).map_err(Error::EntriesDir)?;
entries.clear();
for entry in dir_entries {
let entry = entry.map_err(Error::FileEntry)?;
let path = entry.path();
if !path.is_file() || path.extension().map_or(true, |ext| ext != "conf") {
continue;
}
let entry = Entry::from_path(&path).map_err(move |source| Error::Entry {
path: path.to_path_buf(),
source,
})?;
entries.push(entry);
}
Ok(())
}
pub fn overwrite_loader_conf(&self) -> Result<(), Error> {
let result = Self::try_io(&self.loader_path, |file| {
if let Some(ref default) = self.loader_conf.default {
writeln!(file, "default {}", default)?;
}
if let Some(timeout) = self.loader_conf.timeout {
writeln!(file, "timeout {}", timeout)?;
}
Ok(())
});
result.map_err(Error::LoaderWrite)
}
pub fn overwrite_entry_conf(&self, entry: &str) -> Result<(), Error> {
let entry = match self.get(entry) {
Some(entry) => entry,
None => return Err(Error::NotFound),
};
let result = Self::try_io(
&self.entries_path.join(format!("{}.conf", entry.id)),
move |file| {
writeln!(file, "title {}", entry.title)?;
writeln!(file, "linux {}", entry.linux)?;
if let Some(ref initrd) = entry.initrd {
writeln!(file, "initrd {}", initrd)?;
}
if !entry.options.is_empty() {
writeln!(file, "options: {}", entry.options.join(" "))?;
}
Ok(())
},
);
result.map_err(Error::EntryWrite)
}
fn try_io<F: FnMut(&mut BufWriter<File>) -> io::Result<()>>(
path: &Path,
mut instructions: F,
) -> io::Result<()> {
instructions(&mut BufWriter::new(File::create(path)?))
}
}
#[derive(Debug, Copy, Clone)]
pub enum DefaultState {
NotDefined,
Exists,
DoesNotExist,
}
pub fn kernel_cmdline() -> &'static [&'static str] {
static CMDLINE_BUF: OnceCell<Box<str>> = OnceCell::new();
static CMDLINE: OnceCell<Box<[&'static str]>> = OnceCell::new();
CMDLINE.get_or_init(|| {
let cmdline = CMDLINE_BUF.get_or_init(|| {
fs::read_to_string("/proc/cmdline")
.unwrap_or_default()
.into()
});
cmdline
.split_ascii_whitespace()
.collect::<Vec<&'static str>>()
.into()
})
}