duct/lib.rs
1//! Duct is a library for running child processes. Duct makes it easy to build
2//! pipelines and redirect IO like a shell. At the same time, Duct helps you
3//! write correct, portable code: whitespace is never significant, errors from
4//! child processes get reported by default, and a variety of [gotchas, bugs,
5//! and platform
6//! inconsistencies](https://github.com/oconnor663/duct.py/blob/master/gotchas.md)
7//! are handled for you the Right Way™.
8//!
9//! - [Documentation](https://docs.rs/duct)
10//! - [Crate](https://crates.io/crates/duct)
11//! - [GitHub repo](https://github.com/oconnor663/duct.rs)
12//! - [the same library, in Python](https://github.com/oconnor663/duct.py)
13//!
14//! Examples
15//! --------
16//!
17//! Run a command without capturing any output. Here "hi" is printed directly
18//! to the terminal:
19//!
20//! ```
21//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! # if cfg!(not(windows)) {
23//! use duct::cmd;
24//! cmd!("echo", "hi").run()?;
25//! # }
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! Capture the standard output of a command. Here "hi" is returned as a
31//! `String`:
32//!
33//! ```
34//! # use duct::cmd;
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! # if cfg!(not(windows)) {
37//! let stdout = cmd!("echo", "hi").read()?;
38//! assert_eq!(stdout, "hi");
39//! # }
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! Capture the standard output of a pipeline:
45//!
46//! ```
47//! # use duct::cmd;
48//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
49//! # if cfg!(not(windows)) {
50//! let stdout = cmd!("echo", "hi").pipe(cmd!("sed", "s/i/o/")).read()?;
51//! assert_eq!(stdout, "ho");
52//! # }
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! Merge standard error into standard output and read both incrementally:
58//!
59//! ```
60//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
61//! # if cfg!(not(windows)) {
62//! use duct::cmd;
63//! use std::io::prelude::*;
64//! use std::io::BufReader;
65//!
66//! let big_cmd = cmd!("bash", "-c", "echo out && echo err 1>&2");
67//! let reader = big_cmd.stderr_to_stdout().reader()?;
68//! let mut lines = BufReader::new(reader).lines();
69//! assert_eq!(lines.next().unwrap()?, "out");
70//! assert_eq!(lines.next().unwrap()?, "err");
71//! # }
72//! # Ok(())
73//! # }
74//! ```
75//!
76//! Children that exit with a non-zero status return an error by default:
77//!
78//! ```
79//! # use duct::cmd;
80//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
81//! # if cfg!(not(windows)) {
82//! let result = cmd!("false").run();
83//! assert!(result.is_err());
84//! let result = cmd!("false").unchecked().run();
85//! assert!(result.is_ok());
86//! # }
87//! # Ok(())
88//! # }
89//! ```
90
91use env_name_str::EnvNameString;
92use shared_child::SharedChild;
93use shared_thread::SharedThread;
94use std::collections::HashMap;
95use std::ffi::{OsStr, OsString};
96use std::fmt;
97use std::fs::File;
98use std::io;
99use std::io::prelude::*;
100use std::mem;
101use std::path::{Path, PathBuf};
102use std::process::{Command, ExitStatus, Output, Stdio};
103use std::sync::{Arc, MutexGuard, OnceLock, RwLock};
104
105#[cfg(not(windows))]
106use std::os::unix::prelude::*;
107#[cfg(windows)]
108use std::os::windows::prelude::*;
109
110#[cfg(not(windows))]
111use std::os::fd::IntoRawFd as IntoRawFdOrHandle;
112#[cfg(windows)]
113use std::os::windows::io::IntoRawHandle as IntoRawFdOrHandle;
114
115mod env_name_str;
116
117/// Unix-specific extensions to duct, for sending signals.
118#[cfg(unix)]
119pub mod unix;
120
121// enums defined below
122use ExpressionInner::*;
123use IoExpressionInner::*;
124
125/// Create a command given a program name and a collection of arguments. See
126/// also the [`cmd!`](macro.cmd.html) macro, which doesn't require a collection.
127///
128/// # Example
129///
130/// ```
131/// use duct::cmd;
132///
133/// let args = vec!["foo", "bar", "baz"];
134///
135/// # // NOTE: Normally this wouldn't work on Windows, but we have an "echo"
136/// # // binary that gets built for our main tests, and it's sitting around by
137/// # // the time we get here. If this ever stops working, then we can disable
138/// # // the tests that depend on it.
139/// let output = cmd("echo", &args).read();
140///
141/// assert_eq!("foo bar baz", output.unwrap());
142/// ```
143pub fn cmd<T, U>(program: T, args: U) -> Expression
144where
145 T: IntoExecutablePath,
146 U: IntoIterator,
147 U::Item: Into<OsString>,
148{
149 let mut argv_vec = Vec::new();
150 argv_vec.push(program.to_executable());
151 argv_vec.extend(args.into_iter().map(Into::<OsString>::into));
152 Expression::new(Cmd(argv_vec))
153}
154
155/// Create a command with any number of of positional arguments, which may be
156/// different types (anything that implements
157/// [`Into<OsString>`](https://doc.rust-lang.org/std/convert/trait.From.html)).
158/// See also the [`cmd`](fn.cmd.html) function, which takes a collection of
159/// arguments.
160///
161/// # Example
162///
163/// ```
164/// use duct::cmd;
165/// use std::path::Path;
166///
167/// let arg1 = "foo";
168/// let arg2 = "bar".to_owned();
169/// let arg3 = Path::new("baz");
170///
171/// let output = cmd!("echo", arg1, arg2, arg3).read();
172///
173/// assert_eq!("foo bar baz", output.unwrap());
174/// ```
175#[macro_export]
176macro_rules! cmd {
177 ( $program:expr $(, $arg:expr )* $(,)? ) => {
178 {
179 use std::ffi::OsString;
180 let args: std::vec::Vec<OsString> = std::vec![$( Into::<OsString>::into($arg) ),*];
181 $crate::cmd($program, args)
182 }
183 };
184}
185
186/// The central objects in Duct, created with
187/// [`cmd`](fn.cmd.html) or [`cmd!`](macro.cmd.html).
188///
189/// Expressions can be combined with [`pipe`](struct.Expression.html#method.pipe) and executed with
190/// [`run`](struct.Expression.html#method.run), [`read`](struct.Expression.html#method.read),
191/// [`start`](struct.Expression.html#method.start), or
192/// [`reader`](struct.Expression.html#method.reader). There are many other methods to control their
193/// execution, like [`stdin_bytes`](struct.Expression.html#method.stdin_bytes),
194/// [`stdout_capture`](struct.Expression.html#method.stdout_capture),
195/// [`env`](struct.Expression.html#method.env), and
196/// [`unchecked`](struct.Expression.html#method.unchecked).
197///
198/// Expressions are immutable, and they do a lot of
199/// [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) sharing
200/// internally, so all of the methods below take `&self` and return a new
201/// `Expression` cheaply.
202///
203/// Expressions using `pipe` form trees, and the order in which you call
204/// different methods can matter, just like it matters where you put
205/// redirections in Bash. For example, each of these expressions suppresses
206/// output differently:
207///
208/// ```no_run
209/// # use duct::cmd;
210/// # fn main() -> std::io::Result<()> {
211/// // Only suppress stderr on the left side.
212/// cmd!("foo").stderr_null().pipe(cmd!("bar")).run()?; // foo 2>/dev/null | bar
213///
214/// // Only suppress stderr on the right side.
215/// cmd!("foo").pipe(cmd!("bar").stderr_null()).run()?; // foo | bar 2>/dev/null
216///
217/// // Suppress stderr on both sides.
218/// cmd!("foo").pipe(cmd!("bar")).stderr_null().run()?; // (foo | bar) 2>/dev/null
219/// # Ok(())
220/// # }
221/// ```
222#[derive(Clone)]
223#[must_use]
224pub struct Expression(Arc<ExpressionInner>);
225
226impl Expression {
227 /// Execute an expression, wait for it to complete, and return a
228 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html)
229 /// object containing the results. Nothing is captured by default, but if
230 /// you build the expression with
231 /// [`stdout_capture`](struct.Expression.html#method.stdout_capture) or
232 /// [`stderr_capture`](struct.Expression.html#method.stderr_capture) then
233 /// the `Output` will hold those captured bytes.
234 ///
235 /// # Errors
236 ///
237 /// In addition to all the IO errors possible with
238 /// [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html),
239 /// `run` will return an
240 /// [`ErrorKind::Other`](https://doc.rust-lang.org/std/io/enum.ErrorKind.html)
241 /// IO error if child returns a non-zero exit status. To suppress this error
242 /// and return an `Output` even when the exit status is non-zero, use the
243 /// [`unchecked`](struct.Expression.html#method.unchecked) method.
244 ///
245 /// # Example
246 ///
247 /// ```
248 /// # use duct::cmd;
249 /// # fn main() {
250 /// # if cfg!(not(windows)) {
251 /// let output = cmd!("echo", "hi").stdout_capture().run().unwrap();
252 /// assert_eq!(b"hi\n".to_vec(), output.stdout);
253 /// # }
254 /// # }
255 /// ```
256 pub fn run(&self) -> io::Result<Output> {
257 // This could be optimized to avoid creating a background threads, by
258 // using the current thread to read stdout or stderr if only one of
259 // them is captured, or by using async IO to read both.
260 self.start()?.into_output()
261 }
262
263 /// Execute an expression, capture its standard output, and return the
264 /// captured output as a `String`. This is a convenience wrapper around
265 /// [`reader`](struct.Expression.html#method.reader). Like backticks and
266 /// `$()` in the shell, `read` trims trailing newlines.
267 ///
268 /// # Errors
269 ///
270 /// In addition to all the errors possible with
271 /// [`run`](struct.Expression.html#method.run), `read` will return an error
272 /// if the captured bytes aren't valid UTF-8.
273 ///
274 /// # Example
275 ///
276 /// ```
277 /// # use duct::cmd;
278 /// # fn main() {
279 /// # if cfg!(not(windows)) {
280 /// let output = cmd!("echo", "hi").stdout_capture().read().unwrap();
281 /// assert_eq!("hi", output);
282 /// # }
283 /// # }
284 /// ```
285 pub fn read(&self) -> io::Result<String> {
286 let mut reader = self.reader()?;
287 let mut output = String::new();
288 reader.read_to_string(&mut output)?;
289 while output.ends_with('\n') || output.ends_with('\r') {
290 output.truncate(output.len() - 1);
291 }
292 Ok(output)
293 }
294
295 /// Start running an expression, and immediately return a
296 /// [`Handle`](struct.Handle.html) that represents all the child processes.
297 /// This is analogous to the
298 /// [`spawn`](https://doc.rust-lang.org/std/process/struct.Command.html#method.spawn)
299 /// method in the standard library. The `Handle` may be shared between
300 /// multiple threads.
301 ///
302 /// # Example
303 ///
304 /// ```
305 /// # use duct::cmd;
306 /// # fn main() {
307 /// # if cfg!(not(windows)) {
308 /// let handle = cmd!("echo", "hi").stdout_capture().start().unwrap();
309 /// let output = handle.wait().unwrap();
310 /// assert_eq!(b"hi\n".to_vec(), output.stdout);
311 /// # }
312 /// # }
313 /// ```
314 pub fn start(&self) -> io::Result<Handle> {
315 let stdout_capture = OutputCaptureContext::new();
316 let stderr_capture = OutputCaptureContext::new();
317 let context = IoContext::new(&stdout_capture, &stderr_capture);
318
319 Ok(Handle {
320 inner: self.0.start(context)?,
321 result: OnceLock::new(),
322 readers: RwLock::new((
323 stdout_capture.maybe_read_thread(),
324 stderr_capture.maybe_read_thread(),
325 )),
326 })
327 }
328
329 /// Start running an expression, and immediately return a
330 /// [`ReaderHandle`](struct.ReaderHandle.html) attached to the child's
331 /// stdout. This is similar to `.stdout_capture().start()`, but it returns
332 /// the reader to the caller rather than reading from a background thread.
333 ///
334 /// Note that because this method doesn't read child output on a background
335 /// thread, it's a best practice to only create one `ReaderHandle` at a
336 /// time. Child processes with a lot of output will eventually block if
337 /// their stdout pipe isn't read from. If you have multiple children
338 /// running, but you're only reading from one of them at a time, that could
339 /// block the others and lead to performance issues or deadlocks. For
340 /// reading from multiple children at once, prefer
341 /// `.stdout_capture().start()`.
342 ///
343 /// # Example
344 ///
345 /// ```
346 /// # use duct::cmd;
347 /// # use std::io::prelude::*;
348 /// # fn main() {
349 /// # if cfg!(not(windows)) {
350 /// let mut reader = cmd!("echo", "hi").reader().unwrap();
351 /// let mut stdout = Vec::new();
352 /// reader.read_to_end(&mut stdout).unwrap();
353 /// assert_eq!(b"hi\n".to_vec(), stdout);
354 /// # }
355 /// # }
356 /// ```
357 pub fn reader(&self) -> io::Result<ReaderHandle> {
358 let stdout_capture = OutputCaptureContext::new();
359 let stderr_capture = OutputCaptureContext::new();
360 let context = IoContext::new(&stdout_capture, &stderr_capture);
361 let handle = Handle {
362 inner: self.stdout_capture().0.start(context)?,
363 result: OnceLock::new(),
364 readers: RwLock::new((None, stderr_capture.maybe_read_thread())),
365 };
366 Ok(ReaderHandle {
367 handle,
368 reader: stdout_capture.pair.into_inner().expect("pipe opened").0,
369 })
370 }
371
372 /// Join two expressions into a pipe expression, where the standard output
373 /// of the left will be hooked up to the standard input of the right, like
374 /// `|` in the shell.
375 ///
376 /// # Errors
377 ///
378 /// During execution, if one side of the pipe returns a non-zero exit
379 /// status, that becomes the status of the whole pipe. If both sides return
380 /// non-zero, and one of them is
381 /// [`unchecked`](struct.Expression.html#method.unchecked), the checked
382 /// side wins. Otherwise the right side wins. This is close to the behavior
383 /// of Bash with `-o pipefail`.
384 ///
385 /// During spawning, if the left side of the pipe spawns successfully but
386 /// the right side fails to spawn (e.g. the binary doesn't exist), the left
387 /// side will be cleaned up without blocking, which might require spawning
388 /// a thread. See the [`Handle`] docs for more on this behavior.
389 ///
390 /// # Example
391 ///
392 /// ```
393 /// # use duct::cmd;
394 /// # fn main() {
395 /// # if cfg!(not(windows)) {
396 /// let output = cmd!("echo", "hi").pipe(cmd!("sed", "s/h/p/")).read();
397 /// assert_eq!("pi", output.unwrap());
398 /// # }
399 /// # }
400 /// ```
401 pub fn pipe<T: Into<Expression>>(&self, right: T) -> Expression {
402 Self::new(Pipe(self.clone(), right.into()))
403 }
404
405 /// Use bytes or a string as input for an expression, like `<<<` in the
406 /// shell. A worker thread will write the input at runtime.
407 ///
408 /// # Example
409 ///
410 /// ```
411 /// # use duct::cmd;
412 /// # fn main() {
413 /// # if cfg!(not(windows)) {
414 /// // Many types implement Into<Vec<u8>>. Here's a string.
415 /// let output = cmd!("cat").stdin_bytes("foo").read().unwrap();
416 /// assert_eq!("foo", output);
417 ///
418 /// // And here's a byte slice.
419 /// let output = cmd!("cat").stdin_bytes(&b"foo"[..]).read().unwrap();
420 /// assert_eq!("foo", output);
421 /// # }
422 /// # }
423 /// ```
424 pub fn stdin_bytes<T: Into<Vec<u8>>>(&self, bytes: T) -> Expression {
425 Self::new(Io(StdinBytes(Arc::new(bytes.into())), self.clone()))
426 }
427
428 /// Open a file at the given path and use it as input for an expression,
429 /// like `<` in the shell.
430 ///
431 /// # Example
432 ///
433 /// ```
434 /// # use duct::cmd;
435 /// # fn main() {
436 /// # if cfg!(not(windows)) {
437 /// // Many types implement Into<PathBuf>, including &str.
438 /// let output = cmd!("head", "-c", "3").stdin_path("/dev/zero").read().unwrap();
439 /// assert_eq!("\0\0\0", output);
440 /// # }
441 /// # }
442 /// ```
443 pub fn stdin_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
444 Self::new(Io(StdinPath(path.into()), self.clone()))
445 }
446
447 /// Use an already opened file or pipe as input for an expression.
448 ///
449 /// # Example
450 ///
451 /// ```
452 /// # use duct::cmd;
453 /// # fn main() {
454 /// # if cfg!(not(windows)) {
455 /// let input_file = std::fs::File::open("/dev/zero").unwrap();
456 /// let output = cmd!("head", "-c", "3").stdin_file(input_file).read().unwrap();
457 /// assert_eq!("\0\0\0", output);
458 /// # }
459 /// # }
460 /// ```
461 pub fn stdin_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
462 Self::new(Io(StdinFile(owned_from_raw(file)), self.clone()))
463 }
464
465 /// Use `/dev/null` (or `NUL` on Windows) as input for an expression.
466 ///
467 /// # Example
468 ///
469 /// ```
470 /// # use duct::cmd;
471 /// # fn main() {
472 /// # if cfg!(not(windows)) {
473 /// let output = cmd!("cat").stdin_null().read().unwrap();
474 /// assert_eq!("", output);
475 /// # }
476 /// # }
477 /// ```
478 pub fn stdin_null(&self) -> Expression {
479 Self::new(Io(StdinNull, self.clone()))
480 }
481
482 /// Open a file at the given path and use it as output for an expression,
483 /// like `>` in the shell.
484 ///
485 /// # Example
486 ///
487 /// ```
488 /// # use duct::cmd;
489 /// # fn main() {
490 /// # use std::io::prelude::*;
491 /// # if cfg!(not(windows)) {
492 /// // Many types implement Into<PathBuf>, including &str.
493 /// let path = cmd!("mktemp").read().unwrap();
494 /// cmd!("echo", "wee").stdout_path(&path).run().unwrap();
495 /// let mut output = String::new();
496 /// std::fs::File::open(&path).unwrap().read_to_string(&mut output).unwrap();
497 /// assert_eq!("wee\n", output);
498 /// # }
499 /// # }
500 /// ```
501 pub fn stdout_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
502 Self::new(Io(StdoutPath(path.into()), self.clone()))
503 }
504
505 /// Use an already opened file or pipe as output for an expression.
506 ///
507 /// # Example
508 ///
509 /// ```
510 /// # use duct::cmd;
511 /// # fn main() {
512 /// # use std::io::prelude::*;
513 /// # if cfg!(not(windows)) {
514 /// let path = cmd!("mktemp").read().unwrap();
515 /// let file = std::fs::File::create(&path).unwrap();
516 /// cmd!("echo", "wee").stdout_file(file).run().unwrap();
517 /// let mut output = String::new();
518 /// std::fs::File::open(&path).unwrap().read_to_string(&mut output).unwrap();
519 /// assert_eq!("wee\n", output);
520 /// # }
521 /// # }
522 /// ```
523 pub fn stdout_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
524 Self::new(Io(StdoutFile(owned_from_raw(file)), self.clone()))
525 }
526
527 /// Use `/dev/null` (or `NUL` on Windows) as output for an expression.
528 ///
529 /// # Example
530 ///
531 /// ```
532 /// # use duct::cmd;
533 /// # fn main() {
534 /// // This echo command won't print anything.
535 /// cmd!("echo", "foo", "bar", "baz").stdout_null().run().unwrap();
536 ///
537 /// // And you won't get anything even if you try to read its output! The
538 /// // null redirect happens farther down in the expression tree than the
539 /// // implicit `stdout_capture`, and so it takes precedence.
540 /// let output = cmd!("echo", "foo", "bar", "baz").stdout_null().read().unwrap();
541 /// assert_eq!("", output);
542 /// # }
543 /// ```
544 pub fn stdout_null(&self) -> Expression {
545 Self::new(Io(StdoutNull, self.clone()))
546 }
547
548 /// Capture the standard output of an expression. The captured bytes will
549 /// be available on the `stdout` field of the
550 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html)
551 /// object returned by [`run`](struct.Expression.html#method.run) or
552 /// [`wait`](struct.Handle.html#method.wait). Output is read by a
553 /// background thread, so the child will never block writing to stdout. But
554 /// note that [`read`](struct.Expression.html#method.read) and
555 /// [`reader`](struct.Expression.html#method.reader) can be more
556 /// convenient, and they don't require the background thread.
557 ///
558 /// # Example
559 ///
560 /// ```
561 /// # use duct::cmd;
562 /// # fn main() {
563 /// # if cfg!(not(windows)) {
564 /// // The most direct way to read stdout bytes is `stdout_capture`.
565 /// let output1 = cmd!("echo", "foo").stdout_capture().run().unwrap().stdout;
566 /// assert_eq!(&b"foo\n"[..], &output1[..]);
567 ///
568 /// // The `read` method is a shorthand for `stdout_capture`, and it also
569 /// // does string parsing and newline trimming.
570 /// let output2 = cmd!("echo", "foo").read().unwrap();
571 /// assert_eq!("foo", output2)
572 /// # }
573 /// # }
574 /// ```
575 pub fn stdout_capture(&self) -> Expression {
576 Self::new(Io(StdoutCapture, self.clone()))
577 }
578
579 /// Join the standard output of an expression to its standard error pipe,
580 /// similar to `1>&2` in the shell.
581 ///
582 /// # Example
583 ///
584 /// ```
585 /// # use duct::cmd;
586 /// # fn main() {
587 /// # if cfg!(not(windows)) {
588 /// let output = cmd!("echo", "foo").stdout_to_stderr().stderr_capture().run().unwrap();
589 /// assert_eq!(&b"foo\n"[..], &output.stderr[..]);
590 /// # }
591 /// # }
592 /// ```
593 pub fn stdout_to_stderr(&self) -> Expression {
594 Self::new(Io(StdoutToStderr, self.clone()))
595 }
596
597 /// Open a file at the given path and use it as error output for an
598 /// expression, like `2>` in the shell.
599 ///
600 /// # Example
601 ///
602 /// ```
603 /// # use duct::cmd;
604 /// # fn main() {
605 /// # use std::io::prelude::*;
606 /// # if cfg!(not(windows)) {
607 /// // Many types implement Into<PathBuf>, including &str.
608 /// let path = cmd!("mktemp").read().unwrap();
609 /// cmd!("sh", "-c", "echo wee >&2").stderr_path(&path).run().unwrap();
610 /// let mut error_output = String::new();
611 /// std::fs::File::open(&path).unwrap().read_to_string(&mut error_output).unwrap();
612 /// assert_eq!("wee\n", error_output);
613 /// # }
614 /// # }
615 /// ```
616 pub fn stderr_path<T: Into<PathBuf>>(&self, path: T) -> Expression {
617 Self::new(Io(StderrPath(path.into()), self.clone()))
618 }
619
620 /// Use an already opened file or pipe as error output for an expression.
621 ///
622 /// # Example
623 ///
624 /// ```
625 /// # use duct::cmd;
626 /// # fn main() {
627 /// # use std::io::prelude::*;
628 /// # if cfg!(not(windows)) {
629 /// let path = cmd!("mktemp").read().unwrap();
630 /// let file = std::fs::File::create(&path).unwrap();
631 /// cmd!("sh", "-c", "echo wee >&2").stderr_file(file).run().unwrap();
632 /// let mut error_output = String::new();
633 /// std::fs::File::open(&path).unwrap().read_to_string(&mut error_output).unwrap();
634 /// assert_eq!("wee\n", error_output);
635 /// # }
636 /// # }
637 /// ```
638 pub fn stderr_file<T: IntoRawFdOrHandle>(&self, file: T) -> Expression {
639 Self::new(Io(StderrFile(owned_from_raw(file)), self.clone()))
640 }
641
642 /// Use `/dev/null` (or `NUL` on Windows) as error output for an expression.
643 ///
644 /// # Example
645 ///
646 /// ```
647 /// # use duct::cmd;
648 /// # fn main() {
649 /// # if cfg!(not(windows)) {
650 /// // This echo-to-stderr command won't print anything.
651 /// cmd!("sh", "-c", "echo foo bar baz >&2").stderr_null().run().unwrap();
652 /// # }
653 /// # }
654 /// ```
655 pub fn stderr_null(&self) -> Expression {
656 Self::new(Io(StderrNull, self.clone()))
657 }
658
659 /// Capture the error output of an expression. The captured bytes will be
660 /// available on the `stderr` field of the `Output` object returned by
661 /// [`run`](struct.Expression.html#method.run) or
662 /// [`wait`](struct.Handle.html#method.wait). Output is read by a
663 /// background thread, so the child will never block writing to stderr.
664 ///
665 /// # Example
666 ///
667 /// ```
668 /// # use duct::cmd;
669 /// # fn main() {
670 /// # if cfg!(not(windows)) {
671 /// let output_obj = cmd!("sh", "-c", "echo foo >&2").stderr_capture().run().unwrap();
672 /// assert_eq!(&b"foo\n"[..], &output_obj.stderr[..]);
673 /// # }
674 /// # }
675 /// ```
676 pub fn stderr_capture(&self) -> Expression {
677 Self::new(Io(StderrCapture, self.clone()))
678 }
679
680 /// Join the standard error of an expression to its standard output pipe,
681 /// similar to `2>&1` in the shell.
682 ///
683 /// # Example
684 ///
685 /// ```
686 /// # use duct::cmd;
687 /// # fn main() {
688 /// # if cfg!(not(windows)) {
689 /// let error_output = cmd!("sh", "-c", "echo foo >&2").stderr_to_stdout().read().unwrap();
690 /// assert_eq!("foo", error_output);
691 /// # }
692 /// # }
693 /// ```
694 pub fn stderr_to_stdout(&self) -> Expression {
695 Self::new(Io(StderrToStdout, self.clone()))
696 }
697
698 /// Swap the stdout and stderr of an expression.
699 ///
700 /// # Example
701 ///
702 /// ```
703 /// # use duct::cmd;
704 /// # fn main() {
705 /// # if cfg!(not(windows)) {
706 /// let output = cmd!("sh", "-c", "echo foo && echo bar >&2")
707 /// .stdout_stderr_swap()
708 /// .stdout_capture()
709 /// .stderr_capture()
710 /// .run()
711 /// .unwrap();
712 /// assert_eq!(b"bar\n", &*output.stdout);
713 /// assert_eq!(b"foo\n", &*output.stderr);
714 /// # }
715 /// # }
716 /// ```
717 pub fn stdout_stderr_swap(&self) -> Expression {
718 Self::new(Io(StdoutStderrSwap, self.clone()))
719 }
720
721 /// Set the working directory where the expression will execute.
722 ///
723 /// Note that in some languages (Rust and Python at least), there are
724 /// tricky platform differences in the way relative exe paths interact with
725 /// child working directories. In particular, the exe path will be
726 /// interpreted relative to the child dir on Unix, but relative to the
727 /// parent dir on Windows. Duct prefers the Windows behavior, and in order
728 /// to get that behavior on all platforms it calls
729 /// [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html)
730 /// on relative exe paths when `dir` is in use. Paths in this sense are any
731 /// program name containing a path separator, regardless of the type. (Note
732 /// also that `Path` and `PathBuf` program names get a `./` prepended to
733 /// them automatically by the
734 /// [`IntoExecutablePath`](trait.IntoExecutablePath.html) trait, and so
735 /// will always contain a separator.)
736 ///
737 /// # Errors
738 ///
739 /// Canonicalization can fail on some filesystems, or if the current
740 /// directory has been removed, and
741 /// [`run`](struct.Expression.html#method.run) will return those errors
742 /// rather than trying any sneaky workarounds.
743 ///
744 /// # Example
745 ///
746 /// ```
747 /// # use duct::cmd;
748 /// # fn main() {
749 /// # if cfg!(not(windows)) {
750 /// let output = cmd!("pwd").dir("/").read().unwrap();
751 /// assert_eq!("/", output);
752 /// # }
753 /// # }
754 /// ```
755 pub fn dir<T: Into<PathBuf>>(&self, path: T) -> Expression {
756 Self::new(Io(Dir(path.into()), self.clone()))
757 }
758
759 /// Set a variable in the expression's environment.
760 ///
761 /// # Example
762 ///
763 /// ```
764 /// # use duct::cmd;
765 /// # fn main() {
766 /// # if cfg!(not(windows)) {
767 /// let output = cmd!("sh", "-c", "echo $FOO").env("FOO", "bar").read().unwrap();
768 /// assert_eq!("bar", output);
769 /// # }
770 /// # }
771 /// ```
772 pub fn env<T, U>(&self, name: T, val: U) -> Expression
773 where
774 T: Into<OsString>,
775 U: Into<OsString>,
776 {
777 Self::new(Io(Env(EnvNameString::from(name), val.into()), self.clone()))
778 }
779
780 /// Remove a variable from the expression's environment.
781 ///
782 /// Note that all the environment functions try to do whatever the platform
783 /// does with respect to case sensitivity. That means that
784 /// `env_remove("foo")` will unset the uppercase variable `FOO` on Windows,
785 /// but not on Unix.
786 ///
787 /// # Example
788 ///
789 /// ```
790 /// # use duct::cmd;
791 /// # fn main() {
792 /// # if cfg!(not(windows)) {
793 /// std::env::set_var("TESTING", "true");
794 /// let output = cmd!("sh", "-c", "echo a${TESTING}b")
795 /// .env_remove("TESTING")
796 /// .read()
797 /// .unwrap();
798 /// assert_eq!("ab", output);
799 /// # }
800 /// # }
801 /// ```
802 pub fn env_remove<T>(&self, name: T) -> Expression
803 where
804 T: Into<OsString>,
805 {
806 Self::new(Io(EnvRemove(EnvNameString::from(name)), self.clone()))
807 }
808
809 /// Set the expression's entire environment, from a collection of
810 /// name-value pairs (like a `HashMap`). Note that some environment
811 /// variables are required for normal program execution (like `SystemRoot`
812 /// on Windows), so copying the parent's environment is usually preferable
813 /// to starting with an empty one.
814 ///
815 /// # Example
816 ///
817 /// ```
818 /// # use duct::cmd;
819 /// # fn main() {
820 /// # use std::collections::HashMap;
821 /// # if cfg!(not(windows)) {
822 /// let mut env_map: HashMap<_, _> = std::env::vars().collect();
823 /// env_map.insert("FOO".into(), "bar".into());
824 /// let output = cmd!("sh", "-c", "echo $FOO").full_env(&env_map).read().unwrap();
825 /// assert_eq!("bar", output);
826 /// // The IntoIterator/Into<OsString> bounds are pretty flexible. Passing
827 /// // by value works here too.
828 /// let output = cmd!("sh", "-c", "echo $FOO").full_env(env_map).read().unwrap();
829 /// assert_eq!("bar", output);
830 /// # }
831 /// # }
832 /// ```
833 pub fn full_env<T, U, V>(&self, name_vals: T) -> Expression
834 where
835 T: IntoIterator<Item = (U, V)>,
836 U: Into<OsString>,
837 V: Into<OsString>,
838 {
839 let env_map = name_vals
840 .into_iter()
841 .map(|(k, v)| (EnvNameString::from(k), v.into()))
842 .collect();
843 Self::new(Io(FullEnv(env_map), self.clone()))
844 }
845
846 /// Prevent a non-zero exit status from causing
847 /// [`run`](struct.Expression.html#method.run) or
848 /// [`read`](struct.Expression.html#method.read) to return an error. The
849 /// unchecked exit code will still be there on the `Output` returned by
850 /// `run`; its value doesn't change.
851 ///
852 /// "Uncheckedness" sticks to an exit code as it bubbles up through
853 /// complicated pipelines, but it doesn't "infect" other exit codes. So for
854 /// example, if only one sub-expression in a pipe has `unchecked`, then
855 /// errors returned by the other side will still be checked. That said,
856 /// most commonly you'll just call `unchecked` right before `run`, and
857 /// it'll apply to an entire expression.
858 ///
859 /// # Example
860 ///
861 /// Note the differences among these three cases:
862 ///
863 /// ```no_run
864 /// # use duct::cmd;
865 /// # fn main() -> std::io::Result<()> {
866 /// // Don't check errors on the left side.
867 /// cmd!("foo").unchecked().pipe(cmd!("bar")).run()?;
868 ///
869 /// // Don't check errors on the right side.
870 /// cmd!("foo").pipe(cmd!("bar").unchecked()).run()?;
871 ///
872 /// // Don't check errors on either side.
873 /// cmd!("foo").pipe(cmd!("bar")).unchecked().run()?;
874 /// # Ok(())
875 /// # }
876 /// ```
877 pub fn unchecked(&self) -> Expression {
878 Self::new(Io(Unchecked, self.clone()))
879 }
880
881 /// Add a hook for modifying
882 /// [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html)
883 /// objects immediately before they're executed.
884 ///
885 /// The hook is called for each command in its sub-expression, and each time the expression is
886 /// executed. The call happens after other features like `stdout` and `env` have been applied,
887 /// so any changes made by the hook take priority. More than one hook can be added, in which
888 /// case the innermost is executed last. For example, if one call to `before_spawn` is applied
889 /// to an entire pipe expression, and another call is applied to just one command within the
890 /// pipe, the hook for the entire pipeline will be called first over the command where both
891 /// hooks apply.
892 ///
893 /// This is intended for rare and tricky cases, like callers who want to change the group ID of
894 /// their child processes, or who want to run code in `before_exec`. Most callers shouldn't
895 /// need to use it.
896 ///
897 /// # Example
898 ///
899 /// ```
900 /// # use duct::cmd;
901 /// # fn main() {
902 /// let output = cmd!("echo", "foo")
903 /// .before_spawn(|cmd| {
904 /// // Sneakily add an extra argument.
905 /// cmd.arg("bar");
906 /// Ok(())
907 /// })
908 /// .read()
909 /// .unwrap();
910 /// assert_eq!("foo bar", output);
911 /// # }
912 /// ```
913 pub fn before_spawn<F>(&self, hook: F) -> Expression
914 where
915 F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
916 {
917 Self::new(Io(BeforeSpawn(BeforeSpawnHook::new(hook)), self.clone()))
918 }
919
920 fn new(inner: ExpressionInner) -> Expression {
921 Expression(Arc::new(inner))
922 }
923}
924
925// Delegate to the ExpressionInner for debug formatting. This avoids printing
926// redundant Expression() constructors around everything.
927impl fmt::Debug for Expression {
928 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
929 self.0.fmt(f)
930 }
931}
932
933// Implementing Into<Expression> for references lets us accept both references
934// and values in `pipe`.
935impl From<&Expression> for Expression {
936 fn from(expr: &Expression) -> Expression {
937 expr.clone()
938 }
939}
940
941/// A handle to a running [`Expression`], returned by the
942/// [`start`](struct.Expression.html#method.start) method.
943///
944/// Calling `start` followed by
945/// [`into_output`](struct.Handle.html#method.into_output) on the handle is
946/// equivalent to [`run`](struct.Expression.html#method.run). Note that unlike
947/// [`std::process::Child`](https://doc.rust-lang.org/std/process/struct.Child.html),
948/// most of the methods on `Handle` take `&self` rather than `&mut self`, and a
949/// `Handle` may be shared between multiple threads.
950///
951/// If you drop a `Handle` without first [`wait`][Handle::wait]ing on the child to exit, it will
952/// [`try_wait`][Handle::try_wait] internally to see if it can reap the child. If the child is
953/// still running, its handle will be added to a global list and polled whenever new child
954/// processes are spawned. This avoids leaking [zombie
955/// processes](https://en.wikipedia.org/wiki/Zombie_process) on Unix platforms. This `Drop`
956/// implementation is omitted on Windows, where zombies aren't a problem.
957///
958/// See the [`shared_child`](https://github.com/oconnor663/shared_child.rs)
959/// crate for implementation details behind making handles thread safe.
960#[derive(Debug)]
961pub struct Handle {
962 inner: HandleInner,
963 result: OnceLock<(ExpressionStatus, Output)>,
964 readers: RwLock<(Option<ReaderThread>, Option<ReaderThread>)>,
965}
966
967impl Handle {
968 /// Wait for the running [`Expression`] to finish, and return a reference to its
969 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html).
970 /// Multiple threads may wait at the same time, and waiting more than once returns the same
971 /// output again.
972 ///
973 /// # Errors
974 ///
975 /// In addition to all the IO errors possible with
976 /// [`std::process::Child`](https://doc.rust-lang.org/std/process/struct.Child.html), `wait`
977 /// will return an [`ErrorKind::Other`](https://doc.rust-lang.org/std/io/enum.ErrorKind.html)
978 /// IO error if the child returns a non-zero exit status. To suppress this error and return an
979 /// `Output` even when the exit status is non-zero, use the
980 /// [`unchecked`](struct.Expression.html#method.unchecked) method.
981 pub fn wait(&self) -> io::Result<&Output> {
982 wait_on_handle_and_output(self, WaitMode::Blocking)?;
983 self.try_wait().transpose().expect("already exited")
984 }
985
986 /// Same as [`wait`][Self::wait], but with a timeout. If the running [`Expression`] finishes
987 /// within the timeout (or if it's already finished), return a reference to its
988 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html).
989 /// Otherwise, return `Ok(None)`.
990 ///
991 /// # Errors
992 ///
993 /// Same as [`wait`][Self::wait].
994 ///
995 /// # Panics
996 ///
997 /// Panics if `Instant::now() + timeout` overflows.
998 #[cfg(feature = "timeout")]
999 pub fn wait_timeout(&self, timeout: std::time::Duration) -> io::Result<Option<&Output>> {
1000 // `Instant` doesn't currently support saturating operations, so this addition can panic.
1001 // See https://internals.rust-lang.org/t/instant-systemtime-min-max/21375.
1002 let deadline = std::time::Instant::now() + timeout;
1003 self.wait_deadline(deadline)
1004 }
1005
1006 /// Same as [`wait_timeout`][Self::wait_timeout], but with a deadline instead of a timeout.
1007 ///
1008 /// # Errors
1009 ///
1010 /// Same as [`wait`][Self::wait].
1011 #[cfg(feature = "timeout")]
1012 pub fn wait_deadline(&self, deadline: std::time::Instant) -> io::Result<Option<&Output>> {
1013 wait_on_handle_and_output(self, WaitMode::Deadline(deadline))?;
1014 self.try_wait()
1015 }
1016
1017 /// Check whether the running [`Expression`] has already finished. If it has, return a
1018 /// reference to its
1019 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html).
1020 /// Otherwise, return `Ok(None)`.
1021 ///
1022 /// # Errors
1023 ///
1024 /// Same as [`wait`][Self::wait].
1025 pub fn try_wait(&self) -> io::Result<Option<&Output>> {
1026 let Some((expression_status, output)) =
1027 wait_on_handle_and_output(self, WaitMode::NonBlocking)?
1028 else {
1029 return Ok(None);
1030 };
1031 // If the child returned a "checked" non-zero exit status, make that an error.
1032 if expression_status.is_checked_error() {
1033 return Err(io::Error::other(expression_status.message()));
1034 }
1035 Ok(Some(output))
1036 }
1037
1038 /// Same as [`wait`][Self::wait], but consume the `Handle` and return its
1039 /// [`std::process::Output`](https://doc.rust-lang.org/std/process/struct.Output.html) by
1040 /// value. Calling [`start`](struct.Expression.html#method.start) followed by `into_output` is
1041 /// equivalent to [`run`](struct.Expression.html#method.run).
1042 ///
1043 /// # Errors
1044 ///
1045 /// Same as [`wait`][Self::wait].
1046 pub fn into_output(self) -> io::Result<Output> {
1047 self.wait()?;
1048 let (_, output) = self.result.into_inner().expect("result missing");
1049 Ok(output)
1050 }
1051
1052 /// Kill all the child processes in the running [`Expression`].
1053 ///
1054 /// Note that as with
1055 /// [`std::process::Child::kill`](https://doc.rust-lang.org/beta/std/process/struct.Child.html#method.kill),
1056 /// this does not kill any grandchild processes that the children have
1057 /// spawned on their own. It only kills the child processes that Duct
1058 /// spawned itself. See
1059 /// [`gotchas.md`](https://github.com/oconnor663/duct.py/blob/master/gotchas.md)
1060 /// for an extensive discussion of this behavior.
1061 ///
1062 /// This method does not wait on the child processes to exit. Calling [`wait`][Handle::wait]
1063 /// after `kill` usually returns quickly, but there are edge cases where it might not. The most
1064 /// common case is if a grandchild process has inherited one or more of the child's
1065 /// stdin/stdout/stderr pipes, and a worker thread related to
1066 /// [`stdin_bytes`][Expression::stdin_bytes]/[`stdout_capture`][Expression::stdout_capture]/[`stderr_capture`][Expression::stderr_capture]
1067 /// is still running. The kill signal might also be delayed if the child is blocked reading an
1068 /// unresponsive FUSE filesystem, or paused by a debugger.
1069 pub fn kill(&self) -> io::Result<()> {
1070 self.inner.kill()
1071 }
1072
1073 /// Return a `Vec<u32>` containing the PIDs of all of the child processes.
1074 /// The PIDs are given in pipeline order, from left to right.
1075 pub fn pids(&self) -> Vec<u32> {
1076 self.inner.pids()
1077 }
1078}
1079
1080// Wait on the handle and on captured output. Depending on the mode, this wait might or might not
1081// be blocking. This does not do any status checking.
1082fn wait_on_handle_and_output(
1083 handle: &Handle,
1084 mode: WaitMode,
1085) -> io::Result<Option<&(ExpressionStatus, Output)>> {
1086 let Some(status) = handle.inner.wait(mode)? else {
1087 return Ok(None);
1088 };
1089 // We need non-blocking waiters (try_wait) to be able to access the SharedThread IO readers,
1090 // even while a blocking waiter (wait) is blocking, so we can't take RwLock::write until we
1091 // know the threads have already exited.
1092 let shared_lock = handle.readers.read().unwrap();
1093 let (maybe_stdout_reader, maybe_stderr_reader) = &*shared_lock;
1094 if let Some(stdout_reader) = maybe_stdout_reader {
1095 if mode.maybe_join_io_thread(stdout_reader)?.is_none() {
1096 return Ok(None);
1097 }
1098 }
1099 if let Some(stderr_reader) = maybe_stderr_reader {
1100 if mode.maybe_join_io_thread(stderr_reader)?.is_none() {
1101 return Ok(None);
1102 }
1103 }
1104 drop(shared_lock);
1105
1106 // At this point we know that the child and all its IO threads (if any) have exited, so we can
1107 // collect output without blocking. Take the RwLock::write lock and take ownership of the
1108 // output vectors. If another thread has already done this, .unwrap_or_default() will return
1109 // empty Vecs, and result.set() will be a no-op.
1110 let mut unique_lock = handle.readers.write().unwrap();
1111 let (maybe_stdout_reader, maybe_stderr_reader) = &mut *unique_lock;
1112 let stdout: Vec<u8> = maybe_stdout_reader
1113 .take()
1114 .map(SharedThread::into_output)
1115 .transpose()
1116 .expect("IO errors already short-circuited")
1117 .unwrap_or_default();
1118 let stderr: Vec<u8> = maybe_stderr_reader
1119 .take()
1120 .map(SharedThread::into_output)
1121 .transpose()
1122 .expect("IO errors already short-circuited")
1123 .unwrap_or_default();
1124 let output = Output {
1125 status: status.status,
1126 stdout,
1127 stderr,
1128 };
1129 let _ = handle.result.set((status, output)); // might already be set
1130 Ok(handle.result.get())
1131}
1132
1133#[derive(Debug)]
1134enum ExpressionInner {
1135 Cmd(Vec<OsString>),
1136 Pipe(Expression, Expression),
1137 Io(IoExpressionInner, Expression),
1138}
1139
1140impl ExpressionInner {
1141 fn start(&self, context: IoContext) -> io::Result<HandleInner> {
1142 Ok(match self {
1143 Cmd(argv) => HandleInner::Child(ChildHandle::start(argv, context)?),
1144 Pipe(left, right) => {
1145 HandleInner::Pipe(Box::new(PipeHandle::start(left, right, context)?))
1146 }
1147 Io(io_inner, expr) => start_io(io_inner, expr, context)?,
1148 })
1149 }
1150}
1151
1152#[derive(Debug)]
1153enum HandleInner {
1154 Child(ChildHandle),
1155 // If the left side of a pipe fails to start, there's nothing to wait for,
1156 // and we return an error immediately. But if the right side fails to start,
1157 // the caller still needs to wait on the left, and we must return a handle.
1158 // Thus the handle preserves the right side's errors here.
1159 Pipe(Box<PipeHandle>),
1160 StdinBytes(Box<StdinBytesHandle>),
1161 // Why does "uncheckedness" need to be a handle type and not just a field on
1162 // IoContext? Because when one side of a pipe is checked, that side's errors
1163 // take priority over the checked side, even when the pipe expression
1164 // *itself* is also unchecked.
1165 Unchecked(Box<HandleInner>),
1166}
1167
1168impl HandleInner {
1169 fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
1170 match self {
1171 HandleInner::Child(child_handle) => child_handle.wait(mode),
1172 HandleInner::Pipe(pipe_handle) => pipe_handle.wait(mode),
1173 HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.wait(mode),
1174 HandleInner::Unchecked(inner_handle) => {
1175 Ok(inner_handle.wait(mode)?.map(|mut status| {
1176 status.checked = false;
1177 status
1178 }))
1179 }
1180 }
1181 }
1182
1183 fn kill(&self) -> io::Result<()> {
1184 match self {
1185 HandleInner::Child(child_handle) => child_handle.kill(),
1186 HandleInner::Pipe(pipe_handle) => pipe_handle.kill(),
1187 HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.kill(),
1188 HandleInner::Unchecked(inner_handle) => inner_handle.kill(),
1189 }
1190 }
1191
1192 fn pids(&self) -> Vec<u32> {
1193 match self {
1194 HandleInner::Child(child_handle) => vec![child_handle.child().id()],
1195 HandleInner::Pipe(pipe_handle) => pipe_handle.pids(),
1196 HandleInner::StdinBytes(stdin_bytes_handle) => stdin_bytes_handle.inner_handle.pids(),
1197 HandleInner::Unchecked(inner_handle) => inner_handle.pids(),
1198 }
1199 }
1200}
1201
1202// Use std::process::Child instead of SharedChild to avoid taking extra locks when we poll.
1203#[cfg(not(windows))]
1204static LEAKED_CHILDREN: RwLock<Vec<std::process::Child>> = RwLock::new(Vec::new());
1205
1206// In `impl Drop for ChildHandle` below, children who are still running when they're dropped get
1207// added to this global list. Poll the list to reap as many zombie children as possible each time
1208// we spawn a new child process. See the comments in the Drop impl regarding tradeoffs.
1209#[cfg(not(windows))]
1210fn cleanup_leaked_children() {
1211 if !LEAKED_CHILDREN.read().unwrap().is_empty() {
1212 LEAKED_CHILDREN.write().unwrap().retain_mut(|child| {
1213 match child.try_wait() {
1214 Ok(Some(_)) => false, // remove
1215 Ok(None) => true, // retain
1216 Err(e) => {
1217 // try_wait errors require odd circumstances to trigger. For example, something
1218 // else might call libc::waitpid (or its safe wrapper from `nix`) and reap the
1219 // child, causing us to get a "process not found" error here. If that happens
1220 // in a test, go ahead and panic, but otherwise ignore the error. The most
1221 // important thing is that we don't leave the whole process in a broken state
1222 // where every future call to ChildHandle::start returns an error. Also, it
1223 // might not be helpful or appropriate to report this error to our caller.
1224 // Remember that this is lazy, global cleanup of some state that might belong
1225 // to some other thread, running code from some other crate. Let it go...
1226 if cfg!(test) {
1227 panic!("cleanup_leaked_children failed: {e}");
1228 }
1229 false // remove
1230 }
1231 }
1232 });
1233 }
1234}
1235
1236#[derive(Debug)]
1237struct ChildHandle {
1238 child: Option<shared_child::SharedChild>, // optional so that `drop` can take ownership
1239 command_string: String,
1240}
1241
1242impl ChildHandle {
1243 fn start(argv: &[OsString], context: IoContext) -> io::Result<ChildHandle> {
1244 // See comments in the Drop impl below.
1245 #[cfg(not(windows))]
1246 cleanup_leaked_children();
1247
1248 let exe = canonicalize_exe_path_for_dir(&argv[0], &context)?;
1249 let mut command = Command::new(exe);
1250 command.args(&argv[1..]);
1251 if !matches!(context.stdin, IoValue::ParentStdin) {
1252 command.stdin(context.stdin.into_stdio()?);
1253 }
1254 if !matches!(context.stdout, IoValue::ParentStdout) {
1255 command.stdout(context.stdout.into_stdio()?);
1256 }
1257 if !matches!(context.stderr, IoValue::ParentStderr) {
1258 command.stderr(context.stderr.into_stdio()?);
1259 }
1260 if let Some(dir) = context.dir {
1261 command.current_dir(dir);
1262 }
1263 command.env_clear();
1264 for (name, val) in context.env {
1265 command.env(name, val);
1266 }
1267 // The innermost hooks are pushed last, and we execute them last.
1268 for hook in context.before_spawn_hooks.iter() {
1269 hook.call(&mut command)?;
1270 }
1271
1272 // See comments below about why we take this lock (macOS only).
1273 let spawn_guard = pipe_and_spawn_lock_guard();
1274 let shared_child = SharedChild::spawn(&mut command)?;
1275 drop(spawn_guard);
1276
1277 let command_string = format!("{:?}", argv);
1278 Ok(ChildHandle {
1279 child: Some(shared_child),
1280 command_string,
1281 })
1282 }
1283
1284 // a helper to reduce the need for .as_ref().unwrap() everywhere
1285 fn child(&self) -> &SharedChild {
1286 self.child
1287 .as_ref()
1288 .expect("ChildHandle should not yet have been dropped")
1289 }
1290
1291 fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
1292 let maybe_status = mode.maybe_wait_on_child(self.child())?;
1293 if let Some(status) = maybe_status {
1294 Ok(Some(ExpressionStatus {
1295 status,
1296 checked: true,
1297 command: self.command_string.clone(),
1298 }))
1299 } else {
1300 Ok(None)
1301 }
1302 }
1303
1304 fn kill(&self) -> io::Result<()> {
1305 self.child().kill()
1306 }
1307}
1308
1309// Use Drop to prevent zombie processes on Unix. Zombies aren't a thing on Windows, so omit the
1310// entire impl there.
1311#[cfg(not(windows))]
1312impl Drop for ChildHandle {
1313 fn drop(&mut self) {
1314 let child = self.child.take().expect("only drop should take the child");
1315 match child.try_wait() {
1316 // The child has been cleaned up. Cool.
1317 Ok(Some(_)) => (),
1318
1319 // Ignore IO errors here unless we're running tests. It's hard to provoke these without
1320 // doing something very weird (transmute, libc::waitpid, etc).
1321 Err(e) => {
1322 if cfg!(test) {
1323 panic!("ChildHandle cleanup failed: {e}");
1324 }
1325 }
1326
1327 // This child is still running, but the caller is never going to wait on it. Add it to
1328 // a global list of (potentially) zombie children. We poll this list whenever we spawn
1329 // new child processes, to mitigate leaks. This is the same strategy used in the
1330 // CPython `subprocess` module:
1331 // - https://github.com/python/cpython/blob/v3.13.3/Lib/subprocess.py#L1133-L1146
1332 // - https://github.com/python/cpython/blob/v3.13.3/Lib/subprocess.py#L268-L285
1333 // The main downside of this strategy is that spawning N child processes becomes O(N^2)
1334 // if you leak all of them and they're all long-lived. I think that's an ok tradeoff
1335 // for a couple of reasons:
1336 // 1. Dropping un-waited-for child handles isn't usually what you want to be doing in
1337 // your happy path, because it means you can't hear about error codes your
1338 // children return. Children whose handles are retained don't enter this list and
1339 // don't contribute to O(N^2) behavior.
1340 // 2. Callers who do "very advanced" things (say, a systemd clone) probably shouldn't
1341 // be using Duct. They need more fine-grained control than Duct is designed to
1342 // provide.
1343 Ok(None) => LEAKED_CHILDREN.write().unwrap().push(child.into_inner()),
1344 }
1345 }
1346}
1347
1348#[derive(Debug)]
1349struct PipeHandle {
1350 left_handle: HandleInner,
1351 right_handle: HandleInner,
1352}
1353
1354impl PipeHandle {
1355 fn start(left: &Expression, right: &Expression, context: IoContext) -> io::Result<PipeHandle> {
1356 let (reader, writer) = open_pipe_protected()?;
1357 // dup'ing stdin/stdout isn't strictly necessary, but no big deal
1358 let mut left_context = context.try_clone()?;
1359 left_context.stdout = IoValue::Handle(writer.into());
1360 let mut right_context = context;
1361 right_context.stdin = IoValue::Handle(reader.into());
1362 let left_handle = left.0.start(left_context)?;
1363 // The left side has already started. If we fail to start the right
1364 // side, ChildHandle::drop will clean it up one way or another. Note
1365 // that `right_context` is passed by value here, so if start() returns
1366 // an error, all pipe readers will already have been closed, and
1367 // there's a decent chance the left side will exit quickly via EPIPE
1368 // before we .try_wait() it in ChildHandle::drop.
1369 let right_handle = right.0.start(right_context)?;
1370 Ok(PipeHandle {
1371 left_handle,
1372 right_handle,
1373 })
1374 }
1375
1376 fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
1377 // Wait on both sides first, without propagating any errors.
1378 let left_wait_result = self.left_handle.wait(mode);
1379 let right_wait_result = self.right_handle.wait(mode);
1380
1381 // Now we deal with errors from either of those waits. The left wait
1382 // happened first, so that one takes precedence. Note that this is the
1383 // reverse order of exit status precedence.
1384 let left_status = left_wait_result?;
1385 let right_status = right_wait_result?;
1386
1387 // If both waits succeeded, return one of the two statuses.
1388 Ok(pipe_status_precedence(left_status, right_status))
1389 }
1390
1391 // As with wait, we need to call kill on both sides even if the left side
1392 // returns an error.
1393 fn kill(&self) -> io::Result<()> {
1394 let left_kill_result = self.left_handle.kill();
1395 let right_kill_result = self.right_handle.kill();
1396 // As with wait, the left side happened first, so its errors take
1397 // precedence.
1398 left_kill_result.and(right_kill_result)
1399 }
1400
1401 fn pids(&self) -> Vec<u32> {
1402 let mut pids = self.left_handle.pids();
1403 pids.extend_from_slice(&self.right_handle.pids());
1404 pids
1405 }
1406}
1407
1408// The rules of precedence are:
1409// 1) If either side unfinished, the result is unfinished.
1410// 2) Checked errors trump unchecked errors.
1411// 3) Any errors trump success.
1412// 4) All else equal, the right side wins (as in Bash).
1413fn pipe_status_precedence(
1414 left_maybe_status: Option<ExpressionStatus>,
1415 right_maybe_status: Option<ExpressionStatus>,
1416) -> Option<ExpressionStatus> {
1417 let (left_status, right_status) = match (left_maybe_status, right_maybe_status) {
1418 (Some(left), Some(right)) => (left, right),
1419 _ => return None,
1420 };
1421 Some(if right_status.is_checked_error() {
1422 right_status
1423 } else if left_status.is_checked_error() {
1424 left_status
1425 } else if !right_status.status.success() {
1426 right_status
1427 } else {
1428 left_status
1429 })
1430}
1431
1432fn start_io(
1433 io_inner: &IoExpressionInner,
1434 expr_inner: &Expression,
1435 mut context: IoContext,
1436) -> io::Result<HandleInner> {
1437 match io_inner {
1438 StdinBytes(v) => {
1439 return Ok(HandleInner::StdinBytes(Box::new(StdinBytesHandle::start(
1440 expr_inner,
1441 context,
1442 Arc::clone(v),
1443 )?)));
1444 }
1445 StdinPath(p) => {
1446 context.stdin = IoValue::Handle(File::open(p)?.into());
1447 }
1448 StdinFile(f) => {
1449 context.stdin = IoValue::Handle(f.try_clone()?);
1450 }
1451 StdinNull => {
1452 context.stdin = IoValue::Null;
1453 }
1454 StdoutPath(p) => {
1455 context.stdout = IoValue::Handle(File::create(p)?.into());
1456 }
1457 StdoutFile(f) => {
1458 context.stdout = IoValue::Handle(f.try_clone()?);
1459 }
1460 StdoutNull => {
1461 context.stdout = IoValue::Null;
1462 }
1463 StdoutCapture => {
1464 context.stdout = IoValue::Handle(context.stdout_capture.write_pipe()?.into());
1465 }
1466 StdoutToStderr => {
1467 context.stdout = context.stderr.try_clone()?;
1468 }
1469 StderrPath(p) => {
1470 context.stderr = IoValue::Handle(File::create(p)?.into());
1471 }
1472 StderrFile(f) => {
1473 context.stderr = IoValue::Handle(f.try_clone()?);
1474 }
1475 StderrNull => {
1476 context.stderr = IoValue::Null;
1477 }
1478 StderrCapture => {
1479 context.stderr = IoValue::Handle(context.stderr_capture.write_pipe()?.into());
1480 }
1481 StderrToStdout => {
1482 context.stderr = context.stdout.try_clone()?;
1483 }
1484 StdoutStderrSwap => {
1485 mem::swap(&mut context.stdout, &mut context.stderr);
1486 }
1487 Dir(p) => {
1488 context.dir = Some(p.clone());
1489 }
1490 Env(name, val) => {
1491 // Note that HashMap::insert overwrites a preexisting *value*, but not a preexisting
1492 // *key*. We rely on this to match platform behavior on Windows, where the original
1493 // casing of a variable name is preserved even if an equivalent name with a different
1494 // casing is added.
1495 context.env.insert(name.clone(), val.clone());
1496 }
1497 EnvRemove(name) => {
1498 context.env.remove(name);
1499 }
1500 FullEnv(map) => {
1501 context.env = map.clone();
1502 }
1503 Unchecked => {
1504 let inner_handle = expr_inner.0.start(context)?;
1505 return Ok(HandleInner::Unchecked(Box::new(inner_handle)));
1506 }
1507 BeforeSpawn(hook) => {
1508 context.before_spawn_hooks.push(hook.clone());
1509 }
1510 }
1511 expr_inner.0.start(context)
1512}
1513
1514#[derive(Debug)]
1515struct StdinBytesHandle {
1516 inner_handle: HandleInner,
1517 writer_thread: SharedThread<io::Result<()>>,
1518}
1519
1520impl StdinBytesHandle {
1521 fn start(
1522 expression: &Expression,
1523 mut context: IoContext,
1524 input: Arc<Vec<u8>>,
1525 ) -> io::Result<StdinBytesHandle> {
1526 let (reader, mut writer) = open_pipe_protected()?;
1527 context.stdin = IoValue::Handle(reader.into());
1528 let inner_handle = expression.0.start(context)?;
1529 let writer_thread = SharedThread::spawn(move || {
1530 // Broken pipe errors are expected here. Suppress them.
1531 match writer.write_all(&input) {
1532 Err(e) if e.kind() != io::ErrorKind::BrokenPipe => Err(e),
1533 _ => Ok(()),
1534 }
1535 });
1536 Ok(StdinBytesHandle {
1537 inner_handle,
1538 writer_thread,
1539 })
1540 }
1541
1542 fn wait(&self, mode: WaitMode) -> io::Result<Option<ExpressionStatus>> {
1543 let maybe_status = self.inner_handle.wait(mode)?;
1544 // Even if the child has exited, some grandchild process might keep this pipe open and keep
1545 // reading from it. It's tempting not to wait for this IO thread at all and just let it run
1546 // in the background, but that wouldn't work if the current process exited. (When the main
1547 // function returns and the process exits, all background threads are terminated.) Waiting
1548 // on this Handle might be the last thing the caller does in main, who knows, so for
1549 // correctness a blocking waiter does need to wait until this IO thread is finished.
1550 let io_finished = mode.maybe_join_io_thread(&self.writer_thread)?.is_some();
1551 if !io_finished {
1552 return Ok(None);
1553 }
1554 Ok(maybe_status)
1555 }
1556
1557 fn kill(&self) -> io::Result<()> {
1558 self.inner_handle.kill()
1559 }
1560}
1561
1562#[derive(Debug)]
1563enum IoExpressionInner {
1564 StdinBytes(Arc<Vec<u8>>),
1565 StdinPath(PathBuf),
1566 StdinFile(FdOrHandle),
1567 StdinNull,
1568 StdoutPath(PathBuf),
1569 StdoutFile(FdOrHandle),
1570 StdoutNull,
1571 StdoutCapture,
1572 StdoutToStderr,
1573 StderrPath(PathBuf),
1574 StderrFile(FdOrHandle),
1575 StderrNull,
1576 StderrCapture,
1577 StderrToStdout,
1578 StdoutStderrSwap,
1579 Dir(PathBuf),
1580 Env(EnvNameString, OsString),
1581 EnvRemove(EnvNameString),
1582 FullEnv(HashMap<EnvNameString, OsString>),
1583 Unchecked,
1584 BeforeSpawn(BeforeSpawnHook),
1585}
1586
1587type HookFn = Arc<dyn Fn(&mut Command) -> io::Result<()> + Send + Sync>;
1588
1589#[derive(Clone)]
1590struct BeforeSpawnHook {
1591 inner: HookFn,
1592}
1593
1594impl BeforeSpawnHook {
1595 fn new<F>(hook: F) -> Self
1596 where
1597 F: Fn(&mut Command) -> io::Result<()> + Send + Sync + 'static,
1598 {
1599 Self {
1600 inner: Arc::new(hook),
1601 }
1602 }
1603
1604 fn call(&self, command: &mut Command) -> io::Result<()> {
1605 (self.inner)(command)
1606 }
1607}
1608
1609impl fmt::Debug for BeforeSpawnHook {
1610 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1611 write!(f, "<closure>")
1612 }
1613}
1614
1615// An IoContext represents the file descriptors child processes are talking to at execution time.
1616// It's initialized in run(), with dups of the stdin/stdout/stderr pipes, and then passed down to
1617// sub-expressions. Compound expressions will clone() it, and redirections will modify it.
1618#[derive(Debug)]
1619struct IoContext<'a> {
1620 stdin: IoValue,
1621 stdout: IoValue,
1622 stderr: IoValue,
1623 stdout_capture: &'a OutputCaptureContext,
1624 stderr_capture: &'a OutputCaptureContext,
1625 dir: Option<PathBuf>,
1626 env: HashMap<EnvNameString, OsString>,
1627 before_spawn_hooks: Vec<BeforeSpawnHook>,
1628}
1629
1630impl<'a> IoContext<'a> {
1631 // Returns (context, stdout_reader, stderr_reader).
1632 fn new(
1633 stdout_capture: &'a OutputCaptureContext,
1634 stderr_capture: &'a OutputCaptureContext,
1635 ) -> Self {
1636 Self {
1637 stdin: IoValue::ParentStdin,
1638 stdout: IoValue::ParentStdout,
1639 stderr: IoValue::ParentStderr,
1640 stdout_capture,
1641 stderr_capture,
1642 dir: None,
1643 env: std::env::vars_os().map(|(k, v)| (k.into(), v)).collect(),
1644 before_spawn_hooks: Vec::new(),
1645 }
1646 }
1647
1648 fn try_clone(&self) -> io::Result<IoContext<'a>> {
1649 Ok(IoContext {
1650 stdin: self.stdin.try_clone()?,
1651 stdout: self.stdout.try_clone()?,
1652 stderr: self.stderr.try_clone()?,
1653 stdout_capture: self.stdout_capture,
1654 stderr_capture: self.stderr_capture,
1655 dir: self.dir.clone(),
1656 env: self.env.clone(),
1657 before_spawn_hooks: self.before_spawn_hooks.clone(),
1658 })
1659 }
1660}
1661
1662#[derive(Debug)]
1663enum IoValue {
1664 ParentStdin,
1665 ParentStdout,
1666 ParentStderr,
1667 Null,
1668 Handle(FdOrHandle),
1669}
1670
1671impl IoValue {
1672 fn try_clone(&self) -> io::Result<IoValue> {
1673 Ok(match self {
1674 IoValue::ParentStdin => IoValue::ParentStdin,
1675 IoValue::ParentStdout => IoValue::ParentStdout,
1676 IoValue::ParentStderr => IoValue::ParentStderr,
1677 IoValue::Null => IoValue::Null,
1678 IoValue::Handle(f) => IoValue::Handle(f.try_clone()?),
1679 })
1680 }
1681
1682 fn into_stdio(self) -> io::Result<Stdio> {
1683 Ok(match self {
1684 IoValue::ParentStdin => os_pipe::dup_stdin()?.into(),
1685 IoValue::ParentStdout => os_pipe::dup_stdout()?.into(),
1686 IoValue::ParentStderr => os_pipe::dup_stderr()?.into(),
1687 IoValue::Null => Stdio::null(),
1688 IoValue::Handle(f) => f.into(),
1689 })
1690 }
1691}
1692
1693// This struct keeps track of a child exit status, whether or not it's been
1694// unchecked(), and what the command was that gave it (for error messages).
1695#[derive(Clone, Debug)]
1696struct ExpressionStatus {
1697 status: ExitStatus,
1698 checked: bool,
1699 command: String,
1700}
1701
1702impl ExpressionStatus {
1703 fn is_checked_error(&self) -> bool {
1704 self.checked && !self.status.success()
1705 }
1706
1707 fn message(&self) -> String {
1708 format!(
1709 "command {} exited with code {}",
1710 self.command,
1711 self.exit_code_string()
1712 )
1713 }
1714
1715 #[cfg(not(windows))]
1716 fn exit_code_string(&self) -> String {
1717 if self.status.code().is_none() {
1718 return format!("<signal {}>", self.status.signal().unwrap());
1719 }
1720 self.status.code().unwrap().to_string()
1721 }
1722
1723 #[cfg(windows)]
1724 fn exit_code_string(&self) -> String {
1725 self.status.code().unwrap().to_string()
1726 }
1727}
1728
1729fn canonicalize_exe_path_for_dir(exe_name: &OsStr, context: &IoContext) -> io::Result<OsString> {
1730 // There's a tricky interaction between exe paths and `dir`. Exe paths can
1731 // be relative, and so we have to ask: Is an exe path interpreted relative
1732 // to the parent's cwd, or the child's? The answer is that it's platform
1733 // dependent! >.< (Windows uses the parent's cwd, but because of the
1734 // fork-chdir-exec pattern, Unix usually uses the child's.)
1735 //
1736 // We want to use the parent's cwd consistently, because that saves the
1737 // caller from having to worry about whether `dir` will have side effects,
1738 // and because it's easy for the caller to use Path::join if they want to.
1739 // That means that when `dir` is in use, we need to detect exe names that
1740 // are relative paths, and absolutify them. We want to do that as little as
1741 // possible though, both because canonicalization can fail, and because we
1742 // prefer to let the caller control the child's argv[0].
1743 //
1744 // We never want to absolutify a name like "emacs", because that's probably
1745 // a program in the PATH rather than a local file. So we look for slashes
1746 // in the name to determine what's a filepath and what isn't. Note that
1747 // anything given as a std::path::Path will always have a slash by the time
1748 // we get here, because we specialize the IntoExecutablePath trait to
1749 // prepend a ./ to them when they're relative. This leaves the case where
1750 // Windows users might pass a local file like "foo.bat" as a string, which
1751 // we can't distinguish from a global program name. However, because the
1752 // Windows has the preferred "relative to parent's cwd" behavior already,
1753 // this case actually works without our help. (Windows users previously
1754 // needed to watch out for local files shadowing global program names, but
1755 // Rust 1.58 improved this.)
1756
1757 let has_separator = exe_name
1758 .to_string_lossy()
1759 .chars()
1760 .any(std::path::is_separator);
1761 let is_relative = Path::new(exe_name).is_relative();
1762 if context.dir.is_some() && has_separator && is_relative {
1763 Path::new(exe_name).canonicalize().map(Into::into)
1764 } else {
1765 Ok(exe_name.to_owned())
1766 }
1767}
1768
1769// We want to allow Path("foo") to refer to the local file "./foo" on
1770// Unix, and we want to *prevent* Path("echo") from referring to the
1771// global "echo" command on either Unix or Windows. Prepend a dot to all
1772// relative paths to accomplish both of those.
1773fn dotify_relative_exe_path(path: &Path) -> PathBuf {
1774 // This is a no-op if path is absolute or begins with a Windows prefix.
1775 Path::new(".").join(path)
1776}
1777
1778/// An implementation detail of [`cmd`](fn.cmd.html), to distinguish paths from
1779/// other string types.
1780///
1781/// `Path("foo.sh")` means the file named `foo.sh` in the current directory.
1782/// However if you try to execute that path with
1783/// [`std::process::Command`](https://doc.rust-lang.org/std/process/struct.Command.html),
1784/// Unix will get upset that it doesn't have a leading `./`. Rust knows that the
1785/// string is a path, but that distinction gets lost by the time execution
1786/// happens.
1787///
1788/// To execute relative paths correctly, duct prepends the `./` to them
1789/// automatically. This trait captures the distinction between the path types
1790/// and other types of strings, which don't get modified. See the trait bounds
1791/// on [`cmd`](fn.cmd.html).
1792pub trait IntoExecutablePath {
1793 fn to_executable(self) -> OsString;
1794}
1795
1796// TODO: Get rid of most of these impls once specialization lands.
1797
1798impl IntoExecutablePath for &Path {
1799 fn to_executable(self) -> OsString {
1800 dotify_relative_exe_path(self).into()
1801 }
1802}
1803
1804impl IntoExecutablePath for PathBuf {
1805 fn to_executable(self) -> OsString {
1806 dotify_relative_exe_path(&self).into()
1807 }
1808}
1809
1810impl IntoExecutablePath for &PathBuf {
1811 fn to_executable(self) -> OsString {
1812 dotify_relative_exe_path(self).into()
1813 }
1814}
1815
1816impl IntoExecutablePath for &str {
1817 fn to_executable(self) -> OsString {
1818 self.into()
1819 }
1820}
1821
1822impl IntoExecutablePath for String {
1823 fn to_executable(self) -> OsString {
1824 self.into()
1825 }
1826}
1827
1828impl IntoExecutablePath for &String {
1829 fn to_executable(self) -> OsString {
1830 self.into()
1831 }
1832}
1833
1834impl IntoExecutablePath for &OsStr {
1835 fn to_executable(self) -> OsString {
1836 self.into()
1837 }
1838}
1839
1840impl IntoExecutablePath for OsString {
1841 fn to_executable(self) -> OsString {
1842 self
1843 }
1844}
1845
1846impl IntoExecutablePath for &OsString {
1847 fn to_executable(self) -> OsString {
1848 self.into()
1849 }
1850}
1851
1852// io::Error doesn't implement clone directly, so we kind of hack it together.
1853fn clone_io_error(error: &io::Error) -> io::Error {
1854 if let Some(code) = error.raw_os_error() {
1855 io::Error::from_raw_os_error(code)
1856 } else {
1857 io::Error::new(error.kind(), error.to_string())
1858 }
1859}
1860
1861#[derive(Clone, Copy, Debug)]
1862enum WaitMode {
1863 // block until everything is finished, as in .wait()
1864 Blocking,
1865 // don't block at all, as in .try_wait()
1866 NonBlocking,
1867 // block with a deadline, as in .wait_deadline() or .wait_timeout()
1868 #[cfg(feature = "timeout")]
1869 Deadline(std::time::Instant),
1870}
1871
1872impl WaitMode {
1873 fn maybe_wait_on_child(self, child: &SharedChild) -> io::Result<Option<ExitStatus>> {
1874 match self {
1875 WaitMode::Blocking => child.wait().map(Some),
1876 WaitMode::NonBlocking => child.try_wait(),
1877 #[cfg(feature = "timeout")]
1878 WaitMode::Deadline(deadline) => child.wait_deadline(deadline),
1879 }
1880 }
1881
1882 /// Returns Ok(true) if IO finished successfully, Ok(false) if IO is not yet finished (and the
1883 /// mode is not Blocking), or Err(e) if IO failed with an error.
1884 fn maybe_join_io_thread<T>(
1885 self,
1886 io_thread: &SharedThread<io::Result<T>>,
1887 ) -> io::Result<Option<&T>> {
1888 match self {
1889 WaitMode::Blocking => match io_thread.join() {
1890 Ok(val) => Ok(Some(val)),
1891 Err(e) => Err(clone_io_error(e)),
1892 },
1893 WaitMode::NonBlocking => match io_thread.try_join() {
1894 Some(Ok(val)) => Ok(Some(val)),
1895 Some(Err(e)) => Err(clone_io_error(e)),
1896 None => Ok(None),
1897 },
1898 #[cfg(feature = "timeout")]
1899 WaitMode::Deadline(deadline) => match io_thread.join_deadline(deadline) {
1900 Some(Ok(val)) => Ok(Some(val)),
1901 Some(Err(e)) => Err(clone_io_error(e)),
1902 None => Ok(None),
1903 },
1904 }
1905 }
1906}
1907
1908type ReaderThread = SharedThread<io::Result<Vec<u8>>>;
1909
1910#[derive(Debug)]
1911struct OutputCaptureContext {
1912 pair: OnceLock<(os_pipe::PipeReader, os_pipe::PipeWriter)>,
1913}
1914
1915impl OutputCaptureContext {
1916 fn new() -> Self {
1917 Self {
1918 pair: OnceLock::new(),
1919 }
1920 }
1921
1922 fn write_pipe(&self) -> io::Result<os_pipe::PipeWriter> {
1923 // OnceLock::get_or_try_init would be nice if it stabilizes.
1924 match self.pair.get() {
1925 Some((_, writer)) => writer.try_clone(),
1926 None => {
1927 let (reader, writer) = open_pipe_protected()?;
1928 let clone = writer.try_clone();
1929 self.pair.set((reader, writer)).unwrap();
1930 clone
1931 }
1932 }
1933 }
1934
1935 // Only spawn a read thread if the write pipe was used.
1936 fn maybe_read_thread(self) -> Option<ReaderThread> {
1937 if let Some((mut reader, _)) = self.pair.into_inner() {
1938 Some(SharedThread::spawn(move || {
1939 let mut output = Vec::new();
1940 reader.read_to_end(&mut output)?;
1941 Ok(output)
1942 }))
1943 } else {
1944 None
1945 }
1946 }
1947}
1948
1949/// An incremental reader created with the
1950/// [`Expression::reader`](struct.Expression.html#method.reader) method.
1951///
1952/// When this reader reaches EOF, it automatically calls
1953/// [`wait`](struct.Handle.html#method.wait) on the inner handle. If the child
1954/// returns a non-zero exit status, the read at EOF will return an error,
1955/// unless you use [`unchecked`](struct.Expression.html#method.unchecked).
1956///
1957/// Both `ReaderHandle` and `&ReaderHandle` implement
1958/// [`std::io::Read`](https://doc.rust-lang.org/std/io/trait.Read.html). The
1959/// latter makes it possible for one thread to
1960/// [`kill`](struct.ReaderHandle.html#method.kill) the `ReaderHandle` while
1961/// another thread is reading it. That can be useful for effectively canceling
1962/// the read and unblocking the reader thread. However, note that killed child
1963/// processes return a non-zero exit status, which is an error for the reader
1964/// by default, unless you use
1965/// [`unchecked`](struct.Expression.html#method.unchecked).
1966///
1967/// `ReaderHandle` contains a [`Handle`] internally, and it takes the same
1968/// approach to cleaning up zombie processess on Unix.
1969///
1970/// # Example
1971///
1972/// ```
1973/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1974/// # if cfg!(not(windows)) {
1975/// use duct::cmd;
1976/// use duct::ReaderHandle;
1977/// use std::sync::Arc;
1978/// use std::io::prelude::*;
1979///
1980/// // This child process prints a single byte and then sleeps.
1981/// //
1982/// // CAUTION: Using Bash for this example would probably hang, because Bash
1983/// // would spawn a `sleep` grandchild processes, and that grandchild wouldn't
1984/// // receive the kill signal.
1985/// let python_child = "\
1986/// import sys
1987/// import time
1988/// print()
1989/// sys.stdout.flush()
1990/// time.sleep(24 * 60 * 60)
1991/// ";
1992/// let reader: ReaderHandle = cmd!("python3", "-c", python_child)
1993/// .unchecked()
1994/// .reader()?;
1995///
1996/// // Spawn two threads that both try to read the single byte. Whichever one
1997/// // succeeds then calls kill() to unblock the other.
1998/// let arc_reader: Arc<ReaderHandle> = Arc::new(reader);
1999/// let mut threads = Vec::new();
2000/// for _ in 0..2 {
2001/// let arc_reader = arc_reader.clone();
2002/// threads.push(std::thread::spawn(move || -> std::io::Result<()> {
2003/// let mut single_byte = [0u8];
2004/// (&*arc_reader).read(&mut single_byte)?;
2005/// arc_reader.kill()?;
2006/// Ok(())
2007/// }));
2008/// }
2009///
2010/// // Join both threads. Because of the kill() above, both threads will exit
2011/// // quickly.
2012/// for thread in threads {
2013/// thread.join().unwrap()?;
2014/// }
2015/// # }
2016/// # Ok(())
2017/// # }
2018/// ```
2019#[derive(Debug)]
2020pub struct ReaderHandle {
2021 handle: Handle,
2022 reader: os_pipe::PipeReader,
2023}
2024
2025impl ReaderHandle {
2026 /// Check whether the underlying expression is finished. This is equivalent
2027 /// to [`Handle::try_wait`](struct.Handle.html#method.try_wait). If the
2028 /// `ReaderHandle` has indicated EOF successfully, then it's guaranteed
2029 /// that this method will return `Ok(Some(_))`.
2030 ///
2031 /// Note that the
2032 /// [`stdout`](https://doc.rust-lang.org/std/process/struct.Output.html#structfield.stdout)
2033 /// field of the returned
2034 /// [`Output`](https://doc.rust-lang.org/std/process/struct.Output.html)
2035 /// will always be empty, because the `ReaderHandle` itself owns the
2036 /// child's stdout pipe.
2037 pub fn try_wait(&self) -> io::Result<Option<&Output>> {
2038 self.handle.try_wait()
2039 }
2040
2041 /// Kill all the child processes in the running expression.
2042 ///
2043 /// See [`Handle::kill`]. Note that as with
2044 /// [`std::process::Child::kill`](https://doc.rust-lang.org/beta/std/process/struct.Child.html#method.kill),
2045 /// this does not kill any grandchild processes that the children have
2046 /// spawned on their own. It only kills the child processes that Duct
2047 /// spawned itself. This is **especially relevant** for `ReaderHandle`,
2048 /// because if you're using `kill` to unblock another thread that's
2049 /// reading, an unkilled grandchild process might keep the child's stdout
2050 /// pipe open and keep your reader thread blocked. For that use case, you
2051 /// need to ensure that any grandchild processes your child might spawn are
2052 /// going to be short-lived. See
2053 /// [`gotchas.md`](https://github.com/oconnor663/duct.py/blob/master/gotchas.md)
2054 /// for an extensive discussion of these issues.
2055 pub fn kill(&self) -> io::Result<()> {
2056 self.handle.kill()
2057 }
2058
2059 /// Return a `Vec<u32>` containing the PIDs of all of the child processes.
2060 /// The PIDs are given in pipeline order, from left to right.
2061 pub fn pids(&self) -> Vec<u32> {
2062 self.handle.pids()
2063 }
2064}
2065
2066impl Read for &ReaderHandle {
2067 /// Note that if you don't use
2068 /// [`unchecked`](struct.Expression.html#method.unchecked), and the child
2069 /// returns a non-zero exit status, the final call to `read` will return an
2070 /// error, just as [`run`](struct.Expression.html#method.run) would.
2071 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
2072 let n = (&self.reader).read(buf)?;
2073 if n == 0 && !buf.is_empty() {
2074 // EOF detected. Wait on the child to clean it up before returning.
2075 self.handle.wait()?;
2076 }
2077 Ok(n)
2078 }
2079}
2080
2081impl Read for ReaderHandle {
2082 /// Note that if you don't use
2083 /// [`unchecked`](struct.Expression.html#method.unchecked), and the child
2084 /// returns a non-zero exit status, the final call to `read` will return an
2085 /// error, just as [`run`](struct.Expression.html#method.run) would.
2086 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
2087 (&*self).read(buf)
2088 }
2089}
2090
2091#[cfg(not(windows))]
2092type FdOrHandle = OwnedFd;
2093#[cfg(windows)]
2094type FdOrHandle = OwnedHandle;
2095
2096// Without these conversions this crate could be 100% safe code, so this is kind of a shame, but I
2097// don't want to change the trait bounds on stdin_file/stdout_file/stderr_file. There are types
2098// that implement IntoRawFd but not Into<OwnedFd>, including RawFd itself.
2099#[cfg(not(windows))]
2100fn owned_from_raw(raw: impl IntoRawFd) -> OwnedFd {
2101 unsafe { OwnedFd::from_raw_fd(raw.into_raw_fd()) }
2102}
2103#[cfg(windows)]
2104fn owned_from_raw(raw: impl IntoRawHandle) -> OwnedHandle {
2105 unsafe { OwnedHandle::from_raw_handle(raw.into_raw_handle()) }
2106}
2107
2108fn open_pipe_protected() -> io::Result<(os_pipe::PipeReader, os_pipe::PipeWriter)> {
2109 let _guard = pipe_and_spawn_lock_guard(); // See comments below.
2110 os_pipe::pipe()
2111}
2112
2113#[allow(unreachable_code)]
2114fn pipe_and_spawn_lock_guard() -> Option<MutexGuard<'static, ()>> {
2115 // macOS and some other Unixes are missing the pipe2() syscall, which means that opening pipes
2116 // can't atomically set CLOEXEC. That creates a race condition between pipe opening threads and
2117 // child spawning threads, where children can unintentionally inherit extra pipes, possibly
2118 // leading to deadlocks. Use a global lock to prevent this race within Duct itself.
2119 // Unfortunately, callers who open their own pipes with e.g. std::io::pipe(), and who can't
2120 // guarantee that other (macOS) threads aren't spawning child processes at the same time, need
2121 // to create their own global lock.
2122 //
2123 // Ideally we'd keep this list of targets in sync with the `os_pipe` create, which also should
2124 // try to stay in sync with `std::io::pipe()`. In practice I haven't set up any automation for
2125 // that, so if you're reading this in 10+ years it might be stale :)
2126 #[cfg(any(target_os = "aix", target_vendor = "apple", target_os = "haiku"))]
2127 {
2128 use std::sync::Mutex;
2129 static PIPE_OPENING_LOCK: Mutex<()> = Mutex::new(());
2130 return Some(PIPE_OPENING_LOCK.lock().unwrap());
2131 }
2132 // On Windows, Linux, and most other Unixes, this lock isn't needed.
2133 None
2134}
2135
2136#[cfg(test)]
2137mod test;