introduce proxmox-systemd crate

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2024-07-23 13:15:13 +02:00
parent cecd08df58
commit ab41d326e4
12 changed files with 280 additions and 0 deletions

View File

@ -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" }

View File

@ -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 {

View File

@ -20,6 +20,7 @@ fn parse_hex_digit(d: u8) -> Result<u8, Error> {
}
/// Escape strings for usage in systemd unit names
#[deprecated = "use proxmox_systemd::escape_unit"]
pub fn escape_unit<P: AsRef<[u8]>>(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<String, Error> {
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<PathBuf, Error> {
Ok(OsString::from_vec(unescape_unit_do(text)?).into())
}

View File

@ -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

View File

@ -0,0 +1,5 @@
rust-proxmox-systemd (0.1.0-1) bookworm; urgency=medium
* initial split out of proxmox-sys
-- Proxmox Support Team <support@proxmox.com> Tue, 23 Jul 2024 13:15:03 +0200

View File

@ -0,0 +1,18 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
*
Copyright: 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
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 <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,7 @@
overlay = "."
crate_src_path = ".."
maintainer = "Proxmox Support Team <support@proxmox.com>"
[source]
vcs_git = "git://git.proxmox.com/git/proxmox.git"
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"

View File

@ -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<P: AsRef<[u8]>>(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, UnescapeError> {
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<PathBuf, UnescapeError> {
Ok(OsString::from_vec(unescape_unit_do(text)?).into())
}
/// Unescape strings used in systemd unit names
fn unescape_unit_do(text: &str) -> Result<Vec<u8>, UnescapeError> {
let mut i = text.as_bytes();
let mut data: Vec<u8> = 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<u8, UnescapeError> {
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"))
}

View File

@ -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<I: AsRef<OsStr>>(
identifier: I,
priority: c_int,
level_prefix: bool,
) -> Result<OwnedFd, io::Error> {
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) })
}
}

View File

@ -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;

View File

@ -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)
}

View File

@ -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<c_int, io::Error> {
if ret < 0 {
Err(io::Error::from_raw_os_error(-ret))
} else {
Ok(ret)
}
}