#![cfg_attr(feature = "docs", doc = include_str!("../README.md"))]
use std::{collections::hash_map::DefaultHasher, fs::File, hash::Hasher, path::PathBuf};
#[cfg(unix)]
use std::{
os::unix::{io::RawFd, net::UnixListener},
path::Path,
};
pub use containerd_shim_protos as protos;
#[cfg(unix)]
use nix::ioctl_write_ptr_bad;
pub use protos::{
shim::shim::DeleteResponse,
ttrpc::{context::Context, Result as TtrpcResult},
};
#[cfg(unix)]
ioctl_write_ptr_bad!(ioctl_set_winsz, libc::TIOCSWINSZ, libc::winsize);
#[cfg(windows)]
use std::{fs::OpenOptions, os::windows::prelude::OpenOptionsExt};
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED;
#[cfg(feature = "async")]
pub use crate::asynchronous::*;
pub use crate::error::{Error, Result};
#[cfg(not(feature = "async"))]
pub use crate::synchronous::*;
#[macro_use]
pub mod error;
mod args;
pub use args::{parse, Flags};
#[cfg(feature = "async")]
pub mod asynchronous;
pub mod cgroup;
pub mod event;
mod logger;
pub mod monitor;
pub mod mount;
mod reap;
#[cfg(not(feature = "async"))]
pub mod synchronous;
mod sys;
pub mod util;
pub mod api {
pub use super::protos::{
api::Status,
shim::{oci::Options, shim::*},
types::empty::Empty,
};
}
macro_rules! cfg_not_async {
($($item:item)*) => {
$(
#[cfg(not(feature = "async"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "async"))))]
$item
)*
}
}
macro_rules! cfg_async {
($($item:item)*) => {
$(
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
$item
)*
}
}
cfg_not_async! {
pub use crate::synchronous::*;
pub use crate::synchronous::publisher;
pub use protos::shim::shim_ttrpc::Task;
pub use protos::ttrpc::TtrpcContext;
}
cfg_async! {
pub use crate::asynchronous::*;
pub use crate::asynchronous::publisher;
pub use protos::shim_async::Task;
pub use protos::ttrpc::r#async::TtrpcContext;
}
const TTRPC_ADDRESS: &str = "TTRPC_ADDRESS";
#[derive(Debug, Default)]
pub struct Config {
pub no_setup_logger: bool,
pub no_reaper: bool,
pub no_sub_reaper: bool,
}
#[derive(Debug, Default)]
pub struct StartOpts {
pub id: String,
pub publish_binary: String,
pub address: String,
pub ttrpc_address: String,
pub namespace: String,
pub debug: bool,
}
#[cfg(unix)]
const SOCKET_FD: RawFd = 3;
#[cfg(target_os = "linux")]
pub const SOCKET_ROOT: &str = "/run/containerd";
#[cfg(target_os = "macos")]
pub const SOCKET_ROOT: &str = "/var/run/containerd";
#[cfg(target_os = "windows")]
pub const SOCKET_ROOT: &str = r"\\.\pipe\containerd-containerd";
pub fn socket_address(socket_path: &str, namespace: &str, id: &str) -> String {
let path = PathBuf::from(socket_path)
.join(namespace)
.join(id)
.display()
.to_string();
let hash = {
let mut hasher = DefaultHasher::new();
hasher.write(path.as_bytes());
hasher.finish()
};
if cfg!(unix) {
format!("unix://{}/{:x}.sock", SOCKET_ROOT, hash)
} else if cfg!(windows) {
format!(r"\\.\pipe\containerd-shim-{}-pipe", hash)
} else {
panic!("unsupported platform")
}
}
#[cfg(unix)]
fn parse_sockaddr(addr: &str) -> &str {
if let Some(addr) = addr.strip_prefix("unix://") {
return addr;
}
if let Some(addr) = addr.strip_prefix("vsock://") {
return addr;
}
addr
}
#[cfg(windows)]
fn start_listener(address: &str) -> std::io::Result<()> {
let mut opts = OpenOptions::new();
opts.read(true)
.write(true)
.custom_flags(FILE_FLAG_OVERLAPPED);
if let Ok(f) = opts.open(address) {
info!("found existing named pipe: {}", address);
drop(f);
return Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"address already exists",
));
}
Ok(())
}
#[cfg(unix)]
fn start_listener(address: &str) -> std::io::Result<UnixListener> {
let path = parse_sockaddr(address);
if let Some(parent) = Path::new(path).parent() {
std::fs::create_dir_all(parent)?;
}
UnixListener::bind(path)
}
pub struct Console {
pub file: File,
}
#[cfg(test)]
mod tests {
use crate::start_listener;
#[test]
#[cfg(unix)]
fn test_start_listener() {
let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().to_str().unwrap().to_owned();
let socket = path + "/ns1/id1/socket";
let _listener = start_listener(&socket).unwrap();
let _listener2 = start_listener(&socket).expect_err("socket should already in use");
let socket2 = socket + "/socket";
assert!(start_listener(&socket2).is_err());
let path = tmpdir.path().to_str().unwrap().to_owned();
let txt_file = path + "demo.txt";
std::fs::write(&txt_file, "test").unwrap();
assert!(start_listener(&txt_file).is_err());
let context = std::fs::read_to_string(&txt_file).unwrap();
assert_eq!(context, "test");
}
#[test]
#[cfg(windows)]
fn test_start_listener_windows() {
use mio::windows::NamedPipe;
let named_pipe = "\\\\.\\pipe\\test-pipe-duplicate".to_string();
start_listener(&named_pipe).unwrap();
let _pipe_server = NamedPipe::new(named_pipe.clone()).unwrap();
start_listener(&named_pipe).expect_err("address already exists");
}
}