mount_fstab/write.rs
1//! fstab serialization and file writing.
2//!
3//! Provides `Display` implementations for `Fstab` and `Entry` with proper
4//! escape encoding, plus safe file writing (overwrite and atomic via
5//! tempfile + fsync + rename).
6
7use crate::escape::encode_escapes;
8use crate::types::{Entry, Fstab};
9use std::fmt;
10use std::io;
11use std::path::Path;
12
13/// Format an [`Fstab`] for display / serialization.
14///
15/// Outputs the intro comment (if any), each entry with its preceding
16/// comment (if any), and the trailing comment (if any).
17impl fmt::Display for Fstab {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 if let Some(ref intro) = self.intro_comment {
20 f.write_str(intro)?;
21 }
22 for entry in &self.entries {
23 if let Some(ref comment) = entry.comment {
24 f.write_str(comment)?;
25 }
26 write!(f, "{entry}")?;
27 }
28 if let Some(ref trailing) = self.trailing_comment {
29 f.write_str(trailing)?;
30 }
31 Ok(())
32 }
33}
34
35/// Format an [`Entry`] as an fstab data line.
36///
37/// The output includes the spec (with escape encoding), mount point
38/// (with escape encoding), filesystem type, and options. If either
39/// `freq` or `passno` is non-zero, both are included.
40impl fmt::Display for Entry {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(
43 f,
44 "{} {} {} {}",
45 encode_escapes(&self.spec.to_string()),
46 encode_escapes(&self.file.to_string()),
47 self.vfstype,
48 self.options,
49 )?;
50 if self.freq != 0 || self.passno != 0 {
51 write!(f, " {} {}", self.freq, self.passno)?;
52 }
53 writeln!(f)?;
54 Ok(())
55 }
56}
57
58impl Fstab {
59 /// Write the fstab to a file, overwriting if it exists.
60 ///
61 /// # Examples
62 ///
63 /// ```no_run
64 /// # use mount_fstab::Fstab;
65 /// let fstab = Fstab::new();
66 /// fstab.write_to("/tmp/fstab").unwrap();
67 /// ```
68 ///
69 /// # Errors
70 ///
71 /// Returns [`std::io::Error`] if the file cannot be written.
72 pub fn write_to(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
73 let content = self.to_string();
74 std::fs::write(path, content)
75 }
76
77 /// Atomically write the fstab to a file using a temp file, fsync, and rename.
78 ///
79 /// This ensures the file is never in a partially-written state:
80 /// 1. Write to a temporary file in the same directory
81 /// 2. Call `sync_all()` to flush data to disk
82 /// 3. Rename the temporary file over the target path (atomic on POSIX)
83 ///
84 /// # Examples
85 ///
86 /// ```no_run
87 /// # use mount_fstab::Fstab;
88 /// let fstab = Fstab::parse_str("UUID=root / ext4 defaults 0 1\n").unwrap();
89 /// fstab.write_atomic("/etc/fstab").unwrap();
90 /// ```
91 ///
92 /// # Errors
93 ///
94 /// Returns [`std::io::Error`] if the temp file cannot be created, written,
95 /// synced, or renamed.
96 pub fn write_atomic(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
97 let path = path.as_ref();
98 let parent = path.parent().unwrap_or(Path::new("."));
99 let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
100 let content = self.to_string();
101 io::Write::write_all(&mut tmp, content.as_bytes())?;
102 tmp.as_file_mut().sync_all()?;
103 tmp.persist(path).map_err(|e| e.error)?;
104 Ok(())
105 }
106}