Add the signal-hook-sys crate

For C code that extracts stuff. Because the libc crate is not really
very helpful there:

* The SI_* constants are missing.
* The si_pid/si_uid methods are available only on few selected targets,
  not everywhere.
This commit is contained in:
Michal 'vorner' Vaner
2020-12-09 10:11:17 +01:00
parent 523348c589
commit 05bf2a660b
12 changed files with 348 additions and 6 deletions
+6 -3
View File
@@ -21,7 +21,10 @@ jobs:
- stable
- beta
- nightly
# Introduction of self: Arc<..>, needed for the iterator module
- 1.36.0
# Introduction of non_exhaustive, used at certain exfiltrators
- 1.40.0
runs-on: ${{ matrix.os }}
@@ -170,7 +173,7 @@ jobs:
uses: Swatinem/rust-cache@v1
- name: Run clippy linter
run: cargo clippy --all --tests -- -D clippy::all -D warnings
run: cargo clippy --all --all-features --tests -- -D clippy::all -D warnings
# There's bunch of platforms that have some weird quirks (or we don't know
# that they don't). While fully compiling and testing on them is a bit of a
@@ -210,7 +213,7 @@ jobs:
uses: Swatinem/rust-cache@v1
- name: Run the check
run: cargo check --all --tests --target=${{ matrix.target }}
run: cargo check --all --all-features --tests --target=${{ matrix.target }}
# Check some either weirder platforms, but these support only the base crate,
# not all the fancy async ones.
@@ -242,4 +245,4 @@ jobs:
uses: Swatinem/rust-cache@v1
- name: Run the check
run: cargo check --tests --target=${{ matrix.target }}
run: cargo check --tests --all-features --target=${{ matrix.target }}
+1
View File
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
tags
.ccls-cache
Generated
+9
View File
@@ -908,6 +908,7 @@ dependencies = [
"libc",
"serial_test",
"signal-hook-registry 1.2.2",
"signal-hook-sys",
]
[[package]]
@@ -950,6 +951,14 @@ dependencies = [
"libc",
]
[[package]]
name = "signal-hook-sys"
version = "0.1.0"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "signal-hook-tokio"
version = "0.1.0"
+3
View File
@@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" }
[features]
default = ["iterator"]
iterator = []
extended-siginfo = ["iterator", "signal-hook-sys"]
[workspace]
members = [
@@ -27,11 +28,13 @@ members = [
"signal-hook-tokio",
"signal-hook-mio",
"signal-hook-async-std",
"signal-hook-sys",
]
[dependencies]
libc = "~0.2"
signal-hook-registry = { version = "~1.2", path = "signal-hook-registry" }
signal-hook-sys = { version = "~0.1", path = "signal-hook-sys", optional = true }
[dev-dependencies]
serial_test = "~0.5"
+4 -2
View File
@@ -8,16 +8,18 @@
set -ex
rm -f Cargo.lock
cargo build --all --exclude signal-hook-async-std
cargo build --all --exclude signal-hook-async-std --exclude signal-hook-sys
if [ "$RUST_VERSION" = 1.36.0 ] ; then
exit
fi
if [ "$OS" = "windows-latest" ] ; then
if [ "$OS" = "windows-latest" ] || [ "$RUST_VERSION" = 1.40.0 ]; then
# The async support crates rely on the iterator module
# which isn't available for windows. So exclude them
# from the build.
# Also, some dependencies of the runtimes don't like 1.40.
EXCLUDE_FROM_BUILD="--exclude signal-hook-mio --exclude signal-hook-tokio --exclude signal-hook-async-std"
else
EXCLUDE_FROM_BUILD=""
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "signal-hook-sys"
version = "0.1.0"
authors = ["Michal 'vorner' Vaner <vorner@vorner.cz>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "~0.2"
[build-dependencies]
cc = "~1"
+5
View File
@@ -0,0 +1,5 @@
use cc::Build;
fn main() {
Build::new().file("src/extract.c").compile("extract");
}
+51
View File
@@ -0,0 +1,51 @@
#include <stdbool.h>
#include <signal.h>
#include <stdint.h>
struct Const {
int native;
// The signal this applies to, or -1 if it applies to anything.
int signal;
uint8_t translated;
};
// Warning: must be in sync with the rust source code
struct Const consts[] = {
#ifdef SI_KERNEL
{ SI_KERNEL, -1, 1 },
#endif
{ SI_USER, -1, 2 },
#ifdef SI_TKILL
{ SI_TKILL, -1, 3 },
#endif
{ SI_QUEUE, -1, 4 },
{ SI_MESGQ, -1, 5 },
{ CLD_EXITED, SIGCHLD, 6 },
{ CLD_KILLED, SIGCHLD, 7 },
{ CLD_DUMPED, SIGCHLD, 8 },
{ CLD_TRAPPED, SIGCHLD, 9 },
{ CLD_STOPPED, SIGCHLD, 10 },
{ CLD_CONTINUED, SIGCHLD, 11 },
};
uint8_t sighook_signal_cause(const siginfo_t *info) {
const size_t const_len = sizeof consts / sizeof *consts;
size_t i;
for (i = 0; i < const_len; i ++) {
if (
consts[i].native == info->si_code &&
(consts[i].signal == -1 || consts[i].signal == info->si_signo)
) {
return consts[i].translated;
}
}
return 0; // The "Unknown" variant
}
pid_t sighook_signal_pid(const siginfo_t *info) {
return info->si_pid;
}
uid_t sighook_signal_uid(const siginfo_t *info) {
return info->si_uid;
}
+123
View File
@@ -0,0 +1,123 @@
//! Low-level internals of [`signal-hook`](https://docs.rs/signal-hook).
//!
//! This crate contains some internal APIs, split off to a separate crate for technical reasons. Do
//! not use directly. There are no stability guarantees, no documentation and you should use
//! `signal-hook` directly.
#[doc(hidden)]
pub mod internal {
use libc::{pid_t, siginfo_t, uid_t};
// Careful: make sure the signature and the constants match the C source
extern "C" {
fn sighook_signal_cause(info: &siginfo_t) -> Cause;
fn sighook_signal_pid(info: &siginfo_t) -> pid_t;
fn sighook_signal_uid(info: &siginfo_t) -> uid_t;
}
// Warning: must be in sync with the C code
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
#[repr(u8)]
pub enum Cause {
Unknown = 0,
Kernel = 1,
User = 2,
TKill = 3,
Queue = 4,
MesgQ = 5,
Exited = 6,
Killed = 7,
Dumped = 8,
Trapped = 9,
Stopped = 10,
Continued = 11,
}
impl Cause {
// The MacOs doesn't use the SI_* constants and leaves si_code at 0. But it doesn't use an
// union, it has a good-behaved struct with fields and therefore we *can* read the values,
// even though they'd contain nonsense (zeroes). We wipe that out later.
#[cfg(target_os = "macos")]
fn has_process(self) -> bool {
true
}
#[cfg(not(target_os = "macos"))]
fn has_process(self) -> bool {
use Cause::*;
match self {
Unknown | Kernel => false,
User | TKill | Queue | MesgQ | Exited | Killed | Dumped | Trapped | Stopped
| Continued => true,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Process {
pub pid: pid_t,
pub uid: uid_t,
}
impl Process {
/**
* Extract the process information.
*
* # Safety
*
* The `info` must have a `si_code` corresponding to some situation that has the `si_pid`
* and `si_uid` filled in.
*/
unsafe fn extract(info: &siginfo_t) -> Self {
Self {
pid: sighook_signal_pid(info),
uid: sighook_signal_uid(info),
}
}
pub const fn to_u64(self) -> u64 {
let pid = self.pid as u32; // With overflow for negative ones
let uid = self.uid as u32;
((pid as u64) << 32) | (uid as u64)
}
pub const fn from_u64(encoded: u64) -> Self {
let pid = ((encoded >> 32) as u32) as _;
let uid = (encoded as u32) as _;
Self { pid, uid }
}
pub const EMPTY: Self = Self { pid: -1, uid: 0 };
pub const NO_PROCESS: Self = Self { pid: -1, uid: 1 };
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct SigInfo {
pub cause: Cause,
pub process: Option<Process>,
}
impl SigInfo {
// Note: shall be async-signal-safe
pub fn extract(info: &siginfo_t) -> Self {
let cause = unsafe { sighook_signal_cause(info) };
let process = if cause.has_process() {
let process = unsafe { Process::extract(info) };
// On macos we don't have the si_code to go by, but we can go by the values being
// empty there.
if cfg!(target_os = "macos") && process.pid == 0 && process.uid == 0 {
None
} else {
Some(process)
}
} else {
None
};
Self { cause, process }
}
}
}
@@ -11,6 +11,9 @@
//! Currently, the trait is sealed and all methods hidden. This is likely temporary, until some
//! experience with them is gained.
#[cfg(feature = "extended-siginfo")]
pub mod origin;
use std::sync::atomic::{AtomicBool, Ordering};
use libc::{c_int, siginfo_t};
+123
View File
@@ -0,0 +1,123 @@
//! An exfiltrator providing the process that caused the signal.
//!
//! The [`WithOrigin`] is an [`Exfiltrator`][crate::iterator::exfiltrator::Exfiltrator] that
//! provides the information about sending process in addition to the signal number, through the
//! [`Origin`] type.
//!
//! See the [`WithOrigin`] example.
use std::sync::atomic::{AtomicU64, Ordering};
use libc::{c_int, pid_t, siginfo_t, uid_t};
use signal_hook_sys::internal::{Process as IProcess, SigInfo};
use super::sealed::Exfiltrator;
/// Information about process, as presented in the signal metadata.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct Process {
/// The process ID.
pub pid: pid_t,
/// The user owning the process.
pub uid: uid_t,
}
impl From<IProcess> for Process {
fn from(p: IProcess) -> Self {
Self {
pid: p.pid,
uid: p.uid,
}
}
}
/// Information about a signal and its origin.
///
/// This is produced by the [`WithOrigin`] exfiltrator. See the example there.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Origin {
/// The signal that happened.
pub signal: c_int,
/// Information about the process that caused the signal.
///
/// Note that not all signals are caused by a specific process or have the information
/// available („fault“ signals like `SIGBUS` don't have, any signal may be sent by the kernel
/// instead of a specific process).
///
/// This is filled in whenever available. For most signals, this is the process that sent the
/// signal (by `kill` or similar), for `SIGCHLD` it is the child that caused the signal.
pub process: Option<Process>,
// TODO: Figure out a better encoding somehow and expose other info, including the Cause
}
#[doc(hidden)]
#[derive(Debug)]
pub struct OriginStorage(AtomicU64);
impl Default for OriginStorage {
fn default() -> Self {
Self(AtomicU64::new(IProcess::EMPTY.to_u64()))
}
}
/// The [`Exfiltrator`][crate::iterator::exfiltrator::Exfiltrator] that produces [`Origin`] of
/// signals.
///
/// # Examples
///
/// ```rust
/// # use signal_hook::SIGUSR1;
/// # use signal_hook::iterator::SignalsInfo;
/// # use signal_hook::iterator::exfiltrator::origin::WithOrigin;
/// #
/// # fn main() -> Result<(), std::io::Error> {
/// // Subscribe to SIGUSR1, with information about the process.
/// let signals = SignalsInfo::<WithOrigin>::new(&[SIGUSR1])?;
///
/// // Send a signal to ourselves.
/// let my_pid = unsafe { libc::getpid() };
/// unsafe { libc::kill(my_pid, SIGUSR1) };
///
/// // Grab the signal and look into the details.
/// let received = signals.forever().next().unwrap();
///
/// assert_eq!(SIGUSR1, received.signal);
/// assert_eq!(my_pid, received.process.unwrap().pid);
/// # Ok(()) }
/// ```
#[derive(Copy, Clone, Debug, Default)]
pub struct WithOrigin;
unsafe impl Exfiltrator for WithOrigin {
type Storage = OriginStorage;
type Output = Origin;
fn supports_signal(&self, _: c_int) -> bool {
true
}
fn store(&self, slot: &OriginStorage, _: c_int, info: &siginfo_t) {
let value = SigInfo::extract(info)
.process
.unwrap_or(IProcess::NO_PROCESS)
.to_u64();
slot.0.store(value, Ordering::SeqCst);
}
fn load(&self, slot: &OriginStorage, signal: c_int) -> Option<Origin> {
let value = slot.0.swap(IProcess::EMPTY.to_u64(), Ordering::SeqCst);
match IProcess::from_u64(value) {
IProcess::EMPTY => None,
IProcess::NO_PROCESS => Some(Origin {
signal,
process: None,
}),
process => Some(Origin {
signal,
process: Some(process.into()),
}),
}
}
}
+7 -1
View File
@@ -4,7 +4,7 @@
//! the [`SignalsInfo`] structure which is able to iterate over the
//! incoming signals. The structure is parametrized by an
//! [`Exfiltrator`][self::exfiltrator::Exfiltrator], which specifies what information is returned
//! for each delivered signal.
//! for each delivered signal. Note that some exfiltrators are behind a feature flag.
//!
//! The [`Signals`] is a type alias for the common case when it is enough to get the signal number.
//!
@@ -89,6 +89,12 @@ use self::exfiltrator::{Exfiltrator, SignalOnly};
/// The controller handle can be shared between as many threads as you like using its
/// [`clone`][Handle::clone] method.
///
/// # Exfiltrators
///
/// The [`SignalOnly]` provides only the signal number. There are further exfiltrators available in
/// the [`exfiltrator`] module. Note that some of them are behind feature flags that need to be
/// enabled.
///
/// # Examples
///
/// ```rust