add support for systemd-logind (#8483)

* add support for systemd-logind

Add support for systemd-logind for systems where `/var/run/utmp` does not exist any more (e.g. Ubuntu 25.04).

Here is some context explaining the switch from utmp to systemd-logind:
https://www.thkukuk.de/blog/Y2038_glibc_utmp_64bit/

Fixes https://github.com/uutils/coreutils/issues/8376

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* fix duplicated boot record

When feature feat_systemd_logind is enabled and utmp is present, two boot records were printed.

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* improve error forwarding

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* remove unnecessary negation in if/else block

Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>

* use getpwuid_r() instead of getpwuid()

getpwuid() is not thread-safe.

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* improve error handling

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* use Path instead of string

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* use closure to reduce the number of clones() calls

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* use MaybeUninit instead of mem::zeroed()

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* systemd-logind: add some unit-tests

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* use sysconf(_SC_GETPW_R_SIZE_MAX) instead of 1024

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* fix cspell

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* add auto-enablement of feat_systemd_logind in GNUmakefile

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>

* Revert "add auto-enablement of feat_systemd_logind in GNUmakefile"

This breaks "Tests/BusyBox test suite" in CI.

This reverts commit 16626d4653.

---------

Signed-off-by: Etienne Cordonnier <ecordonnier@snap.com>
Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
Etienne
2025-08-23 19:05:40 +02:00
committed by GitHub
parent 5ff5f38544
commit bba4d99042
16 changed files with 1018 additions and 41 deletions
+4 -4
View File
@@ -5,7 +5,7 @@ name: CICD
# spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain fuzzers dedupe devel profdata
# spell-checker:ignore (people) Peltoche rivy dtolnay Anson dawidd
# spell-checker:ignore (shell/tools) binutils choco clippy dmake dpkg esac fakeroot fdesc fdescfs gmake grcov halium lcov libclang libfuse libssl limactl mkdir nextest nocross pacman popd printf pushd redoxer rsync rustc rustfmt rustup shopt sccache utmpdump xargs
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils defconfig DESTDIR gecos getenforce gnueabihf issuecomment maint manpages msys multisize noconfirm nofeatures nullglob onexitbegin onexitend pell runtest Swatinem tempfile testsuite toybox uutils
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils defconfig DESTDIR gecos getenforce gnueabihf issuecomment maint manpages msys multisize noconfirm nofeatures nullglob onexitbegin onexitend pell runtest Swatinem tempfile testsuite toybox uutils libsystemd
env:
PROJECT_NAME: coreutils
@@ -425,7 +425,7 @@ jobs:
run: |
## Install dependencies
sudo apt-get update
sudo apt-get install jq libselinux1-dev
sudo apt-get install jq libselinux1-dev libsystemd-dev
- name: "`make install`"
shell: bash
run: |
@@ -714,9 +714,9 @@ jobs:
esac
case '${{ matrix.job.os }}' in
ubuntu-*)
# selinux headers needed to build tests
# selinux and systemd headers needed to build tests
sudo apt-get -y update
sudo apt-get -y install libselinux1-dev
sudo apt-get -y install libselinux1-dev libsystemd-dev
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
+3 -3
View File
@@ -1,7 +1,7 @@
name: Code Quality
# spell-checker:ignore (people) reactivecircus Swatinem dtolnay juliangruber pell taplo
# spell-checker:ignore (misc) TERMUX noaudio pkill swiftshader esac sccache pcoreutils shopt subshell dequote
# spell-checker:ignore (misc) TERMUX noaudio pkill swiftshader esac sccache pcoreutils shopt subshell dequote libsystemd
on:
pull_request:
@@ -110,8 +110,8 @@ jobs:
## Install/setup prerequisites
case '${{ matrix.job.os }}' in
ubuntu-*)
# selinux headers needed to enable all features
sudo apt-get -y install libselinux1-dev
# selinux and systemd headers needed to enable all features
sudo apt-get -y install libselinux1-dev libsystemd-dev
;;
esac
- name: "`cargo clippy` lint testing"
+7 -3
View File
@@ -1,6 +1,6 @@
name: FreeBSD
# spell-checker:ignore sshfs usesh vmactions taiki Swatinem esac fdescfs fdesc sccache nextest copyback
# spell-checker:ignore sshfs usesh vmactions taiki Swatinem esac fdescfs fdesc sccache nextest copyback logind
env:
# * style job configuration
@@ -140,7 +140,7 @@ jobs:
usesh: true
sync: rsync
copyback: false
prepare: pkg install -y curl gmake sudo
prepare: pkg install -y curl gmake sudo jq
run: |
## Prepare, build, and test
# implementation modelled after ref: <https://github.com/rust-lang/rustup/pull/2783>
@@ -194,7 +194,11 @@ jobs:
export RUST_BACKTRACE=1
export CARGO_TERM_COLOR=always
if (test -z "\$FAULT"); then cargo nextest run --hide-progress-bar --profile ci --features '${{ matrix.job.features }}' || FAULT=1 ; fi
if (test -z "\$FAULT"); then cargo nextest run --hide-progress-bar --profile ci --all-features -p uucore || FAULT=1 ; fi
# There is no systemd-logind on FreeBSD, so test all features except feat_systemd_logind ( https://github.com/rust-lang/cargo/issues/3126#issuecomment-2523441905 )
if (test -z "\$FAULT"); then
UUCORE_FEATURES=\$(cargo metadata --format-version=1 --no-deps -p uucore | jq -r '.packages[] | select(.name == "uucore") | .features | keys | .[]' | grep -v "feat_systemd_logind" | paste -s -d "," -)
cargo nextest run --hide-progress-bar --profile ci --features "\$UUCORE_FEATURES" -p uucore || FAULT=1
fi
# Test building with make
if (test -z "\$FAULT"); then make PROFILE=ci || FAULT=1 ; fi
# Clean to avoid to rsync back the files
+9 -1
View File
@@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl unnested
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl unnested logind
[package]
name = "coreutils"
@@ -38,6 +38,14 @@ uudoc = ["zip", "dep:uuhelp_parser"]
## Optional feature for stdbuf
# "feat_external_libstdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it
feat_external_libstdbuf = ["stdbuf/feat_external_libstdbuf"]
# "feat_systemd_logind" == enable feat_systemd_logind support for utmpx replacement
feat_systemd_logind = [
"pinky/feat_systemd_logind",
"uptime/feat_systemd_logind",
"users/feat_systemd_logind",
"uucore/feat_systemd_logind",
"who/feat_systemd_logind",
]
# "feat_acl" == enable support for ACLs (access control lists; by using`--features feat_acl`)
# NOTE:
# * On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.
+5
View File
@@ -1,3 +1,5 @@
# spell-checker:ignore logind
[package]
name = "uu_pinky"
description = "pinky ~ (uutils) display user information"
@@ -14,6 +16,9 @@ readme.workspace = true
[lints]
workspace = true
[features]
feat_systemd_logind = ["uucore/feat_systemd_logind"]
[lib]
path = "src/pinky.rs"
+3 -3
View File
@@ -14,7 +14,7 @@ use uucore::entries::{Locate, Passwd};
use uucore::error::{FromIo, UResult};
use uucore::libc::S_IWGRP;
use uucore::translate;
use uucore::utmpx::{self, Utmpx, time};
use uucore::utmpx::{self, Utmpx, UtmpxRecord, time};
use std::io::BufReader;
use std::io::prelude::*;
@@ -137,7 +137,7 @@ fn idle_string(when: i64) -> String {
})
}
fn time_string(ut: &Utmpx) -> String {
fn time_string(ut: &UtmpxRecord) -> String {
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
@@ -158,7 +158,7 @@ fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
}
impl Pinky {
fn print_entry(&self, ut: &Utmpx) -> std::io::Result<()> {
fn print_entry(&self, ut: &UtmpxRecord) -> std::io::Result<()> {
let mut pts_path = PathBuf::from("/dev");
pts_path.push(ut.tty_device().as_str());
+5
View File
@@ -1,3 +1,5 @@
# spell-checker:ignore logind
[package]
name = "uu_uptime"
description = "uptime ~ (uutils) display dynamic system information"
@@ -14,6 +16,9 @@ readme.workspace = true
[lints]
workspace = true
[features]
feat_systemd_logind = ["uucore/feat_systemd_logind"]
[lib]
path = "src/uptime.rs"
+5
View File
@@ -1,3 +1,5 @@
# spell-checker:ignore logind
[package]
name = "uu_users"
description = "users ~ (uutils) display names of currently logged-in users"
@@ -14,6 +16,9 @@ readme.workspace = true
[lints]
workspace = true
[features]
feat_systemd_logind = ["uucore/feat_systemd_logind"]
[lib]
path = "src/users.rs"
+1 -1
View File
@@ -69,7 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let filename = maybe_file.unwrap_or(utmpx::DEFAULT_FILE.as_ref());
users = Utmpx::iter_all_records_from(filename)
.filter(Utmpx::is_user_process)
.filter(|ut| ut.is_user_process())
.map(|ut| ut.user())
.collect::<Vec<_>>();
};
+6
View File
@@ -1,3 +1,6 @@
# spell-checker:ignore logind
[package]
name = "uu_who"
description = "who ~ (uutils) display information about currently logged-in users"
@@ -14,6 +17,9 @@ readme.workspace = true
[lints]
workspace = true
[features]
feat_systemd_logind = ["uucore/feat_systemd_logind"]
[lib]
path = "src/who.rs"
+12 -12
View File
@@ -14,7 +14,7 @@ use uucore::libc::{S_IWGRP, STDIN_FILENO, ttyname};
use uucore::translate;
use uucore::LocalizedCommand;
use uucore::utmpx::{self, Utmpx, time};
use uucore::utmpx::{self, UtmpxRecord, time};
use std::borrow::Cow;
use std::ffi::CStr;
@@ -162,7 +162,7 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
})
}
fn time_string(ut: &Utmpx) -> String {
fn time_string(ut: &UtmpxRecord) -> String {
// "%b %e %H:%M"
let time_format: Vec<time::format_description::FormatItem> =
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
@@ -202,14 +202,14 @@ impl Who {
utmpx::DEFAULT_FILE
};
if self.short_list {
let users = Utmpx::iter_all_records_from(f)
.filter(Utmpx::is_user_process)
let users = utmpx::Utmpx::iter_all_records_from(f)
.filter(|ut| ut.is_user_process())
.map(|ut| ut.user())
.collect::<Vec<_>>();
println!("{}", users.join(" "));
println!("{}", translate!("who-user-count", "count" => users.len()));
} else {
let records = Utmpx::iter_all_records_from(f);
let records = utmpx::Utmpx::iter_all_records_from(f);
if self.include_heading {
self.print_heading();
@@ -248,7 +248,7 @@ impl Who {
}
#[inline]
fn print_runlevel(&self, ut: &Utmpx) {
fn print_runlevel(&self, ut: &UtmpxRecord) {
let last = (ut.pid() / 256) as u8 as char;
let curr = (ut.pid() % 256) as u8 as char;
let runlevel_line = translate!("who-runlevel", "level" => curr);
@@ -268,7 +268,7 @@ impl Who {
}
#[inline]
fn print_clockchange(&self, ut: &Utmpx) {
fn print_clockchange(&self, ut: &UtmpxRecord) {
self.print_line(
"",
' ',
@@ -282,7 +282,7 @@ impl Who {
}
#[inline]
fn print_login(&self, ut: &Utmpx) {
fn print_login(&self, ut: &UtmpxRecord) {
let comment = translate!("who-login-id", "id" => ut.terminal_suffix());
let pidstr = format!("{}", ut.pid());
self.print_line(
@@ -298,7 +298,7 @@ impl Who {
}
#[inline]
fn print_deadprocs(&self, ut: &Utmpx) {
fn print_deadprocs(&self, ut: &UtmpxRecord) {
let comment = translate!("who-login-id", "id" => ut.terminal_suffix());
let pidstr = format!("{}", ut.pid());
let e = ut.exit_status();
@@ -316,7 +316,7 @@ impl Who {
}
#[inline]
fn print_initspawn(&self, ut: &Utmpx) {
fn print_initspawn(&self, ut: &UtmpxRecord) {
let comment = translate!("who-login-id", "id" => ut.terminal_suffix());
let pidstr = format!("{}", ut.pid());
self.print_line(
@@ -332,7 +332,7 @@ impl Who {
}
#[inline]
fn print_boottime(&self, ut: &Utmpx) {
fn print_boottime(&self, ut: &UtmpxRecord) {
self.print_line(
"",
' ',
@@ -345,7 +345,7 @@ impl Who {
);
}
fn print_user(&self, ut: &Utmpx) -> UResult<()> {
fn print_user(&self, ut: &UtmpxRecord) -> UResult<()> {
let mut p = PathBuf::from("/dev");
p.push(ut.tty_device().as_str());
let mesg;
+2 -1
View File
@@ -1,4 +1,4 @@
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal tzdb zoneinfo
# spell-checker:ignore (features) bigdecimal zerocopy extendedbigdecimal tzdb zoneinfo logind
[package]
name = "uucore"
@@ -113,6 +113,7 @@ fs = ["dunce", "libc", "winapi-util", "windows-sys"]
fsext = ["libc", "windows-sys"]
fsxattr = ["xattr"]
lines = []
feat_systemd_logind = ["utmpx", "libc"]
format = [
"bigdecimal",
"extendedbigdecimal",
+3 -1
View File
@@ -4,7 +4,7 @@
// file that was distributed with this source code.
// features ~ feature-gated modules (core/bundler file)
//
// spell-checker:ignore (features) extendedbigdecimal
// spell-checker:ignore (features) extendedbigdecimal logind
#[cfg(feature = "backup-control")]
pub mod backup_control;
@@ -74,6 +74,8 @@ pub mod fsxattr;
pub mod selinux;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub mod signals;
#[cfg(feature = "feat_systemd_logind")]
pub mod systemd_logind;
#[cfg(all(
unix,
not(target_os = "android"),
File diff suppressed because it is too large Load Diff
+173 -11
View File
@@ -3,6 +3,8 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//
// spell-checker:ignore logind
//! Aims to provide platform-independent methods to obtain login records
//!
//! **ONLY** support linux, macos and freebsd for the time being
@@ -39,6 +41,9 @@ use std::path::Path;
use std::ptr;
use std::sync::{Mutex, MutexGuard};
#[cfg(feature = "feat_systemd_logind")]
use crate::features::systemd_logind;
pub use self::ut::*;
// See the FAQ at https://wiki.musl-libc.org/faq#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs?
@@ -278,18 +283,30 @@ impl Utmpx {
/// This will use the default location, or the path [`Utmpx::iter_all_records_from`]
/// was most recently called with.
///
/// On systems with systemd-logind feature enabled at compile time,
/// this will use systemd-logind instead of traditional utmp files.
///
/// 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 {
let iter = UtmpxIter::new();
unsafe {
// This can technically fail, and it would be nice to detect that,
// but it doesn't return anything so we'd have to do nasty things
// with errno.
#[cfg_attr(target_env = "musl", allow(deprecated))]
setutxent();
#[cfg(feature = "feat_systemd_logind")]
{
// Use systemd-logind instead of traditional utmp when feature is enabled
UtmpxIter::new_systemd()
}
#[cfg(not(feature = "feat_systemd_logind"))]
{
let iter = UtmpxIter::new();
unsafe {
// This can technically fail, and it would be nice to detect that,
// but it doesn't return anything so we'd have to do nasty things
// with errno.
#[cfg_attr(target_env = "musl", allow(deprecated))]
setutxent();
}
iter
}
iter
}
/// Iterate through all the utmp records from a specific file.
@@ -298,8 +315,20 @@ impl Utmpx {
///
/// This function affects subsequent calls to [`Utmpx::iter_all_records`].
///
/// On systems with systemd-logind feature enabled at compile time,
/// if the path matches the default utmp file, this will use systemd-logind
/// instead of traditional utmp files.
///
/// The same caveats as for [`Utmpx::iter_all_records`] apply.
pub fn iter_all_records_from<P: AsRef<Path>>(path: P) -> UtmpxIter {
#[cfg(feature = "feat_systemd_logind")]
{
// Use systemd-logind for default utmp file when feature is enabled
if path.as_ref() == Path::new(DEFAULT_FILE) {
return UtmpxIter::new_systemd();
}
}
let iter = UtmpxIter::new();
let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
unsafe {
@@ -336,6 +365,8 @@ pub struct UtmpxIter {
/// Ensure UtmpxIter is !Send. Technically redundant because MutexGuard
/// is also !Send.
phantom: PhantomData<std::rc::Rc<()>>,
#[cfg(feature = "feat_systemd_logind")]
systemd_iter: Option<systemd_logind::SystemdUtmpxIter>,
}
impl UtmpxIter {
@@ -345,13 +376,144 @@ impl UtmpxIter {
Self {
guard,
phantom: PhantomData,
#[cfg(feature = "feat_systemd_logind")]
systemd_iter: None,
}
}
#[cfg(feature = "feat_systemd_logind")]
fn new_systemd() -> Self {
// PoisonErrors can safely be ignored
let guard = LOCK.lock().unwrap_or_else(|err| err.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()
}
};
Self {
guard,
phantom: PhantomData,
systemd_iter: Some(systemd_iter),
}
}
}
/// Wrapper type that can hold either traditional utmpx records or systemd records
pub enum UtmpxRecord {
Traditional(Box<Utmpx>),
#[cfg(feature = "feat_systemd_logind")]
Systemd(systemd_logind::SystemdUtmpxCompat),
}
impl UtmpxRecord {
/// A.K.A. ut.ut_type
pub fn record_type(&self) -> i16 {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.record_type(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.record_type(),
}
}
/// A.K.A. ut.ut_pid
pub fn pid(&self) -> i32 {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.pid(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.pid(),
}
}
/// A.K.A. ut.ut_id
pub fn terminal_suffix(&self) -> String {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.terminal_suffix(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.terminal_suffix(),
}
}
/// A.K.A. ut.ut_user
pub fn user(&self) -> String {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.user(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.user(),
}
}
/// A.K.A. ut.ut_host
pub fn host(&self) -> String {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.host(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.host(),
}
}
/// A.K.A. ut.ut_line
pub fn tty_device(&self) -> String {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.tty_device(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.tty_device(),
}
}
/// A.K.A. ut.ut_tv
pub fn login_time(&self) -> time::OffsetDateTime {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.login_time(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.login_time(),
}
}
/// A.K.A. ut.ut_exit
///
/// Return (e_termination, e_exit)
pub fn exit_status(&self) -> (i16, i16) {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.exit_status(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.exit_status(),
}
}
/// check if the record is a user process
pub fn is_user_process(&self) -> bool {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.is_user_process(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.is_user_process(),
}
}
/// Canonicalize host name using DNS
pub fn canon_host(&self) -> IOResult<String> {
match self {
UtmpxRecord::Traditional(utmpx) => utmpx.canon_host(),
#[cfg(feature = "feat_systemd_logind")]
UtmpxRecord::Systemd(systemd) => systemd.canon_host(),
}
}
}
impl Iterator for UtmpxIter {
type Item = Utmpx;
type Item = UtmpxRecord;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(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)
return systemd_iter.next().map(UtmpxRecord::Systemd);
}
}
// Traditional utmp path
unsafe {
#[cfg_attr(target_env = "musl", allow(deprecated))]
let res = getutxent();
@@ -362,9 +524,9 @@ impl Iterator for UtmpxIter {
// call to getutxent(), so we have to read it now.
// All the strings live inline in the struct as arrays, which
// makes things easier.
Some(Utmpx {
Some(UtmpxRecord::Traditional(Box::new(Utmpx {
inner: ptr::read(res as *const _),
})
})))
}
}
}
+3 -1
View File
@@ -5,7 +5,7 @@
//! library ~ (core/bundler file)
// #![deny(missing_docs)] //TODO: enable this
//
// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal myutil
// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal myutil logind
// * feature-gated external crates (re-shared as public internal modules)
#[cfg(feature = "libc")]
@@ -67,6 +67,8 @@ pub use crate::features::ranges;
pub use crate::features::ringbuffer;
#[cfg(feature = "sum")]
pub use crate::features::sum;
#[cfg(feature = "feat_systemd_logind")]
pub use crate::features::systemd_logind;
#[cfg(feature = "time")]
pub use crate::features::time;
#[cfg(feature = "update-control")]