Skip to content

Commit 9155c00

Browse files
committed
Ensure that temporary argfile lives longer them command execution
1 parent 8e0e68e commit 9155c00

File tree

2 files changed

+55
-45
lines changed

2 files changed

+55
-45
lines changed

crates/cargo-util/src/process_builder.rs

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::fmt;
1313
use std::io;
1414
use std::iter::once;
1515
use std::path::Path;
16-
use std::process::{Child, Command, ExitStatus, Output, Stdio};
16+
use std::process::{Command, ExitStatus, Output, Stdio};
1717

1818
/// A builder object for an external process, similar to [`std::process::Command`].
1919
#[derive(Clone, Debug)]
@@ -213,11 +213,19 @@ impl ProcessBuilder {
213213

214214
/// Like [`Command::status`] but with a better error message.
215215
pub fn status(&self) -> Result<ExitStatus> {
216-
self.build_and_spawn(|_| {})
217-
.and_then(|mut child| child.wait())
218-
.with_context(|| {
219-
ProcessError::new(&format!("could not execute process {self}"), None, None)
220-
})
216+
self._status()
217+
.with_context(|| ProcessError::could_not_execute(self))
218+
}
219+
220+
fn _status(&self) -> io::Result<ExitStatus> {
221+
let mut cmd = self.build_command();
222+
match cmd.spawn() {
223+
Err(ref e) if self.should_retry_with_argfile(e) => {}
224+
Err(e) => return Err(e),
225+
Ok(mut child) => return child.wait(),
226+
}
227+
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
228+
cmd.spawn()?.wait()
221229
}
222230

223231
/// Runs the process, waiting for completion, and mapping non-success exit codes to an error.
@@ -256,15 +264,19 @@ impl ProcessBuilder {
256264

257265
/// Like [`Command::output`] but with a better error message.
258266
pub fn output(&self) -> Result<Output> {
259-
self.build_and_spawn(|cmd| {
260-
cmd.stdout(Stdio::piped())
261-
.stderr(Stdio::piped())
262-
.stdin(Stdio::null());
263-
})
264-
.and_then(|child| child.wait_with_output())
265-
.with_context(|| {
266-
ProcessError::new(&format!("could not execute process {self}"), None, None)
267-
})
267+
self._output()
268+
.with_context(|| ProcessError::could_not_execute(self))
269+
}
270+
271+
fn _output(&self) -> io::Result<Output> {
272+
let mut cmd = self.build_command();
273+
match piped(&mut cmd).spawn() {
274+
Err(ref e) if self.should_retry_with_argfile(e) => {}
275+
Err(e) => return Err(e),
276+
Ok(child) => return child.wait_with_output(),
277+
}
278+
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
279+
piped(&mut cmd).spawn()?.wait_with_output()
268280
}
269281

270282
/// Executes the process, returning the stdio output, or an error if non-zero exit status.
@@ -303,12 +315,20 @@ impl ProcessBuilder {
303315
let mut callback_error = None;
304316
let mut stdout_pos = 0;
305317
let mut stderr_pos = 0;
318+
319+
let spawn = |mut cmd| {
320+
match piped(&mut cmd).spawn() {
321+
Err(ref e) if self.should_retry_with_argfile(e) => {}
322+
Err(e) => return Err(e),
323+
Ok(child) => return Ok((child, None)),
324+
};
325+
let (mut cmd, argfile) = self.build_command_with_argfile()?;
326+
Ok((piped(&mut cmd).spawn()?, Some(argfile)))
327+
};
328+
306329
let status = (|| {
307-
let mut child = self.build_and_spawn(|cmd| {
308-
cmd.stdout(Stdio::piped())
309-
.stderr(Stdio::piped())
310-
.stdin(Stdio::null());
311-
})?;
330+
let cmd = self.build_command();
331+
let (mut child, _argfile) = spawn(cmd)?;
312332
let out = child.stdout.take().unwrap();
313333
let err = child.stderr.take().unwrap();
314334
read2(out, err, &mut |is_out, data, eof| {
@@ -356,9 +376,7 @@ impl ProcessBuilder {
356376
})?;
357377
child.wait()
358378
})()
359-
.with_context(|| {
360-
ProcessError::new(&format!("could not execute process {}", self), None, None)
361-
})?;
379+
.with_context(|| ProcessError::could_not_execute(self))?;
362380
let output = Output {
363381
status,
364382
stdout,
@@ -386,28 +404,6 @@ impl ProcessBuilder {
386404
Ok(output)
387405
}
388406

389-
/// Builds a command from `ProcessBuilder` and spawn it.
390-
///
391-
/// There is a risk when spawning a process, it might hit "command line
392-
/// too big" OS error. To handle those kind of OS errors, this method try
393-
/// to reinvoke the command with a `@<path>` argfile that contains all the
394-
/// arguments.
395-
///
396-
/// * `apply`: Modify the command before invoking. Useful for updating [`Stdio`].
397-
fn build_and_spawn(&self, apply: impl Fn(&mut Command)) -> io::Result<Child> {
398-
let mut cmd = self.build_command();
399-
apply(&mut cmd);
400-
401-
match cmd.spawn() {
402-
Err(ref e) if self.should_retry_with_argfile(e) => {
403-
let (mut cmd, _argfile) = self.build_command_with_argfile()?;
404-
apply(&mut cmd);
405-
cmd.spawn()
406-
}
407-
res => res,
408-
}
409-
}
410-
411407
/// Builds the command with an `@<path>` argfile that contains all the
412408
/// arguments. This is primarily served for rustc/rustdoc command family.
413409
fn build_command_with_argfile(&self) -> io::Result<(Command, NamedTempFile)> {
@@ -509,6 +505,13 @@ impl ProcessBuilder {
509505
}
510506
}
511507

508+
/// Creates new pipes for stderr and stdout. Ignores stdin.
509+
fn piped(cmd: &mut Command) -> &mut Command {
510+
cmd.stdout(Stdio::piped())
511+
.stderr(Stdio::piped())
512+
.stdin(Stdio::null())
513+
}
514+
512515
#[cfg(unix)]
513516
mod imp {
514517
use super::{ProcessBuilder, ProcessError};

crates/cargo-util/src/process_error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ impl ProcessError {
9595
stderr: stderr.map(|s| s.to_vec()),
9696
}
9797
}
98+
99+
/// Creates a [`ProcessError`] with "could not execute process {cmd}".
100+
///
101+
/// * `cmd` is usually but not limited to [`std::process::Command`].
102+
pub fn could_not_execute(cmd: impl fmt::Display) -> ProcessError {
103+
ProcessError::new(&format!("could not execute process {cmd}"), None, None)
104+
}
98105
}
99106

100107
/// Converts an [`ExitStatus`] to a human-readable string suitable for

0 commit comments

Comments
 (0)