diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs
index a0b96e25a0cba..6b58173b34304 100644
--- a/src/tools/run-make-support/src/command.rs
+++ b/src/tools/run-make-support/src/command.rs
@@ -15,7 +15,7 @@ use crate::{
 /// This is a custom command wrapper that simplifies working with commands and makes it easier to
 /// ensure that we check the exit status of executed processes.
 ///
-/// # A [`Command`] must be executed
+/// # A [`Command`] must be executed exactly once
 ///
 /// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
 /// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
@@ -23,26 +23,73 @@ use crate::{
 /// containing constructed but never executed commands is dangerous because it can give a false
 /// sense of confidence.
 ///
+/// Each [`Command`] invocation can also only be executed once, because we want to enforce
+/// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
+/// cloneable.
+///
+/// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
+///
 /// [`run`]: Self::run
 /// [`run_fail`]: Self::run_fail
 /// [`run_unchecked`]: Self::run_unchecked
 #[derive(Debug)]
 pub struct Command {
     cmd: StdCommand,
-    stdin: Option<Box<[u8]>>,
+    // Convience for providing a quick stdin buffer.
+    stdin_buf: Option<Box<[u8]>>,
+
+    // Configurations for child process's std{in,out,err} handles.
+    stdin: Option<Stdio>,
+    stdout: Option<Stdio>,
+    stderr: Option<Stdio>,
+
+    // Emulate linear type semantics.
     drop_bomb: DropBomb,
+    already_executed: bool,
 }
 
 impl Command {
     #[track_caller]
     pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
         let program = program.as_ref();
-        Self { cmd: StdCommand::new(program), stdin: None, drop_bomb: DropBomb::arm(program) }
+        Self {
+            cmd: StdCommand::new(program),
+            stdin_buf: None,
+            drop_bomb: DropBomb::arm(program),
+            stdin: None,
+            stdout: None,
+            stderr: None,
+            already_executed: false,
+        }
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.stdin = Some(input.as_ref().to_vec().into_boxed_slice());
+    /// Specify a stdin input buffer. This is a convenience helper,
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
+        self
+    }
+
+    /// Configuration for the child process’s standard input (stdin) handle.
+    ///
+    /// See [`std::process::Command::stdin`].
+    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stdin = Some(cfg.into());
+        self
+    }
+
+    /// Configuration for the child process’s standard output (stdout) handle.
+    ///
+    /// See [`std::process::Command::stdout`].
+    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stdout = Some(cfg.into());
+        self
+    }
+
+    /// Configuration for the child process’s standard error (stderr) handle.
+    ///
+    /// See [`std::process::Command::stderr`].
+    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stderr = Some(cfg.into());
         self
     }
 
@@ -105,6 +152,8 @@ impl Command {
     }
 
     /// Run the constructed command and assert that it is successfully run.
