use std::{
error::Error,
fmt::{self, Display, Formatter},
io,
};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct ProcessId(libc::pid_t);
impl ProcessId {
#[must_use]
pub const fn new(raw: libc::pid_t) -> Option<Self> {
if raw > 0 { Some(Self(raw)) } else { None }
}
#[must_use]
pub const fn get(self) -> libc::pid_t {
self.0
}
}
impl Display for ProcessId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, formatter)
}
}
impl TryFrom<libc::pid_t> for ProcessId {
type Error = InvalidProcessId;
fn try_from(raw: libc::pid_t) -> Result<Self, Self::Error> {
Self::new(raw).ok_or(InvalidProcessId(raw))
}
}
impl From<ProcessId> for libc::pid_t {
fn from(pid: ProcessId) -> Self {
pid.get()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidProcessId(libc::pid_t);
impl InvalidProcessId {
#[must_use]
pub const fn raw(self) -> libc::pid_t {
self.0
}
}
impl Display for InvalidProcessId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "process ID must be positive, got {}", self.0)
}
}
impl Error for InvalidProcessId {}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct ProcessGroupId(libc::pid_t);
impl ProcessGroupId {
#[must_use]
pub const fn new(raw: libc::pid_t) -> Option<Self> {
if raw > 0 { Some(Self(raw)) } else { None }
}
#[must_use]
pub const fn get(self) -> libc::pid_t {
self.0
}
}
impl Display for ProcessGroupId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, formatter)
}
}
impl TryFrom<libc::pid_t> for ProcessGroupId {
type Error = InvalidProcessGroupId;
fn try_from(raw: libc::pid_t) -> Result<Self, Self::Error> {
Self::new(raw).ok_or(InvalidProcessGroupId(raw))
}
}
impl From<ProcessGroupId> for libc::pid_t {
fn from(pgid: ProcessGroupId) -> Self {
pgid.get()
}
}
impl From<ProcessId> for ProcessGroupId {
fn from(pid: ProcessId) -> Self {
Self(pid.get())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidProcessGroupId(libc::pid_t);
impl InvalidProcessGroupId {
#[must_use]
pub const fn raw(self) -> libc::pid_t {
self.0
}
}
impl Display for InvalidProcessGroupId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
formatter,
"process-group ID must be positive, got {}",
self.0
)
}
}
impl Error for InvalidProcessGroupId {}
#[must_use]
pub fn current_process_id() -> ProcessId {
let raw = unsafe { libc::getpid() };
debug_assert!(raw > 0);
ProcessId(raw)
}
#[must_use]
pub fn current_process_group_id() -> ProcessGroupId {
let raw = unsafe { libc::getpgrp() };
debug_assert!(raw > 0);
ProcessGroupId(raw)
}
pub fn process_group(process: ProcessId) -> io::Result<ProcessGroupId> {
loop {
let raw = unsafe { libc::getpgid(process.get()) };
if raw >= 0 {
return ProcessGroupId::try_from(raw)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error));
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
pub fn create_current_process_group() -> io::Result<ProcessGroupId> {
setpgid_retry(0, 0)?;
Ok(ProcessGroupId::from(current_process_id()))
}
pub fn create_process_group(process: ProcessId) -> io::Result<ProcessGroupId> {
setpgid_retry(process.get(), process.get())?;
Ok(ProcessGroupId::from(process))
}
pub fn join_process_group(process: ProcessId, group: ProcessGroupId) -> io::Result<()> {
setpgid_retry(process.get(), group.get())
}
fn setpgid_retry(target_process: libc::pid_t, target_group: libc::pid_t) -> io::Result<()> {
loop {
if unsafe { libc::setpgid(target_process, target_group) } == 0 {
return Ok(());
}
let error = io::Error::last_os_error();
if error.kind() != io::ErrorKind::Interrupted {
return Err(error);
}
}
}
#[cfg(test)]
mod tests {
use super::{ProcessGroupId, ProcessId};
#[test]
fn identifiers_reject_zero_and_negative_values() {
assert_eq!(ProcessId::new(0), None);
assert_eq!(ProcessId::new(-1), None);
assert_eq!(ProcessGroupId::new(0), None);
assert_eq!(ProcessGroupId::new(-1), None);
}
#[test]
fn identifiers_preserve_positive_values() {
let process = ProcessId::new(42);
let group = ProcessGroupId::new(42);
assert_eq!(process.map(ProcessId::get), Some(42));
assert_eq!(group.map(ProcessGroupId::get), Some(42));
}
}