Promote util::run to bread_utils::proc; delegate write_atomic
This crate's util::run/run_with_stdin/run_ok/Output was cited as the one
correct, timeout-guarded subprocess implementation in the ecosystem worth
sharing — it's now promoted verbatim into bread-utils and re-exported here
so every existing crate::util::{run, ...} call site (notify.rs, nm.rs,
status.rs, tailscale.rs) keeps working unchanged. write_atomic similarly
delegates to bread_utils::atomic::write_atomic (path dependency for now,
see the TODO in Cargo.toml). home_dir/command_exists/timestamp stay local
— they're breadcrumbs-specific, not duplicated elsewhere.
New worktree for this repo (none existed from tonight's earlier passes,
since breadcrumbs needed no audit fixes then).
Builds clean; all 74 unit tests + 25 CLI integration tests pass.
This commit is contained in:
parent
b786060517
commit
59416fb273
3 changed files with 191 additions and 140 deletions
176
Cargo.lock
generated
176
Cargo.lock
generated
|
|
@ -38,7 +38,7 @@ version = "1.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -49,19 +49,35 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-utils"
|
||||||
|
version = "0.2.3"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadcrumbs"
|
name = "breadcrumbs"
|
||||||
version = "2.1.5"
|
version = "2.1.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bread-utils",
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.1"
|
version = "4.6.1"
|
||||||
|
|
@ -108,12 +124,44 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
|
|
@ -148,6 +196,21 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|
@ -160,6 +223,12 @@ version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
|
|
@ -178,6 +247,17 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|
@ -247,6 +327,26 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.23"
|
version = "0.8.23"
|
||||||
|
|
@ -300,12 +400,27 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|
@ -315,6 +430,63 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ clap = { version = "4", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern
|
||||||
|
bread-utils = { path = "../bread-ecosystem-fix-worktree/bread-utils" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
|
||||||
153
src/util.rs
153
src/util.rs
|
|
@ -1,9 +1,5 @@
|
||||||
use std::fs;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
use std::thread;
|
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
pub fn home_dir() -> PathBuf {
|
pub fn home_dir() -> PathBuf {
|
||||||
std::env::var_os("HOME")
|
std::env::var_os("HOME")
|
||||||
|
|
@ -16,35 +12,13 @@ pub fn home_dir() -> PathBuf {
|
||||||
/// concurrent reader (the watch daemon reloads config every tick) and never
|
/// concurrent reader (the watch daemon reloads config every tick) and never
|
||||||
/// leaves a half-written file behind on crash. Because the temp file is created
|
/// leaves a half-written file behind on crash. Because the temp file is created
|
||||||
/// with `mode` up front, secrets never exist world-readable even briefly.
|
/// with `mode` up front, secrets never exist world-readable even briefly.
|
||||||
|
///
|
||||||
|
/// Delegates to `bread_utils::atomic::write_atomic` — this crate's own
|
||||||
|
/// version of this function was promoted verbatim into the shared
|
||||||
|
/// `bread-utils` crate (see that crate's `atomic` module doc comment) as
|
||||||
|
/// the one implementation in the ecosystem that already got this right.
|
||||||
pub fn write_atomic(path: &Path, contents: &str, mode: u32) -> std::io::Result<()> {
|
pub fn write_atomic(path: &Path, contents: &str, mode: u32) -> std::io::Result<()> {
|
||||||
let dir = path.parent().unwrap_or_else(|| Path::new("."));
|
bread_utils::atomic::write_atomic(path, contents, Some(mode))
|
||||||
fs::create_dir_all(dir)?;
|
|
||||||
let stem = path
|
|
||||||
.file_name()
|
|
||||||
.and_then(|s| s.to_str())
|
|
||||||
.unwrap_or("breadcrumbs");
|
|
||||||
let tmp = dir.join(format!(".{stem}.tmp.{}", std::process::id()));
|
|
||||||
|
|
||||||
let mut open = fs::OpenOptions::new();
|
|
||||||
open.write(true).create(true).truncate(true);
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
|
||||||
open.mode(mode);
|
|
||||||
}
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
let _ = mode;
|
|
||||||
|
|
||||||
let res = (|| {
|
|
||||||
let mut f = open.open(&tmp)?;
|
|
||||||
f.write_all(contents.as_bytes())?;
|
|
||||||
f.sync_all()?;
|
|
||||||
fs::rename(&tmp, path)
|
|
||||||
})();
|
|
||||||
if res.is_err() {
|
|
||||||
let _ = fs::remove_file(&tmp);
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn command_exists(name: &str) -> bool {
|
pub fn command_exists(name: &str) -> bool {
|
||||||
|
|
@ -58,111 +32,14 @@ pub fn command_exists(name: &str) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
// `Output`, `run`, `run_with_stdin`, and `run_ok` used to be implemented
|
||||||
pub struct Output {
|
// directly in this module; that implementation was promoted verbatim into
|
||||||
pub success: bool,
|
// `bread_utils::proc` as the shared timeout-guarded subprocess runner for
|
||||||
pub stdout: String,
|
// the whole ecosystem (several sibling repos shelled out with no timeout at
|
||||||
pub stderr: String,
|
// all). Re-exported here so every existing `crate::util::{run, ...}` call
|
||||||
}
|
// site in this crate keeps working unchanged.
|
||||||
|
#[allow(unused_imports)] // Output isn't named directly elsewhere in this crate, but stays part of this module's public API.
|
||||||
impl Output {
|
pub use bread_utils::proc::{run, run_ok, run_with_stdin, Output};
|
||||||
pub fn failed() -> Output {
|
|
||||||
Output {
|
|
||||||
success: false,
|
|
||||||
stdout: String::new(),
|
|
||||||
stderr: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a command with a hard timeout. The child is killed if it overruns so a
|
|
||||||
/// hung nmcli/tailscale can never wedge the daemon.
|
|
||||||
pub fn run(prog: &str, args: &[&str], timeout: Duration) -> Output {
|
|
||||||
run_with_stdin(prog, args, None, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [`run`], but feeds `stdin` to the child's standard input. Used to hand
|
|
||||||
/// secrets (e.g. Wi-Fi PSKs) to `nmcli --ask` without exposing them in argv,
|
|
||||||
/// where any local user could read them via `ps`.
|
|
||||||
pub fn run_with_stdin(prog: &str, args: &[&str], stdin: Option<&str>, timeout: Duration) -> Output {
|
|
||||||
let stdin_cfg = if stdin.is_some() {
|
|
||||||
Stdio::piped()
|
|
||||||
} else {
|
|
||||||
Stdio::null()
|
|
||||||
};
|
|
||||||
let mut child = match Command::new(prog)
|
|
||||||
.args(args)
|
|
||||||
// Pin the C locale so message text we parse (nmcli states, monitor
|
|
||||||
// lines) is stable English regardless of the user's LANG. SSID/value
|
|
||||||
// bytes are unaffected.
|
|
||||||
.env("LC_ALL", "C")
|
|
||||||
.env("LANG", "C")
|
|
||||||
.stdin(stdin_cfg)
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(_) => return Output::failed(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut stdout_pipe = child.stdout.take();
|
|
||||||
let mut stderr_pipe = child.stderr.take();
|
|
||||||
|
|
||||||
let out_handle = thread::spawn(move || {
|
|
||||||
let mut buf = String::new();
|
|
||||||
if let Some(ref mut p) = stdout_pipe {
|
|
||||||
let _ = p.read_to_string(&mut buf);
|
|
||||||
}
|
|
||||||
buf
|
|
||||||
});
|
|
||||||
let err_handle = thread::spawn(move || {
|
|
||||||
let mut buf = String::new();
|
|
||||||
if let Some(ref mut p) = stderr_pipe {
|
|
||||||
let _ = p.read_to_string(&mut buf);
|
|
||||||
}
|
|
||||||
buf
|
|
||||||
});
|
|
||||||
|
|
||||||
// Feed stdin only after the reader threads are draining stdout/stderr, so a
|
|
||||||
// child that writes more than a pipe buffer before consuming stdin can't
|
|
||||||
// deadlock against our blocking write.
|
|
||||||
if let Some(data) = stdin {
|
|
||||||
if let Some(mut sink) = child.stdin.take() {
|
|
||||||
let _ = sink.write_all(data.as_bytes());
|
|
||||||
// Drop closes the pipe so the child's read sees EOF.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let status = loop {
|
|
||||||
match child.try_wait() {
|
|
||||||
Ok(Some(s)) => break Some(s),
|
|
||||||
Ok(None) => {
|
|
||||||
if start.elapsed() >= timeout {
|
|
||||||
let _ = child.kill();
|
|
||||||
let _ = child.wait();
|
|
||||||
break None;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(50));
|
|
||||||
}
|
|
||||||
Err(_) => break None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdout = out_handle.join().unwrap_or_default();
|
|
||||||
let stderr = err_handle.join().unwrap_or_default();
|
|
||||||
|
|
||||||
Output {
|
|
||||||
success: status.map(|s| s.success()).unwrap_or(false),
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_ok(prog: &str, args: &[&str], timeout: Duration) -> bool {
|
|
||||||
run(prog, args, timeout).success
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Local "YYYY-MM-DD HH:MM:SS". Uses `date` for correct local time, falling
|
/// Local "YYYY-MM-DD HH:MM:SS". Uses `date` for correct local time, falling
|
||||||
/// back to a dependency-free UTC computation if it is unavailable.
|
/// back to a dependency-free UTC computation if it is unavailable.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue