From ab41d326e414ccfde11283f277a26a7a86e6898c Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Tue, 23 Jul 2024 13:15:13 +0200 Subject: [PATCH] introduce proxmox-systemd crate Signed-off-by: Wolfgang Bumiller --- Cargo.toml | 2 + proxmox-rest-server/src/daemon.rs | 5 ++ proxmox-sys/src/systemd.rs | 3 + proxmox-systemd/Cargo.toml | 15 ++++ proxmox-systemd/debian/changelog | 5 ++ proxmox-systemd/debian/copyright | 18 ++++ proxmox-systemd/debian/debcargo.toml | 7 ++ proxmox-systemd/src/escape.rs | 125 +++++++++++++++++++++++++++ proxmox-systemd/src/journal.rs | 27 ++++++ proxmox-systemd/src/lib.rs | 9 ++ proxmox-systemd/src/notify.rs | 43 +++++++++ proxmox-systemd/src/sys.rs | 21 +++++ 12 files changed, 280 insertions(+) create mode 100644 proxmox-systemd/Cargo.toml create mode 100644 proxmox-systemd/debian/changelog create mode 100644 proxmox-systemd/debian/copyright create mode 100644 proxmox-systemd/debian/debcargo.toml create mode 100644 proxmox-systemd/src/escape.rs create mode 100644 proxmox-systemd/src/journal.rs create mode 100644 proxmox-systemd/src/lib.rs create mode 100644 proxmox-systemd/src/notify.rs create mode 100644 proxmox-systemd/src/sys.rs diff --git a/Cargo.toml b/Cargo.toml index ca0527af..d396d46b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "proxmox-subscription", "proxmox-sys", "proxmox-syslog-api", + "proxmox-systemd", "proxmox-tfa", "proxmox-time", "proxmox-time-api", @@ -139,6 +140,7 @@ proxmox-serde = { version = "0.1.1", path = "proxmox-serde", features = [ "serde proxmox-shared-memory = { version = "0.3.0", path = "proxmox-shared-memory" } proxmox-sortable-macro = { version = "0.1.3", path = "proxmox-sortable-macro" } proxmox-sys = { version = "0.6.0", path = "proxmox-sys" } +proxmox-systemd = { version = "0.1.0", path = "proxmox-systemd" } proxmox-tfa = { version = "5.0.0", path = "proxmox-tfa" } proxmox-time = { version = "2.0.0", path = "proxmox-time" } proxmox-uuid = { version = "1.0.1", path = "proxmox-uuid" } diff --git a/proxmox-rest-server/src/daemon.rs b/proxmox-rest-server/src/daemon.rs index 3b5af3a6..5253a32b 100644 --- a/proxmox-rest-server/src/daemon.rs +++ b/proxmox-rest-server/src/daemon.rs @@ -375,6 +375,7 @@ where #[link(name = "systemd")] extern "C" { + #[deprecated = "use proxmox_systemd::journal::stream_fd"] fn sd_journal_stream_fd( identifier: *const c_uchar, priority: c_int, @@ -385,6 +386,7 @@ extern "C" { } /// Systemd sercice startup states (see: ``man sd_notify``) +#[deprecated = "use proxmox_systemd::SystemdNotify instead"] pub enum SystemdNotify { Ready, Reloading, @@ -394,6 +396,8 @@ pub enum SystemdNotify { } /// Tells systemd the startup state of the service (see: ``man sd_notify``) +#[deprecated = "use proxmox_systemd::notify::SystemdNotify::notify() instead"] +#[allow(deprecated)] pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> { let message = match state { SystemdNotify::Ready => { @@ -417,6 +421,7 @@ pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> { } /// Waits until all previously sent messages with sd_notify are processed +#[deprecated = "use proxmox_systemd::notify::barrier() instead"] pub fn systemd_notify_barrier(timeout: u64) -> Result<(), Error> { let rc = unsafe { sd_notify_barrier(0, timeout) }; if rc < 0 { diff --git a/proxmox-sys/src/systemd.rs b/proxmox-sys/src/systemd.rs index dceadfd6..d5284090 100644 --- a/proxmox-sys/src/systemd.rs +++ b/proxmox-sys/src/systemd.rs @@ -20,6 +20,7 @@ fn parse_hex_digit(d: u8) -> Result { } /// Escape strings for usage in systemd unit names +#[deprecated = "use proxmox_systemd::escape_unit"] pub fn escape_unit>(unit: P, is_path: bool) -> String { escape_unit_bytes(unit.as_ref(), is_path) } @@ -59,11 +60,13 @@ fn escape_unit_bytes(mut unit: &[u8], is_path: bool) -> String { } /// Unescape strings used in systemd unit names +#[deprecated = "use proxmox_systemd::unescape_unit"] pub fn unescape_unit(text: &str) -> Result { Ok(String::from_utf8(unescape_unit_do(text)?)?) } /// Unescape strings used in systemd unit names +#[deprecated = "use proxmox_systemd::unescape_unit_path"] pub fn unescape_unit_path(text: &str) -> Result { Ok(OsString::from_vec(unescape_unit_do(text)?).into()) } diff --git a/proxmox-systemd/Cargo.toml b/proxmox-systemd/Cargo.toml new file mode 100644 index 00000000..9fb7dbe7 --- /dev/null +++ b/proxmox-systemd/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "proxmox-systemd" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = """ +Utilities for dealing with systemd unit files and communicating with systemd. +""" + +exclude.workspace = true + +[dependencies] +libc.workspace = true diff --git a/proxmox-systemd/debian/changelog b/proxmox-systemd/debian/changelog new file mode 100644 index 00000000..4ef493e3 --- /dev/null +++ b/proxmox-systemd/debian/changelog @@ -0,0 +1,5 @@ +rust-proxmox-systemd (0.1.0-1) bookworm; urgency=medium + + * initial split out of proxmox-sys + + -- Proxmox Support Team Tue, 23 Jul 2024 13:15:03 +0200 diff --git a/proxmox-systemd/debian/copyright b/proxmox-systemd/debian/copyright new file mode 100644 index 00000000..869939c3 --- /dev/null +++ b/proxmox-systemd/debian/copyright @@ -0,0 +1,18 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: + * +Copyright: 2024 Proxmox Server Solutions GmbH +License: AGPL-3.0-or-later + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) any + later version. + . + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + details. + . + You should have received a copy of the GNU Affero General Public License along + with this program. If not, see . diff --git a/proxmox-systemd/debian/debcargo.toml b/proxmox-systemd/debian/debcargo.toml new file mode 100644 index 00000000..b7864cdb --- /dev/null +++ b/proxmox-systemd/debian/debcargo.toml @@ -0,0 +1,7 @@ +overlay = "." +crate_src_path = ".." +maintainer = "Proxmox Support Team " + +[source] +vcs_git = "git://git.proxmox.com/git/proxmox.git" +vcs_browser = "https://git.proxmox.com/?p=proxmox.git" diff --git a/proxmox-systemd/src/escape.rs b/proxmox-systemd/src/escape.rs new file mode 100644 index 00000000..f73beed3 --- /dev/null +++ b/proxmox-systemd/src/escape.rs @@ -0,0 +1,125 @@ +use std::error::Error as StdError; +use std::ffi::OsString; +use std::fmt; +use std::os::unix::ffi::OsStringExt; +use std::path::PathBuf; + +/// Escape strings for usage in systemd unit names +pub fn escape_unit>(unit: P, is_path: bool) -> String { + escape_unit_bytes(unit.as_ref(), is_path) +} + +fn escape_unit_bytes(mut unit: &[u8], is_path: bool) -> String { + if is_path { + while !unit.is_empty() && unit[0] == b'/' { + unit = &unit[1..]; + } + + if unit.is_empty() { + return String::from("-"); + } + } + + 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')) + { + use std::fmt::Write as _; + let _ = write!(escaped, "\\x{:02x}", c); + } else { + escaped.push(*c as char); + } + } + escaped +} + +#[derive(Debug)] +pub enum UnescapeError { + Msg(&'static str), + Utf8Error(std::string::FromUtf8Error), +} + +impl StdError for UnescapeError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self { + Self::Utf8Error(e) => Some(e), + _ => None, + } + } +} + +impl fmt::Display for UnescapeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Msg(err) => f.write_str(err), + Self::Utf8Error(err) => fmt::Display::fmt(err, f), + } + } +} + +/// Unescape strings used in systemd unit names +pub fn unescape_unit(text: &str) -> Result { + String::from_utf8(unescape_unit_do(text)?).map_err(UnescapeError::Utf8Error) +} + +/// Unescape strings used in systemd unit names +pub fn unescape_unit_path(text: &str) -> Result { + Ok(OsString::from_vec(unescape_unit_do(text)?).into()) +} + +/// Unescape strings used in systemd unit names +fn unescape_unit_do(text: &str) -> Result, UnescapeError> { + 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 { + return Err(UnescapeError::Msg("short input")); + } + if i[1] != b'x' { + return Err(UnescapeError::Msg("unknown 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..] + } + } + + Ok(data) +} + +fn parse_hex_digit(d: u8) -> Result { + if d.is_ascii_digit() { + return Ok(d - b'0'); + } + if (b'A'..=b'F').contains(&d) { + return Ok(d - b'A' + 10); + } + if (b'a'..=b'f').contains(&d) { + return Ok(d - b'a' + 10); + } + Err(UnescapeError::Msg("invalid hex digit")) +} diff --git a/proxmox-systemd/src/journal.rs b/proxmox-systemd/src/journal.rs new file mode 100644 index 00000000..7fbdcaa3 --- /dev/null +++ b/proxmox-systemd/src/journal.rs @@ -0,0 +1,27 @@ +use std::ffi::{c_int, CString, OsStr}; +use std::io; +use std::os::fd::{FromRawFd, OwnedFd}; +use std::os::unix::ffi::OsStrExt; + +use crate::sys; + +pub fn stream_fd>( + identifier: I, + priority: c_int, + level_prefix: bool, +) -> Result { + let ident = CString::new(identifier.as_ref().as_bytes()).map_err(|_| { + io::Error::new( + io::ErrorKind::Other, + "invalid identifier for journal stream", + ) + })?; + let fd = unsafe { + sys::sd_journal_stream_fd(ident.as_bytes().as_ptr(), priority, level_prefix as c_int) + }; + if fd < 0 { + Err(std::io::Error::from_raw_os_error(-fd)) + } else { + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) + } +} diff --git a/proxmox-systemd/src/lib.rs b/proxmox-systemd/src/lib.rs new file mode 100644 index 00000000..456d88c3 --- /dev/null +++ b/proxmox-systemd/src/lib.rs @@ -0,0 +1,9 @@ +//! Systemd communication. + +pub(crate) mod sys; + +mod escape; +pub use escape::{escape_unit, unescape_unit, unescape_unit_path, UnescapeError}; + +pub mod journal; +pub mod notify; diff --git a/proxmox-systemd/src/notify.rs b/proxmox-systemd/src/notify.rs new file mode 100644 index 00000000..6053d250 --- /dev/null +++ b/proxmox-systemd/src/notify.rs @@ -0,0 +1,43 @@ +use std::ffi::CString; +use std::io; + +use crate::sys; + +/// Systemd service startup states (see: ``man sd_notify``) +#[derive(Clone, Debug)] +pub enum SystemdNotify { + Ready, + Reloading, + Stopping, + Status(String), + MainPid(libc::pid_t), +} + +impl SystemdNotify { + /// Tells systemd the startup state of the service (see: ``man sd_notify``) + /// + /// If a `SystemdNotify::Status` message cannot be converted to a C-String this returns an + /// `io::ErrorKind::InvalidInput`. + pub fn notify(self) -> Result<(), io::Error> { + let cs; + let message = match self { + SystemdNotify::Ready => c"READY=1", + SystemdNotify::Reloading => c"RELOADING=1", + SystemdNotify::Stopping => c"STOPPING=1", + SystemdNotify::Status(msg) => { + cs = CString::new(msg)?; + &cs + } + SystemdNotify::MainPid(pid) => { + cs = CString::new(format!("MAINPID={}", pid))?; + &cs + } + }; + sys::check_call(unsafe { sys::sd_notify(0, message.as_ptr()) }).map(drop) + } +} + +/// Waits until all previously sent messages with sd_notify are processed +pub fn barrier(timeout: u64) -> Result<(), io::Error> { + sys::check_call(unsafe { sys::sd_notify_barrier(0, timeout) }).map(drop) +} diff --git a/proxmox-systemd/src/sys.rs b/proxmox-systemd/src/sys.rs new file mode 100644 index 00000000..eb0ccd88 --- /dev/null +++ b/proxmox-systemd/src/sys.rs @@ -0,0 +1,21 @@ +use std::ffi::{c_char, c_int, c_uchar}; +use std::io; + +#[link(name = "systemd")] +extern "C" { + pub fn sd_journal_stream_fd( + identifier: *const c_uchar, + priority: c_int, + level_prefix: c_int, + ) -> c_int; + pub fn sd_notify(unset_environment: c_int, state: *const c_char) -> c_int; + pub fn sd_notify_barrier(unset_environment: c_int, timeout: u64) -> c_int; +} + +pub fn check_call(ret: c_int) -> Result { + if ret < 0 { + Err(io::Error::from_raw_os_error(-ret)) + } else { + Ok(ret) + } +}