1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#[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()
})
}