diff --git a/Cargo.toml b/Cargo.toml index a9a0d34d..490c17a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "pbs-buildcfg", "pbs-datastore", "pbs-runtime", + "pbs-systemd", "pbs-tools", ] @@ -98,6 +99,7 @@ pbs-api-types = { path = "pbs-api-types" } pbs-buildcfg = { path = "pbs-buildcfg" } pbs-datastore = { path = "pbs-datastore" } pbs-runtime = { path = "pbs-runtime" } +pbs-systemd = { path = "pbs-systemd" } pbs-tools = { path = "pbs-tools" } [features] diff --git a/Makefile b/Makefile index d513af07..297f0819 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ SUBCRATES := \ pbs-buildcfg \ pbs-datastore \ pbs-runtime \ + pbs-systemd \ pbs-tools ifeq ($(BUILD_MODE), release) diff --git a/pbs-systemd/Cargo.toml b/pbs-systemd/Cargo.toml new file mode 100644 index 00000000..a95aba2e --- /dev/null +++ b/pbs-systemd/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pbs-systemd" +version = "0.1.0" +authors = ["Proxmox Support Team "] +edition = "2018" +description = "common systemd-related helpers, but no unit parsing" + +[dependencies] +anyhow = "1.0" +bitflags = "1.2.1" +lazy_static = "1.4" +nom = "5.1" + +proxmox = { version = "0.11.5", default-features = false } + +pbs-tools = { path = "../pbs-tools" } diff --git a/pbs-systemd/src/lib.rs b/pbs-systemd/src/lib.rs new file mode 100644 index 00000000..b4ab4b72 --- /dev/null +++ b/pbs-systemd/src/lib.rs @@ -0,0 +1,5 @@ +pub mod time; + +mod parse_time; +mod unit; +pub use unit::*; diff --git a/src/tools/systemd/parse_time.rs b/pbs-systemd/src/parse_time.rs similarity index 99% rename from src/tools/systemd/parse_time.rs rename to pbs-systemd/src/parse_time.rs index c0f58b04..05ac5672 100644 --- a/src/tools/systemd/parse_time.rs +++ b/pbs-systemd/src/parse_time.rs @@ -5,7 +5,7 @@ use lazy_static::lazy_static; use super::time::*; -use crate::tools::nom::{ +use pbs_tools::nom::{ parse_complete_line, parse_u64, parse_error, IResult, }; diff --git a/src/tools/systemd/time.rs b/pbs-systemd/src/time.rs similarity index 100% rename from src/tools/systemd/time.rs rename to pbs-systemd/src/time.rs diff --git a/pbs-systemd/src/unit.rs b/pbs-systemd/src/unit.rs new file mode 100644 index 00000000..811493fe --- /dev/null +++ b/pbs-systemd/src/unit.rs @@ -0,0 +1,174 @@ +use anyhow::{bail, Error}; + +use pbs_tools::run_command; + +/// Escape strings for usage in systemd unit names +pub fn escape_unit(mut unit: &str, is_path: bool) -> String { + if is_path { + unit = unit.trim_matches('/'); + if unit.is_empty() { + return String::from("-"); + } + } + + let unit = unit.as_bytes(); + + let mut escaped = String::new(); + + for (i, c) in unit.iter().enumerate() { + if *c == b'/' { + escaped.push('-'); + continue; + } + if (i == 0 && *c == b'.') + || !(*c == b'_' + || *c == b'.' + || (*c >= b'0' && *c <= b'9') + || (*c >= b'A' && *c <= b'Z') + || (*c >= b'a' && *c <= b'z')) + { + escaped.push_str(&format!("\\x{:0x}", c)); + } else { + escaped.push(*c as char); + } + } + escaped +} + +fn parse_hex_digit(d: u8) -> Result { + if d >= b'0' && d <= b'9' { + return Ok(d - b'0'); + } + if d >= b'A' && d <= b'F' { + return Ok(d - b'A' + 10); + } + if d >= b'a' && d <= b'f' { + return Ok(d - b'a' + 10); + } + bail!("got invalid hex digit"); +} + +/// Unescape strings used in systemd unit names +pub fn unescape_unit(text: &str) -> Result { + let mut i = text.as_bytes(); + + let mut data: Vec = Vec::new(); + + loop { + if i.is_empty() { + break; + } + let next = i[0]; + if next == b'\\' { + if i.len() < 4 { + bail!("short input"); + } + if i[1] != b'x' { + bail!("unkwnown escape sequence"); + } + let h1 = parse_hex_digit(i[2])?; + let h0 = parse_hex_digit(i[3])?; + data.push(h1 << 4 | h0); + i = &i[4..] + } else if next == b'-' { + data.push(b'/'); + i = &i[1..] + } else { + data.push(next); + i = &i[1..] + } + } + + let text = String::from_utf8(data)?; + + Ok(text) +} + +pub fn reload_daemon() -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("daemon-reload"); + + run_command(command, None)?; + + Ok(()) +} + +pub fn disable_unit(unit: &str) -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("disable"); + command.arg(unit); + + run_command(command, None)?; + + Ok(()) +} + +pub fn enable_unit(unit: &str) -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("enable"); + command.arg(unit); + + run_command(command, None)?; + + Ok(()) +} + +pub fn start_unit(unit: &str) -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("start"); + command.arg(unit); + + run_command(command, None)?; + + Ok(()) +} + +pub fn stop_unit(unit: &str) -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("stop"); + command.arg(unit); + + run_command(command, None)?; + + Ok(()) +} + +pub fn reload_unit(unit: &str) -> Result<(), Error> { + let mut command = std::process::Command::new("systemctl"); + command.arg("try-reload-or-restart"); + command.arg(unit); + + run_command(command, None)?; + + Ok(()) +} + +#[test] +fn test_escape_unit() -> Result<(), Error> { + fn test_escape(i: &str, expected: &str, is_path: bool) { + let escaped = escape_unit(i, is_path); + assert_eq!(escaped, expected); + let unescaped = unescape_unit(&escaped).unwrap(); + if is_path { + let mut p = i.trim_matches('/'); + if p.is_empty() { + p = "/"; + } + assert_eq!(p, unescaped); + } else { + assert_eq!(i, unescaped); + } + } + + test_escape(".test", "\\x2etest", false); + test_escape("t.est", "t.est", false); + test_escape("_test_", "_test_", false); + + test_escape("/", "-", false); + test_escape("//", "--", false); + + test_escape("/", "-", true); + test_escape("//", "-", true); + + Ok(()) +} diff --git a/pbs-tools/Cargo.toml b/pbs-tools/Cargo.toml index 8087aed5..a041da09 100644 --- a/pbs-tools/Cargo.toml +++ b/pbs-tools/Cargo.toml @@ -10,6 +10,7 @@ description = "common tools used throughout pbs" anyhow = "1.0" libc = "0.2" nix = "0.19.1" +nom = "5.1" regex = "1.2" serde = "1.0" serde_json = "1.0" diff --git a/src/tools/systemd/mod.rs b/src/tools/systemd/mod.rs index c1760781..d0ae2b43 100644 --- a/src/tools/systemd/mod.rs +++ b/src/tools/systemd/mod.rs @@ -1,172 +1,7 @@ -pub mod types; +pub use pbs_systemd::reload_daemon; +pub use pbs_systemd::time; +pub use pbs_systemd::{disable_unit, enable_unit, reload_unit, start_unit, stop_unit}; +pub use pbs_systemd::{escape_unit, unescape_unit}; + pub mod config; - -mod parse_time; -pub mod time; - -use anyhow::{bail, Error}; - -/// Escape strings for usage in systemd unit names -pub fn escape_unit(mut unit: &str, is_path: bool) -> String { - - if is_path { - unit = unit.trim_matches('/'); - if unit.is_empty() { - return String::from("-"); - } - } - - let unit = unit.as_bytes(); - - let mut escaped = String::new(); - - for (i, c) in unit.iter().enumerate() { - if *c == b'/' { - escaped.push('-'); - continue; - } - if (i == 0 && *c == b'.') - || !(*c == b'_' || - *c == b'.' || - (*c >= b'0' && *c <= b'9') || - (*c >= b'A' && *c <= b'Z') || - (*c >= b'a' && *c <= b'z')) { - escaped.push_str(&format!("\\x{:0x}", c)); - } else { - escaped.push(*c as char); - } - } - escaped -} - -fn parse_hex_digit(d: u8) -> Result { - if d >= b'0' && d <= b'9' { return Ok(d - b'0'); } - if d >= b'A' && d <= b'F' { return Ok(d - b'A' + 10); } - if d >= b'a' && d <= b'f' { return Ok(d - b'a' + 10); } - bail!("got invalid hex digit"); -} - -/// Unescape strings used in systemd unit names -pub fn unescape_unit(text: &str) -> Result { - - let mut i = text.as_bytes(); - - let mut data: Vec = Vec::new(); - - loop { - if i.is_empty() { break; } - let next = i[0]; - if next == b'\\' { - if i.len() < 4 { bail!("short input"); } - if i[1] != b'x' { bail!("unkwnown escape sequence"); } - let h1 = parse_hex_digit(i[2])?; - let h0 = parse_hex_digit(i[3])?; - data.push(h1<<4|h0); - i = &i[4..] - } else if next == b'-' { - data.push(b'/'); - i = &i[1..] - } else { - data.push(next); - i = &i[1..] - } - } - - let text = String::from_utf8(data)?; - - Ok(text) -} - -pub fn reload_daemon() -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("daemon-reload"); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -pub fn disable_unit(unit: &str) -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("disable"); - command.arg(unit); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -pub fn enable_unit(unit: &str) -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("enable"); - command.arg(unit); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -pub fn start_unit(unit: &str) -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("start"); - command.arg(unit); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -pub fn stop_unit(unit: &str) -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("stop"); - command.arg(unit); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -pub fn reload_unit(unit: &str) -> Result<(), Error> { - - let mut command = std::process::Command::new("systemctl"); - command.arg("try-reload-or-restart"); - command.arg(unit); - - crate::tools::run_command(command, None)?; - - Ok(()) -} - -#[test] -fn test_escape_unit() -> Result<(), Error> { - - fn test_escape(i: &str, expected: &str, is_path: bool) { - let escaped = escape_unit(i, is_path); - assert_eq!(escaped, expected); - let unescaped = unescape_unit(&escaped).unwrap(); - if is_path { - let mut p = i.trim_matches('/'); - if p.is_empty() { p = "/"; } - assert_eq!(p, unescaped); - } else { - assert_eq!(i, unescaped); - } - } - - test_escape(".test", "\\x2etest", false); - test_escape("t.est", "t.est", false); - test_escape("_test_", "_test_", false); - - test_escape("/", "-", false); - test_escape("//", "--", false); - - test_escape("/", "-", true); - test_escape("//", "-", true); - - Ok(()) -} +pub mod types;