Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libsystemd"
version = "0.7.2"
version = "0.8.0"
authors = ["Luca Bruno <lucab@lucabruno.net>", "Sebastian Wiesner <sebastian@swsnr.de>"]
license = "MIT/Apache-2.0"
repository = "https://github.com/lucab/libsystemd-rs"
Expand All @@ -10,29 +10,29 @@ keywords = ["systemd", "linux"]
categories = ["api-bindings", "os::unix-apis"]
readme = "README.md"
exclude = [
".gitignore",
".travis.yml",
".gitignore",
".travis.yml",
]
edition = "2021"
rust-version = "1.69"
edition = "2024"
rust-version = "1.85"

[dependencies]
hmac = "^0.12"
libc = "^0.2"
log = "^0.4"
nix = { version = "^0.29", default-features = false, features = ["dir", "fs", "socket", "process", "uio"] }
nom = "8"
serde = { version = "^1.0.91", features = ["derive"] }
nix = { version = "^0.31", default-features = false, features = ["dir", "fs", "socket", "process", "uio"] }
nom = "^8.0"
serde = { version = "^1.0.228", features = ["derive"] }
sha2 = "^0.10"
thiserror = "^2.0"
uuid = { version = "^1.0", features = ["serde"] }
once_cell = "^1.8"
uuid = { version = "^1.20", features = ["serde"] }
once_cell = "^1.21"

[dev-dependencies]
quickcheck = "^1.0"
serde_json = "^1.0"
rand = "^0.9"
pretty_assertions = "^1.0"
pretty_assertions = "^1.4"

[[test]]
name = "connected_to_journal"
Expand Down
110 changes: 70 additions & 40 deletions src/activation.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::errors::{Context, SdError};
use nix::fcntl::{fcntl, FdFlag, F_SETFD};
use nix::sys::socket::getsockname;
use nix::sys::socket::{AddressFamily, SockaddrLike, SockaddrStorage};
use crate::errors::{Context as _, SdError};
use crate::libc::fcntl;
use nix::fcntl::{F_SETFD, FdFlag};
use nix::sys::socket::{AddressFamily, SockaddrLike as _, SockaddrStorage, getsockname};
use nix::sys::stat::fstat;
use std::convert::TryFrom;
use std::env;
use std::mem::MaybeUninit;
use std::os::fd::BorrowedFd;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::process;

Expand All @@ -14,18 +16,23 @@ const SD_LISTEN_FDS_START: RawFd = 3;
/// Trait for checking the type of a file descriptor.
pub trait IsType {
/// Returns true if a file descriptor is a FIFO.
#[must_use]
fn is_fifo(&self) -> bool;

/// Returns true if a file descriptor is a special file.
#[must_use]
fn is_special(&self) -> bool;

/// Returns true if a file descriptor is a `PF_INET` socket.
#[must_use]
fn is_inet(&self) -> bool;

/// Returns true if a file descriptor is a `PF_UNIX` socket.
#[must_use]
fn is_unix(&self) -> bool;

/// Returns true if a file descriptor is a POSIX message queue descriptor.
#[must_use]
fn is_mq(&self) -> bool;
}

