mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
rust: add the systemd_shared crate
This adds the crate programs are written against. It's the idiomatic layer on top of systemd_shared_sys and only contains what a first program needs. Nothing is added speculatively. This is the core of it, the remaining modules follow one by one. There's no std and, like in the kernel's Rust code, no alloc crate either. Its collections abort the program when memory runs out but an allocation failure has to be handled like it is in C. So the crate fills the gaps std and alloc would fill otherwise: - Box and Vec work like the kernel's. Everything that allocates returns AllocError on failure, which converts into -ENOMEM, and there are no infallible variants. Memory comes from malloc() and posix_memalign() so Rust and C allocations share one heap. There's no global allocator, so a program that uses the alloc crate regardless doesn't link. - The panic handler logs the location at LOG_CRIT and aborts like a failed assert(). - print!() and println!() write to C's stdout so their output stays in order with the output of the C code a program calls. - The prelude brings in Box and Vec. The precompiled core crate is built for unwinding and refers to rust_eh_personality. With panic=abort nothing unwinds so that's an abort() stub. A program that pulls in std doesn't link as std brings a second panic handler. The modules are: - errno: Errno is a positive errno value that is never zero so Result<(), Errno> is a machine word. Every errno has an associated constant such as Errno::ENOENT. The symbolic name comes from errno_name_no_fallback() and the message from strerror_or_eof(). check() handles the negative errno convention and from_result() the int an extern "C" callback returns. Conversions from TryFromIntError, Utf8Error and AllocError make ? just work. - log: The log_*! macros mirror their C counterparts. log_*_errno! evaluates to the Errno just like the C version returns it. Passing SYNTHETIC_ERRNO(EINVAL) as the error leaves out ERRNO= like SYNTHETIC_ERRNO() does in C. Messages are formatted into a LINE_MAX buffer on the stack and handed to log_dispatch_internal(). That's the path log_internalv() takes after vsnprintf(). So there's no allocation and truncation works like in C. file!() is the remapped path so CODE_FILE= matches the C programs. - cstr: OwnedCStr is for strings C allocated. It can also be created via strdup(), memdup_suffix0() and formatting and reports -ENOMEM instead of aborting when memory runs out. Formatting measures the output first and then formats into one allocation of exactly that size, like the kernel's CString::try_from_fmt(). display() shows a C string in a log message without allocating and strip_prefix() is startswith(). - prelude: use systemd_shared::prelude::* brings in everything a program needs. Names mirror the C names with Rust casing and without repeating the namespace. Every unsafe block carries a SAFETY comment and every unsafe fn a Safety section. The build enforces both. .clippy.toml refuses abort(). panic!() logs why first so use that instead. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
1 parent
8f10df6fa6
commit
626261b486
10 files changed
+1282
No files matched your search
@@ -2,3 +2,7 @@
|
||||
|
||||
msrv = "1.85"
|
||||
check-private-items = true
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "systemd_shared_sys::abort"
|
||||
reason = "panic!() instead, the panic handler logs where and why before aborting"
|
||||
@@ -0,0 +1,406 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! Memory allocation that never aborts, the way the kernel's Rust support does it.
|
||||
//!
|
||||
//! The `alloc` crate is not used: its collections abort the program when an allocation fails. [`Box`] and
|
||||
//! [`Vec`] have no such paths. Everything that allocates returns a `Result` with [`AllocError`], which converts
|
||||
//! into `-ENOMEM`, so a failed allocation is handled like it is in C. Memory comes from `malloc()` and friends,
|
||||
//! as for the C code, so Rust and C allocations share one heap.
|
||||
//!
|
||||
//! No global allocator is registered either. If the `alloc` crate sneaks in, the link fails.
|
||||
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem::{self, ManuallyDrop};
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::ptr::{self, NonNull};
|
||||
use core::slice;
|
||||
|
||||
use crate::sys;
|
||||
|
||||
/// An allocation failed, because memory ran out or the size does not fit the address space. Converts into
|
||||
/// `-ENOMEM`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct AllocError;
|
||||
|
||||
/// What `malloc()` guarantees at least: twice the pointer size on glibc, 16 bytes on musl. This is the lower
|
||||
/// bound of the two.
|
||||
const MIN_ALIGN: usize = 2 * mem::size_of::<usize>();
|
||||
|
||||
fn malloc_aligns(layout: Layout) -> bool {
|
||||
// malloc(n) only guarantees the alignment of an object of n bytes.
|
||||
layout.align() <= MIN_ALIGN && layout.align() <= layout.size()
|
||||
}
|
||||
|
||||
/// Allocates memory for `layout`, with `malloc()` or, for alignments it does not guarantee,
|
||||
/// `posix_memalign()`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `layout` must not be zero-sized.
|
||||
unsafe fn alloc(layout: Layout) -> Result<NonNull<u8>, AllocError> {
|
||||
let p = if malloc_aligns(layout) {
|
||||
// SAFETY: plain call into libc.
|
||||
unsafe { sys::malloc(layout.size()) }
|
||||
} else {
|
||||
let mut p: *mut c_void = ptr::null_mut();
|
||||
// posix_memalign() wants a multiple of the pointer size.
|
||||
let align = layout.align().max(mem::size_of::<usize>());
|
||||
// SAFETY: align is a power of two and a multiple of the pointer size, p is a valid out-pointer.
|
||||
if unsafe { sys::posix_memalign(&mut p, align, layout.size()) } != 0 {
|
||||
return Err(AllocError);
|
||||
}
|
||||
p
|
||||
};
|
||||
NonNull::new(p.cast()).ok_or(AllocError)
|
||||
}
|
||||
|
||||
/// Moves the allocation `p` from `old` to `new`, which have the same alignment. On failure `p` is untouched.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `p` must come from [`alloc()`] or [`realloc()`] for `old`, and `new` must not be zero-sized.
|
||||
unsafe fn realloc(p: NonNull<u8>, old: Layout, new: Layout) -> Result<NonNull<u8>, AllocError> {
|
||||
if malloc_aligns(new) {
|
||||
// SAFETY: p came from malloc(), posix_memalign() or realloc(), all of which realloc() takes, and it keeps
|
||||
// the alignment malloc() guarantees for the new size.
|
||||
let n = unsafe { sys::realloc(p.as_ptr().cast(), new.size()) };
|
||||
return NonNull::new(n.cast()).ok_or(AllocError);
|
||||
}
|
||||
|
||||
// SAFETY: new is not zero-sized, as the caller guarantees.
|
||||
let n = unsafe { alloc(new)? };
|
||||
// SAFETY: both blocks are valid for the smaller of the two sizes and do not overlap, the old one is ours to
|
||||
// free.
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(p.as_ptr(), n.as_ptr(), old.size().min(new.size()));
|
||||
free(p);
|
||||
}
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `p` must come from [`alloc()`] or [`realloc()`] and not have been freed.
|
||||
unsafe fn free(p: NonNull<u8>) {
|
||||
// SAFETY: p came from malloc(), posix_memalign() or realloc(), as the caller guarantees.
|
||||
unsafe { sys::free(p.as_ptr().cast()) }
|
||||
}
|
||||
|
||||
/// An owned `T` on the heap, like `alloc::boxed::Box` but without the paths that abort when memory runs out:
|
||||
/// [`Box::new()`] returns an error instead.
|
||||
pub struct Box<T>(NonNull<T>, PhantomData<T>);
|
||||
|
||||
impl<T> Box<T> {
|
||||
/// Moves `x` to the heap.
|
||||
pub fn new(x: T) -> Result<Box<T>, AllocError> {
|
||||
let layout = Layout::new::<T>();
|
||||
let p = if layout.size() == 0 {
|
||||
NonNull::dangling()
|
||||
} else {
|
||||
// SAFETY: the layout is not zero-sized.
|
||||
unsafe { alloc(layout)? }.cast::<T>()
|
||||
};
|
||||
// SAFETY: p is valid for writes of a T and suitably aligned.
|
||||
unsafe { p.as_ptr().write(x) };
|
||||
Ok(Box(p, PhantomData))
|
||||
}
|
||||
|
||||
/// Hands the value over as a raw pointer, e.g. for C to keep as userdata. [`Box::from_raw()`] takes it back.
|
||||
pub fn into_raw(b: Box<T>) -> *mut T {
|
||||
ManuallyDrop::new(b).0.as_ptr()
|
||||
}
|
||||
|
||||
/// Takes back what [`Box::into_raw()`] handed out.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `p` must come from [`Box::into_raw()`] and not have been taken back before.
|
||||
pub unsafe fn from_raw(p: *mut T) -> Box<T> {
|
||||
// SAFETY: into_raw() never hands out NULL, as the caller guarantees.
|
||||
Box(unsafe { NonNull::new_unchecked(p) }, PhantomData)
|
||||
}
|
||||
|
||||
/// Moves the value back out of the heap.
|
||||
pub fn into_inner(b: Box<T>) -> T {
|
||||
let b = ManuallyDrop::new(b);
|
||||
// SAFETY: the value is initialized and moved out once, the box is not dropped.
|
||||
let x = unsafe { b.0.as_ptr().read() };
|
||||
// SAFETY: the memory is ours, its value was moved out above.
|
||||
unsafe { free_box(b.0) };
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees the memory of a box without dropping its value.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `p` must be the pointer of a box whose memory was not freed yet.
|
||||
unsafe fn free_box<T>(p: NonNull<T>) {
|
||||
if mem::size_of::<T>() != 0 {
|
||||
// SAFETY: a box allocates exactly when T is not zero-sized, with alloc(), as the caller guarantees.
|
||||
unsafe { free(p.cast()) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Box<T> {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: the value is initialized and dropped once, then its memory is freed.
|
||||
unsafe {
|
||||
ptr::drop_in_place(self.0.as_ptr());
|
||||
free_box(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Box<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
// SAFETY: the value is initialized and lives as long as the box.
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Box<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
// SAFETY: the value is initialized, lives as long as the box and the box is borrowed mutably.
|
||||
unsafe { self.0.as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Box<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(**self).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: a box owns its value like the value owns itself.
|
||||
unsafe impl<T: Send> Send for Box<T> {}
|
||||
// SAFETY: as above.
|
||||
unsafe impl<T: Sync> Sync for Box<T> {}
|
||||
|
||||
/// A growable array on the heap, like `alloc::vec::Vec` but without the paths that abort when memory runs out:
|
||||
/// whatever may allocate returns an error instead.
|
||||
pub struct Vec<T> {
|
||||
ptr: NonNull<T>,
|
||||
cap: usize,
|
||||
len: usize,
|
||||
_owns: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Vec<T> {
|
||||
const IS_ZST: bool = mem::size_of::<T>() == 0;
|
||||
|
||||
/// An empty array, without allocating.
|
||||
pub const fn new() -> Vec<T> {
|
||||
Vec {
|
||||
ptr: NonNull::dangling(),
|
||||
// Zero-sized elements never need memory.
|
||||
cap: if Self::IS_ZST { usize::MAX } else { 0 },
|
||||
len: 0,
|
||||
_owns: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// An empty array with room for `capacity` elements.
|
||||
pub fn with_capacity(capacity: usize) -> Result<Vec<T>, AllocError> {
|
||||
let mut v = Vec::new();
|
||||
v.reserve_exact(capacity)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// The number of elements.
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Whether there are no elements.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// How many elements fit without allocating.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.cap
|
||||
}
|
||||
|
||||
/// Makes room for at least `additional` more elements, growing the allocation geometrically.
|
||||
pub fn reserve(&mut self, additional: usize) -> Result<(), AllocError> {
|
||||
let needed = self.len.checked_add(additional).ok_or(AllocError)?;
|
||||
if needed <= self.cap {
|
||||
return Ok(());
|
||||
}
|
||||
self.grow(needed.max(self.cap.saturating_mul(2)).max(4))
|
||||
}
|
||||
|
||||
/// Makes room for exactly `additional` more elements.
|
||||
pub fn reserve_exact(&mut self, additional: usize) -> Result<(), AllocError> {
|
||||
let needed = self.len.checked_add(additional).ok_or(AllocError)?;
|
||||
if needed <= self.cap {
|
||||
return Ok(());
|
||||
}
|
||||
self.grow(needed)
|
||||
}
|
||||
|
||||
fn grow(&mut self, cap: usize) -> Result<(), AllocError> {
|
||||
// Zero-sized elements never get here, their capacity is usize::MAX.
|
||||
let new = Layout::array::<T>(cap).map_err(|_| AllocError)?;
|
||||
let p = if self.cap == 0 {
|
||||
// SAFETY: T is not zero-sized and cap is not zero, hence neither is the layout.
|
||||
unsafe { alloc(new)? }
|
||||
} else {
|
||||
let old = Layout::array::<T>(self.cap).map_err(|_| AllocError)?;
|
||||
// SAFETY: the buffer was allocated for old, new is not zero-sized.
|
||||
unsafe { realloc(self.ptr.cast(), old, new)? }
|
||||
};
|
||||
self.ptr = p.cast();
|
||||
self.cap = cap;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends `v`, allocating if there is no room left.
|
||||
pub fn push(&mut self, v: T) -> Result<(), AllocError> {
|
||||
self.reserve(1)?;
|
||||
// SAFETY: reserve() made room for one more element past len.
|
||||
unsafe { self.ptr.as_ptr().add(self.len).write(v) };
|
||||
self.len += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the last element and returns it.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
self.len -= 1;
|
||||
// SAFETY: the element at the old last index is initialized and no longer counted, it is read once.
|
||||
Some(unsafe { self.ptr.as_ptr().add(self.len).read() })
|
||||
}
|
||||
|
||||
/// Drops the elements past `len`, keeping the allocation.
|
||||
pub fn truncate(&mut self, len: usize) {
|
||||
if len >= self.len {
|
||||
return;
|
||||
}
|
||||
// SAFETY: the elements from len on are initialized and within the allocation.
|
||||
let tail = ptr::slice_from_raw_parts_mut(unsafe { self.ptr.as_ptr().add(len) }, self.len - len);
|
||||
self.len = len;
|
||||
// SAFETY: the tail is no longer counted, so it is dropped exactly once.
|
||||
unsafe { ptr::drop_in_place(tail) };
|
||||
}
|
||||
|
||||
/// Drops all elements, keeping the allocation.
|
||||
pub fn clear(&mut self) {
|
||||
self.truncate(0);
|
||||
}
|
||||
|
||||
/// The elements.
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
// SAFETY: the first len elements are initialized, ptr is non-null and aligned even when nothing is
|
||||
// allocated.
|
||||
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
|
||||
}
|
||||
|
||||
/// The elements, mutably.
|
||||
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||
// SAFETY: as in as_slice(), and the array is borrowed mutably.
|
||||
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Vec<T> {
|
||||
/// Appends clones of the elements of `other`.
|
||||
pub fn extend_from_slice(&mut self, other: &[T]) -> Result<(), AllocError> {
|
||||
self.reserve(other.len())?;
|
||||
for x in other {
|
||||
// SAFETY: reserve() made room for other.len() more elements past len.
|
||||
unsafe { self.ptr.as_ptr().add(self.len).write(x.clone()) };
|
||||
self.len += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An array of `n` clones of `value`.
|
||||
pub fn from_elem(value: T, n: usize) -> Result<Vec<T>, AllocError> {
|
||||
let mut v = Vec::with_capacity(n)?;
|
||||
for _ in 0..n {
|
||||
v.push(value.clone())?;
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Vec<T> {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
if !Self::IS_ZST && self.cap != 0 {
|
||||
// SAFETY: the buffer was allocated by alloc() or realloc() and its elements are dropped.
|
||||
unsafe { free(self.ptr.cast()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Vec<T> {
|
||||
fn default() -> Vec<T> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Vec<T> {
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &[T] {
|
||||
self.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Vec<T> {
|
||||
fn deref_mut(&mut self) -> &mut [T] {
|
||||
self.as_mut_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a Vec<T> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = slice::Iter<'a, T>;
|
||||
|
||||
fn into_iter(self) -> slice::Iter<'a, T> {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a mut Vec<T> {
|
||||
type Item = &'a mut T;
|
||||
type IntoIter = slice::IterMut<'a, T>;
|
||||
|
||||
fn into_iter(self) -> slice::IterMut<'a, T> {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Vec<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.as_slice().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq<U>, U> PartialEq<[U]> for Vec<T> {
|
||||
fn eq(&self, other: &[U]) -> bool {
|
||||
self.as_slice() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq<U>, U, const N: usize> PartialEq<[U; N]> for Vec<T> {
|
||||
fn eq(&self, other: &[U; N]) -> bool {
|
||||
self.as_slice() == other
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: a vector owns its elements like the elements own themselves.
|
||||
unsafe impl<T: Send> Send for Vec<T> {}
|
||||
// SAFETY: as above.
|
||||
unsafe impl<T: Sync> Sync for Vec<T> {}
|
||||
@@ -0,0 +1,167 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! Strings that C allocated.
|
||||
|
||||
use core::ffi::{c_char, c_void, CStr};
|
||||
use core::fmt::{self, Write};
|
||||
use core::ptr::{self, NonNull};
|
||||
|
||||
use crate::errno::{Errno, Result};
|
||||
use crate::sys;
|
||||
|
||||
/// A NUL-terminated string that was `malloc()`ed by C and is `free()`d on drop, the `_cleanup_free_ char *`
|
||||
/// of Rust.
|
||||
pub struct OwnedCStr(NonNull<c_char>);
|
||||
|
||||
impl OwnedCStr {
|
||||
/// Takes ownership of a string returned by a C helper, `None` for NULL.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `p` must be NULL or a `malloc()`ed NUL-terminated string that nothing else frees.
|
||||
pub unsafe fn from_raw(p: *mut c_char) -> Option<Self> {
|
||||
NonNull::new(p).map(OwnedCStr)
|
||||
}
|
||||
|
||||
/// Borrows the string.
|
||||
pub fn as_cstr(&self) -> &CStr {
|
||||
// SAFETY: invariant of from_raw().
|
||||
unsafe { CStr::from_ptr(self.0.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&CStr> for OwnedCStr {
|
||||
type Error = Errno;
|
||||
|
||||
/// `strdup()`, `-ENOMEM` instead of aborting when memory runs out.
|
||||
fn try_from(s: &CStr) -> Result<OwnedCStr> {
|
||||
// SAFETY: s is a C string, strdup() returns a copy or NULL.
|
||||
unsafe { OwnedCStr::from_raw(sys::strdup(s.as_ptr())) }.ok_or(Errno::ENOMEM)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats into a new string, `-ENOMEM` instead of aborting when memory runs out, `-EINVAL` if formatting
|
||||
/// an argument fails or the result has a NUL byte. Like the kernel's `CString::try_from_fmt()`, the output is
|
||||
/// measured first and then formatted into one allocation of exactly that size, without copying.
|
||||
pub fn try_format(args: fmt::Arguments<'_>) -> Result<OwnedCStr> {
|
||||
/// Counts the bytes formatting produces.
|
||||
struct Measure(usize);
|
||||
|
||||
impl Write for Measure {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0 = self.0.checked_add(s.len()).ok_or(fmt::Error)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes into a buffer of the measured size, never past it.
|
||||
struct Buffer {
|
||||
p: NonNull<u8>,
|
||||
size: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Write for Buffer {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let end = self
|
||||
.len
|
||||
.checked_add(s.len())
|
||||
.filter(|&end| end <= self.size)
|
||||
.ok_or(fmt::Error)?;
|
||||
// SAFETY: the bytes up to end are within the buffer, which s does not overlap.
|
||||
unsafe { ptr::copy_nonoverlapping(s.as_ptr(), self.p.as_ptr().add(self.len), s.len()) };
|
||||
self.len = end;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut m = Measure(0);
|
||||
m.write_fmt(args).map_err(|_| Errno::EINVAL)?;
|
||||
let size = m.0.checked_add(1).ok_or(Errno::ENOMEM)?;
|
||||
|
||||
// SAFETY: plain call into libc.
|
||||
let p = NonNull::new(unsafe { sys::malloc(size) }.cast::<u8>()).ok_or(Errno::ENOMEM)?;
|
||||
let mut b = Buffer {
|
||||
p,
|
||||
size: size - 1,
|
||||
len: 0,
|
||||
};
|
||||
let r = b.write_fmt(args);
|
||||
// SAFETY: the first b.len bytes were written above.
|
||||
if r.is_err() || unsafe { core::slice::from_raw_parts(p.as_ptr(), b.len) }.contains(&0) {
|
||||
// SAFETY: allocated above and not handed out.
|
||||
unsafe { sys::free(p.as_ptr().cast()) };
|
||||
return Err(Errno::EINVAL);
|
||||
}
|
||||
// SAFETY: b.len is below size, the terminator fits.
|
||||
unsafe { p.as_ptr().add(b.len).write(0) };
|
||||
// SAFETY: a malloc()ed NUL-terminated string that nothing else frees.
|
||||
unsafe { OwnedCStr::from_raw(p.as_ptr().cast()) }.ok_or(Errno::ENOMEM)
|
||||
}
|
||||
|
||||
/// `startswith()`: the rest of `s` after `prefix`, a C string as well.
|
||||
pub fn strip_prefix<'a>(s: &'a CStr, prefix: &[u8]) -> Option<&'a CStr> {
|
||||
CStr::from_bytes_with_nul(s.to_bytes_with_nul().strip_prefix(prefix)?).ok()
|
||||
}
|
||||
|
||||
/// Displays a C string, with invalid UTF-8 replaced like `to_string_lossy()` does, but without allocating.
|
||||
pub fn display(s: &CStr) -> Display<'_> {
|
||||
Display(s)
|
||||
}
|
||||
|
||||
/// See [`display()`].
|
||||
pub struct Display<'a>(&'a CStr);
|
||||
|
||||
impl fmt::Display for Display<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Ok(s) = self.0.to_str() {
|
||||
return f.pad(s);
|
||||
}
|
||||
for chunk in self.0.to_bytes().utf8_chunks() {
|
||||
f.write_str(chunk.valid())?;
|
||||
if !chunk.invalid().is_empty() {
|
||||
f.write_str("\u{FFFD}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OwnedCStr {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: invariant of from_raw().
|
||||
unsafe { sys::free(self.0.as_ptr().cast::<c_void>()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Deref for OwnedCStr {
|
||||
type Target = CStr;
|
||||
|
||||
fn deref(&self) -> &CStr {
|
||||
self.as_cstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<CStr> for OwnedCStr {
|
||||
fn as_ref(&self) -> &CStr {
|
||||
self.as_cstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::borrow::Borrow<CStr> for OwnedCStr {
|
||||
fn borrow(&self) -> &CStr {
|
||||
self.as_cstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OwnedCStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&display(self.as_cstr()), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for OwnedCStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(self.as_cstr(), f)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! The negative-errno return convention.
|
||||
|
||||
use core::ffi::{c_char, c_int, CStr};
|
||||
use core::fmt;
|
||||
use core::num::NonZero;
|
||||
|
||||
use crate::cstr;
|
||||
use crate::sys;
|
||||
|
||||
/// A positive errno value.
|
||||
///
|
||||
/// C code in systemd returns `-errno` on failure and [`check`] turns that into `Err(Errno)`. The value is
|
||||
/// never zero, so `Result<(), Errno>` is one machine word.
|
||||
///
|
||||
/// ```ignore
|
||||
/// use systemd_shared::{check, cstr, Errno};
|
||||
///
|
||||
/// assert_eq!(check(-2), Err(Errno::ENOENT));
|
||||
/// assert_eq!(Errno::ENOENT.negative(), -2);
|
||||
/// assert_eq!(Errno::ENOENT.name(), Some(c"ENOENT"));
|
||||
/// assert_eq!(
|
||||
/// cstr::try_format(format_args!("{}", Errno::ENOENT))?.as_cstr(),
|
||||
/// c"No such file or directory"
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Errno(NonZero<c_int>);
|
||||
|
||||
const EINVAL_NZ: NonZero<c_int> = match NonZero::new(sys::EINVAL as c_int) {
|
||||
Some(n) => n,
|
||||
None => panic!("EINVAL is zero"),
|
||||
};
|
||||
|
||||
macro_rules! declare_errno {
|
||||
($($name:ident),* $(,)?) => {
|
||||
impl Errno {
|
||||
$(
|
||||
#[doc = concat!("`", stringify!($name), "`.")]
|
||||
pub const $name: Errno = Errno::from_code(sys::$name as c_int);
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare_errno!(
|
||||
E2BIG,
|
||||
EACCES,
|
||||
EADDRINUSE,
|
||||
EADDRNOTAVAIL,
|
||||
EADV,
|
||||
EAFNOSUPPORT,
|
||||
EAGAIN,
|
||||
EALREADY,
|
||||
EBADE,
|
||||
EBADF,
|
||||
EBADFD,
|
||||
EBADMSG,
|
||||
EBADR,
|
||||
EBADRQC,
|
||||
EBADSLT,
|
||||
EBFONT,
|
||||
EBUSY,
|
||||
ECANCELED,
|
||||
ECHILD,
|
||||
ECHRNG,
|
||||
ECOMM,
|
||||
ECONNABORTED,
|
||||
ECONNREFUSED,
|
||||
ECONNRESET,
|
||||
EDEADLK,
|
||||
EDEADLOCK,
|
||||
EDESTADDRREQ,
|
||||
EDOM,
|
||||
EDOTDOT,
|
||||
EDQUOT,
|
||||
EEXIST,
|
||||
EFAULT,
|
||||
EFBIG,
|
||||
EHOSTDOWN,
|
||||
EHOSTUNREACH,
|
||||
EHWPOISON,
|
||||
EIDRM,
|
||||
EILSEQ,
|
||||
EINPROGRESS,
|
||||
EINTR,
|
||||
EINVAL,
|
||||
EIO,
|
||||
EISCONN,
|
||||
EISDIR,
|
||||
EISNAM,
|
||||
EKEYEXPIRED,
|
||||
EKEYREJECTED,
|
||||
EKEYREVOKED,
|
||||
EL2HLT,
|
||||
EL2NSYNC,
|
||||
EL3HLT,
|
||||
EL3RST,
|
||||
ELIBACC,
|
||||
ELIBBAD,
|
||||
ELIBEXEC,
|
||||
ELIBMAX,
|
||||
ELIBSCN,
|
||||
ELNRNG,
|
||||
ELOOP,
|
||||
EMEDIUMTYPE,
|
||||
EMFILE,
|
||||
EMLINK,
|
||||
EMSGSIZE,
|
||||
EMULTIHOP,
|
||||
ENAMETOOLONG,
|
||||
ENAVAIL,
|
||||
ENETDOWN,
|
||||
ENETRESET,
|
||||
ENETUNREACH,
|
||||
ENFILE,
|
||||
ENOANO,
|
||||
ENOBUFS,
|
||||
ENOCSI,
|
||||
ENODATA,
|
||||
ENODEV,
|
||||
ENOENT,
|
||||
ENOEXEC,
|
||||
ENOKEY,
|
||||
ENOLCK,
|
||||
ENOLINK,
|
||||
ENOMEDIUM,
|
||||
ENOMEM,
|
||||
ENOMSG,
|
||||
ENONET,
|
||||
ENOPKG,
|
||||
ENOPROTOOPT,
|
||||
ENOSPC,
|
||||
ENOSR,
|
||||
ENOSTR,
|
||||
ENOSYS,
|
||||
ENOTBLK,
|
||||
ENOTCONN,
|
||||
ENOTDIR,
|
||||
ENOTEMPTY,
|
||||
ENOTNAM,
|
||||
ENOTRECOVERABLE,
|
||||
ENOTSOCK,
|
||||
ENOTSUP,
|
||||
ENOTTY,
|
||||
ENOTUNIQ,
|
||||
ENXIO,
|
||||
EOPNOTSUPP,
|
||||
EOVERFLOW,
|
||||
EOWNERDEAD,
|
||||
EPERM,
|
||||
EPFNOSUPPORT,
|
||||
EPIPE,
|
||||
EPROTO,
|
||||
EPROTONOSUPPORT,
|
||||
EPROTOTYPE,
|
||||
ERANGE,
|
||||
EREMCHG,
|
||||
EREMOTE,
|
||||
EREMOTEIO,
|
||||
ERESTART,
|
||||
ERFKILL,
|
||||
EROFS,
|
||||
ESHUTDOWN,
|
||||
ESOCKTNOSUPPORT,
|
||||
ESPIPE,
|
||||
ESRCH,
|
||||
ESRMNT,
|
||||
ESTALE,
|
||||
ESTRPIPE,
|
||||
ETIME,
|
||||
ETIMEDOUT,
|
||||
ETOOMANYREFS,
|
||||
ETXTBSY,
|
||||
EUCLEAN,
|
||||
EUNATCH,
|
||||
EUSERS,
|
||||
EWOULDBLOCK,
|
||||
EXDEV,
|
||||
EXFULL,
|
||||
);
|
||||
|
||||
impl Errno {
|
||||
/// Constructs from either sign convention.
|
||||
///
|
||||
/// Zero is not an error, and `c_int::MIN` has no positive counterpart: the C helpers assert on both in
|
||||
/// developer mode, here they are a `debug_assert!` and map to `EINVAL` otherwise.
|
||||
pub const fn from_code(code: c_int) -> Errno {
|
||||
debug_assert!(code != 0 && code != c_int::MIN, "not an errno value");
|
||||
match NonZero::new(code.wrapping_abs()) {
|
||||
Some(n) if code != c_int::MIN => Errno(n),
|
||||
_ => Errno(EINVAL_NZ),
|
||||
}
|
||||
}
|
||||
|
||||
/// The positive errno value.
|
||||
pub const fn code(self) -> c_int {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// The value as C code returns it.
|
||||
pub const fn negative(self) -> c_int {
|
||||
-self.0.get()
|
||||
}
|
||||
|
||||
/// The symbolic name, `errno_name_no_fallback()`; `None` for values the tree does not know.
|
||||
pub fn name(self) -> Option<&'static CStr> {
|
||||
// SAFETY: errno_name_no_fallback() returns a static string or NULL.
|
||||
let p = unsafe { sys::errno_name_no_fallback(self.0.get()) };
|
||||
if p.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: p is a static NUL-terminated string.
|
||||
Some(unsafe { CStr::from_ptr(p) })
|
||||
}
|
||||
|
||||
/// The calling thread's current `errno`, `negative_errno()`.
|
||||
pub fn last_os_error() -> Errno {
|
||||
// SAFETY: plain call into libsystemd-shared.
|
||||
Errno::from_code(unsafe { sys::negative_errno() })
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Errno::from_code()`], so that the `log_*_errno!` macros take what a C helper returned.
|
||||
impl From<c_int> for Errno {
|
||||
fn from(code: c_int) -> Errno {
|
||||
Errno::from_code(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::num::TryFromIntError> for Errno {
|
||||
fn from(_: core::num::TryFromIntError) -> Errno {
|
||||
Errno::ERANGE
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::str::Utf8Error> for Errno {
|
||||
fn from(_: core::str::Utf8Error) -> Errno {
|
||||
Errno::EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::alloc::AllocError> for Errno {
|
||||
fn from(_: crate::alloc::AllocError) -> Errno {
|
||||
Errno::ENOMEM
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::convert::Infallible> for Errno {
|
||||
fn from(e: core::convert::Infallible) -> Errno {
|
||||
match e {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The message, as `%m` prints it.
|
||||
impl fmt::Display for Errno {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut buf: [c_char; sys::ERRNO_BUF_LEN as usize] = [0; sys::ERRNO_BUF_LEN as usize];
|
||||
// SAFETY: strerror_or_eof() is the GNU strerror_r() on either libc for a non-zero errno: it returns buf
|
||||
// or a static string, both NUL-terminated, and never writes past the length it is given.
|
||||
let s = unsafe { CStr::from_ptr(sys::strerror_or_eof(self.0.get(), buf.as_mut_ptr(), buf.len())) };
|
||||
fmt::Display::fmt(&cstr::display(s), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Errno {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.name() {
|
||||
Some(name) => write!(f, "Errno({})", cstr::display(name)),
|
||||
None => write!(f, "Errno({})", self.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for Errno {}
|
||||
|
||||
/// The result type of everything that talks to libsystemd-shared.
|
||||
pub type Result<T = (), E = Errno> = core::result::Result<T, E>;
|
||||
|
||||
/// Maps a systemd-style return value (negative errno on failure) to a [`Result`].
|
||||
pub fn check(r: c_int) -> Result<c_int> {
|
||||
if r < 0 {
|
||||
Err(Errno::from_code(r))
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a closure and maps its [`Result`] to the `int` an `extern "C"` callback hands back to C: the value
|
||||
/// on success, `-errno` on failure.
|
||||
///
|
||||
/// ```ignore
|
||||
/// use core::ffi::c_int;
|
||||
/// use systemd_shared::{from_result, Errno};
|
||||
///
|
||||
/// assert_eq!(from_result(|| Ok(3)), 3);
|
||||
/// assert_eq!(from_result(|| Err(Errno::EBUSY)), -16);
|
||||
/// ```
|
||||
pub fn from_result(f: impl FnOnce() -> Result<c_int>) -> c_int {
|
||||
match f() {
|
||||
Ok(r) => r,
|
||||
Err(e) => e.negative(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! Rust interface to libsystemd-shared.
|
||||
//!
|
||||
//! [`sys`] is the bindgen-generated FFI crate: every function, type and constant of `src/basic/`,
|
||||
//! `src/shared/`, the public `sd-*.h` headers and the libc/kernel surface systemd code sees, with C trampolines
|
||||
//! for the static inline helpers. Nothing in it is safe to call; the modules here wrap what programs need in the
|
||||
//! idiomatic form: owned types with `Drop` for what C frees explicitly, [`Result`] for the negative-errno
|
||||
//! convention, `log_*!` macros that feed systemd's logging and the command line framework of the C
|
||||
//! programs. A program that needs something [`sys`] has and this crate lacks adds the wrapper here.
|
||||
//!
|
||||
//! Binaries written in Rust link libsystemd-shared dynamically, exactly like the C binaries do.
|
||||
//!
|
||||
//! There is no std, and like in the kernel's Rust code there is no `alloc` either: its collections abort the
|
||||
//! program when memory runs out. [`alloc`] has a [`Box`](alloc::Box) and a [`Vec`](alloc::Vec) instead,
|
||||
//! whose allocating functions return an error. This crate fills the other gaps std would fill: a panic is
|
||||
//! logged at `LOG_CRIT` and aborts the program, and [`print!`] and [`println!`] write to C's `stdout`. A
|
||||
//! program that pulls in std regardless does not link, std brings a second panic handler, and neither does one
|
||||
//! that allocates through `alloc`, there is no global allocator.
|
||||
|
||||
#![no_std]
|
||||
|
||||
pub use systemd_shared_sys as sys;
|
||||
|
||||
pub mod alloc;
|
||||
pub mod cstr;
|
||||
pub mod errno;
|
||||
pub mod log;
|
||||
mod panic;
|
||||
pub mod prelude;
|
||||
pub mod stdio;
|
||||
|
||||
pub use errno::{check, from_result, Errno, Result};
|
||||
@@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! systemd's logging, driven from Rust.
|
||||
//!
|
||||
//! The `log_*!` macros mirror their C counterparts: `log_info!("...")` logs, `log_error_errno!(e, "...")`
|
||||
//! logs and evaluates to the [`Errno`](crate::Errno), so `return Err(log_error_errno!(r, "..."))` reads
|
||||
//! like `return log_error_errno(r, "...")` in C. Messages are formatted into a `LINE_MAX` buffer on the
|
||||
//! stack and handed to `log_dispatch_internal()`, the same path `log_internalv()` takes after `vsnprintf()`:
|
||||
//! no allocation, and long messages are truncated the way C truncates them. Rust has no `__func__`,
|
||||
//! `CODE_FUNC=` carries the module path instead.
|
||||
|
||||
use core::ffi::{c_int, CStr};
|
||||
use core::fmt::{self, Write};
|
||||
use core::ptr;
|
||||
|
||||
use crate::sys;
|
||||
|
||||
/// The syslog levels, as the `int` the C helpers take.
|
||||
pub const LOG_EMERG: c_int = sys::LOG_EMERG as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_ALERT: c_int = sys::LOG_ALERT as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_CRIT: c_int = sys::LOG_CRIT as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_ERR: c_int = sys::LOG_ERR as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_WARNING: c_int = sys::LOG_WARNING as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_NOTICE: c_int = sys::LOG_NOTICE as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_INFO: c_int = sys::LOG_INFO as c_int;
|
||||
/// See [`LOG_EMERG`].
|
||||
pub const LOG_DEBUG: c_int = sys::LOG_DEBUG as c_int;
|
||||
|
||||
/// `log_setup()`, as called from every C main function.
|
||||
pub fn setup() {
|
||||
// SAFETY: plain call into libsystemd-shared.
|
||||
unsafe { sys::log_setup() }
|
||||
}
|
||||
|
||||
/// The current maximum log level, `log_get_max_level()`.
|
||||
pub fn max_level() -> c_int {
|
||||
// SAFETY: plain call into libsystemd-shared.
|
||||
unsafe { sys::log_get_max_level() }
|
||||
}
|
||||
|
||||
/// Turns a NUL-terminated string literal into a `CStr` at compile time. Backend of the `log_*!` macros.
|
||||
#[doc(hidden)]
|
||||
pub const fn __cstr(s: &'static str) -> &'static CStr {
|
||||
match CStr::from_bytes_with_nul(s.as_bytes()) {
|
||||
Ok(c) => c,
|
||||
Err(_) => panic!("not a NUL-terminated string"),
|
||||
}
|
||||
}
|
||||
|
||||
const LINE_MAX: usize = sys::LINE_MAX as usize;
|
||||
|
||||
struct Buffer {
|
||||
bytes: [u8; LINE_MAX],
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl fmt::Write for Buffer {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let n = s.len().min(LINE_MAX - 1 - self.len);
|
||||
self.bytes[self.len..self.len + n].copy_from_slice(&s.as_bytes()[..n]);
|
||||
self.len += n;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a message at `level` is logged, the check C's `log_full_errno()` makes before it evaluates the
|
||||
/// arguments. `LOG_PRI()` is the low three bits. Backend of the `log_*!` macros.
|
||||
#[doc(hidden)]
|
||||
pub fn __enabled(level: c_int) -> bool {
|
||||
max_level() >= (level & 7)
|
||||
}
|
||||
|
||||
/// Backend of the `log_*!` macros.
|
||||
#[doc(hidden)]
|
||||
pub fn __log(level: c_int, error: c_int, file: &CStr, line: u32, func: &CStr, args: fmt::Arguments<'_>) {
|
||||
if !__enabled(level) {
|
||||
return;
|
||||
}
|
||||
|
||||
// log_internalv() keeps errno intact, the formatting included, so do we.
|
||||
// SAFETY: errno belongs to the calling thread.
|
||||
let saved = unsafe { *sys::__errno_location() };
|
||||
|
||||
let mut buffer = Buffer {
|
||||
bytes: [0; LINE_MAX],
|
||||
len: 0,
|
||||
};
|
||||
// Buffer::write_str() never fails, it truncates like vsnprintf() does.
|
||||
let _ = buffer.write_fmt(args);
|
||||
buffer.bytes[buffer.len] = 0;
|
||||
// SAFETY: file, func and the buffer are NUL-terminated, the buffer is writable and outlives the call
|
||||
// (log_dispatch_internal() splits it at newlines in place), the optional fields are NULL as in
|
||||
// log_internalv().
|
||||
unsafe {
|
||||
sys::log_dispatch_internal(
|
||||
level,
|
||||
error,
|
||||
file.as_ptr(),
|
||||
line as c_int,
|
||||
func.as_ptr(),
|
||||
ptr::null(),
|
||||
ptr::null(),
|
||||
ptr::null(),
|
||||
ptr::null(),
|
||||
buffer.bytes.as_mut_ptr().cast(),
|
||||
);
|
||||
}
|
||||
// SAFETY: as above.
|
||||
unsafe { *sys::__errno_location() = saved };
|
||||
}
|
||||
|
||||
/// Logs at the given level, `log_full()` in C. The arguments are only evaluated if the level is enabled.
|
||||
#[macro_export]
|
||||
macro_rules! log_full {
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
let __level: ::core::ffi::c_int = $level;
|
||||
if $crate::log::__enabled(__level) {
|
||||
$crate::log::__log(
|
||||
__level,
|
||||
0,
|
||||
const { $crate::log::__cstr(::core::concat!(::core::file!(), "\0")) },
|
||||
::core::line!(),
|
||||
const { $crate::log::__cstr(::core::concat!(::core::module_path!(), "\0")) },
|
||||
::core::format_args!($($arg)*),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// The bit `SYNTHETIC_ERRNO()` sets: the error did not come from the system, the record carries no `ERRNO=`.
|
||||
#[doc(hidden)]
|
||||
pub const __SYNTHETIC_ERRNO: c_int = 1 << 30;
|
||||
|
||||
/// Logs at the given level with an errno and evaluates to the [`Errno`](crate::Errno), `log_full_errno()`
|
||||
/// in C. `SYNTHETIC_ERRNO(EINVAL)` as the error is `SYNTHETIC_ERRNO(EINVAL)` in C: an error the program
|
||||
/// detected itself, logged without `ERRNO=`. The level and the error are always evaluated, the message
|
||||
/// arguments only if the level is enabled.
|
||||
#[macro_export]
|
||||
macro_rules! log_full_errno {
|
||||
(@log $level:expr, $error:expr, $flags:expr, $($arg:tt)*) => {{
|
||||
let __level: ::core::ffi::c_int = $level;
|
||||
let __e: $crate::errno::Errno = ::core::convert::From::from($error);
|
||||
if $crate::log::__enabled(__level) {
|
||||
$crate::log::__log(
|
||||
__level,
|
||||
__e.code() | $flags,
|
||||
const { $crate::log::__cstr(::core::concat!(::core::file!(), "\0")) },
|
||||
::core::line!(),
|
||||
const { $crate::log::__cstr(::core::concat!(::core::module_path!(), "\0")) },
|
||||
::core::format_args!($($arg)*),
|
||||
);
|
||||
}
|
||||
__e
|
||||
}};
|
||||
($level:expr, SYNTHETIC_ERRNO($errno:ident), $($arg:tt)*) => {
|
||||
$crate::log_full_errno!(@log $level, $crate::errno::Errno::$errno, $crate::log::__SYNTHETIC_ERRNO, $($arg)*)
|
||||
};
|
||||
($level:expr, $error:expr, $($arg:tt)*) => {
|
||||
$crate::log_full_errno!(@log $level, $error, 0, $($arg)*)
|
||||
};
|
||||
}
|
||||
|
||||
/// `log_error()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_error { ($($arg:tt)*) => { $crate::log_full!($crate::log::LOG_ERR, $($arg)*) }; }
|
||||
/// `log_warning()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_warning { ($($arg:tt)*) => { $crate::log_full!($crate::log::LOG_WARNING, $($arg)*) }; }
|
||||
/// `log_notice()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_notice { ($($arg:tt)*) => { $crate::log_full!($crate::log::LOG_NOTICE, $($arg)*) }; }
|
||||
/// `log_info()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_info { ($($arg:tt)*) => { $crate::log_full!($crate::log::LOG_INFO, $($arg)*) }; }
|
||||
/// `log_debug()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_debug { ($($arg:tt)*) => { $crate::log_full!($crate::log::LOG_DEBUG, $($arg)*) }; }
|
||||
|
||||
/// `log_error_errno()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_error_errno { ($($arg:tt)*) => { $crate::log_full_errno!($crate::log::LOG_ERR, $($arg)*) }; }
|
||||
/// `log_warning_errno()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_warning_errno { ($($arg:tt)*) => { $crate::log_full_errno!($crate::log::LOG_WARNING, $($arg)*) }; }
|
||||
/// `log_notice_errno()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_notice_errno { ($($arg:tt)*) => { $crate::log_full_errno!($crate::log::LOG_NOTICE, $($arg)*) }; }
|
||||
/// `log_info_errno()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_info_errno { ($($arg:tt)*) => { $crate::log_full_errno!($crate::log::LOG_INFO, $($arg)*) }; }
|
||||
/// `log_debug_errno()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_debug_errno { ($($arg:tt)*) => { $crate::log_full_errno!($crate::log::LOG_DEBUG, $($arg)*) }; }
|
||||
|
||||
/// `log_oom()`.
|
||||
#[macro_export]
|
||||
macro_rules! log_oom {
|
||||
() => {
|
||||
$crate::log_full_errno!(
|
||||
$crate::log::LOG_ERR,
|
||||
$crate::errno::Errno::ENOMEM,
|
||||
"Out of memory."
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -134,3 +134,23 @@ if want_tests != 'false'
|
||||
meson.project_source_root() / 'src/systemd'],
|
||||
suite : 'dist')
|
||||
endif
|
||||
|
||||
############################################################
|
||||
|
||||
# systemd_shared: what Rust code is supposed to use.
|
||||
systemd_shared_sources = files(
|
||||
'lib.rs',
|
||||
'alloc.rs',
|
||||
'cstr.rs',
|
||||
'errno.rs',
|
||||
'log.rs',
|
||||
'panic.rs',
|
||||
'prelude.rs',
|
||||
'stdio.rs',
|
||||
)
|
||||
|
||||
libsystemd_shared_rs = static_library(
|
||||
'systemd_shared',
|
||||
systemd_shared_sources,
|
||||
rust_abi : 'rust',
|
||||
link_with : [libsystemd_shared_sys])
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! Panics. Programs are built with `-C panic=abort`: a panic is logged at `LOG_CRIT` with its location, like a
|
||||
//! failed `assert()` in C, and aborts the program.
|
||||
|
||||
use core::ffi::CStr;
|
||||
use core::panic::PanicInfo;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::log::LOG_CRIT;
|
||||
use crate::sys;
|
||||
|
||||
static PANICKING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Copies `s` into `buf` as a C string, truncated at the buffer size or at an embedded NUL.
|
||||
fn to_cstr<'a>(s: &str, buf: &'a mut [u8]) -> &'a CStr {
|
||||
let n = s.len().min(buf.len() - 1);
|
||||
buf[..n].copy_from_slice(&s.as_bytes()[..n]);
|
||||
buf[n] = 0;
|
||||
CStr::from_bytes_until_nul(buf).unwrap_or(c"")
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo<'_>) -> ! {
|
||||
// A panic while logging the first one aborts right away.
|
||||
if !PANICKING.swap(true, Ordering::Relaxed) {
|
||||
let mut buf = [0u8; 256];
|
||||
match info.location() {
|
||||
Some(l) => crate::log::__log(
|
||||
LOG_CRIT,
|
||||
0,
|
||||
to_cstr(l.file(), &mut buf),
|
||||
l.line(),
|
||||
c"panic",
|
||||
format_args!(
|
||||
"Panic at {}:{}: {}. Aborting.",
|
||||
l.file(),
|
||||
l.line(),
|
||||
info.message()
|
||||
),
|
||||
),
|
||||
None => crate::log::__log(
|
||||
LOG_CRIT,
|
||||
0,
|
||||
c"",
|
||||
0,
|
||||
c"panic",
|
||||
format_args!("Panic: {}. Aborting.", info.message()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Not expect(), clippy 1.85 does not report the call.
|
||||
// SAFETY: plain call into libc.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
unsafe {
|
||||
sys::abort()
|
||||
}
|
||||
}
|
||||
|
||||
// The precompiled core and compiler_builtins crates are built for unwinding and refer to the personality
|
||||
// routine. Nothing unwinds with panic=abort, so it never runs; should a foreign exception ever reach a Rust frame,
|
||||
// abort.
|
||||
#[no_mangle]
|
||||
extern "C" fn rust_eh_personality() -> ! {
|
||||
// Not expect(), clippy 1.85 does not report the call.
|
||||
// SAFETY: plain call into libc.
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
unsafe {
|
||||
sys::abort()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! `use systemd_shared::prelude::*;` brings in what every program needs, including the [`Box`] and [`Vec`]
|
||||
//! that replace the ones of `alloc`.
|
||||
|
||||
pub use crate::alloc::{AllocError, Box, Vec};
|
||||
pub use crate::cstr::OwnedCStr;
|
||||
pub use crate::errno::{check, from_result, Errno, Result};
|
||||
pub use crate::log::{
|
||||
LOG_ALERT, LOG_CRIT, LOG_DEBUG, LOG_EMERG, LOG_ERR, LOG_INFO, LOG_NOTICE, LOG_WARNING,
|
||||
};
|
||||
pub use crate::{
|
||||
log_debug, log_debug_errno, log_error, log_error_errno, log_full, log_full_errno, log_info,
|
||||
log_info_errno, log_notice, log_notice_errno, log_oom, log_warning, log_warning_errno,
|
||||
};
|
||||
pub use crate::{print, println};
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
//! [`print!`] and [`println!`]. They write to C's `stdout`, so that their output and that of C code called by
|
||||
//! the program (`--help`, tables, ...) share one buffer and keep their order. Write errors are ignored, as
|
||||
//! `printf()` ignores them.
|
||||
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
use crate::sys;
|
||||
|
||||
struct Stdout;
|
||||
|
||||
impl Write for Stdout {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
// SAFETY: s is valid for s.len() bytes, stdout is libc's stream.
|
||||
let n = unsafe { sys::fwrite(s.as_ptr().cast(), 1, s.len(), sys::stdout) };
|
||||
if n == s.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(fmt::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend of [`print!`] and [`println!`].
|
||||
#[doc(hidden)]
|
||||
pub fn __print(args: fmt::Arguments<'_>) {
|
||||
let _ = Stdout.write_fmt(args);
|
||||
}
|
||||
|
||||
/// Prints to `stdout`, `printf()`.
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => {
|
||||
$crate::stdio::__print(::core::format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
/// Prints to `stdout` with a newline, `printf("...\n")`.
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => {
|
||||
$crate::print!("\n")
|
||||
};
|
||||
($($arg:tt)*) => {
|
||||
$crate::stdio::__print(::core::format_args!("{}\n", ::core::format_args!($($arg)*)))
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user