libsdbootconf/lib.rs
1//! This library provides a configuration interface for systemd-boot. It parses systemd-boot loader
2//! configuration and systemd-boot entry configuration.
3//!
4//! **NOTE**: Not all fields in <https://www.freedesktop.org/software/systemd/man/systemd-boot.html>
5//! are implemented, this library currently only provides interface for the fields listed on
6//! <https://www.freedesktop.org/wiki/Software/systemd/systemd-boot/>.
7//!
8//! # Create and write a new systemd-boot configuration
9//!
10//! You can use the `SystemdBootConfig` struct to create a new systemd-boot configuration, or use
11//! `SystemdBootConfigBuilder` to build a `SystemdBootConfig` from scratch.
12//!
13//! ```no_run
14//! use libsdbootconf::{ConfigBuilder, EntryBuilder, SystemdBootConfBuilder};
15//!
16//! let systemd_boot_conf = SystemdBootConfBuilder::new("/efi/loader")
17//! .config(ConfigBuilder::new()
18//! .default("5.12.0-aosc-main")
19//! .timeout(5u32)
20//! .build())
21//! .entry(EntryBuilder::new("5.12.0-aosc-main")
22//! .title("AOSC OS x86_64 (5.12.0-aosc-main)")
23//! .version("5.12.0-aosc-main")
24//! .build())
25//! .build();
26//!
27//! // Or
28//! use libsdbootconf::{Config, Entry, SystemdBootConf, Token};
29//!
30//! let systemd_boot_conf = SystemdBootConf::new(
31//! "/efi/loader",
32//! Config::new(Some("5.12.0-aosc-main"), Some(5u32)),
33//! vec![Entry::new(
34//! "5.12.0-aosc-main",
35//! vec![
36//! Token::Title("AOSC OS x86_64 (5.12.0-aosc-main)".to_owned()),
37//! Token::Version("5.12.0-aosc-main".to_owned()),
38//! ],
39//! )]
40//! );
41//!
42//! systemd_boot_conf.write_all().unwrap();
43//! ```
44//!
45//! # Create a new systemd-boot menu entry
46//!
47//! ```no_run
48//! use libsdbootconf::entry::{Entry, EntryBuilder, Token};
49//! use std::path::PathBuf;
50//!
51//! let entry = EntryBuilder::new("5.12.0-aosc-main")
52//! .title("AOSC OS x86_64 (5.12.0-aosc-main)")
53//! .linux("/EFI/linux/vmlinux-5.12.0-aosc-main")
54//! .initrd("/EFI/linux/initramfs-5.12.0-aosc-main.img")
55//! .options("root=/dev/sda1 rw")
56//! .build();
57//!
58//! // Or
59//! let entry = Entry::new(
60//! "5.12.0-aosc-main",
61//! vec![
62//! Token::Title("AOSC OS x86_64 (5.12.0-aosc-main)".to_owned()),
63//! Token::Linux(PathBuf::from("/EFI/linux/vmlinux-5.12.0-aosc-main")),
64//! Token::Initrd(PathBuf::from("/EFI/linux/initramfs-5.12.0-aosc-main.img")),
65//! Token::Options("root=/dev/sda1 rw".to_owned()),
66//! ],
67//! );
68//!
69//! entry.write("/efi/loader/entries/5.12.0-aosc-main.conf").unwrap();
70//! ```
71
72use std::{
73 fs,
74 path::{Path, PathBuf},
75};
76use thiserror::Error;
77
78pub mod config;
79pub mod entry;
80mod macros;
81
82use crate::macros::generate_builder_method;
83pub use config::{Config, ConfigBuilder};
84pub use entry::{Entry, EntryBuilder, Token};
85
86#[derive(Error, Debug)]
87pub enum LibSDBootConfError {
88 #[error("invalid configuration")]
89 ConfigParseError,
90 #[error("invalid entry")]
91 EntryParseError,
92 #[error("invalid entry filename {0}")]
93 InvalidEntryFilename(PathBuf),
94 #[error(transparent)]
95 IOError(#[from] std::io::Error),
96 #[error("invalid token {0}")]
97 InvalidToken(String),
98}
99
100/// An abstraction over the basic structure of systemd-boot configurations.
101#[derive(Default, Debug)]
102pub struct SystemdBootConf {
103 pub working_dir: PathBuf,
104 pub config: Config,
105 pub entries: Vec<Entry>,
106}
107
108impl SystemdBootConf {
109 /// Create a new `SystemdBootConf` with a working directory, a configuration, and a list of
110 /// entries.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use libsdbootconf::{Config, SystemdBootConf};
116 ///
117 /// let systemd_boot_conf = SystemdBootConf::new("/efi/loader", Config::default(), Vec::new());
118 ///
119 /// assert_eq!(systemd_boot_conf.working_dir, std::path::PathBuf::from("/efi/loader"));
120 /// ```
121 pub fn new<P, C, E>(working_dir: P, config: C, entries: E) -> Self
122 where
123 P: Into<PathBuf>,
124 C: Into<Config>,
125 E: Into<Vec<Entry>>,
126 {
127 Self {
128 working_dir: working_dir.into(),
129 config: config.into(),
130 entries: entries.into(),
131 }
132 }
133
134 /// Initialize a new `SystemdBootConf` with a working directory.
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// use libsdbootconf::SystemdBootConf;
140 ///
141 /// let systemd_boot_conf = SystemdBootConf::init("/efi/loader");
142 ///
143 /// assert_eq!(systemd_boot_conf.working_dir, std::path::PathBuf::from("/efi/loader"));
144 /// ```
145 pub fn init<P: Into<PathBuf>>(working_dir: P) -> Self {
146 Self {
147 working_dir: working_dir.into(),
148 ..Default::default()
149 }
150 }
151
152 /// Read from an existing systemd-boot installation.
153 ///
154 /// # Examples
155 ///
156 /// ```no_run
157 /// use libsdbootconf::SystemdBootConf;
158 ///
159 /// let systemd_boot_conf = SystemdBootConf::load("/efi/loader").unwrap();
160 /// ```
161 pub fn load<P: AsRef<Path>>(working_dir: P) -> Result<Self, LibSDBootConfError> {
162 let mut systemd_boot_conf = Self::init(working_dir.as_ref());
163
164 systemd_boot_conf.load_current()?;
165
166 Ok(systemd_boot_conf)
167 }
168
169 /// Read from the current systemd-boot working directory.
170 ///
171 /// # Examples
172 ///
173 /// ```no_run
174 /// use libsdbootconf::SystemdBootConf;
175 ///
176 /// let mut systemd_boot_conf = SystemdBootConf::init("/efi/loader");
177 ///
178 /// systemd_boot_conf.load_current().unwrap();
179 /// ```
180 pub fn load_current(&mut self) -> Result<(), LibSDBootConfError> {
181 let config = Config::load(self.working_dir.join("loader.conf"))?;
182 let mut entries = Vec::new();
183
184 for file in fs::read_dir(self.working_dir.join("entries"))? {
185 let path = file?.path();
186 if path.is_file() {
187 let entry = Entry::load(&path)?;
188 entries.push(entry);
189 }
190 }
191
192 self.config = config;
193 self.entries = entries;
194
195 Ok(())
196 }
197
198 /// Write systemd-boot configuration file to the system.
199 ///
200 /// # Examples
201 ///
202 /// ```no_run
203 /// use libsdbootconf::SystemdBootConf;
204 ///
205 /// let systemd_boot_conf = SystemdBootConf::init("/efi/loader");
206 ///
207 /// systemd_boot_conf.write_config().unwrap();
208 /// ```
209 pub fn write_config(&self) -> Result<(), LibSDBootConfError> {
210 self.config.write(self.working_dir.join("loader.conf"))?;
211
212 Ok(())
213 }
214
215 /// Write all entries to the system.
216 ///
217 /// # Examples
218 ///
219 /// ```no_run
220 /// use libsdbootconf::SystemdBootConf;
221 ///
222 /// let systemd_boot_conf = SystemdBootConf::init("/efi/loader");
223 ///
224 /// systemd_boot_conf.write_entries().unwrap();
225 /// ```
226 pub fn write_entries(&self) -> Result<(), LibSDBootConfError> {
227 for entry in self.entries.iter() {
228 entry.write(
229 self.working_dir
230 .join("entries")
231 .join(format!("{}.conf", entry.id)),
232 )?;
233 }
234
235 Ok(())
236 }
237
238 /// Write all configurations and entries to the system.
239 ///
240 /// # Examples
241 ///
242 /// ```no_run
243 /// use libsdbootconf::SystemdBootConf;
244 ///
245 /// let systemd_boot_conf = SystemdBootConf::init("/efi/loader");
246 ///
247 /// systemd_boot_conf.write_all().unwrap();
248 /// ```
249 pub fn write_all(&self) -> Result<(), LibSDBootConfError> {
250 self.write_config()?;
251 self.write_entries()?;
252
253 Ok(())
254 }
255}
256
257/// Builder for `SystemdBootConf`.
258#[derive(Default, Debug)]
259pub struct SystemdBootConfBuilder {
260 inner: SystemdBootConf,
261}
262
263impl SystemdBootConfBuilder {
264 /// Create an empty `SystemdBootConfBuilder` with a working directory.
265 pub fn new<P: Into<PathBuf>>(working_dir: P) -> Self {
266 Self {
267 inner: SystemdBootConf::init(working_dir),
268 }
269 }
270
271 generate_builder_method!(
272 /// Add a systemd-boot loader `Config`.
273 plain INNER(inner) config(Config)
274 );
275 generate_builder_method!(
276 /// Add a list of `Entry`.
277 into INNER(inner) entries(E: Vec<Entry>)
278 );
279
280 /// Add an `Entry`
281 pub fn entry(mut self, entry: Entry) -> Self {
282 self.inner.entries.push(entry);
283
284 self
285 }
286
287 /// Build the `SystemdBootConf`.
288 pub fn build(self) -> SystemdBootConf {
289 self.inner
290 }
291}