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}