use crate::error::{FstabError, ParseErrorKind};
use crate::escape::decode_escapes;
use crate::fstype::FsType;
use crate::options::Options;
use crate::spec::Spec;
use crate::types::{Entry, Fstab, MountPoint};
use std::path::Path;
fn parse_fstab(input: &str) -> Result<Fstab, FstabError> {
let mut fstab = Fstab::new();
let mut pending_comment = String::new();
let mut saw_blank_after_intro = false;
let mut has_entries = false;
for (line_no, raw_line) in input.lines().enumerate() {
let line_no = line_no + 1; let line = raw_line.trim_end_matches('\r');
let trimmed = line.trim_start_matches([' ', '\t']);
let first_char = trimmed.chars().next();
match first_char {
Some(ch) if ch != '#' => {
let entry = parse_data_line(trimmed, line_no)?;
has_entries = true;
let mut entry = entry;
if !pending_comment.is_empty() {
entry.comment = Some(std::mem::take(&mut pending_comment));
}
fstab.entries.push(entry);
}
_ => {
if first_char == Some('#') {
pending_comment.push_str(line);
pending_comment.push('\n');
} else if first_char.is_none() {
if !has_entries {
if !saw_blank_after_intro && !pending_comment.is_empty() {
pending_comment.push('\n');
fstab.intro_comment = Some(std::mem::take(&mut pending_comment));
saw_blank_after_intro = true;
} else if saw_blank_after_intro {
pending_comment.push('\n');
}
} else {
pending_comment.push('\n');
}
}
}
}
}
if !pending_comment.is_empty() {
if !has_entries {
fstab.intro_comment = Some(pending_comment);
} else {
fstab.trailing_comment = Some(pending_comment);
}
}
Ok(fstab)
}
fn parse_data_line(line: &str, line_no: usize) -> Result<Entry, FstabError> {
let fields: Vec<&str> = line.split([' ', '\t']).filter(|s| !s.is_empty()).collect();
let mut iter = fields.into_iter();
let spec_raw = iter.next().ok_or(FstabError::Parse {
line: line_no,
kind: ParseErrorKind::MissingField("spec"),
})?;
let spec = Spec::parse_raw(spec_raw).map_err(|e| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidSpec(e),
})?;
let file_raw = iter.next().ok_or(FstabError::Parse {
line: line_no,
kind: ParseErrorKind::MissingField("file"),
})?;
let file_decoded = decode_escapes(file_raw);
let file = MountPoint::new(file_decoded).map_err(|e| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidMountPoint(e),
})?;
let fstype_raw = iter.next().ok_or(FstabError::Parse {
line: line_no,
kind: ParseErrorKind::MissingField("vfstype"),
})?;
let fstype = FsType::parse(fstype_raw).map_err(|e| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidFsType(e),
})?;
let options_raw = iter.next();
let options = match options_raw {
Some(raw) => Options::parse(raw).map_err(|e| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidOptions(e),
})?,
None => Options::new(),
};
let freq_raw = iter.next();
let freq = match freq_raw {
Some(raw) => raw.parse::<u32>().map_err(|_| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidFreq(raw.to_owned()),
})?,
None => 0,
};
let passno_raw = iter.next();
let passno = match passno_raw {
Some(raw) => raw.parse::<u32>().map_err(|_| FstabError::Parse {
line: line_no,
kind: ParseErrorKind::InvalidPassNo(raw.to_owned()),
})?,
None => 0,
};
Ok(Entry {
spec,
file,
vfstype: fstype,
options,
freq,
passno,
comment: None,
})
}
impl Fstab {
pub fn parse_str(input: &str) -> Result<Self, FstabError> {
parse_fstab(input)
}
pub fn parse_file(path: impl AsRef<Path>) -> Result<Self, FstabError> {
let input = std::fs::read_to_string(path)?;
Self::parse_str(&input)
}
}