forked from Proxmox/proxmox
introduce proxmox-systemd crate
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
cecd08df58
commit
ab41d326e4
@ -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" }
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
|
15
proxmox-systemd/Cargo.toml
Normal file
15
proxmox-systemd/Cargo.toml
Normal 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
|
5
proxmox-systemd/debian/changelog
Normal file
5
proxmox-systemd/debian/changelog
Normal 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
|
18
proxmox-systemd/debian/copyright
Normal file
18
proxmox-systemd/debian/copyright
Normal 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/>.
|
7
proxmox-systemd/debian/debcargo.toml
Normal file
7
proxmox-systemd/debian/debcargo.toml
Normal 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"
|
125
proxmox-systemd/src/escape.rs
Normal file
125
proxmox-systemd/src/escape.rs
Normal 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"))
|
||||
}
|
27
proxmox-systemd/src/journal.rs
Normal file
27
proxmox-systemd/src/journal.rs
Normal 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) })
|
||||
}
|
||||
}
|
9
proxmox-systemd/src/lib.rs
Normal file
9
proxmox-systemd/src/lib.rs
Normal 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;
|
43
proxmox-systemd/src/notify.rs
Normal file
43
proxmox-systemd/src/notify.rs
Normal 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)
|
||||
}
|
21
proxmox-systemd/src/sys.rs
Normal file
21
proxmox-systemd/src/sys.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user