From 6c3f39c78cf42a4dd025235fa6fef72937a692b7 Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 2 Apr 2026 19:24:30 +0900 Subject: [PATCH 1/2] feat: add Linux-only systemd-logind support with utmp fallback - Gate all feat_systemd_logind #[cfg] attributes with target_os = "linux" so that non-Linux builds are unaffected by the libsystemd link requirement - Fall back to traditional getutxent() when SystemdUtmpxIter::new() fails at runtime, instead of returning an empty iterator --- src/uucore/src/lib/features.rs | 2 +- src/uucore/src/lib/features/utmpx.rs | 63 +++++++++++++++------------- src/uucore/src/lib/lib.rs | 2 +- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index e0e99aee6b0..f1b67233da0 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -92,7 +92,7 @@ pub mod selinux; pub mod signals; #[cfg(all(feature = "smack", target_os = "linux"))] pub mod smack; -#[cfg(feature = "feat_systemd_logind")] +#[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] pub mod systemd_logind; #[cfg(all( unix, diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 6c538f3a7e8..398fbf72ef8 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -41,7 +41,7 @@ use std::path::Path; use std::ptr; use std::sync::{Mutex, MutexGuard}; -#[cfg(feature = "feat_systemd_logind")] +#[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] use crate::features::systemd_logind; pub use self::ut::*; @@ -333,7 +333,7 @@ impl Utmpx { /// Only one instance of [`UtmpxIter`] may be active at a time. This /// function will block as long as one is still active. Beware! pub fn iter_all_records() -> UtmpxIter { - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] { // Use systemd-logind instead of traditional utmp when feature is enabled UtmpxIter::new_systemd() @@ -365,7 +365,7 @@ impl Utmpx { /// /// The same caveats as for [`Utmpx::iter_all_records`] apply. pub fn iter_all_records_from>(path: P) -> UtmpxIter { - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] { // Use systemd-logind for default utmp file when feature is enabled if path.as_ref() == Path::new(DEFAULT_FILE) { @@ -409,7 +409,7 @@ pub struct UtmpxIter { /// Ensure UtmpxIter is !Send. Technically redundant because MutexGuard /// is also !Send. phantom: PhantomData>, - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] systemd_iter: Option, } @@ -422,29 +422,34 @@ impl UtmpxIter { Self { guard, phantom: PhantomData, - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] systemd_iter: None, } } - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] fn new_systemd() -> Self { // PoisonErrors can safely be ignored let guard = LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); - let systemd_iter = match systemd_logind::SystemdUtmpxIter::new() { - Ok(iter) => iter, - Err(_) => { - // Like GNU coreutils: graceful degradation, not fallback to traditional utmp - // Return empty iterator rather than falling back (GNU coreutils also returns 0 when /var/run/utmp is not present, so we don't need to propagate the error here) - systemd_logind::SystemdUtmpxIter::empty() + if let Ok(iter) = systemd_logind::SystemdUtmpxIter::new() { + Self { + guard, + phantom: PhantomData, + systemd_iter: Some(iter), + } + } else { + // Fall back to traditional utmp when systemd-logind is unavailable + unsafe { + #[cfg_attr(target_env = "musl", allow(deprecated))] + setutxent(); + } + Self { + guard, + phantom: PhantomData, + systemd_iter: None, } - }; - Self { - guard, - phantom: PhantomData, - systemd_iter: Some(systemd_iter), } } } @@ -452,7 +457,7 @@ impl UtmpxIter { /// Wrapper type that can hold either traditional utmpx records or systemd records pub enum UtmpxRecord { Traditional(Box), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Systemd(systemd_logind::SystemdUtmpxCompat), } @@ -461,7 +466,7 @@ impl UtmpxRecord { pub fn record_type(&self) -> i16 { match self { Self::Traditional(utmpx) => utmpx.record_type(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.record_type(), } } @@ -470,7 +475,7 @@ impl UtmpxRecord { pub fn pid(&self) -> i32 { match self { Self::Traditional(utmpx) => utmpx.pid(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.pid(), } } @@ -479,7 +484,7 @@ impl UtmpxRecord { pub fn terminal_suffix(&self) -> String { match self { Self::Traditional(utmpx) => utmpx.terminal_suffix(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.terminal_suffix(), } } @@ -488,7 +493,7 @@ impl UtmpxRecord { pub fn user(&self) -> String { match self { Self::Traditional(utmpx) => utmpx.user(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.user(), } } @@ -497,7 +502,7 @@ impl UtmpxRecord { pub fn host(&self) -> String { match self { Self::Traditional(utmpx) => utmpx.host(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.host(), } } @@ -506,7 +511,7 @@ impl UtmpxRecord { pub fn tty_device(&self) -> String { match self { Self::Traditional(utmpx) => utmpx.tty_device(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.tty_device(), } } @@ -515,7 +520,7 @@ impl UtmpxRecord { pub fn login_time(&self) -> time::OffsetDateTime { match self { Self::Traditional(utmpx) => utmpx.login_time(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.login_time(), } } @@ -526,7 +531,7 @@ impl UtmpxRecord { pub fn exit_status(&self) -> (i16, i16) { match self { Self::Traditional(utmpx) => utmpx.exit_status(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.exit_status(), } } @@ -535,7 +540,7 @@ impl UtmpxRecord { pub fn is_user_process(&self) -> bool { match self { Self::Traditional(utmpx) => utmpx.is_user_process(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => systemd.is_user_process(), } } @@ -544,7 +549,7 @@ impl UtmpxRecord { pub fn canon_host(&self) -> IOResult { match self { Self::Traditional(utmpx) => utmpx.canon_host(), - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] Self::Systemd(systemd) => Ok(systemd.canon_host()), } } @@ -553,7 +558,7 @@ impl UtmpxRecord { impl Iterator for UtmpxIter { type Item = UtmpxRecord; fn next(&mut self) -> Option { - #[cfg(feature = "feat_systemd_logind")] + #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] { if let Some(ref mut systemd_iter) = self.systemd_iter { // We have a systemd iterator - use it exclusively (never fall back to traditional utmp) diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 4dfffed2995..ffe3674db29 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -75,7 +75,7 @@ pub use crate::features::ranges; pub use crate::features::ringbuffer; #[cfg(feature = "sum")] pub use crate::features::sum; -#[cfg(feature = "feat_systemd_logind")] +#[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] pub use crate::features::systemd_logind; #[cfg(feature = "time")] pub use crate::features::time; From 6e6246a1b4bf5e04927df31f2af0054593359ac4 Mon Sep 17 00:00:00 2001 From: mattsu Date: Fri, 22 May 2026 08:09:48 +0900 Subject: [PATCH 2/2] fix(utmpx): fix cfg guard for non-Linux builds with feat_systemd_logind The fallback branch used `not(feature)` which would cause a compile error on non-Linux platforms with `feat_systemd_logind` enabled, as neither the systemd nor the traditional utmp block would be selected. Also clarify the comment in `next()` to distinguish initialization-time fallback from exclusive iteration. --- src/uucore/src/lib/features/utmpx.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 398fbf72ef8..aa7a2e75447 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -339,7 +339,7 @@ impl Utmpx { UtmpxIter::new_systemd() } - #[cfg(not(feature = "feat_systemd_logind"))] + #[cfg(not(all(target_os = "linux", feature = "feat_systemd_logind")))] { let iter = UtmpxIter::new(); unsafe { @@ -561,7 +561,9 @@ impl Iterator for UtmpxIter { #[cfg(all(target_os = "linux", feature = "feat_systemd_logind"))] { if let Some(ref mut systemd_iter) = self.systemd_iter { - // We have a systemd iterator - use it exclusively (never fall back to traditional utmp) + // Once a systemd iterator was successfully created, use it exclusively. + // If systemd initialization failed, `systemd_iter` is None and we use + // traditional utmp below. return systemd_iter.next().map(UtmpxRecord::Systemd); } }