mirror of
https://github.com/containers/youki
synced 2024-05-05 23:26:32 +02:00
161 lines
5.1 KiB
Rust
161 lines
5.1 KiB
Rust
//! tty (teletype) for user-system interaction
|
|
|
|
use std::os::unix::fs::symlink;
|
|
use std::os::unix::io::AsRawFd;
|
|
use std::os::unix::prelude::RawFd;
|
|
use std::path::Path;
|
|
|
|
use anyhow::Context;
|
|
use anyhow::{bail, Result};
|
|
use nix::errno::Errno;
|
|
use nix::sys::socket;
|
|
use nix::sys::uio;
|
|
use nix::unistd::dup2;
|
|
use nix::unistd::{close, setsid};
|
|
|
|
const STDIN: i32 = 0;
|
|
const STDOUT: i32 = 1;
|
|
const STDERR: i32 = 2;
|
|
|
|
// TODO: Handling when there isn't console-socket.
|
|
pub fn setup_console_socket(
|
|
container_dir: &Path,
|
|
console_socket_path: &Path,
|
|
socket_name: &str,
|
|
) -> Result<RawFd> {
|
|
let linked = container_dir.join(socket_name);
|
|
symlink(console_socket_path, &linked)?;
|
|
|
|
let mut csocketfd = socket::socket(
|
|
socket::AddressFamily::Unix,
|
|
socket::SockType::Stream,
|
|
socket::SockFlag::empty(),
|
|
None,
|
|
)?;
|
|
csocketfd = match socket::connect(
|
|
csocketfd,
|
|
&socket::SockAddr::Unix(socket::UnixAddr::new(socket_name)?),
|
|
) {
|
|
Err(errno) => {
|
|
if !matches!(errno, Errno::ENOENT) {
|
|
bail!("failed to open {}", socket_name);
|
|
}
|
|
-1
|
|
}
|
|
Ok(()) => csocketfd,
|
|
};
|
|
Ok(csocketfd)
|
|
}
|
|
|
|
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 pty_name: &[u8] = b"/dev/ptmx";
|
|
let iov = [uio::IoVec::from_slice(pty_name)];
|
|
let fds = [openpty_result.master];
|
|
let cmsg = socket::ControlMessage::ScmRights(&fds);
|
|
socket::sendmsg(
|
|
console_fd.as_raw_fd(),
|
|
&iov,
|
|
&[cmsg],
|
|
socket::MsgFlags::empty(),
|
|
None,
|
|
)
|
|
.context("failed to send pty master")?;
|
|
|
|
setsid()?;
|
|
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")?;
|
|
Ok(())
|
|
}
|
|
|
|
fn connect_stdio(stdin: &RawFd, stdout: &RawFd, stderr: &RawFd) -> Result<()> {
|
|
dup2(stdin.as_raw_fd(), STDIN)?;
|
|
dup2(stdout.as_raw_fd(), STDOUT)?;
|
|
// FIXME: Rarely does it fail.
|
|
// error message: `Error: Resource temporarily unavailable (os error 11)`
|
|
dup2(stderr.as_raw_fd(), STDERR)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
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)> {
|
|
let testdir = create_temp_dir(testname)?;
|
|
let rundir_path = Path::join(&testdir, "run");
|
|
let _ = fs::create_dir(&rundir_path)?;
|
|
let socket_path = Path::new(&rundir_path).join("socket");
|
|
let _ = File::create(&socket_path);
|
|
env::set_current_dir(&testdir)?;
|
|
Ok((testdir, rundir_path, socket_path))
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_setup_console_socket() {
|
|
let init = setup("test_setup_console_socket");
|
|
assert!(init.is_ok());
|
|
let (testdir, rundir_path, socket_path) = init.unwrap();
|
|
let lis = UnixListener::bind(Path::join(&testdir, "console-socket"));
|
|
assert!(lis.is_ok());
|
|
let fd = setup_console_socket(&rundir_path, &socket_path, CONSOLE_SOCKET);
|
|
assert!(fd.is_ok());
|
|
assert_ne!(fd.unwrap().as_raw_fd(), -1);
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_setup_console_socket_empty() {
|
|
let init = setup("test_setup_console_socket_empty");
|
|
assert!(init.is_ok());
|
|
let (_testdir, rundir_path, socket_path) = init.unwrap();
|
|
let fd = setup_console_socket(&rundir_path, &socket_path, CONSOLE_SOCKET);
|
|
assert!(fd.is_ok());
|
|
assert_eq!(fd.unwrap().as_raw_fd(), -1);
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_setup_console_socket_invalid() {
|
|
let init = setup("test_setup_console_socket_invalid");
|
|
assert!(init.is_ok());
|
|
let (testdir, rundir_path, socket_path) = init.unwrap();
|
|
let _socket = File::create(Path::join(&testdir, "console-socket"));
|
|
assert!(_socket.is_ok());
|
|
let fd = setup_console_socket(&rundir_path, &socket_path, CONSOLE_SOCKET);
|
|
assert!(fd.is_err());
|
|
}
|
|
|
|
#[test]
|
|
#[serial]
|
|
fn test_setup_console() {
|
|
let init = setup("test_setup_console");
|
|
assert!(init.is_ok());
|
|
let (testdir, rundir_path, socket_path) = init.unwrap();
|
|
let lis = UnixListener::bind(Path::join(&testdir, "console-socket"));
|
|
assert!(lis.is_ok());
|
|
let fd = setup_console_socket(&rundir_path, &socket_path, CONSOLE_SOCKET);
|
|
let status = setup_console(&fd.unwrap());
|
|
assert!(status.is_ok());
|
|
}
|
|
}
|