From: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
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 [thread overview]
Message-ID: <20260721135407.372150-9-a.bied-charreton@proxmox.com> (raw)
In-Reply-To: <20260721135407.372150-1-a.bied-charreton@proxmox.com>
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 <a.bied-charreton@proxmox.com>
---
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 <!nocheck>,
rustc:native <!nocheck>,
libstd-rust-dev <!nocheck>,
librust-libc-0.2+default-dev (>= 0.2.107-~~) <!nocheck>,
+ librust-thiserror-2+default-dev <!nocheck>,
libsystemd-dev <!nocheck>
Maintainer: Proxmox Support Team <support@proxmox.com>
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<FromUtf8Error> 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<Self, Self::Err> {
+ 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<SystemState, SystemctlError> {
+ 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
next prev parent reply other threads:[~2026-07-21 13:55 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-21 13:53 [RFC firewall/manager/proxmox{,-firewall} 00/13] fix #5759: keep firewall rules up across boot and shutdown Arthur Bied-Charreton
2026-07-21 13:53 ` [PATCH pve-manager 01/13] network interface pinning: write new firewall config to local dir Arthur Bied-Charreton
2026-07-21 13:53 ` [PATCH pve-firewall 02/13] firewall: config: sort OPTIONS when serializing Arthur Bied-Charreton
2026-07-21 13:53 ` [PATCH pve-firewall 03/13] d/control: bump libpve-common-perl Arthur Bied-Charreton
2026-07-21 13:53 ` [PATCH pve-firewall 04/13] firewall: dump configs locally after applying Arthur Bied-Charreton
2026-07-21 13:53 ` [PATCH pve-firewall 05/13] fix #5759: firewall: do not remove chains when host is shutting down Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH pve-firewall 06/13] firewall: add restore command Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH pve-firewall 07/13] fix #5759: firewall: restore from dumped config before network-pre Arthur Bied-Charreton
2026-07-21 13:54 ` Arthur Bied-Charreton [this message]
2026-07-21 13:54 ` [PATCH proxmox-firewall 09/13] firewall: fix clippy warnings Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH proxmox-firewall 10/13] fix #5759: firewall: do not clear rules on system shutdown Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH proxmox-firewall 11/13] firewall: dump config to local directory after apply Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH proxmox-firewall 12/13] firewall: add restore command Arthur Bied-Charreton
2026-07-21 13:54 ` [PATCH proxmox-firewall 13/13] fix #5759: firewall: restore from dumped config before network-pre Arthur Bied-Charreton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260721135407.372150-9-a.bied-charreton@proxmox.com \
--to=a.bied-charreton@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.