From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 2B1D01FF0E3 for ; Tue, 21 Jul 2026 15:55:56 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 1ED9621522; Tue, 21 Jul 2026 15:54:43 +0200 (CEST) From: Arthur Bied-Charreton To: pve-devel@lists.proxmox.com Subject: [PATCH proxmox 08/13] systemd: systemctl: add is-system-running helper Date: Tue, 21 Jul 2026 15:54:02 +0200 Message-ID: <20260721135407.372150-9-a.bied-charreton@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260721135407.372150-1-a.bied-charreton@proxmox.com> References: <20260721135407.372150-1-a.bied-charreton@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 2 DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: L7YTY34SXMX3HWPQ5C3OLBPWFQQTAQLG X-Message-ID-Hash: L7YTY34SXMX3HWPQ5C3OLBPWFQQTAQLG X-MailFrom: abied-charreton@jett.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Introduce new `systemctl` module with `is_system_running` helper. In some cases, it can be important to recognize whether a service is being stopped as the result of a system shutdown, as opposed to an explicit `systemctl stop`. For example, firewalls must differentiate between a manual stop and a shutdown. A manual stop should clear the ruleset, while a shutdown should ideally not touch it. `is_system_running` runs `systemctl is-system-running` and parses its output to allow differentiating between those cases. While the `is_` prefix implies a boolean return value by convention, the name mirrors the systemctl subcommand, which seems less confusing than inventing a new name for a wrapper. Signed-off-by: Arthur Bied-Charreton --- proxmox-systemd/Cargo.toml | 1 + proxmox-systemd/debian/control | 2 + proxmox-systemd/src/lib.rs | 2 + proxmox-systemd/src/systemctl.rs | 98 ++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 proxmox-systemd/src/systemctl.rs diff --git a/proxmox-systemd/Cargo.toml b/proxmox-systemd/Cargo.toml index 74ec03d3..387aa3c7 100644 --- a/proxmox-systemd/Cargo.toml +++ b/proxmox-systemd/Cargo.toml @@ -14,3 +14,4 @@ repository.workspace = true [dependencies] libc.workspace = true +thiserror.workspace = true diff --git a/proxmox-systemd/debian/control b/proxmox-systemd/debian/control index 766c9c78..d9e5d3a3 100644 --- a/proxmox-systemd/debian/control +++ b/proxmox-systemd/debian/control @@ -7,6 +7,7 @@ Build-Depends-Arch: cargo:native , rustc:native , libstd-rust-dev , librust-libc-0.2+default-dev (>= 0.2.107-~~) , + librust-thiserror-2+default-dev , libsystemd-dev Maintainer: Proxmox Support Team Standards-Version: 4.7.2 @@ -21,6 +22,7 @@ Multi-Arch: same Depends: ${misc:Depends}, librust-libc-0.2+default-dev (>= 0.2.107-~~), + librust-thiserror-2+default-dev, libsystemd-dev Provides: librust-proxmox-systemd+default-dev (= ${binary:Version}), diff --git a/proxmox-systemd/src/lib.rs b/proxmox-systemd/src/lib.rs index eff61d58..a0d8de2c 100644 --- a/proxmox-systemd/src/lib.rs +++ b/proxmox-systemd/src/lib.rs @@ -9,3 +9,5 @@ pub mod journal; pub mod notify; pub mod sd_id128; + +pub mod systemctl; diff --git a/proxmox-systemd/src/systemctl.rs b/proxmox-systemd/src/systemctl.rs new file mode 100644 index 00000000..e03820bc --- /dev/null +++ b/proxmox-systemd/src/systemctl.rs @@ -0,0 +1,98 @@ +use std::{str::FromStr, string::FromUtf8Error}; + +#[derive(thiserror::Error, Debug)] +pub enum SystemctlError { + #[error("could not run systemctl: {0}")] + Io(#[from] std::io::Error), + #[error("unexpected output: {0}")] + UnexpectedOutput(String), +} + +impl From for SystemctlError { + fn from(value: FromUtf8Error) -> Self { + Self::UnexpectedOutput(format!("output is not valid UTF-8: {value}")) + } +} + +/// Possible operational states of the system as returned by `systemctl is-system-running` [0]. +/// +/// [0]: https://www.freedesktop.org/software/systemd/man/latest/systemctl.html#is-system-running +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum SystemState { + /// Early bootup, before `basic.target` is reached or the [`SystemState::Maintenance`] + /// is entered. + Initializing, + /// Late bootup, before the job queue becomes idle for the first time, or one of the + /// rescue targets are reached. + Starting, + /// The system is fully operational. + Running, + /// The system is operational but one or more units failed. + Degraded, + /// The rescue or emergency target is active. + Maintenance, + /// The manager is shutting down. + Stopping, + /// The manager is not running. Specifically, this is the operational state if an + /// incompatible program is running as system manager (PID 1). + Offline, + /// The operational state could not be determined, due to lack of resources or another + /// error case. + Unknown, +} + +impl std::fmt::Display for SystemState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Initializing => write!(f, "initializing"), + Self::Starting => write!(f, "starting"), + Self::Running => write!(f, "running"), + Self::Degraded => write!(f, "degraded"), + Self::Maintenance => write!(f, "maintenance"), + Self::Stopping => write!(f, "stopping"), + Self::Offline => write!(f, "offline"), + Self::Unknown => write!(f, "unknown"), + } + } +} + +impl FromStr for SystemState { + type Err = SystemctlError; + + fn from_str(s: &str) -> Result { + match s { + "initializing" => Ok(Self::Initializing), + "starting" => Ok(Self::Starting), + "running" => Ok(Self::Running), + "degraded" => Ok(Self::Degraded), + "maintenance" => Ok(Self::Maintenance), + "stopping" => Ok(Self::Stopping), + "offline" => Ok(Self::Offline), + "unknown" => Ok(Self::Unknown), + other => Err(SystemctlError::UnexpectedOutput(other.into())), + } + } +} + +/// Get the current operational state of the system. +/// +/// This runs `systemctl is-system-running` [0] and parses the state printed to stdout. While the +/// command exits non-zero whenever the state is anything other than [`SystemState::Running`], this +/// function returns any recognized state wrapped in `Ok` instead. +/// +/// ## Errors +/// +/// Returns an error if the command could not be spawned, its output was not valid UTF-8, or if the +/// printed state was not one of the known [`SystemState`] variants. +/// +/// [0]: https://www.freedesktop.org/software/systemd/man/latest/systemctl.html#is-system-running +pub fn is_system_running() -> Result { + let output = std::process::Command::new("systemctl") + .arg("is-system-running") + .output()?; + + // Current system state is always printed to stdout. + let stdout = String::from_utf8(output.stdout)?; + + SystemState::from_str(stdout.trim()) +} -- 2.47.3