Skip to content

Commit a7dba9e

Browse files
authored
Fix race between parent and monitor in use_pty (#1130)
2 parents e82a062 + efd5014 commit a7dba9e

File tree

7 files changed

+76
-29
lines changed

7 files changed

+76
-29
lines changed

src/exec/event.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ impl EventHandle {
9191
}
9292
}
9393
}
94+
95+
/// Is this event handle ready to be processed?
96+
pub(super) fn is_active(&self) -> bool {
97+
self.should_poll
98+
}
9499
}
95100

96101
/// The kind of event that will be monitored for a file descriptor.

src/exec/use_pty/backchannel.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,31 +199,31 @@ impl AsFd for ParentBackchannel {
199199
/// Different messages exchanged between the monitor and the parent process using a [`ParentBackchannel`].
200200
#[derive(PartialEq, Eq)]
201201
pub(super) enum MonitorMessage {
202-
ExecCommand,
202+
Edge,
203203
Signal(c_int),
204204
}
205205

206206
impl MonitorMessage {
207207
const LEN: usize = PREFIX_LEN + MONITOR_DATA_LEN;
208-
const EXEC_CMD: Prefix = 0;
208+
const EDGE_CMD: Prefix = 0;
209209
const SIGNAL: Prefix = 1;
210210

211211
fn from_parts(prefix: Prefix, data: MonitorData) -> Self {
212212
match prefix {
213-
Self::EXEC_CMD => Self::ExecCommand,
213+
Self::EDGE_CMD => Self::Edge,
214214
Self::SIGNAL => Self::Signal(data),
215215
_ => unreachable!(),
216216
}
217217
}
218218

219219
fn to_parts(&self) -> (Prefix, MonitorData) {
220220
let prefix = match self {
221-
MonitorMessage::ExecCommand => Self::EXEC_CMD,
221+
MonitorMessage::Edge => Self::EDGE_CMD,
222222
MonitorMessage::Signal(_) => Self::SIGNAL,
223223
};
224224

225225
let data = match self {
226-
MonitorMessage::ExecCommand => 0,
226+
MonitorMessage::Edge => 0,
227227
MonitorMessage::Signal(data) => *data,
228228
};
229229

@@ -234,7 +234,7 @@ impl MonitorMessage {
234234
impl std::fmt::Debug for MonitorMessage {
235235
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236236
match self {
237-
Self::ExecCommand => "ExecCommand".fmt(f),
237+
Self::Edge => "Edge".fmt(f),
238238
&Self::Signal(signal) => write!(f, "Signal({})", signal_fmt(signal)),
239239
}
240240
}

src/exec/use_pty/monitor.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ pub(super) fn exec_monitor(
7575
err
7676
})?;
7777
// Given that `UnixStream` delivers messages in order it shouldn't be possible to
78-
// receive an event different to `ExecCommand` at the beginning.
79-
debug_assert_eq!(event, MonitorMessage::ExecCommand);
78+
// receive an event different to `Edge` at the beginning.
79+
debug_assert_eq!(event, MonitorMessage::Edge);
8080

8181
// FIXME (ogsudo): Some extra config happens here if selinux is available.
8282

@@ -186,6 +186,14 @@ pub(super) fn exec_monitor(
186186
}
187187
}
188188

189+
// Wait for the parent to give us red light before shutting down. This avoids missing
190+
// output when the monitor exits too quickly.
191+
let event = retry_while_interrupted(|| backchannel.recv()).map_err(|err| {
192+
dev_warn!("cannot receive red light from parent: {err}");
193+
err
194+
})?;
195+
debug_assert_eq!(event, MonitorMessage::Edge);
196+
189197
// FIXME (ogsudo): The tty is restored here if selinux is available.
190198

191199
// We call `_exit` instead of `exit` to avoid flushing the parent's IO streams by accident.
@@ -271,8 +279,8 @@ impl<'a> MonitorClosure<'a> {
271279
}
272280
Ok(event) => {
273281
match event {
274-
// We shouldn't receive this event more than once.
275-
MonitorMessage::ExecCommand => unreachable!(),
282+
// We shouldn't receive this event at this point in the protocol
283+
MonitorMessage::Edge => unreachable!(),
276284
// Forward signal to the command.
277285
MonitorMessage::Signal(signal) => {
278286
if let Some(command_pid) = self.command_pid {

src/exec/use_pty/parent.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,10 @@ pub(in crate::exec) fn exec_pty(
217217
drop(backchannels.monitor);
218218

219219
// Send green light to the monitor after closing the follower.
220-
retry_while_interrupted(|| backchannels.parent.send(&MonitorMessage::ExecCommand)).map_err(
221-
|err| {
222-
dev_error!("cannot send green light to monitor: {err}");
223-
err
224-
},
225-
)?;
220+
retry_while_interrupted(|| backchannels.parent.send(&MonitorMessage::Edge)).map_err(|err| {
221+
dev_error!("cannot send green light to monitor: {err}");
222+
err
223+
})?;
226224

227225
let mut closure = ParentClosure::new(
228226
monitor_pid,
@@ -247,6 +245,7 @@ pub(in crate::exec) fn exec_pty(
247245
// FIXME (ogsudo): Retry if `/dev/tty` is revoked.
248246

249247
// Flush the terminal
248+
closure.tty_pipe.right().set_nonblocking()?;
250249
closure.tty_pipe.flush_left().ok();
251250

252251
// Restore the terminal settings
@@ -359,10 +358,19 @@ impl ParentClosure {
359358
}
360359

361360
fn run(&mut self, registry: EventRegistry<Self>) -> io::Result<ExitReason> {
362-
match registry.event_loop(self) {
361+
let result = match registry.event_loop(self) {
363362
StopReason::Break(err) | StopReason::Exit(ParentExit::Backchannel(err)) => Err(err),
364363
StopReason::Exit(ParentExit::Command(exit_reason)) => Ok(exit_reason),
365-
}
364+
};
365+
// Send red light to the monitor after processing all events
366+
retry_while_interrupted(|| self.backchannel.send(&MonitorMessage::Edge)).map_err(
367+
|err| {
368+
dev_error!("cannot send red light to monitor: {err}");
369+
err
370+
},
371+
)?;
372+
373+
result
366374
}
367375

368376
/// Read an event from the backchannel and return the event if it should break the event loop.

src/exec/use_pty/pipe/mod.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,27 @@ impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
119119
}
120120
}
121121

122-
/// Ensure that all the contents of the pipe's internal buffer are written to the left side.
122+
/// Flush the pipe, ensuring that all the contents are written to the left side.
123123
pub(super) fn flush_left(&mut self) -> io::Result<()> {
124-
self.buffer_rl.flush(&mut self.left)
124+
let buffer = &mut self.buffer_rl;
125+
let source = &mut self.right;
126+
let sink = &mut self.left;
127+
128+
// Flush the ring buffer, then process any eventual bytes still in-flight.
129+
buffer.internal.remove(sink)?;
130+
131+
if buffer.write_handle.is_active() {
132+
let mut buf = [0u8; RingBuffer::LEN];
133+
loop {
134+
match source.read(&mut buf) {
135+
Ok(read_bytes) => sink.write_all(&buf[..read_bytes])?,
136+
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
137+
Err(e) => return Err(e),
138+
}
139+
}
140+
}
141+
142+
sink.flush()
125143
}
126144
}
127145

@@ -198,12 +216,4 @@ impl<R: Read, W: Write> Buffer<R, W> {
198216
// Return whether we actually freed up some buffer space
199217
Ok(removed_len > 0)
200218
}
201-
202-
/// Flush this buffer, ensuring that all the contents of its internal buffer are written.
203-
fn flush(&mut self, write: &mut W) -> io::Result<()> {
204-
// Remove bytes from the buffer and write them.
205-
self.internal.remove(write)?;
206-
207-
write.flush()
208-
}
209219
}

src/exec/use_pty/pipe/ring_buffer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(super) struct RingBuffer {
1010

1111
impl RingBuffer {
1212
/// The size of the internal storage of the ring buffer.
13-
const LEN: usize = 8 * 1024;
13+
pub(super) const LEN: usize = 8 * 1024;
1414

1515
/// Create a new, empty buffer.
1616
pub(super) fn new() -> Self {

src/system/term/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ impl PtyLeader {
9797

9898
Ok(())
9999
}
100+
101+
pub(crate) fn set_nonblocking(&self) -> io::Result<()> {
102+
let fd = self.file.as_fd();
103+
// SAFETY: these two calls to fcntl are memory safe (and the file descriptor is valid as well)
104+
unsafe {
105+
let flags = cerr(libc::fcntl(fd.as_raw_fd(), libc::F_GETFL))?;
106+
107+
// Set the O_NONBLOCK flag
108+
cerr(libc::fcntl(
109+
fd.as_raw_fd(),
110+
libc::F_SETFL,
111+
flags | libc::O_NONBLOCK,
112+
))?;
113+
}
114+
Ok(())
115+
}
100116
}
101117

102118
impl io::Read for PtyLeader {

0 commit comments

Comments
 (0)