systemd_boot_conf/
entry.rs1use itertools::Itertools;
2use std::fs::File;
3use std::io::{self, BufRead, BufReader};
4use std::path::Path;
5
6#[derive(Debug, Error)]
7pub enum EntryError {
8 #[error("error reading line in entry file")]
9 Line(#[source] io::Error),
10 #[error("title field is missing")]
11 MisisngTitle,
12 #[error("entry is not a file")]
13 NotAFile,
14 #[error("entry does not have a file name")]
15 NoFilename,
16 #[error("initrd was defined without a value")]
17 NoValueForInitrd,
18 #[error("linux was defined without a value")]
19 NoValueForLinux,
20 #[error("error opening entry file")]
21 Open(#[source] io::Error),
22 #[error("entry has a file name that is not UTF-8")]
23 Utf8Filename,
24}
25
26#[derive(Debug, Default, Clone)]
27pub struct Entry {
28 pub id: Box<str>,
29 pub initrd: Option<Box<str>>,
30 pub linux: Box<str>,
31 pub options: Vec<Box<str>>,
32 pub title: Box<str>,
33}
34
35impl Entry {
36 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, EntryError> {
37 let path = path.as_ref();
38
39 if !path.is_file() {
40 return Err(EntryError::NotAFile);
41 }
42
43 let file_name = match path.file_stem() {
44 Some(file_name) => match file_name.to_str() {
45 Some(file_name) => file_name.to_owned(),
46 None => return Err(EntryError::Utf8Filename),
47 },
48 None => return Err(EntryError::NoFilename),
49 };
50
51 let file = File::open(path).map_err(EntryError::Open)?;
52
53 let mut entry = Entry::default();
54 entry.id = file_name.into();
55
56 for line in BufReader::new(file).lines() {
57 let line = line.map_err(EntryError::Line)?;
58 let mut fields = line.split_whitespace();
59 match fields.next() {
60 Some("title") => entry.title = fields.join(" ").into(),
61 Some("linux") => match fields.next() {
62 Some(value) => entry.linux = value.into(),
63 None => return Err(EntryError::NoValueForLinux),
64 },
65 Some("initrd") => match fields.next() {
66 Some(value) => entry.initrd = Some(value.into()),
67 None => return Err(EntryError::NoValueForInitrd),
68 },
69 Some("options") => entry.options = fields.map(Box::from).collect(),
70 _ => (),
71 }
72 }
73
74 if entry.title.is_empty() {
75 return Err(EntryError::MisisngTitle);
76 }
77
78 Ok(entry)
79 }
80
81 pub fn is_current(&self) -> bool {
87 let initrd = self
88 .initrd
89 .as_ref()
90 .map(|x| ["initrd=", &x.replace('/', "\\")].concat());
91
92 let initrd = initrd.as_ref().map(String::as_str);
93 let options = self.options.iter().map(Box::as_ref);
94
95 let expected_cmdline = initrd.iter().cloned().chain(options);
96
97 crate::kernel_cmdline()
98 .iter()
99 .cloned()
100 .zip(expected_cmdline)
101 .all(|(a, b)| a == b)
102 }
103}