1#[macro_use]
4extern crate thiserror;
5
6pub mod entry;
7pub mod loader;
8
9use self::entry::*;
10use self::loader::*;
11
12use once_cell::sync::OnceCell;
13
14use std::fs;
15use std::fs::File;
16use std::io::prelude::*;
17use std::io::{self, BufWriter};
18use std::path::{Path, PathBuf};
19
20#[derive(Debug, Error)]
21pub enum Error {
22 #[error("error reading loader enrties directory")]
23 EntriesDir(#[source] io::Error),
24 #[error("error parsing entry at {:?}", path)]
25 Entry { path: PathBuf, source: EntryError },
26 #[error("error writing entry file")]
27 EntryWrite(#[source] io::Error),
28 #[error("error reading entry in loader entries directory")]
29 FileEntry(#[source] io::Error),
30 #[error("error parsing loader conf at {:?}", path)]
31 Loader { path: PathBuf, source: LoaderError },
32 #[error("error writing loader file")]
33 LoaderWrite(#[source] io::Error),
34 #[error("entry not found in data structure")]
35 NotFound,
36}
37
38#[derive(Debug, Clone)]
39pub struct SystemdBootConf {
40 pub efi_mount: Box<Path>,
41 pub entries_path: Box<Path>,
42 pub loader_path: Box<Path>,
43 pub entries: Vec<Entry>,
44 pub loader_conf: LoaderConf,
45}
46
47impl SystemdBootConf {
48 pub fn new<P: Into<PathBuf>>(efi_mount: P) -> Result<Self, Error> {
49 let efi_mount = efi_mount.into();
50 let entries_path = efi_mount.join("loader/entries").into();
51 let loader_path = efi_mount.join("loader/loader.conf").into();
52
53 let mut manager = Self {
54 efi_mount: efi_mount.into(),
55 entries_path,
56 loader_path,
57 entries: Vec::default(),
58 loader_conf: LoaderConf::default(),
59 };
60
61 manager.load_conf()?;
62 manager.load_entries()?;
63
64 Ok(manager)
65 }
66
67 pub fn current_entry(&self) -> Option<&Entry> {
74 self.entries.iter().find(|e| e.is_current())
75 }
76
77 pub fn default_entry_exists(&self) -> DefaultState {
79 match self.loader_conf.default {
80 Some(ref default) => {
81 if self.entry_exists(default) {
82 DefaultState::Exists
83 } else {
84 DefaultState::DoesNotExist
85 }
86 }
87 None => DefaultState::NotDefined,
88 }
89 }
90
91 pub fn entry_exists(&self, entry: &str) -> bool {
93 self.entries.iter().any(|e| e.id.as_ref() == entry)
94 }
95
96 pub fn get(&self, entry: &str) -> Option<&Entry> {
98 self.entries.iter().find(|e| e.id.as_ref() == entry)
99 }
100
101 pub fn get_mut(&mut self, entry: &str) -> Option<&mut Entry> {
103 self.entries.iter_mut().find(|e| e.id.as_ref() == entry)
104 }
105
106 pub fn load_conf(&mut self) -> Result<(), Error> {
108 let &mut SystemdBootConf {
109 ref mut loader_conf,
110 ref loader_path,
111 ..
112 } = self;
113
114 *loader_conf = LoaderConf::from_path(loader_path).map_err(move |source| Error::Loader {
115 path: loader_path.to_path_buf(),
116 source,
117 })?;
118
119 Ok(())
120 }
121
122 pub fn load_entries(&mut self) -> Result<(), Error> {
124 let &mut SystemdBootConf {
125 ref mut entries,
126 ref entries_path,
127 ..
128 } = self;
129 let dir_entries = fs::read_dir(entries_path).map_err(Error::EntriesDir)?;
130
131 entries.clear();
132 for entry in dir_entries {
133 let entry = entry.map_err(Error::FileEntry)?;
134 let path = entry.path();
135
136 if !path.is_file() || path.extension().map_or(true, |ext| ext != "conf") {
138 continue;
139 }
140
141 let entry = Entry::from_path(&path).map_err(move |source| Error::Entry {
142 path: path.to_path_buf(),
143 source,
144 })?;
145
146 entries.push(entry);
147 }
148
149 Ok(())
150 }
151
152 pub fn overwrite_loader_conf(&self) -> Result<(), Error> {
154 let result = Self::try_io(&self.loader_path, |file| {
155 if let Some(ref default) = self.loader_conf.default {
156 writeln!(file, "default {}", default)?;
157 }
158
159 if let Some(timeout) = self.loader_conf.timeout {
160 writeln!(file, "timeout {}", timeout)?;
161 }
162
163 Ok(())
164 });
165
166 result.map_err(Error::LoaderWrite)
167 }
168
169 pub fn overwrite_entry_conf(&self, entry: &str) -> Result<(), Error> {
171 let entry = match self.get(entry) {
172 Some(entry) => entry,
173 None => return Err(Error::NotFound),
174 };
175
176 let result = Self::try_io(
177 &self.entries_path.join(format!("{}.conf", entry.id)),
178 move |file| {
179 writeln!(file, "title {}", entry.title)?;
180 writeln!(file, "linux {}", entry.linux)?;
181
182 if let Some(ref initrd) = entry.initrd {
183 writeln!(file, "initrd {}", initrd)?;
184 }
185
186 if !entry.options.is_empty() {
187 writeln!(file, "options: {}", entry.options.join(" "))?;
188 }
189
190 Ok(())
191 },
192 );
193
194 result.map_err(Error::EntryWrite)
195 }
196
197 fn try_io<F: FnMut(&mut BufWriter<File>) -> io::Result<()>>(
198 path: &Path,
199 mut instructions: F,
200 ) -> io::Result<()> {
201 instructions(&mut BufWriter::new(File::create(path)?))
202 }
203}
204
205#[derive(Debug, Copy, Clone)]
206pub enum DefaultState {
207 NotDefined,
208 Exists,
209 DoesNotExist,
210}
211
212pub fn kernel_cmdline() -> &'static [&'static str] {
214 static CMDLINE_BUF: OnceCell<Box<str>> = OnceCell::new();
215 static CMDLINE: OnceCell<Box<[&'static str]>> = OnceCell::new();
216
217 CMDLINE.get_or_init(|| {
218 let cmdline = CMDLINE_BUF.get_or_init(|| {
219 fs::read_to_string("/proc/cmdline")
220 .unwrap_or_default()
221 .into()
222 });
223
224 cmdline
225 .split_ascii_whitespace()
226 .collect::<Vec<&'static str>>()
227 .into()
228 })
229}