Expand All @@ -38,7 +45,7 @@ pub struct FileDescriptor(SocketFd);
/// Possible types of sockets.
#[derive(Debug, Clone)]
enum SocketFd {
/// A FIFO named pipe (see `man 7 fifo`)
/// A FIFO named pipe (see `man 7 fifo`).
Fifo(RawFd),
/// A special file, such as character device nodes or special files in
/// `/proc` and `/sys`.
Expand All @@ -54,22 +61,27 @@ enum SocketFd {
}

impl IsType for FileDescriptor {
#[inline]
fn is_fifo(&self) -> bool {
matches!(self.0, SocketFd::Fifo(_))
}

#[inline]
fn is_special(&self) -> bool {
matches!(self.0, SocketFd::Special(_))
}

#[inline]
fn is_unix(&self) -> bool {
matches!(self.0, SocketFd::Unix(_))
}

#[inline]
fn is_inet(&self) -> bool {
matches!(self.0, SocketFd::Inet(_))
}

#[inline]
fn is_mq(&self) -> bool {
matches!(self.0, SocketFd::Mq(_))
}
Expand All @@ -85,13 +97,13 @@ pub fn receive_descriptors(unset_env: bool) -> Result<Vec<FileDescriptor>, SdErr
log::trace!("LISTEN_PID = {pid:?}; LISTEN_FDS = {fds:?}");

if unset_env {
env::remove_var("LISTEN_PID");
env::remove_var("LISTEN_FDS");
env::remove_var("LISTEN_FDNAMES");
unsafe { env::remove_var("LISTEN_PID") };
unsafe { env::remove_var("LISTEN_FDS") };
unsafe { env::remove_var("LISTEN_FDNAMES") };
}

// Parse `$LISTEN_PID` if present.
if let Err(env::VarError::NotPresent) = pid {
if pid == Err(env::VarError::NotPresent) {
return Ok(vec![]);
}
let pid = pid
Expand All @@ -107,7 +119,7 @@ pub fn receive_descriptors(unset_env: bool) -> Result<Vec<FileDescriptor>, SdErr
}

// Parse `$LISTEN_FDS` if present.
if let Err(env::VarError::NotPresent) = fds {
if fds == Err(env::VarError::NotPresent) {
return Ok(vec![]);
}
let fds = fds
Expand All @@ -131,13 +143,13 @@ pub fn receive_descriptors_with_names(
log::trace!("LISTEN_PID = {pid:?}; LISTEN_FDS = {fds:?}; LISTEN_FDNAMES = {fdnames:?}");

if unset_env {
env::remove_var("LISTEN_PID");
env::remove_var("LISTEN_FDS");
env::remove_var("LISTEN_FDNAMES");
unsafe { env::remove_var("LISTEN_PID") };
unsafe { env::remove_var("LISTEN_FDS") };
unsafe { env::remove_var("LISTEN_FDNAMES") };
}

// Parse `$LISTEN_PID` if present.
if let Err(env::VarError::NotPresent) = pid {
if pid == Err(env::VarError::NotPresent) {
return Ok(vec![]);
}
let pid = pid
Expand All @@ -153,7 +165,7 @@ pub fn receive_descriptors_with_names(
}

// Parse `$LISTEN_FDS` if present.
if let Err(env::VarError::NotPresent) = fds {
if fds == Err(env::VarError::NotPresent) {
return Ok(vec![]);
}
let fds = fds
Expand All @@ -162,7 +174,7 @@ pub fn receive_descriptors_with_names(
.context("failed to parse LISTEN_FDS")?;

// Parse `$LISTEN_FDNAMES` if present.
if let Err(env::VarError::NotPresent) = fdnames {
if fdnames == Err(env::VarError::NotPresent) {
return Ok(vec![]);
}
let fdnames = fdnames.context("failed to get LISTEN_FDNAMES")?;
Expand All @@ -175,14 +187,17 @@ pub fn receive_descriptors_with_names(
}

fn socks_from_fds(fd_count: usize) -> Result<Vec<FileDescriptor>, SdError> {
let raw_fd_count = RawFd::try_from(fd_count)
.with_context(|| format!("overlarge file descriptor index: {fd_count}"))?;

let mut descriptors = Vec::with_capacity(fd_count);
for fd_offset in 0..fd_count {
for fd_offset in 0i32..raw_fd_count {
let fd_num = SD_LISTEN_FDS_START
.checked_add(fd_offset as i32)
.with_context(|| format!("overlarge file descriptor index: {fd_count}"))?;
.checked_add(fd_offset)
.with_context(|| format!("overlarge file descriptor index: {raw_fd_count}"))?;
// Set CLOEXEC on the file descriptors we receive so that they aren't
// passed to programs exec'd from here, just like sd_listen_fds does.
if let Err(errno) = fcntl(fd_num, F_SETFD(FdFlag::FD_CLOEXEC)) {
if let Err(errno) = fcntl(&fd_num, F_SETFD(FdFlag::FD_CLOEXEC)) {
return Err(format!("couldn't set FD_CLOEXEC on {fd_num}: {errno}").into());
}
let fd = FileDescriptor::try_from(fd_num).unwrap_or_else(|(msg, val)| {
Expand All @@ -196,40 +211,53 @@ fn socks_from_fds(fd_count: usize) -> Result<Vec<FileDescriptor>, SdError> {
}

impl IsType for RawFd {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be looked at more closely.

#[inline]
fn is_fifo(&self) -> bool {
fstat(*self)
if *self == -1i32 {
return false;
}
let raw_fd = unsafe { BorrowedFd::borrow_raw(*self) };
fstat(raw_fd)
.map(|stat| (stat.st_mode & 0o0_170_000) == 0o010_000)
.unwrap_or(false)
}

#[inline]
fn is_special(&self) -> bool {
fstat(*self)
if *self == -1i32 {
return false;
}
let raw_fd = unsafe { BorrowedFd::borrow_raw(*self) };
fstat(raw_fd)
.map(|stat| (stat.st_mode & 0o0_170_000) == 0o100_000)
.unwrap_or(false)
}

#[inline]
fn is_inet(&self) -> bool {
getsockname::<SockaddrStorage>(*self)
.map(|addr| {
matches!(
addr.family(),
Some(AddressFamily::Inet) | Some(AddressFamily::Inet6)
Some(AddressFamily::Inet | AddressFamily::Inet6)
)
})
.unwrap_or(false)
}

#[inline]
fn is_unix(&self) -> bool {
getsockname::<SockaddrStorage>(*self)
.map(|addr| matches!(addr.family(), Some(AddressFamily::Unix)))
.unwrap_or(false)
}

#[inline]
fn is_mq(&self) -> bool {
// `nix` does not enable us to test if a raw fd is a mq, so we must drop to libc here.
// SAFETY: `mq_getattr` is specified to return -1 when passed a fd which is not a mq.
// Furthermore, we ignore `attr` and rely only on the return value.
let mut attr = std::mem::MaybeUninit::<libc::mq_attr>::uninit();
let mut attr = MaybeUninit::<libc::mq_attr>::uninit();
let res = unsafe { libc::mq_getattr(*self, attr.as_mut_ptr()) };
res == 0
}
Expand All @@ -238,42 +266,44 @@ impl IsType for RawFd {
impl TryFrom<RawFd> for FileDescriptor {
type Error = (SdError, RawFd);

#[inline]
fn try_from(value: RawFd) -> Result<Self, Self::Error> {
if value.is_fifo() {
return Ok(FileDescriptor(SocketFd::Fifo(value)));
Ok(Self(SocketFd::Fifo(value)))
} else if value.is_special() {
return Ok(FileDescriptor(SocketFd::Special(value)));
Ok(Self(SocketFd::Special(value)))
} else if value.is_inet() {
return Ok(FileDescriptor(SocketFd::Inet(value)));
Ok(Self(SocketFd::Inet(value)))
} else if value.is_unix() {
return Ok(FileDescriptor(SocketFd::Unix(value)));
Ok(Self(SocketFd::Unix(value)))
} else if value.is_mq() {
return Ok(FileDescriptor(SocketFd::Mq(value)));
Ok(Self(SocketFd::Mq(value)))
} else {
let err_msg =
format!("conversion failure, possibly invalid or unknown file descriptor {value}");
Err((err_msg.into(), value))
}

let err_msg =
format!("conversion failure, possibly invalid or unknown file descriptor {value}");
Err((err_msg.into(), value))
}
}

// TODO(lucab): replace with multiple safe `TryInto` helpers plus an `unsafe` fallback.
impl IntoRawFd for FileDescriptor {
#[inline]
fn into_raw_fd(self) -> RawFd {
match self.0 {
SocketFd::Fifo(fd) => fd,
SocketFd::Special(fd) => fd,
SocketFd::Inet(fd) => fd,
SocketFd::Unix(fd) => fd,
SocketFd::Mq(fd) => fd,
SocketFd::Unknown(fd) => fd,
SocketFd::Fifo(fd)
| SocketFd::Special(fd)
| SocketFd::Inet(fd)
| SocketFd::Unix(fd)
| SocketFd::Mq(fd)
| SocketFd::Unknown(fd) => fd,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::activation::{FileDescriptor, IsType as _, SocketFd};

#[test]
fn test_socketype_is_unix() {
Expand Down
13 changes: 7 additions & 6 deletions src/credentials.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::errors::{Context, SdError};
use crate::errors::{Context as _, SdError};
use nix::dir;
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
use std::env;
use std::fs::File;
use std::fs::{File, ReadDir, read_dir};
use std::path::PathBuf;

/// Credential loader for units.
Expand Down Expand Up @@ -37,8 +37,9 @@ impl CredentialsLoader {
}

/// Return the location of the credentials directory, if any.
#[must_use]
pub fn path_from_env() -> Option<PathBuf> {
env::var("CREDENTIALS_DIRECTORY").map(|v| v.into()).ok()
env::var("CREDENTIALS_DIRECTORY").map(PathBuf::from).ok()
}

/// Get credential by ID.
Expand All @@ -54,7 +55,7 @@ impl CredentialsLoader {
/// println!("token size: {}", token_metadata.len());
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn get(&self, id: impl AsRef<str>) -> Result<File, SdError> {
pub fn get<I: AsRef<str>>(&self, id: I) -> Result<File, SdError> {
let cred_path = self.cred_absolute_path(id.as_ref())?;
File::open(&cred_path).map_err(|e| {
let msg = format!("Opening credential at {}: {}", cred_path.display(), e);
Expand Down Expand Up @@ -85,8 +86,8 @@ impl CredentialsLoader {
/// println!("Credential ID: {}", credential.file_name().to_string_lossy());
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
pub fn iter(&self) -> Result<std::fs::ReadDir, SdError> {
std::fs::read_dir(&self.path)
pub fn iter(&self) -> Result<ReadDir, SdError> {
read_dir(&self.path)
.with_context(|| format!("Opening credential directory at {}", self.path.display()))
}
}
Loading