@team-agent/installer 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +120 -9
- package/Cargo.toml +2 -2
- package/crates/team-agent/Cargo.toml +21 -0
- package/crates/team-agent/src/app_server_test_support.rs +258 -0
- package/crates/team-agent/src/cli/adapters.rs +32 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +16 -0
- package/crates/team-agent/src/cli/diagnose.rs +14 -5
- package/crates/team-agent/src/cli/emit.rs +79 -1
- package/crates/team-agent/src/cli/mod.rs +190 -80
- package/crates/team-agent/src/cli/named_address.rs +111 -0
- package/crates/team-agent/src/cli/send.rs +98 -3
- package/crates/team-agent/src/cli/status_port.rs +44 -6
- package/crates/team-agent/src/cli/tests/named_address.rs +135 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +17 -0
- package/crates/team-agent/src/codex_app_server.rs +549 -0
- package/crates/team-agent/src/conpty/backend.rs +822 -0
- package/crates/team-agent/src/conpty/mod.rs +32 -0
- package/crates/team-agent/src/coordinator/backoff.rs +132 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +730 -0
- package/crates/team-agent/src/coordinator/health.rs +97 -11
- package/crates/team-agent/src/coordinator/mod.rs +8 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +2 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +6 -0
- package/crates/team-agent/src/diagnose/orphans.rs +29 -10
- package/crates/team-agent/src/leader/lease.rs +144 -7
- package/crates/team-agent/src/leader/owner_bind.rs +5 -2
- package/crates/team-agent/src/leader/provider_attribution.rs +19 -34
- package/crates/team-agent/src/leader/start.rs +18 -5
- package/crates/team-agent/src/leader/tests/lease_api.rs +179 -0
- package/crates/team-agent/src/lib.rs +36 -0
- package/crates/team-agent/src/lifecycle/launch.rs +207 -94
- package/crates/team-agent/src/lifecycle/lock.rs +40 -33
- package/crates/team-agent/src/lifecycle/restart/agent.rs +10 -18
- package/crates/team-agent/src/lifecycle/restart/common.rs +70 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +63 -0
- package/crates/team-agent/src/mcp_server/wire.rs +6 -7
- package/crates/team-agent/src/messaging/delivery.rs +329 -9
- package/crates/team-agent/src/messaging/leader_receiver.rs +17 -138
- package/crates/team-agent/src/messaging/mod.rs +2 -2
- package/crates/team-agent/src/messaging/results.rs +78 -21
- package/crates/team-agent/src/messaging/tests/runtime.rs +237 -0
- package/crates/team-agent/src/packaging/tests.rs +41 -6
- package/crates/team-agent/src/packaging/types.rs +31 -3
- package/crates/team-agent/src/platform/argv.rs +324 -0
- package/crates/team-agent/src/platform/errors.rs +95 -0
- package/crates/team-agent/src/platform/file_lock.rs +418 -0
- package/crates/team-agent/src/platform/mod.rs +66 -0
- package/crates/team-agent/src/platform/process.rs +555 -0
- package/crates/team-agent/src/state/persist.rs +205 -25
- package/crates/team-agent/src/tmux_backend.rs +94 -13
- package/crates/team-agent/src/transport_factory.rs +751 -0
- package/package.json +4 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
//! Process argv / environ probe.
|
|
2
|
+
//!
|
|
3
|
+
//! ## Batch 4 real implementation (leader msg_0689a63a9e40)
|
|
4
|
+
//!
|
|
5
|
+
//! Batch 4 promotes this module from a Batch 0 signature-only
|
|
6
|
+
//! scaffold to a **byte-preserving migration** of the process ancestry
|
|
7
|
+
//! + argv/env probes used by:
|
|
8
|
+
//!
|
|
9
|
+
//! - `lifecycle/launch.rs::process_ancestry_argv` +
|
|
10
|
+
//! `process_argv_tokens` + `process_parent_pid` — the
|
|
11
|
+
//! `dangerous_auto_approve` inheritance chain (0.5.0 caller-identity
|
|
12
|
+
//! 钉输入 is exactly this test surface).
|
|
13
|
+
//! - `leader/provider_attribution.rs::process_command_line` +
|
|
14
|
+
//! `process_environment` — leader provider attribution.
|
|
15
|
+
//!
|
|
16
|
+
//! ## Platform matrix
|
|
17
|
+
//!
|
|
18
|
+
//! - **Linux**: `/proc/<pid>/cmdline` (NUL-separated argv) and
|
|
19
|
+
//! `/proc/<pid>/environ` (NUL-separated KEY=VALUE).
|
|
20
|
+
//! - **macOS**: `sysctl(KERN_PROCARGS2)` for argv; environ not
|
|
21
|
+
//! currently probed (matches the pre-batch behavior).
|
|
22
|
+
//! - **non-Linux non-macOS Unix**: `ps -p <pid> -o command=` for argv.
|
|
23
|
+
//! - **Windows**: `NtQueryInformationProcess` + PEB is intrusive; for
|
|
24
|
+
//! Batch 4 we return `None` for both `argv_tokens` and
|
|
25
|
+
//! `environ_text` (design §Batch 4 Verification anchor: "unknown
|
|
26
|
+
//! argv must never infer elevated approval; keep worker permission
|
|
27
|
+
//! at provider default or require explicit user consent"). This is
|
|
28
|
+
//! the honest "we don't know" branch — callers already treat
|
|
29
|
+
//! `None` as "no elevation inherited", so Windows leaders default
|
|
30
|
+
//! to safe (non-dangerous) approval mode.
|
|
31
|
+
//!
|
|
32
|
+
//! ## CR C-3 anchor
|
|
33
|
+
//!
|
|
34
|
+
//! The `Option::None` return on Windows deliberately keeps worker
|
|
35
|
+
//! permission at provider default. Design §Batch 4 Verification: "the
|
|
36
|
+
//! safe direction is to avoid elevation, not to assume bypass" —
|
|
37
|
+
//! `detect_dangerous_approval` in `lifecycle/launch.rs` already
|
|
38
|
+
//! iterates `process_ancestry_argv(...)` and defaults to
|
|
39
|
+
//! `disabled_dangerous_approval()` when it finds no matching flag,
|
|
40
|
+
//! so a Windows probe returning `None` on every step lands on the
|
|
41
|
+
//! same disabled default. **This is intentional**: leaking a
|
|
42
|
+
//! bypass via unknown argv would be a MUST-NOT-13 假绿 failure.
|
|
43
|
+
|
|
44
|
+
/// Return the argv tokens for `pid`, or `None` if unavailable on
|
|
45
|
+
/// this platform.
|
|
46
|
+
///
|
|
47
|
+
/// - Linux: `/proc/<pid>/cmdline` NUL-separated (byte-preserving
|
|
48
|
+
/// migration of `lifecycle/launch.rs:3648-3656`).
|
|
49
|
+
/// - macOS: `sysctl(KERN_PROCARGS2)` (migration of
|
|
50
|
+
/// `lifecycle/launch.rs:3659-3711`).
|
|
51
|
+
/// - Other Unix: `ps -p <pid> -o command=` split on whitespace
|
|
52
|
+
/// (migration of `lifecycle/launch.rs:3714-3729`).
|
|
53
|
+
/// - Windows: `None` (design §Batch 4 conservative fallback — never
|
|
54
|
+
/// infer elevated approval from unknown argv).
|
|
55
|
+
pub fn argv_tokens(pid: u32) -> Option<Vec<String>> {
|
|
56
|
+
#[cfg(target_os = "linux")]
|
|
57
|
+
{
|
|
58
|
+
argv_tokens_linux(pid)
|
|
59
|
+
}
|
|
60
|
+
#[cfg(target_os = "macos")]
|
|
61
|
+
{
|
|
62
|
+
argv_tokens_macos(pid)
|
|
63
|
+
}
|
|
64
|
+
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
|
|
65
|
+
{
|
|
66
|
+
argv_tokens_ps_fallback(pid)
|
|
67
|
+
}
|
|
68
|
+
#[cfg(windows)]
|
|
69
|
+
{
|
|
70
|
+
let _ = pid;
|
|
71
|
+
None
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Return the environ text (NUL-joined) for `pid`, or `None`.
|
|
76
|
+
///
|
|
77
|
+
/// - Linux: `/proc/<pid>/environ` verbatim (migration of
|
|
78
|
+
/// `leader/provider_attribution.rs:113-115`).
|
|
79
|
+
/// - Other platforms: `None` (matches the pre-batch
|
|
80
|
+
/// `#[cfg(not(target_os="linux"))]` stub in
|
|
81
|
+
/// `leader/provider_attribution.rs:135-139`).
|
|
82
|
+
pub fn environ_text(pid: u32) -> Option<String> {
|
|
83
|
+
#[cfg(target_os = "linux")]
|
|
84
|
+
{
|
|
85
|
+
environ_text_linux(pid)
|
|
86
|
+
}
|
|
87
|
+
#[cfg(not(target_os = "linux"))]
|
|
88
|
+
{
|
|
89
|
+
let _ = pid;
|
|
90
|
+
None
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Return the parent PID of `pid`, or `None` if unavailable.
|
|
95
|
+
///
|
|
96
|
+
/// Byte-preserving migration of `lifecycle/launch.rs::process_parent_pid`:
|
|
97
|
+
/// - Unix: `ps -p <pid> -o ppid=` parsed as u32.
|
|
98
|
+
/// - Windows: Toolhelp32 snapshot walked for the entry matching
|
|
99
|
+
/// `pid`. Same technique as `platform::process::current_parent_pid`.
|
|
100
|
+
pub fn parent_pid(pid: u32) -> Option<u32> {
|
|
101
|
+
#[cfg(unix)]
|
|
102
|
+
{
|
|
103
|
+
parent_pid_unix(pid)
|
|
104
|
+
}
|
|
105
|
+
#[cfg(windows)]
|
|
106
|
+
{
|
|
107
|
+
parent_pid_windows(pid)
|
|
108
|
+
}
|
|
109
|
+
#[cfg(not(any(unix, windows)))]
|
|
110
|
+
{
|
|
111
|
+
let _ = pid;
|
|
112
|
+
None
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
117
|
+
// Linux implementation.
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
#[cfg(target_os = "linux")]
|
|
121
|
+
fn argv_tokens_linux(pid: u32) -> Option<Vec<String>> {
|
|
122
|
+
let bytes = std::fs::read(format!("/proc/{pid}/cmdline")).ok()?;
|
|
123
|
+
let argv_tokens = String::from_utf8_lossy(&bytes)
|
|
124
|
+
.split('\0')
|
|
125
|
+
.filter(|token| !token.is_empty())
|
|
126
|
+
.map(str::to_string)
|
|
127
|
+
.collect::<Vec<_>>();
|
|
128
|
+
(!argv_tokens.is_empty()).then_some(argv_tokens)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[cfg(target_os = "linux")]
|
|
132
|
+
fn environ_text_linux(pid: u32) -> Option<String> {
|
|
133
|
+
String::from_utf8(std::fs::read(format!("/proc/{pid}/environ")).ok()?).ok()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
137
|
+
// macOS implementation.
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
#[cfg(target_os = "macos")]
|
|
141
|
+
fn argv_tokens_macos(pid: u32) -> Option<Vec<String>> {
|
|
142
|
+
use std::mem::size_of;
|
|
143
|
+
let mut mib = [
|
|
144
|
+
libc::CTL_KERN,
|
|
145
|
+
libc::KERN_PROCARGS2,
|
|
146
|
+
i32::try_from(pid).ok()?,
|
|
147
|
+
];
|
|
148
|
+
let mut size = 0usize;
|
|
149
|
+
let rc = unsafe {
|
|
150
|
+
libc::sysctl(
|
|
151
|
+
mib.as_mut_ptr(),
|
|
152
|
+
mib.len() as u32,
|
|
153
|
+
std::ptr::null_mut(),
|
|
154
|
+
&mut size,
|
|
155
|
+
std::ptr::null_mut(),
|
|
156
|
+
0,
|
|
157
|
+
)
|
|
158
|
+
};
|
|
159
|
+
if rc != 0 || size <= size_of::<libc::c_int>() {
|
|
160
|
+
return None;
|
|
161
|
+
}
|
|
162
|
+
let mut buf = vec![0u8; size];
|
|
163
|
+
let rc = unsafe {
|
|
164
|
+
libc::sysctl(
|
|
165
|
+
mib.as_mut_ptr(),
|
|
166
|
+
mib.len() as u32,
|
|
167
|
+
buf.as_mut_ptr().cast(),
|
|
168
|
+
&mut size,
|
|
169
|
+
std::ptr::null_mut(),
|
|
170
|
+
0,
|
|
171
|
+
)
|
|
172
|
+
};
|
|
173
|
+
if rc != 0 || size <= size_of::<libc::c_int>() {
|
|
174
|
+
return None;
|
|
175
|
+
}
|
|
176
|
+
let argc = i32::from_ne_bytes(buf.get(..size_of::<libc::c_int>())?.try_into().ok()?) as usize;
|
|
177
|
+
let mut offset = size_of::<libc::c_int>();
|
|
178
|
+
while offset < size && buf[offset] != 0 {
|
|
179
|
+
offset += 1;
|
|
180
|
+
}
|
|
181
|
+
while offset < size && buf[offset] == 0 {
|
|
182
|
+
offset += 1;
|
|
183
|
+
}
|
|
184
|
+
let raw = String::from_utf8_lossy(&buf[offset..size]);
|
|
185
|
+
let argv_tokens = raw
|
|
186
|
+
.split('\0')
|
|
187
|
+
.filter(|token| !token.is_empty())
|
|
188
|
+
.take(argc)
|
|
189
|
+
.map(str::to_string)
|
|
190
|
+
.collect::<Vec<_>>();
|
|
191
|
+
(!argv_tokens.is_empty()).then_some(argv_tokens)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
195
|
+
// Generic Unix fallback (`ps -p <pid> -o ...`).
|
|
196
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
|
|
199
|
+
fn argv_tokens_ps_fallback(pid: u32) -> Option<Vec<String>> {
|
|
200
|
+
let output = std::process::Command::new("ps")
|
|
201
|
+
.args(["-p", &pid.to_string(), "-o", "command="])
|
|
202
|
+
.output()
|
|
203
|
+
.ok()?;
|
|
204
|
+
if !output.status.success() {
|
|
205
|
+
return None;
|
|
206
|
+
}
|
|
207
|
+
let text = String::from_utf8_lossy(&output.stdout);
|
|
208
|
+
let argv_tokens = text
|
|
209
|
+
.split_whitespace()
|
|
210
|
+
.filter(|token| !token.is_empty())
|
|
211
|
+
.map(str::to_string)
|
|
212
|
+
.collect::<Vec<_>>();
|
|
213
|
+
(!argv_tokens.is_empty()).then_some(argv_tokens)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[cfg(unix)]
|
|
217
|
+
fn parent_pid_unix(pid: u32) -> Option<u32> {
|
|
218
|
+
let output = std::process::Command::new("ps")
|
|
219
|
+
.args(["-p", &pid.to_string(), "-o", "ppid="])
|
|
220
|
+
.output()
|
|
221
|
+
.ok()?;
|
|
222
|
+
if !output.status.success() {
|
|
223
|
+
return None;
|
|
224
|
+
}
|
|
225
|
+
String::from_utf8_lossy(&output.stdout)
|
|
226
|
+
.trim()
|
|
227
|
+
.parse::<u32>()
|
|
228
|
+
.ok()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
232
|
+
// Windows implementation.
|
|
233
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
#[cfg(windows)]
|
|
236
|
+
fn parent_pid_windows(pid: u32) -> Option<u32> {
|
|
237
|
+
use windows::Win32::System::Diagnostics::ToolHelp::{
|
|
238
|
+
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
|
|
239
|
+
TH32CS_SNAPPROCESS,
|
|
240
|
+
};
|
|
241
|
+
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }.ok()?;
|
|
242
|
+
let mut entry = PROCESSENTRY32W {
|
|
243
|
+
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
|
|
244
|
+
..Default::default()
|
|
245
|
+
};
|
|
246
|
+
let mut result = unsafe { Process32FirstW(snapshot, &mut entry) };
|
|
247
|
+
while result.is_ok() {
|
|
248
|
+
if entry.th32ProcessID == pid {
|
|
249
|
+
let ppid = entry.th32ParentProcessID;
|
|
250
|
+
unsafe {
|
|
251
|
+
let _ = windows::Win32::Foundation::CloseHandle(snapshot);
|
|
252
|
+
}
|
|
253
|
+
return Some(ppid);
|
|
254
|
+
}
|
|
255
|
+
result = unsafe { Process32NextW(snapshot, &mut entry) };
|
|
256
|
+
}
|
|
257
|
+
unsafe {
|
|
258
|
+
let _ = windows::Win32::Foundation::CloseHandle(snapshot);
|
|
259
|
+
}
|
|
260
|
+
None
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
264
|
+
// Tests.
|
|
265
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
#[cfg(test)]
|
|
268
|
+
mod tests {
|
|
269
|
+
use super::*;
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn argv_tokens_returns_some_for_own_pid_on_unix_or_none_on_windows() {
|
|
273
|
+
// Byte-preserving migration of the ancestry probe. On Unix
|
|
274
|
+
// this must return a non-empty argv for our own process (test
|
|
275
|
+
// binary). On Windows the honest `None` return is intentional:
|
|
276
|
+
// callers see the "no elevation inherited" default branch,
|
|
277
|
+
// which is safer than reading unknown argv (CR C-3 anchor).
|
|
278
|
+
let my = std::process::id();
|
|
279
|
+
let argv = argv_tokens(my);
|
|
280
|
+
#[cfg(unix)]
|
|
281
|
+
{
|
|
282
|
+
let argv = argv.expect("unix must return Some argv for own pid");
|
|
283
|
+
assert!(!argv.is_empty());
|
|
284
|
+
// At least argv[0] should look like a test binary path.
|
|
285
|
+
let argv0 = &argv[0];
|
|
286
|
+
assert!(!argv0.is_empty());
|
|
287
|
+
}
|
|
288
|
+
#[cfg(windows)]
|
|
289
|
+
{
|
|
290
|
+
// Windows conservative fallback: None, so
|
|
291
|
+
// `detect_dangerous_approval` defaults to disabled.
|
|
292
|
+
assert!(argv.is_none());
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn parent_pid_returns_some_on_both_platforms() {
|
|
298
|
+
// Batch 4: parent_pid via `ps -o ppid=` (unix) or Toolhelp32
|
|
299
|
+
// (windows) — both must resolve our own ppid.
|
|
300
|
+
let my = std::process::id();
|
|
301
|
+
let ppid = parent_pid(my);
|
|
302
|
+
assert!(ppid.is_some(), "parent_pid must resolve own pid on both unix and windows");
|
|
303
|
+
assert!(ppid.unwrap() > 0);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#[test]
|
|
307
|
+
fn environ_text_linux_only_returns_something_for_own_pid() {
|
|
308
|
+
// The environ probe is Linux-only by design (matches
|
|
309
|
+
// `leader/provider_attribution.rs:113-115`). Other platforms
|
|
310
|
+
// honestly return None; callers already treat that as
|
|
311
|
+
// "no env-based attribution".
|
|
312
|
+
let my = std::process::id();
|
|
313
|
+
let env = environ_text(my);
|
|
314
|
+
#[cfg(target_os = "linux")]
|
|
315
|
+
{
|
|
316
|
+
let env = env.expect("linux must return Some environ");
|
|
317
|
+
assert!(env.contains('='));
|
|
318
|
+
}
|
|
319
|
+
#[cfg(not(target_os = "linux"))]
|
|
320
|
+
{
|
|
321
|
+
assert!(env.is_none());
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//! Retryable OS error classifier + human-readable os_error_name.
|
|
2
|
+
//!
|
|
3
|
+
//! Batch 0 signature only. Batch 2 migrates the raw
|
|
4
|
+
//! `libc::{EACCES, EPERM, EBUSY, ENOSPC}` matching in
|
|
5
|
+
//! `state/persist.rs::retryable_replace_error` onto these helpers so
|
|
6
|
+
//! Windows sees the same classification via
|
|
7
|
+
//! `io::ErrorKind` + raw-code mapping.
|
|
8
|
+
|
|
9
|
+
use std::io;
|
|
10
|
+
|
|
11
|
+
/// True when the OS error is a transient replace failure worth
|
|
12
|
+
/// retrying (EACCES on Windows during antivirus scan, EBUSY on
|
|
13
|
+
/// mounted filesystems, etc.). Batch 2 wires the callsite.
|
|
14
|
+
#[allow(dead_code)]
|
|
15
|
+
pub fn retryable_replace_error(error: &io::Error) -> bool {
|
|
16
|
+
// Batch 2 will migrate `state/persist.rs::retryable_replace_error`
|
|
17
|
+
// here. Batch 0 provides the signature only.
|
|
18
|
+
#[cfg(unix)]
|
|
19
|
+
{
|
|
20
|
+
matches!(
|
|
21
|
+
error.raw_os_error(),
|
|
22
|
+
Some(c)
|
|
23
|
+
if c == libc::EACCES
|
|
24
|
+
|| c == libc::EPERM
|
|
25
|
+
|| c == libc::EBUSY
|
|
26
|
+
|| c == libc::ENOSPC
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
#[cfg(not(unix))]
|
|
30
|
+
{
|
|
31
|
+
// Windows: retryable on sharing violation / access denied
|
|
32
|
+
// during antivirus scans. Map by `io::ErrorKind` so we don't
|
|
33
|
+
// depend on `windows-sys` raw codes at this layer.
|
|
34
|
+
matches!(
|
|
35
|
+
error.kind(),
|
|
36
|
+
io::ErrorKind::PermissionDenied | io::ErrorKind::WouldBlock
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Human-readable name for a raw OS error code — used in diagnostics
|
|
42
|
+
/// so operators see `EACCES` instead of `13`.
|
|
43
|
+
#[allow(dead_code)]
|
|
44
|
+
pub fn os_error_name(error: &io::Error) -> Option<&'static str> {
|
|
45
|
+
#[cfg(unix)]
|
|
46
|
+
{
|
|
47
|
+
match error.raw_os_error()? {
|
|
48
|
+
c if c == libc::EACCES => Some("EACCES"),
|
|
49
|
+
c if c == libc::EPERM => Some("EPERM"),
|
|
50
|
+
c if c == libc::EBUSY => Some("EBUSY"),
|
|
51
|
+
c if c == libc::ENOSPC => Some("ENOSPC"),
|
|
52
|
+
c if c == libc::EWOULDBLOCK => Some("EWOULDBLOCK"),
|
|
53
|
+
c if c == libc::ESRCH => Some("ESRCH"),
|
|
54
|
+
_ => None,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
#[cfg(not(unix))]
|
|
58
|
+
{
|
|
59
|
+
// Batch 2 will map ERROR_ACCESS_DENIED / ERROR_SHARING_VIOLATION
|
|
60
|
+
// / ERROR_LOCK_VIOLATION here. Batch 0 leaves the mapping to
|
|
61
|
+
// `io::Error::kind()`.
|
|
62
|
+
let _ = error;
|
|
63
|
+
None
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[cfg(test)]
|
|
68
|
+
mod tests {
|
|
69
|
+
use super::*;
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn retryable_replace_error_matches_eacces_on_unix() {
|
|
73
|
+
#[cfg(unix)]
|
|
74
|
+
{
|
|
75
|
+
let err = io::Error::from_raw_os_error(libc::EACCES);
|
|
76
|
+
assert!(retryable_replace_error(&err));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[test]
|
|
81
|
+
fn retryable_replace_error_rejects_random_error() {
|
|
82
|
+
// A generic "not found" is not a replace-race condition.
|
|
83
|
+
let err = io::Error::from(io::ErrorKind::NotFound);
|
|
84
|
+
assert!(!retryable_replace_error(&err));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn os_error_name_returns_stable_wire_string_on_unix() {
|
|
89
|
+
#[cfg(unix)]
|
|
90
|
+
{
|
|
91
|
+
let err = io::Error::from_raw_os_error(libc::EACCES);
|
|
92
|
+
assert_eq!(os_error_name(&err), Some("EACCES"));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|