agent: set stdout/err vsock stream as blocking before passing to child

In passfd io mode, when not using a terminal, the stdout/stderr vsock
streams are directly used as the stdout/stderr of the child process.
These streams are non-blocking by default.

The stdout/stderr of the process should be blocking, otherwise
the process may encounter EAGAIN error when writing to stdout/stderr.

Fixes: #6714
Signed-off-by: Zixuan Tan <tanzixuan.me@gmail.com>
This commit is contained in:
Zixuan Tan 2023-11-04 17:22:39 +08:00
parent cfb262d02f
commit 7874ef5fd2

View File

@ -5,7 +5,7 @@
use libc::pid_t;
use std::fs::File;
use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::io::{AsRawFd, RawFd, IntoRawFd};
use tokio::sync::mpsc::Sender;
use tokio_vsock::VsockStream;
@ -137,6 +137,13 @@ impl ProcessOperations for Process {
}
}
fn set_blocking(fd: RawFd) -> Result<()> {
let flags = fcntl(fd, FcntlArg::F_GETFL)?;
let new_flags = !OFlag::O_NONBLOCK & OFlag::from_bits_truncate(flags);
fcntl(fd, FcntlArg::F_SETFL(new_flags))?;
Ok(())
}
impl Process {
pub fn new(
logger: &Logger,
@ -189,8 +196,11 @@ impl Process {
p.stdin = Some(stdin);
if let Some(stdout) = p.proc_io.as_mut().map(|io| io.stdout.take()).flatten() {
p.stdout = Some(stdout.as_raw_fd());
std::mem::forget(stdout);
let fd = stdout.into_raw_fd();
// The stdout/stderr of the process should be blocking, otherwise
// the process may encounter EAGAIN error when writing to stdout/stderr.
set_blocking(fd)?;
p.stdout = Some(fd);
} else {
let (pstdout, stdout) = create_extended_pipe(OFlag::O_CLOEXEC, pipe_size)?;
p.parent_stdout = Some(pstdout);
@ -198,8 +208,9 @@ impl Process {
}
if let Some(stderr) = p.proc_io.as_mut().map(|io| io.stderr.take()).flatten() {
p.stderr = Some(stderr.as_raw_fd());
std::mem::forget(stderr);
let fd = stderr.into_raw_fd();
set_blocking(fd)?;
p.stderr = Some(fd);
} else {
let (pstderr, stderr) = create_extended_pipe(OFlag::O_CLOEXEC, pipe_size)?;
p.parent_stderr = Some(pstderr);