process_control/lib.rs
1//! This crate allows running a process with resource limits, such as a running
2//! time, and the option to terminate it automatically afterward. The latter is
3//! surprisingly difficult to achieve on Unix, since process identifiers can be
4//! arbitrarily reassigned when no longer used. Thus, it would be extremely
5//! easy to inadvertently terminate an unexpected process. This crate protects
6//! against that possibility.
7//!
8//! Methods for setting limits are available on [`ChildExt`], which is
9//! implemented for [`Child`]. They each return a builder of options to
10//! configure how the limit should be applied.
11//!
12//! <div class="warning">
13//!
14//! This crate should not be used for security. There are many ways that a
15//! process can bypass resource limits. The limits are only intended for simple
16//! restriction of harmless processes.
17//!
18//! </div>
19//!
20//! # Features
21//!
22//! These features are optional and can be enabled or disabled in a
23//! "Cargo.toml" file.
24//!
25//! ### Optional Features
26//!
27//! - **parking\_lot** -
28//! Changes the implementation to use crate [parking\_lot] on targets missing
29//! some syscalls. This feature will reduce the likelihood of resource
30//! starvation for those targets.
31//!
32//! # Implementation
33//!
34//! All traits are [sealed], meaning that they can only be implemented by this
35//! crate. Otherwise, backward compatibility would be more difficult to
36//! maintain for new features.
37//!
38//! # Comparable Crates
39//!
40//! - [wait-timeout] -
41//! Made for a related purpose but does not provide the same functionality.
42//! Processes cannot be terminated automatically, and there is no counterpart
43//! of [`ChildExt::controlled_with_output`] to read output while setting a
44//! timeout. This crate aims to fill in those gaps and simplify the
45//! implementation, now that [`Receiver::recv_timeout`] exists.
46//!
47//! # Examples
48//!
49//! ```
50//! use std::io;
51//! use std::process::Command;
52//! use std::process::Stdio;
53//! use std::time::Duration;
54//!
55//! use process_control::ChildExt;
56//! use process_control::Control;
57//!
58//! let message = "hello world";
59//! let process = Command::new("echo")
60//! .arg(message)
61//! .stdout(Stdio::piped())
62//! .spawn()?;
63//!
64//! let output = process
65//! .controlled_with_output()
66//! .time_limit(Duration::from_secs(1))
67//! .terminate_for_timeout()
68//! .wait()?
69//! .ok_or_else(|| {
70//! io::Error::new(io::ErrorKind::TimedOut, "Process timed out")
71//! })?;
72//! assert!(output.status.success());
73//! assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
74//! #
75//! # Ok::<_, io::Error>(())
76//! ```
77//!
78//! [parking\_lot]: https://crates.io/crates/parking_lot
79//! [`Receiver::recv_timeout`]: ::std::sync::mpsc::Receiver::recv_timeout
80//! [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed
81//! [wait-timeout]: https://crates.io/crates/wait-timeout
82
83// Only require a nightly compiler when building documentation for docs.rs.
84// This is a private option that should not be used.
85// https://github.com/rust-lang/docs.rs/issues/147#issuecomment-389544407
86#![cfg_attr(process_control_docs_rs, feature(doc_cfg))]
87#![warn(unused_results)]
88
89use std::fmt;
90use std::fmt::Debug;
91use std::fmt::Display;
92use std::fmt::Formatter;
93use std::io;
94#[cfg(any(doc, unix))]
95use std::os::raw::c_int;
96use std::process;
97use std::process::Child;
98use std::str;
99use std::time::Duration;
100
101mod control;
102
103#[cfg_attr(unix, path = "unix/mod.rs")]
104#[cfg_attr(windows, path = "windows/mod.rs")]
105mod imp;
106
107type WaitResult<T> = io::Result<Option<T>>;
108
109#[rustfmt::skip]
110macro_rules! unix_method {
111 ( $method:ident , $return_type:ty ) => {
112 #[doc = concat!(
113 "Equivalent to [`ExitStatusExt::",
114 stringify!($method),
115 "`][method].
116
117[method]: ::std::os::unix::process::ExitStatusExt::",
118 stringify!($method),
119 )]
120 #[cfg(any(doc, unix))]
121 #[cfg_attr(process_control_docs_rs, doc(cfg(unix)))]
122 #[inline]
123 #[must_use]
124 pub fn $method(&self) -> $return_type {
125 self.inner.$method()
126 }
127 };
128}
129
130/// Equivalent to [`process::ExitStatus`] but allows for greater accuracy.
131#[derive(Copy, Clone, Debug, Eq, PartialEq)]
132#[must_use]
133pub struct ExitStatus {
134 inner: imp::ExitStatus,
135 std: process::ExitStatus,
136}
137
138impl ExitStatus {
139 fn new(inner: imp::ExitStatus, std: process::ExitStatus) -> Self {
140 debug_assert_eq!(inner, std.into());
141 Self { inner, std }
142 }
143
144 /// Equivalent to [`process::ExitStatus::success`].
145 #[inline]
146 #[must_use]
147 pub fn success(self) -> bool {
148 self.inner.success()
149 }
150
151 /// Equivalent to [`process::ExitStatus::code`], but a more accurate value
152 /// will be returned if possible.
153 #[inline]
154 #[must_use]
155 pub fn code(self) -> Option<i64> {
156 self.inner.code().map(Into::into)
157 }
158
159 unix_method!(continued, bool);
160 unix_method!(core_dumped, bool);
161 unix_method!(signal, Option<c_int>);
162 unix_method!(stopped_signal, Option<c_int>);
163
164 /// Converts this structure to a corresponding [`process::ExitStatus`]
165 /// instance.
166 ///
167 /// Since this type can represent more exit codes, it will attempt to
168 /// provide an equivalent representation using the standard library type.
169 /// However, if converted back to this structure, detailed information may
170 /// have been lost.
171 ///
172 /// # Examples
173 ///
174 /// ```
175 /// # use std::io;
176 /// use std::process::Command;
177 /// use std::time::Duration;
178 ///
179 /// use process_control::ChildExt;
180 /// use process_control::Control;
181 ///
182 /// let exit_status = Command::new("echo")
183 /// .spawn()?
184 /// .controlled()
185 /// .time_limit(Duration::from_secs(1))
186 /// .terminate_for_timeout()
187 /// .wait()?
188 /// .expect("process timed out");
189 /// assert!(exit_status.success());
190 /// assert!(exit_status.into_std_lossy().success());
191 /// #
192 /// # Ok::<_, io::Error>(())
193 /// ```
194 #[inline]
195 #[must_use]
196 pub fn into_std_lossy(self) -> process::ExitStatus {
197 self.std
198 }
199}
200
201impl AsMut<Self> for ExitStatus {
202 #[inline]
203 fn as_mut(&mut self) -> &mut Self {
204 self
205 }
206}
207
208impl AsRef<Self> for ExitStatus {
209 #[inline]
210 fn as_ref(&self) -> &Self {
211 self
212 }
213}
214
215impl Display for ExitStatus {
216 #[inline]
217 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
218 Display::fmt(&self.inner, f)
219 }
220}
221
222impl From<process::ExitStatus> for ExitStatus {
223 #[inline]
224 fn from(value: process::ExitStatus) -> Self {
225 Self::new(value.into(), value)
226 }
227}
228
229/// Equivalent to [`process::Output`] but holds an instance of [`ExitStatus`]
230/// from this crate.
231#[derive(Clone, Eq, PartialEq)]
232#[must_use]
233pub struct Output {
234 /// Equivalent to [`process::Output::status`].
235 pub status: ExitStatus,
236
237 /// Equivalent to [`process::Output::stdout`].
238 pub stdout: Vec<u8>,
239
240 /// Equivalent to [`process::Output::stderr`].
241 pub stderr: Vec<u8>,
242}
243
244impl Output {
245 /// Converts this structure to a corresponding [`process::Output`]
246 /// instance.
247 ///
248 /// For more information, see [`ExitStatus::into_std_lossy`].
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// # use std::io;
254 /// use std::process::Command;
255 /// use std::process::Stdio;
256 /// use std::time::Duration;
257 ///
258 /// use process_control::ChildExt;
259 /// use process_control::Control;
260 ///
261 /// let message = "foobar";
262 /// let output = Command::new("echo")
263 /// .arg(message)
264 /// .stdout(Stdio::piped())
265 /// .spawn()?
266 /// .controlled_with_output()
267 /// .time_limit(Duration::from_secs(1))
268 /// .terminate_for_timeout()
269 /// .wait()?
270 /// .expect("process timed out");
271 /// assert!(output.status.success());
272 /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
273 ///
274 /// let output = output.into_std_lossy();
275 /// assert!(output.status.success());
276 /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
277 /// #
278 /// # Ok::<_, io::Error>(())
279 /// ```
280 #[inline]
281 #[must_use]
282 pub fn into_std_lossy(self) -> process::Output {
283 process::Output {
284 status: self.status.into_std_lossy(),
285 stdout: self.stdout,
286 stderr: self.stderr,
287 }
288 }
289}
290
291impl AsMut<ExitStatus> for Output {
292 #[inline]
293 fn as_mut(&mut self) -> &mut ExitStatus {
294 &mut self.status
295 }
296}
297
298impl AsRef<ExitStatus> for Output {
299 #[inline]
300 fn as_ref(&self) -> &ExitStatus {
301 &self.status
302 }
303}
304
305struct DebugBuffer<'a>(&'a [u8]);
306
307impl Debug for DebugBuffer<'_> {
308 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
309 f.write_str("\"")?;
310
311 let mut string = self.0;
312 while !string.is_empty() {
313 let mut invalid = &b""[..];
314 let valid = str::from_utf8(string).unwrap_or_else(|error| {
315 let (valid, string) = string.split_at(error.valid_up_to());
316
317 let invalid_length =
318 error.error_len().unwrap_or_else(|| string.len());
319 invalid = &string[..invalid_length];
320
321 // SAFETY: This slice was validated to be UTF-8.
322 unsafe { str::from_utf8_unchecked(valid) }
323 });
324
325 Display::fmt(&valid.escape_debug(), f)?;
326 string = &string[valid.len()..];
327
328 for byte in invalid {
329 write!(f, "\\x{:02X}", byte)?;
330 }
331 string = &string[invalid.len()..];
332 }
333
334 f.write_str("\"")
335 }
336}
337
338impl Debug for Output {
339 #[inline]
340 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
341 f.debug_struct("Output")
342 .field("status", &self.status)
343 .field("stdout", &DebugBuffer(&self.stdout))
344 .field("stderr", &DebugBuffer(&self.stderr))
345 .finish()
346 }
347}
348
349impl From<process::Output> for Output {
350 #[inline]
351 fn from(value: process::Output) -> Self {
352 Self {
353 status: value.status.into(),
354 stdout: value.stdout,
355 stderr: value.stderr,
356 }
357 }
358}
359
360impl From<Output> for ExitStatus {
361 #[inline]
362 fn from(value: Output) -> Self {
363 value.status
364 }
365}
366
367/// A function to be called for reads from a specific process pipe ([stdout] or
368/// [stderr]).
369///
370/// When additional bytes are read from the pipe, they are passed to this
371/// function, which determines whether to include them in [`Output`]. The
372/// number of bytes is not guaranteed to be consistent and may not match the
373/// number written at any time by the command on the other side of the stream.
374///
375/// If this function returns `Ok(false)`, the passed output will be discarded
376/// and not included in [`Output`]. Errors will be propagated to
377/// [`Control::wait`]. For more complex cases, where specific portions of read
378/// bytes should be included, this function can return `false` and maintain the
379/// output buffer itself.
380///
381/// # Examples
382///
383/// ```
384/// use std::io;
385/// use std::io::Write;
386/// use std::process::Command;
387/// use std::process::Stdio;
388///
389/// use process_control::ChildExt;
390/// use process_control::Control;
391///
392/// let message = "foobar";
393/// let output = Command::new("echo")
394/// .arg(message)
395/// .stdout(Stdio::piped())
396/// .spawn()?
397/// .controlled_with_output()
398/// // Stream output while collecting it.
399/// .stdout_filter(|x| io::stdout().write_all(x).map(|()| true))
400/// .wait()?
401/// .expect("process timed out");
402/// assert!(output.status.success());
403/// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
404/// #
405/// # Ok::<_, io::Error>(())
406/// ```
407///
408/// [stderr]: Control::stderr_filter
409/// [stdout]: Control::stdout_filter
410pub trait PipeFilter:
411 'static + FnMut(&[u8]) -> io::Result<bool> + Send
412{
413}
414
415impl<T> PipeFilter for T where
416 T: 'static + FnMut(&[u8]) -> io::Result<bool> + Send
417{
418}
419
420/// A temporary wrapper for process limits.
421#[attr_alias::eval]
422#[must_use]
423pub trait Control: private::Sealed {
424 /// The type returned by [`wait`].
425 ///
426 /// [`wait`]: Self::wait
427 type Result;
428
429 /// Sets the total virtual memory limit for the process in bytes.
430 ///
431 /// If the process attempts to allocate memory in excess of this limit, the
432 /// allocation will fail. The type of failure will depend on the platform,
433 /// and the process might terminate if it cannot handle it.
434 ///
435 /// Small memory limits are safe, but they might prevent the operating
436 /// system from starting the process.
437 #[attr_alias(memory_limit, cfg(any(doc, *)))]
438 #[attr_alias(memory_limit, cfg_attr(process_control_docs_rs, doc(cfg(*))))]
439 #[must_use]
440 fn memory_limit(self, limit: usize) -> Self;
441
442 /// Sets the total time limit for the process in milliseconds.
443 ///
444 /// A process that exceeds this limit will not be terminated unless
445 /// [`terminate_for_timeout`] is called.
446 ///
447 /// [`terminate_for_timeout`]: Self::terminate_for_timeout
448 #[must_use]
449 fn time_limit(self, limit: Duration) -> Self;
450
451 /// Causes [`wait`] to never suppress an error.
452 ///
453 /// Typically, errors terminating the process will be ignored, as they are
454 /// often less important than the result. However, when this method is
455 /// called, those errors will be returned as well.
456 ///
457 /// [`wait`]: Self::wait
458 #[must_use]
459 fn strict_errors(self) -> Self;
460
461 /// Causes the process to be terminated if it exceeds the time limit.
462 ///
463 /// Process identifier reuse by the system will be mitigated. There should
464 /// never be a scenario that causes an unintended process to be terminated.
465 #[must_use]
466 fn terminate_for_timeout(self) -> Self;
467
468 /// Calls a filter function for each write to [stdout].
469 ///
470 /// For more information, see [`PipeFilter`].
471 ///
472 /// # Panics
473 ///
474 /// Panics if [`Command::stdout`] has not been set to [`Stdio::piped`].
475 ///
476 /// [`Command::stdout`]: ::std::process::Command::stdout
477 /// [`Stdio::piped`]: ::std::process::Stdio::piped
478 /// [stdout]: Output::stdout
479 #[must_use]
480 fn stdout_filter<T>(self, listener: T) -> Self
481 where
482 Self: Control<Result = Output>,
483 T: PipeFilter;
484
485 /// Calls a filter function for each write to [stderr].
486 ///
487 /// For more information, see [`PipeFilter`].
488 ///
489 /// # Panics
490 ///
491 /// Panics if [`Command::stderr`] has not been set to [`Stdio::piped`].
492 ///
493 /// [`Command::stderr`]: ::std::process::Command::stderr
494 /// [`Stdio::piped`]: ::std::process::Stdio::piped
495 /// [stderr]: Output::stderr
496 #[must_use]
497 fn stderr_filter<T>(self, listener: T) -> Self
498 where
499 Self: Control<Result = Output>,
500 T: PipeFilter;
501
502 /// Runs the process to completion, aborting if it exceeds the time limit.
503 ///
504 /// At least one additional thread might be created to wait on the process
505 /// without blocking the current thread.
506 ///
507 /// If the time limit is exceeded before the process finishes, `Ok(None)`
508 /// will be returned. However, the process will not be terminated in that
509 /// case unless [`terminate_for_timeout`] is called beforehand. It is
510 /// recommended to always call that method to allow system resources to be
511 /// freed.
512 ///
513 /// The stdin handle to the process, if it exists, will be closed before
514 /// waiting. Otherwise, the process would assuredly time out when reading
515 /// from that pipe.
516 ///
517 /// This method cannot guarantee that the same [`io::ErrorKind`] variants
518 /// will be returned in the future for the same types of failures. Allowing
519 /// these breakages is required to enable calling [`Child::kill`]
520 /// internally.
521 ///
522 /// [`terminate_for_timeout`]: Self::terminate_for_timeout
523 fn wait(self) -> WaitResult<Self::Result>;
524}
525
526/// Extensions to [`Child`] for easily terminating processes.
527///
528/// For more information, see [the module-level documentation][module].
529///
530/// [module]: self
531pub trait ChildExt: private::Sealed {
532 /// Creates an instance of [`Control`] that yields [`ExitStatus`] for this
533 /// process.
534 ///
535 /// This method parallels [`Child::wait`] but allows setting limits on the
536 /// process.
537 ///
538 /// # Examples
539 ///
540 /// ```
541 /// # use std::io;
542 /// use std::process::Command;
543 /// use std::time::Duration;
544 ///
545 /// use process_control::ChildExt;
546 /// use process_control::Control;
547 ///
548 /// let exit_status = Command::new("echo")
549 /// .spawn()?
550 /// .controlled()
551 /// .time_limit(Duration::from_secs(1))
552 /// .terminate_for_timeout()
553 /// .wait()?
554 /// .expect("process timed out");
555 /// assert!(exit_status.success());
556 /// #
557 /// # Ok::<_, io::Error>(())
558 /// ```
559 #[must_use]
560 fn controlled(&mut self) -> impl Control<Result = ExitStatus> + Debug;
561
562 /// Creates an instance of [`Control`] that yields [`Output`] for this
563 /// process.
564 ///
565 /// This method parallels [`Child::wait_with_output`] but allows setting
566 /// limits on the process.
567 ///
568 /// # Examples
569 ///
570 /// ```
571 /// # use std::io;
572 /// use std::process::Command;
573 /// use std::process::Stdio;
574 /// use std::time::Duration;
575 ///
576 /// use process_control::ChildExt;
577 /// use process_control::Control;
578 ///
579 /// let message = "foobar";
580 /// let output = Command::new("echo")
581 /// .arg(message)
582 /// .stdout(Stdio::piped())
583 /// .spawn()?
584 /// .controlled_with_output()
585 /// .time_limit(Duration::from_secs(1))
586 /// .terminate_for_timeout()
587 /// .wait()?
588 /// .expect("process timed out");
589 /// assert!(output.status.success());
590 /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
591 /// #
592 /// # Ok::<_, io::Error>(())
593 /// ```
594 #[must_use]
595 fn controlled_with_output(self) -> impl Control<Result = Output> + Debug;
596}
597
598impl ChildExt for Child {
599 #[inline]
600 fn controlled(&mut self) -> impl Control<Result = ExitStatus> + Debug {
601 control::Buffer::new(self)
602 }
603
604 #[inline]
605 fn controlled_with_output(self) -> impl Control<Result = Output> + Debug {
606 control::Buffer::new(self)
607 }
608}
609
610mod private {
611 use std::process::Child;
612
613 use super::control;
614
615 pub trait Sealed {}
616 impl Sealed for Child {}
617 impl<P> Sealed for control::Buffer<P> where P: control::Process {}
618}