Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions src/cutils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,7 @@ pub unsafe fn os_string_from_ptr(ptr: *const libc::c_char) -> OsString {
}
}

/// Rust's standard library IsTerminal just directly calls isatty, which
/// we don't want since this performs IOCTL calls on them and file descriptors are under
/// the control of the user; so this checks if they are a character device first.
pub fn safe_isatty(fildes: BorrowedFd) -> bool {
fn fstat_mode_set(fildes: &BorrowedFd, mask: libc::mode_t) -> bool {
// The Rust standard library doesn't have FileTypeExt on Std{in,out,err}, so we
// can't just use FileTypeExt::is_char_device and have to resort to libc::fstat.
let mut maybe_stat = std::mem::MaybeUninit::<libc::stat>::uninit();
Expand All @@ -83,19 +80,30 @@ pub fn safe_isatty(fildes: BorrowedFd) -> bool {
let mode = unsafe { maybe_stat.assume_init() }.st_mode;

// To complicate matters further, the S_ISCHR macro isn't in libc as well.
let is_char_device = (mode & libc::S_IFMT) == libc::S_IFCHR;
(mode & libc::S_IFMT) == mask
} else {
false
}
}
/// Rust's standard library IsTerminal just directly calls isatty, which
/// we don't want since this performs IOCTL calls on them and file descriptors are under
/// the control of the user; so this checks if they are a character device first.
pub fn safe_isatty(fildes: BorrowedFd) -> bool {
let is_char_device = fstat_mode_set(&fildes, libc::S_IFCHR);

if is_char_device {
// SAFETY: isatty will return 0 or 1
unsafe { libc::isatty(fildes.as_raw_fd()) != 0 }
} else {
false
}
if is_char_device {
// SAFETY: isatty will return 0 or 1
unsafe { libc::isatty(fildes.as_raw_fd()) != 0 }
} else {
false
}
}

/// Check whether the file descriptor is a pipe
pub fn is_fifo(fildes: BorrowedFd) -> bool {
fstat_mode_set(&fildes, libc::S_IFIFO)
}

#[allow(clippy::undocumented_unsafe_blocks)]
#[cfg(test)]
mod test {
Expand Down
10 changes: 10 additions & 0 deletions src/exec/use_pty/parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ pub(in crate::exec) fn exec_pty(
command.stderr(Stdio::inherit());
}

// If there is another process later in the pipeline, don't interfere
// with its access to the Tty
if io::stdout().is_pipe() {
foreground = false;
}

// Copy terminal settings from `/dev/tty` to the pty.
if let Err(err) = user_tty.copy_to(&pty.follower) {
dev_error!("cannot copy terminal settings to pty: {err}");
Expand Down Expand Up @@ -158,6 +164,10 @@ pub(in crate::exec) fn exec_pty(
}
};

if !foreground {
tty_pipe.disable_input(&mut registry);
}

// SAFETY: There should be no other threads at this point.
let ForkResult::Parent(monitor_pid) = (unsafe { fork() }).map_err(|err| {
dev_error!("cannot fork monitor process: {err}");
Expand Down
39 changes: 28 additions & 11 deletions src/exec/use_pty/pipe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(super) struct Pipe<L, R> {
right: R,
buffer_lr: Buffer<L, R>,
buffer_rl: Buffer<R, L>,
background: bool,
}

impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
Expand All @@ -40,6 +41,7 @@ impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
),
left,
right,
background: false,
}
}

Expand All @@ -66,9 +68,17 @@ impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
self.buffer_rl.write_handle.ignore(registry);
}

/// Stop the poll events of the left end of this pipe.
pub(super) fn disable_input<T: Process>(&mut self, registry: &mut EventRegistry<T>) {
self.buffer_lr.read_handle.ignore(registry);
self.background = true;
}

/// Resume the poll events of this pipe
pub(super) fn resume_events<T: Process>(&mut self, registry: &mut EventRegistry<T>) {
self.buffer_lr.read_handle.resume(registry);
if !self.background {
self.buffer_lr.read_handle.resume(registry);
}
self.buffer_lr.write_handle.resume(registry);
self.buffer_rl.read_handle.resume(registry);
self.buffer_rl.write_handle.resume(registry);
Expand All @@ -82,7 +92,12 @@ impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
) -> io::Result<()> {
match poll_event {
PollEvent::Readable => self.buffer_lr.read(&mut self.left, registry),
PollEvent::Writable => self.buffer_rl.write(&mut self.left, registry),
PollEvent::Writable => {
if self.buffer_rl.write(&mut self.left, registry)? {
self.buffer_rl.read_handle.resume(registry);
}
Ok(())
}
}
}

Expand All @@ -94,7 +109,13 @@ impl<L: Read + Write + AsFd, R: Read + Write + AsFd> Pipe<L, R> {
) -> io::Result<()> {
match poll_event {
PollEvent::Readable => self.buffer_rl.read(&mut self.right, registry),
PollEvent::Writable => self.buffer_lr.write(&mut self.right, registry),
PollEvent::Writable => {
if self.buffer_lr.write(&mut self.right, registry)? && !self.background {
self.buffer_lr.read_handle.resume(registry);
}

Ok(())
}
}
}

Expand Down Expand Up @@ -164,22 +185,18 @@ impl<R: Read, W: Write> Buffer<R, W> {
&mut self,
write: &mut W,
registry: &mut EventRegistry<T>,
) -> io::Result<()> {
) -> io::Result<bool> {
// If the buffer is empty, there is nothing to be written.
if self.internal.is_empty() {
self.write_handle.ignore(registry);
return Ok(());
return Ok(false);
}

// Remove bytes from the buffer and write them.
let removed_len = self.internal.remove(write)?;

// If we removed something, the buffer is not full anymore and we can resume reading.
if removed_len > 0 {
self.read_handle.resume(registry);
}

Ok(())
// Return whether we actually freed up some buffer space
Ok(removed_len > 0)
}

/// Flush this buffer, ensuring that all the contents of its internal buffer are written.
Expand Down
7 changes: 6 additions & 1 deletion src/system/term/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{

use libc::{ioctl, winsize, TIOCSWINSZ};

use crate::cutils::{cerr, os_string_from_ptr, safe_isatty};
use crate::cutils::{cerr, is_fifo, os_string_from_ptr, safe_isatty};

use super::interface::ProcessId;

Expand Down Expand Up @@ -157,6 +157,7 @@ pub(crate) trait Terminal: sealed::Sealed {
fn make_controlling_terminal(&self) -> io::Result<()>;
fn ttyname(&self) -> io::Result<OsString>;
fn is_terminal(&self) -> bool;
fn is_pipe(&self) -> bool;
fn tcgetsid(&self) -> io::Result<ProcessId>;
}

Expand Down Expand Up @@ -200,6 +201,10 @@ impl<F: AsFd> Terminal for F {
safe_isatty(self.as_fd())
}

fn is_pipe(&self) -> bool {
is_fifo(self.as_fd())
}

fn tcgetsid(&self) -> io::Result<ProcessId> {
// SAFETY: tcgetsid cannot cause UB
let id = cerr(unsafe { libc::tcgetsid(self.as_fd().as_raw_fd()) })?;
Expand Down
Loading