+    ///
+    /// By default, std{in,out,err} are [`Stdio::piped()`].
     #[track_caller]
     pub fn run(&mut self) -> CompletedProcess {
         let output = self.command_output();
@@ -115,6 +164,8 @@ impl Command {
     }
 
     /// Run the constructed command and assert that it does not successfully run.
+    ///
+    /// By default, std{in,out,err} are [`Stdio::piped()`].
     #[track_caller]
     pub fn run_fail(&mut self) -> CompletedProcess {
         let output = self.command_output();
@@ -124,10 +175,10 @@ impl Command {
         output
     }
 
-    /// Run the command but do not check its exit status.
-    /// Only use if you explicitly don't care about the exit status.
-    /// Prefer to use [`Self::run`] and [`Self::run_fail`]
-    /// whenever possible.
+    /// Run the command but do not check its exit status. Only use if you explicitly don't care
+    /// about the exit status.
+    ///
+    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
     #[track_caller]
     pub fn run_unchecked(&mut self) -> CompletedProcess {
         self.command_output()
@@ -135,13 +186,19 @@ impl Command {
 
     #[track_caller]
     fn command_output(&mut self) -> CompletedProcess {
+        if self.already_executed {
+            panic!("command was already executed");
+        } else {
+            self.already_executed = true;
+        }
+
         self.drop_bomb.defuse();
         // let's make sure we piped all the input and outputs
-        self.cmd.stdin(Stdio::piped());
-        self.cmd.stdout(Stdio::piped());
-        self.cmd.stderr(Stdio::piped());
+        self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
+        self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
+        self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
 
-        let output = if let Some(input) = &self.stdin {
+        let output = if let Some(input) = &self.stdin_buf {
             let mut child = self.cmd.spawn().unwrap();
 
             {
diff --git a/src/tools/run-make-support/src/external_deps/llvm.rs b/src/tools/run-make-support/src/external_deps/llvm.rs
index e9315856cd775..2522c4aeb9365 100644
--- a/src/tools/run-make-support/src/external_deps/llvm.rs
+++ b/src/tools/run-make-support/src/external_deps/llvm.rs
@@ -227,9 +227,10 @@ impl LlvmFilecheck {
         Self { cmd }
     }
 
-    /// Pipe a read file into standard input containing patterns that will be matched against the .patterns(path) call.
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Provide a buffer representing standard input containing patterns that will be matched
+    /// against the `.patterns(path)` call.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs
index cece58d29566c..f60ea972839c1 100644
--- a/src/tools/run-make-support/src/external_deps/rustc.rs
+++ b/src/tools/run-make-support/src/external_deps/rustc.rs
@@ -291,9 +291,9 @@ impl Rustc {
         self
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Specify a stdin input buffer.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/external_deps/rustdoc.rs b/src/tools/run-make-support/src/external_deps/rustdoc.rs
index 96b1c719e2e3e..df5e5d8a2e84a 100644
--- a/src/tools/run-make-support/src/external_deps/rustdoc.rs
+++ b/src/tools/run-make-support/src/external_deps/rustdoc.rs
@@ -85,9 +85,9 @@ impl Rustdoc {
         self
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Specify a stdin input buffer.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 989d00d4c2f97..fefafb95b33b1 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -34,6 +34,7 @@ pub mod rfs {
 }
 
 // Re-exports of third-party library crates.
+// tidy-alphabetical-start
 pub use bstr;
 pub use gimli;
 pub use libc;
@@ -41,6 +42,7 @@ pub use object;
 pub use regex;
 pub use serde_json;
 pub use wasmparser;
+// tidy-alphabetical-end
 
 // Re-exports of external dependencies.
 pub use external_deps::{c_build, cc, clang, htmldocck, llvm, python, rustc, rustdoc};
diff --git a/tests/run-make/clear-error-blank-output/rmake.rs b/tests/run-make/clear-error-blank-output/rmake.rs
index e0f042cf805b5..22e030691f289 100644
--- a/tests/run-make/clear-error-blank-output/rmake.rs
+++ b/tests/run-make/clear-error-blank-output/rmake.rs
@@ -8,6 +8,6 @@
 use run_make_support::rustc;
 
 fn main() {
-    let output = rustc().output("").stdin(b"fn main() {}").run_fail();
+    let output = rustc().output("").stdin_buf(b"fn main() {}").run_fail();
     output.assert_stderr_not_contains("panic");
 }
diff --git a/tests/run-make/comment-section/rmake.rs b/tests/run-make/comment-section/rmake.rs
index 1557f50dbd024..ccfc38e870d88 100644
--- a/tests/run-make/comment-section/rmake.rs
+++ b/tests/run-make/comment-section/rmake.rs
@@ -14,7 +14,7 @@ fn main() {
 
     rustc()
         .arg("-")
-        .stdin("fn main() {}")
+        .stdin_buf("fn main() {}")
         .emit("link,obj")
         .arg("-Csave-temps")
         .target(target)
diff --git a/tests/run-make/compile-stdin/rmake.rs b/tests/run-make/compile-stdin/rmake.rs
index f93080dfdc4fe..b8244335855c5 100644
--- a/tests/run-make/compile-stdin/rmake.rs
+++ b/tests/run-make/compile-stdin/rmake.rs
@@ -8,6 +8,6 @@
 use run_make_support::{run, rustc};
 
 fn main() {
-    rustc().arg("-").stdin("fn main() {}").run();
+    rustc().arg("-").stdin_buf("fn main() {}").run();
     run("rust_out");
 }
diff --git a/tests/run-make/libtest-junit/rmake.rs b/tests/run-make/libtest-junit/rmake.rs
index d631313ed9210..5917660b6c7ff 100644
--- a/tests/run-make/libtest-junit/rmake.rs
+++ b/tests/run-make/libtest-junit/rmake.rs
@@ -21,7 +21,7 @@ fn run_tests(extra_args: &[&str], expected_file: &str) {
         .run_fail();
     let test_stdout = &cmd_out.stdout_utf8();
 
-    python_command().arg("validate_junit.py").stdin(test_stdout).run();
+    python_command().arg("validate_junit.py").stdin_buf(test_stdout).run();
 
     diff()
         .expected_file(expected_file)
diff --git a/tests/run-make/llvm-ident/rmake.rs b/tests/run-make/llvm-ident/rmake.rs
index 9699d0579f6ef..47e6fc4de15b3 100644
--- a/tests/run-make/llvm-ident/rmake.rs
+++ b/tests/run-make/llvm-ident/rmake.rs
@@ -17,7 +17,7 @@ fn main() {
         .codegen_units(16)
         .opt_level("2")
         .target(&env_var("TARGET"))
-        .stdin("fn main(){}")
+        .stdin_buf("fn main(){}")
         .run();
 
     // `llvm-dis` is used here since `--emit=llvm-ir` does not emit LLVM IR
diff --git a/tests/run-make/llvm-outputs/rmake.rs b/tests/run-make/llvm-outputs/rmake.rs
index b8ac21c98a010..632e9a09ba503 100644
--- a/tests/run-make/llvm-outputs/rmake.rs
+++ b/tests/run-make/llvm-outputs/rmake.rs
@@ -11,8 +11,8 @@ fn main() {
         let p = cwd();
         path_bc = p.join("nonexistant_dir_bc");
         path_ir = p.join("nonexistant_dir_ir");
-        rustc().input("-").stdin("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run();
-        rustc().input("-").stdin("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run();
+        rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run();
+        rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run();
         assert!(path_bc.exists());
         assert!(path_ir.exists());
     });
diff --git a/tests/run-make/no-builtins-attribute/rmake.rs b/tests/run-make/no-builtins-attribute/rmake.rs
index 1e15b0c03f1d9..7182c65a2c59e 100644
--- a/tests/run-make/no-builtins-attribute/rmake.rs
+++ b/tests/run-make/no-builtins-attribute/rmake.rs
@@ -9,5 +9,5 @@ use run_make_support::{llvm_filecheck, rfs, rustc};
 fn main() {
     rustc().input("no_builtins.rs").emit("link").run();
     rustc().input("main.rs").emit("llvm-ir").run();
-    llvm_filecheck().patterns("filecheck.main.txt").stdin(rfs::read("main.ll")).run();
+    llvm_filecheck().patterns("filecheck.main.txt").stdin_buf(rfs::read("main.ll")).run();
 }
diff --git a/tests/run-make/pgo-branch-weights/rmake.rs b/tests/run-make/pgo-branch-weights/rmake.rs
index ef33d009dbb20..105c2fafc5a10 100644
--- a/tests/run-make/pgo-branch-weights/rmake.rs
+++ b/tests/run-make/pgo-branch-weights/rmake.rs
@@ -35,5 +35,8 @@ fn main() {
         .codegen_units(1)
         .emit("llvm-ir")
         .run();
-    llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run();
+    llvm_filecheck()
+        .patterns("filecheck-patterns.txt")
+        .stdin_buf(rfs::read("interesting.ll"))
+        .run();
 }
diff --git a/tests/run-make/pgo-indirect-call-promotion/rmake.rs b/tests/run-make/pgo-indirect-call-promotion/rmake.rs
index d0ccfd8a4d7e4..28232eb256621 100644
--- a/tests/run-make/pgo-indirect-call-promotion/rmake.rs
+++ b/tests/run-make/pgo-indirect-call-promotion/rmake.rs
@@ -29,5 +29,8 @@ fn main() {
         .codegen_units(1)
         .emit("llvm-ir")
         .run();
-    llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run();
+    llvm_filecheck()
+        .patterns("filecheck-patterns.txt")
+        .stdin_buf(rfs::read("interesting.ll"))
+        .run();
 }
diff --git a/tests/run-make/pgo-use/rmake.rs b/tests/run-make/pgo-use/rmake.rs
index cad49372266e8..276af9ea263a7 100644
--- a/tests/run-make/pgo-use/rmake.rs
+++ b/tests/run-make/pgo-use/rmake.rs
@@ -51,5 +51,5 @@ fn main() {
     let lines: Vec<_> = ir.lines().rev().collect();
     let mut reversed_ir = lines.join("\n");
     reversed_ir.push('\n');
-    llvm_filecheck().patterns("filecheck-patterns.txt").stdin(reversed_ir.as_bytes()).run();
+    llvm_filecheck().patterns("filecheck-patterns.txt").stdin_buf(reversed_ir.as_bytes()).run();
 }
diff --git a/tests/run-make/separate-link/rmake.rs b/tests/run-make/separate-link/rmake.rs
index e91b25489bc59..0ec7334e75de7 100644
--- a/tests/run-make/separate-link/rmake.rs
+++ b/tests/run-make/separate-link/rmake.rs
@@ -8,7 +8,7 @@
 use run_make_support::{run, rustc};
 
 fn main() {
-    rustc().stdin(b"fn main(){}").arg("-Zno-link").arg("-").run();
+    rustc().stdin_buf(b"fn main(){}").arg("-Zno-link").arg("-").run();
     rustc().arg("-Zlink-only").input("rust_out.rlink").run();
     run("rust_out");
 }
diff --git a/tests/run-make/static-pie/rmake.rs b/tests/run-make/static-pie/rmake.rs
index 74f86bb8163b0..1557c170f56de 100644
--- a/tests/run-make/static-pie/rmake.rs
+++ b/tests/run-make/static-pie/rmake.rs
@@ -28,7 +28,7 @@ fn ok_compiler_version(compiler: &str) -> bool {
     }
 
     let compiler_output =
-        cmd(compiler).stdin(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8();
+        cmd(compiler).stdin_buf(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8();
     let re = Regex::new(r"(?m)^(\d+)").unwrap();
     let version: u32 =
         re.captures(&compiler_output).unwrap().get(1).unwrap().as_str().parse().unwrap();
diff --git a/tests/run-make/stdin-rustc/rmake.rs b/tests/run-make/stdin-rustc/rmake.rs
index 68e9cf2fbd803..2d634dd455eb2 100644
--- a/tests/run-make/stdin-rustc/rmake.rs
+++ b/tests/run-make/stdin-rustc/rmake.rs
@@ -14,7 +14,7 @@ const NOT_UTF8: &[u8] = &[0xff, 0xff, 0xff];
 
 fn main() {
     // echo $HELLO_WORLD | rustc -
-    rustc().arg("-").stdin(HELLO_WORLD).run();
+    rustc().arg("-").stdin_buf(HELLO_WORLD).run();
     assert!(
         PathBuf::from(if !is_windows() { "rust_out" } else { "rust_out.exe" })
             .try_exists()
@@ -22,7 +22,7 @@ fn main() {
     );
 
     // echo $NOT_UTF8 | rustc -
-    rustc().arg("-").stdin(NOT_UTF8).run_fail().assert_stderr_contains(
+    rustc().arg("-").stdin_buf(NOT_UTF8).run_fail().assert_stderr_contains(
         "error: couldn't read from stdin, as it did not contain valid UTF-8",
     );
 }
diff --git a/tests/run-make/stdin-rustdoc/rmake.rs b/tests/run-make/stdin-rustdoc/rmake.rs
index de47a24fbdd4f..30f97b8a2cd75 100644
--- a/tests/run-make/stdin-rustdoc/rmake.rs
+++ b/tests/run-make/stdin-rustdoc/rmake.rs
@@ -15,11 +15,11 @@ fn main() {
     let out_dir = PathBuf::from("doc");
 
     // rustdoc -
-    rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run();
+    rustdoc().arg("-").out_dir(&out_dir).stdin_buf(INPUT).run();
     assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap());
 
     // rustdoc --test -
-    rustdoc().arg("--test").arg("-").stdin(INPUT).run();
+    rustdoc().arg("--test").arg("-").stdin_buf(INPUT).run();
 
     // rustdoc file.rs -
     rustdoc().arg("file.rs").arg("-").run_fail();
diff --git a/tests/run-make/sysroot-crates-are-unstable/rmake.rs b/tests/run-make/sysroot-crates-are-unstable/rmake.rs
index 2240d87237b1a..c81c7fafdab0b 100644
--- a/tests/run-make/sysroot-crates-are-unstable/rmake.rs
+++ b/tests/run-make/sysroot-crates-are-unstable/rmake.rs
@@ -34,7 +34,7 @@ fn check_crate_is_unstable(cr: &Crate) {
         .target(target())
         .extern_(name, path)
         .input("-")
-        .stdin(format!("extern crate {name};"))
+        .stdin_buf(format!("extern crate {name};"))
         .run_fail();
 
     // Make sure it failed for the intended reason, not some other reason.
diff --git a/tests/run-make/unknown-mod-stdin/rmake.rs b/tests/run-make/unknown-mod-stdin/rmake.rs
index 0fe5c78ed0ff0..6be3119c0fd15 100644
--- a/tests/run-make/unknown-mod-stdin/rmake.rs
+++ b/tests/run-make/unknown-mod-stdin/rmake.rs
@@ -12,7 +12,7 @@
 use run_make_support::{diff, rustc};
 
 fn main() {
-    let out = rustc().crate_type("rlib").stdin(b"mod unknown;").arg("-").run_fail();
+    let out = rustc().crate_type("rlib").stdin_buf(b"mod unknown;").arg("-").run_fail();
     diff()
         .actual_text("actual-stdout", out.stdout_utf8())
         .expected_file("unknown-mod.stdout")
diff --git a/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs b/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs
index 130781a429371..e58762aeb6db7 100644
--- a/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs
+++ b/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs
@@ -78,19 +78,23 @@ fn check(func_re: &str, mut checks: &str) {
     // This is because frame pointers are optional, and them being enabled requires
     // an additional `popq` in the pattern checking file.
     if func_re == "std::io::stdio::_print::[[:alnum:]]+" {
-        let output = llvm_filecheck().stdin(&dump).patterns(checks).run_unchecked();
+        let output = llvm_filecheck().stdin_buf(&dump).patterns(checks).run_unchecked();
         if !output.status().success() {
             checks = "print.without_frame_pointers.checks";
-            llvm_filecheck().stdin(&dump).patterns(checks).run();
+            llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
         }
     } else {
-        llvm_filecheck().stdin(&dump).patterns(checks).run();
+        llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
     }
     if !["rust_plus_one_global_asm", "cmake_plus_one_c_global_asm", "cmake_plus_one_cxx_global_asm"]
         .contains(&func_re)
     {
         // The assembler cannot avoid explicit `ret` instructions. Sequences
         // of `shlq $0x0, (%rsp); lfence; retq` are used instead.
-        llvm_filecheck().args(&["--implicit-check-not", "ret"]).stdin(dump).patterns(checks).run();
+        llvm_filecheck()
+            .args(&["--implicit-check-not", "ret"])
+            .stdin_buf(dump)
+            .patterns(checks)
+            .run();
     }
 }