Merge pull request #326 from LibreQoE/format_preserving_toml

Format-preserving TOML editing
This commit is contained in:
Robert Chacón 2023-04-11 09:57:00 -06:00 committed by GitHub
commit f70c4ca639
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 40 deletions

16
src/rust/Cargo.lock generated
View File

@ -812,7 +812,7 @@ dependencies = [
"atomic", "atomic",
"pear", "pear",
"serde", "serde",
"toml 0.5.11", "toml",
"uncased", "uncased",
"version_check", "version_check",
] ]
@ -1436,7 +1436,7 @@ dependencies = [
"serde_json", "serde_json",
"sha2", "sha2",
"thiserror", "thiserror",
"toml 0.7.2", "toml_edit",
"uuid", "uuid",
] ]
@ -2787,18 +2787,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.1" version = "0.6.1"

View File

@ -6,7 +6,7 @@ license = "GPL-2.0-only"
[dependencies] [dependencies]
thiserror = "1" thiserror = "1"
toml = "0" toml_edit = { version = "0", features = [ "serde" ] }
serde = { version = "1.0", features = [ "derive" ] } serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1" serde_json = "1"
csv = "1" csv = "1"

View File

@ -68,7 +68,7 @@ impl WebUsers {
fn save_to_disk(&self) -> Result<(), AuthenticationError> { fn save_to_disk(&self) -> Result<(), AuthenticationError> {
let path = Self::path()?; let path = Self::path()?;
let new_contents = toml::to_string(&self); let new_contents = toml_edit::ser::to_string(&self);
if let Err(e) = new_contents { if let Err(e) = new_contents {
return Err(AuthenticationError::SerializationError(e)); return Err(AuthenticationError::SerializationError(e));
} }
@ -109,7 +109,7 @@ impl WebUsers {
} else { } else {
// Load from disk // Load from disk
if let Ok(raw) = read_to_string(path) { if let Ok(raw) = read_to_string(path) {
let parse_result = toml::from_str(&raw); let parse_result = toml_edit::de::from_str(&raw);
if let Ok(users) = parse_result { if let Ok(users) = parse_result {
Ok(users) Ok(users)
} else { } else {
@ -255,7 +255,7 @@ pub enum AuthenticationError {
#[error("Unable to load /etc/lqos.conf")] #[error("Unable to load /etc/lqos.conf")]
UnableToLoadEtcLqos, UnableToLoadEtcLqos,
#[error("Unable to serialize to TOML")] #[error("Unable to serialize to TOML")]
SerializationError(toml::ser::Error), SerializationError(toml_edit::ser::Error),
#[error("Unable to remove existing web users file")] #[error("Unable to remove existing web users file")]
UnableToDelete, UnableToDelete,
#[error("Unable to open lqusers.toml for writing. Check permissions?")] #[error("Unable to open lqusers.toml for writing. Check permissions?")]

View File

@ -1,6 +1,7 @@
//! Manages the `/etc/lqos.conf` file. //! Manages the `/etc/lqos.conf` file.
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use toml_edit::{Document, value};
use std::{fs, path::Path}; use std::{fs, path::Path};
use thiserror::Error; use thiserror::Error;
@ -136,17 +137,27 @@ impl EtcLqos {
return Err(EtcLqosError::ConfigDoesNotExist); return Err(EtcLqosError::ConfigDoesNotExist);
} }
if let Ok(raw) = std::fs::read_to_string("/etc/lqos.conf") { if let Ok(raw) = std::fs::read_to_string("/etc/lqos.conf") {
let config_result: Result<Self, toml::de::Error> = toml::from_str(&raw); let document = raw.parse::<Document>();
match config_result { match document {
Ok(mut config) => {
check_config(&mut config);
Ok(config)
}
Err(e) => { Err(e) => {
error!("Unable to parse TOML from /etc/lqos.conf"); error!("Unable to parse TOML from /etc/lqos.conf");
error!("Full error: {:?}", e); error!("Full error: {:?}", e);
Err(EtcLqosError::CannotParseToml) Err(EtcLqosError::CannotParseToml)
} }
Ok(mut config_doc) => {
let cfg = toml_edit::de::from_document::<EtcLqos>(config_doc.clone());
match cfg {
Ok(mut cfg) => {
check_config(&mut config_doc, &mut cfg);
Ok(cfg)
}
Err(e) => {
error!("Unable to parse TOML from /etc/lqos.conf");
error!("Full error: {:?}", e);
Err(EtcLqosError::CannotParseToml)
}
}
}
} }
} else { } else {
error!("Unable to read contents of /etc/lqos.conf"); error!("Unable to read contents of /etc/lqos.conf");
@ -156,7 +167,7 @@ impl EtcLqos {
/// Saves changes made to /etc/lqos.conf /// Saves changes made to /etc/lqos.conf
/// Copies current configuration into /etc/lqos.conf.backup first /// Copies current configuration into /etc/lqos.conf.backup first
pub fn save(&self) -> Result<(), EtcLqosError> { pub fn save(&self, document: &mut Document) -> Result<(), EtcLqosError> {
let cfg_path = Path::new("/etc/lqos.conf"); let cfg_path = Path::new("/etc/lqos.conf");
let backup_path = Path::new("/etc/lqos.conf.backup"); let backup_path = Path::new("/etc/lqos.conf.backup");
if let Err(e) = std::fs::copy(cfg_path, backup_path) { if let Err(e) = std::fs::copy(cfg_path, backup_path) {
@ -164,26 +175,17 @@ impl EtcLqos {
log::error!("{e:?}"); log::error!("{e:?}");
return Err(EtcLqosError::BackupFail); return Err(EtcLqosError::BackupFail);
} }
let new_cfg = toml::to_string_pretty(&self); let new_cfg = document.to_string();
match new_cfg { if let Err(e) = fs::write(cfg_path, new_cfg) {
Err(e) => { log::error!("Unable to write to /etc/lqos.conf");
log::error!("Unable to serialize new /etc/lqos.conf"); log::error!("{e:?}");
log::error!("{e:?}"); return Err(EtcLqosError::WriteFail);
return Err(EtcLqosError::SerializeFail);
}
Ok(new_cfg) => {
if let Err(e) = fs::write(cfg_path, new_cfg) {
log::error!("Unable to write to /etc/lqos.conf");
log::error!("{e:?}");
return Err(EtcLqosError::WriteFail);
}
}
} }
Ok(()) Ok(())
} }
} }
fn check_config(cfg: &mut EtcLqos) { fn check_config(cfg_doc: &mut Document, cfg: &mut EtcLqos) {
use sha2::digest::Update; use sha2::digest::Update;
use sha2::Digest; use sha2::Digest;
@ -191,6 +193,12 @@ fn check_config(cfg: &mut EtcLqos) {
if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id") { if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id") {
let hash = sha2::Sha256::new().chain(machine_id).finalize(); let hash = sha2::Sha256::new().chain(machine_id).finalize();
cfg.node_id = Some(format!("{:x}", hash)); cfg.node_id = Some(format!("{:x}", hash));
cfg_doc["node_id"] = value(format!("{:x}", hash));
println!("Updating");
if let Err(e) = cfg.save(cfg_doc) {
log::error!("Unable to save /etc/lqos.conf");
log::error!("{e:?}");
}
} }
} }
} }
@ -212,3 +220,23 @@ pub enum EtcLqosError {
#[error("Unable to write to /etc/lqos.conf")] #[error("Unable to write to /etc/lqos.conf")]
WriteFail, WriteFail,
} }
#[cfg(test)]
mod test {
const EXAMPLE_LQOS_CONF: &str = include_str!("../../../lqos.example");
#[test]
fn round_trip_toml() {
let doc = EXAMPLE_LQOS_CONF.parse::<toml_edit::Document>().unwrap();
let reserialized = doc.to_string();
assert_eq!(EXAMPLE_LQOS_CONF, reserialized);
}
#[test]
fn add_node_id() {
let mut doc = EXAMPLE_LQOS_CONF.parse::<toml_edit::Document>().unwrap();
doc["node_id"] = toml_edit::value("test");
let reserialized = doc.to_string();
assert!(reserialized.contains("node_id = \"test\""));
}
}