Skip to content

Commit

Permalink
Implement TTY error
Browse files Browse the repository at this point in the history
Signed-off-by: yihuaf <[email protected]>
  • Loading branch information
yihuaf committed May 5, 2023
1 parent 0a9234f commit 5970ed6
Showing 1 changed file with 110 additions and 32 deletions.
142 changes: 110 additions & 32 deletions crates/libcontainer/src/tty.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
//! tty (teletype) for user-system interaction
use std::io::IoSlice;
use std::os::unix::fs::symlink;
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd;
use std::path::Path;
use crate::error::SyscallWrapperError;

use anyhow::Context;
use anyhow::{bail, Result};
use nix::errno::Errno;
use nix::sys::socket::{self, UnixAddr};
use nix::unistd::close;
use nix::unistd::dup2;
use std::io::IoSlice;
use std::os::unix::fs::symlink;
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd;
use std::path::{Path, PathBuf};

const STDIN: i32 = 0;
const STDOUT: i32 = 1;
const STDERR: i32 = 2;
#[derive(Debug)]
pub enum StdIO {
Stdin = 0,
Stdout = 1,
Stderr = 2,
}

impl From<StdIO> for i32 {
fn from(value: StdIO) -> Self {
match value {
StdIO::Stdin => 0,
StdIO::Stdout => 1,
StdIO::Stderr => 2,
}
}
}

impl std::fmt::Display for StdIO {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StdIO::Stdin => write!(f, "stdin"),
StdIO::Stdout => write!(f, "stdout"),
StdIO::Stderr => write!(f, "stderr"),
}
}
}

#[derive(Debug, thiserror::Error)]
pub enum TTYError {
#[error("failed to connect/duplicate {stdio}: {source:?}")]
ConnectStdIOFailed { source: nix::Error, stdio: StdIO },
#[error("failed to create console socket: {source:?}")]
CreateConsoleSocketFailed {
source: SyscallWrapperError,
container_dir: PathBuf,
console_socket_path: PathBuf,
socket_name: String,
},
#[error("failed to symlink console socket into container_dir: {source:?}")]
SymlinkFailed {
source: SyscallWrapperError,
linked: PathBuf,
console_socket_path: PathBuf,
},
#[error("invalid socker name: {socket_name:?}")]
InvalidSocketName {
socket_name: String,
source: SyscallWrapperError,
},
#[error("failed to create console socket fd: {source:?}")]
CreateConsoleSocketFdFailed { source: SyscallWrapperError },
#[error("could not create pseudo terminal: {source:?}")]
CreatePseudoTerminalFailed { source: nix::Error },
#[error("failed to send pty master: {source:?}")]
SendPtyMasterFailed { source: nix::Error },
#[error("could not close console socket: {source:?}")]
CloseConsoleSocketFailed { source: nix::Error },
}

type Result<T> = std::result::Result<T, TTYError>;

// TODO: Handling when there isn't console-socket.
pub fn setup_console_socket(
Expand All @@ -24,21 +80,33 @@ pub fn setup_console_socket(
socket_name: &str,
) -> Result<RawFd> {
let linked = container_dir.join(socket_name);
symlink(console_socket_path, linked)?;
symlink(console_socket_path, &linked).map_err(|err| TTYError::SymlinkFailed {
source: err.into(),
linked: linked.to_path_buf(),
console_socket_path: console_socket_path.to_path_buf(),
})?;

let mut csocketfd = socket::socket(
socket::AddressFamily::Unix,
socket::SockType::Stream,
socket::SockFlag::empty(),
None,
)?;
csocketfd = match socket::connect(csocketfd, &socket::UnixAddr::new(socket_name)?) {
Err(errno) => {
if !matches!(errno, Errno::ENOENT) {
bail!("failed to open {}", socket_name);
}
-1
}
)
.map_err(|err| TTYError::CreateConsoleSocketFdFailed { source: err.into() })?;
csocketfd = match socket::connect(
csocketfd,
&socket::UnixAddr::new(socket_name).map_err(|err| TTYError::InvalidSocketName {
source: err.into(),
socket_name: socket_name.to_string(),
})?,
) {
Err(Errno::ENOENT) => -1,
Err(errno) => Err(TTYError::CreateConsoleSocketFailed {
source: errno.into(),
container_dir: container_dir.to_path_buf(),
console_socket_path: console_socket_path.to_path_buf(),
socket_name: socket_name.to_string(),
})?,
Ok(()) => csocketfd,
};
Ok(csocketfd)
Expand All @@ -47,8 +115,8 @@ pub fn setup_console_socket(
pub fn setup_console(console_fd: &RawFd) -> Result<()> {
// You can also access pty master, but it is better to use the API.
// ref. https://github.com/containerd/containerd/blob/261c107ffc4ff681bc73988f64e3f60c32233b37/vendor/github.com/containerd/go-runc/console.go#L139-L154
let openpty_result =
nix::pty::openpty(None, None).context("could not create pseudo terminal")?;
let openpty_result = nix::pty::openpty(None, None)
.map_err(|err| TTYError::CreatePseudoTerminalFailed { source: err })?;
let pty_name: &[u8] = b"/dev/ptmx";
let iov = [IoSlice::new(pty_name)];
let fds = [openpty_result.master];
Expand All @@ -60,39 +128,49 @@ pub fn setup_console(console_fd: &RawFd) -> Result<()> {
socket::MsgFlags::empty(),
None,
)
.context("failed to send pty master")?;
.map_err(|err| TTYError::SendPtyMasterFailed { source: err })?;

if unsafe { libc::ioctl(openpty_result.slave, libc::TIOCSCTTY) } < 0 {
log::warn!("could not TIOCSCTTY");
};
let slave = openpty_result.slave;
connect_stdio(&slave, &slave, &slave).context("could not dup tty to stderr")?;
close(console_fd.as_raw_fd()).context("could not close console socket")?;
connect_stdio(&slave, &slave, &slave)?;
close(console_fd.as_raw_fd())
.map_err(|err| TTYError::CloseConsoleSocketFailed { source: err })?;

Ok(())
}

fn connect_stdio(stdin: &RawFd, stdout: &RawFd, stderr: &RawFd) -> Result<()> {
dup2(stdin.as_raw_fd(), STDIN)?;
dup2(stdout.as_raw_fd(), STDOUT)?;
dup2(stdin.as_raw_fd(), StdIO::Stdin.into()).map_err(|err| TTYError::ConnectStdIOFailed {
source: err,
stdio: StdIO::Stdin,
})?;
dup2(stdout.as_raw_fd(), StdIO::Stdout.into()).map_err(|err| TTYError::ConnectStdIOFailed {
source: err,
stdio: StdIO::Stdout,
})?;
// FIXME: Rarely does it fail.
// error message: `Error: Resource temporarily unavailable (os error 11)`
dup2(stderr.as_raw_fd(), STDERR)?;
dup2(stderr.as_raw_fd(), StdIO::Stderr.into()).map_err(|err| TTYError::ConnectStdIOFailed {
source: err,
stdio: StdIO::Stderr,
})?;

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

use crate::utils::{create_temp_dir, TempDir};
use anyhow::Result;
use serial_test::serial;
use std::env;
use std::fs::{self, File};
use std::os::unix::net::UnixListener;
use std::path::PathBuf;

use serial_test::serial;

use crate::utils::{create_temp_dir, TempDir};

const CONSOLE_SOCKET: &str = "console-socket";

fn setup(testname: &str) -> Result<(TempDir, PathBuf, PathBuf)> {
Expand Down

0 comments on commit 5970ed6

Please sign in to comment.