1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright (c) The nextest Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::{
io,
os::windows::{ffi::OsStrExt as _, io::FromRawHandle as _, prelude::OwnedHandle},
ptr::null_mut,
sync::OnceLock,
};
use windows_sys::Win32::{
Foundation as fnd, Security::SECURITY_ATTRIBUTES, Storage::FileSystem as fs,
System::Pipes as pipe,
};
pub struct State {
pub(super) ours: OwnedHandle,
}
pub(super) fn setup_io(cmd: &mut std::process::Command) -> io::Result<State> {
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
static RANDOM_SEQ: OnceLock<AtomicUsize> = OnceLock::new();
let rand_seq = RANDOM_SEQ.get_or_init(|| {
use rand::RngCore;
AtomicUsize::new(rand::rng().next_u32() as _)
});
// A 64kb pipe capacity is the same as a typical Linux default.
const PIPE_BUFFER_CAPACITY: u32 = 64 * 1024;
const FLAGS: u32 =
fs::FILE_FLAG_FIRST_PIPE_INSTANCE | fs::FILE_FLAG_OVERLAPPED | fs::PIPE_ACCESS_INBOUND;
unsafe {
let ours;
let mut wide_path;
let mut tries = 0;
let mut reject_remote_clients_flag = pipe::PIPE_REJECT_REMOTE_CLIENTS;
loop {
tries += 1;
let name = format!(
r"\\.\pipe\__nextest_pipe__.{}.{}",
std::process::id(),
rand_seq.fetch_add(1, SeqCst),
);
wide_path = std::ffi::OsStr::new(&name)
.encode_wide()
.chain(Some(0))
.collect::<Vec<_>>();
let handle = pipe::CreateNamedPipeW(
wide_path.as_ptr(),
FLAGS,
pipe::PIPE_TYPE_BYTE
| pipe::PIPE_READMODE_BYTE
| pipe::PIPE_WAIT
| reject_remote_clients_flag,
1,
PIPE_BUFFER_CAPACITY,
PIPE_BUFFER_CAPACITY,
0,
null_mut(),
);
// We pass the `FILE_FLAG_FIRST_PIPE_INSTANCE` flag above, and we're
// also just doing a best effort at selecting a unique name. If
// `ERROR_ACCESS_DENIED` is returned then it could mean that we
// accidentally conflicted with an already existing pipe, so we try
// again.
//
// Don't try again too much though as this could also perhaps be a
// legit error.
// If `ERROR_INVALID_PARAMETER` is returned, this probably means we're
// running on pre-Vista version where `PIPE_REJECT_REMOTE_CLIENTS` is
// not supported, so we continue retrying without it. This implies
// reduced security on Windows versions older than Vista by allowing
// connections to this pipe from remote machines.
// Proper fix would increase the number of FFI imports and introduce
// significant amount of Windows XP specific code with no clean
// testing strategy
// For more info, see https://github.com/rust-lang/rust/pull/37677.
if handle == fnd::INVALID_HANDLE_VALUE {
let err = io::Error::last_os_error();
let raw_os_err = err.raw_os_error();
if tries < 10 {
if raw_os_err == Some(fnd::ERROR_ACCESS_DENIED as i32) {
continue;
} else if reject_remote_clients_flag != 0
&& raw_os_err == Some(fnd::ERROR_INVALID_PARAMETER as i32)
{
reject_remote_clients_flag = 0;
tries -= 1;
continue;
}
}
return Err(err);
}
ours = OwnedHandle::from_raw_handle(handle as _);
break;
}
// Note we differ from rust here because it sets the SECURITY_ATTRIBUTES
// but that method is not exposed on OpenOptionsExt so we need to do the
// call manually
// Open the write end of the file that we'll pass to the child process
let handle = fs::CreateFileW(
wide_path.as_ptr(),
fnd::GENERIC_WRITE,
0,
&SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as _,
lpSecurityDescriptor: null_mut(),
bInheritHandle: 1,
},
fs::OPEN_EXISTING,
0,
null_mut(),
);
if handle == fnd::INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let handle = OwnedHandle::from_raw_handle(handle as _);
// Use the handle for stdout AND stderr
cmd.stdout(handle.try_clone()?).stderr(handle.try_clone()?);
Ok(State { ours })
}
}