mirror of
https://github.com/containers/youki
synced 2024-05-09 17:16:16 +02:00
Add list command
This commit is contained in:
parent
a105e11427
commit
9d785aa521
|
@ -96,6 +96,7 @@ dependencies = [
|
|||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -868,6 +869,15 @@ dependencies = [
|
|||
"utf8-cstr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabwriter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
|
@ -1013,4 +1023,5 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serial_test",
|
||||
"systemd",
|
||||
"tabwriter",
|
||||
]
|
||||
|
|
|
@ -17,13 +17,14 @@ libc = "0.2.84"
|
|||
log = "0.4"
|
||||
anyhow = "1.0"
|
||||
mio = { version = "0.7", features = ["os-ext", "os-poll"] }
|
||||
chrono = "0.4"
|
||||
chrono = { version="0.4", features = ["serde"] }
|
||||
once_cell = "1.6.0"
|
||||
futures = { version = "0.3", features = ["thread-pool"] }
|
||||
regex = "1.5"
|
||||
oci_spec = { version = "0.1.0", path = "./oci_spec" }
|
||||
systemd = { version = "0.8", default-features = false }
|
||||
dbus = "0.9.2"
|
||||
tabwriter = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
oci_spec = { version = "0.1.0", path = "./oci_spec", features = ["proptests"] }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! An interface trait so that rest of Youki can call
|
||||
//! necessary functions without having to worry about their
|
||||
//! implementation details
|
||||
use std::{any::Any, path::Path};
|
||||
use std::{any::Any, ffi::OsStr, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use caps::{errors::CapsError, CapSet, CapsHashSet};
|
||||
|
@ -12,6 +12,8 @@ use nix::{
|
|||
|
||||
use oci_spec::LinuxRlimit;
|
||||
|
||||
use crate::command::{linux::LinuxCommand, test::TestHelperCommand};
|
||||
|
||||
/// This specifies various kernel/other functionalities required for
|
||||
/// container management
|
||||
pub trait Command {
|
||||
|
@ -23,4 +25,13 @@ pub trait Command {
|
|||
fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<(), CapsError>;
|
||||
fn set_hostname(&self, hostname: &str) -> Result<()>;
|
||||
fn set_rlimit(&self, rlimit: &LinuxRlimit) -> Result<()>;
|
||||
fn get_pwuid(&self, uid: u32) -> Option<Arc<OsStr>>;
|
||||
}
|
||||
|
||||
pub fn create_command() -> Box<dyn Command> {
|
||||
if cfg!(test) {
|
||||
Box::new(TestHelperCommand::default())
|
||||
} else {
|
||||
Box::new(LinuxCommand)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
//! Implements Command trait for Linux systems
|
||||
use std::{any::Any, path::Path};
|
||||
use std::ffi::{CStr, OsStr};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, mem, path::Path, ptr};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use caps::{errors::CapsError, CapSet, CapsHashSet};
|
||||
use libc::{c_char, uid_t};
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
unistd::{fchdir, pivot_root, sethostname},
|
||||
|
@ -27,6 +31,21 @@ use crate::capabilities;
|
|||
#[derive(Clone)]
|
||||
pub struct LinuxCommand;
|
||||
|
||||
impl LinuxCommand {
|
||||
unsafe fn from_raw_buf<'a, T>(p: *const c_char) -> T
|
||||
where
|
||||
T: From<&'a OsStr>,
|
||||
{
|
||||
T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes()))
|
||||
}
|
||||
|
||||
/// Reads data from the `c_passwd` and returns it as a `User`.
|
||||
unsafe fn passwd_to_user(passwd: libc::passwd) -> Arc<OsStr> {
|
||||
let name: Arc<OsStr> = Self::from_raw_buf(passwd.pw_name);
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for LinuxCommand {
|
||||
/// To enable dynamic typing,
|
||||
/// see https://doc.rust-lang.org/std/any/index.html for more information
|
||||
|
@ -118,4 +137,38 @@ impl Command for LinuxCommand {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// taken from https://crates.io/crates/users
|
||||
fn get_pwuid(&self, uid: uid_t) -> Option<Arc<OsStr>> {
|
||||
let mut passwd = unsafe { mem::zeroed::<libc::passwd>() };
|
||||
let mut buf = vec![0; 2048];
|
||||
let mut result = ptr::null_mut::<libc::passwd>();
|
||||
|
||||
loop {
|
||||
let r = unsafe {
|
||||
libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)
|
||||
};
|
||||
|
||||
if r != libc::ERANGE {
|
||||
break;
|
||||
}
|
||||
|
||||
let newsize = buf.len().checked_mul(2)?;
|
||||
buf.resize(newsize, 0);
|
||||
}
|
||||
|
||||
if result.is_null() {
|
||||
// There is no such user, or an error has occurred.
|
||||
// errno gets set if there’s an error.
|
||||
return None;
|
||||
}
|
||||
|
||||
if result != &mut passwd {
|
||||
// The result of getpwuid_r should be its input passwd.
|
||||
return None;
|
||||
}
|
||||
|
||||
let user = unsafe { Self::passwd_to_user(result.read()) };
|
||||
Some(user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! to call syscalls required for container management
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod command;
|
||||
pub mod command;
|
||||
pub mod linux;
|
||||
pub mod test;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{any::Any, cell::RefCell};
|
||||
use std::{any::Any, cell::RefCell, ffi::OsStr, sync::Arc};
|
||||
|
||||
use caps::{errors::CapsError, CapSet, CapsHashSet};
|
||||
use nix::sched::CloneFlags;
|
||||
|
@ -60,6 +60,10 @@ impl Command for TestHelperCommand {
|
|||
fn set_rlimit(&self, _rlimit: &LinuxRlimit) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_pwuid(&self, _: u32) -> Option<Arc<OsStr>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestHelperCommand {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::DateTime;
|
||||
use nix::unistd::Pid;
|
||||
|
||||
use chrono::Utc;
|
||||
use procfs::process::Process;
|
||||
|
||||
use crate::command::command::create_command;
|
||||
|
||||
use crate::container::{ContainerStatus, State};
|
||||
|
||||
/// Structure representing the container data
|
||||
|
@ -39,7 +45,7 @@ impl Container {
|
|||
pub fn status(&self) -> ContainerStatus {
|
||||
self.state.status
|
||||
}
|
||||
pub fn refresh_status(&self) -> Result<Self> {
|
||||
pub fn refresh_status(&mut self) -> Result<Self> {
|
||||
let new_status = match self.pid() {
|
||||
Some(pid) => {
|
||||
// Note that Process::new does not spawn a new process
|
||||
|
@ -60,11 +66,19 @@ impl Container {
|
|||
}
|
||||
None => ContainerStatus::Stopped,
|
||||
};
|
||||
self.update_status(new_status)
|
||||
Ok(self.update_status(new_status))
|
||||
}
|
||||
|
||||
pub fn refresh_state(&self) -> Result<Self> {
|
||||
let state = State::load(&self.root)?;
|
||||
Ok(Self {
|
||||
state,
|
||||
root: self.root.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
log::debug!("Sava container status: {:?} in {:?}", self, self.root);
|
||||
log::debug!("Save container status: {:?} in {:?}", self, self.root);
|
||||
self.state.save(&self.root)
|
||||
}
|
||||
|
||||
|
@ -85,24 +99,50 @@ impl Container {
|
|||
}
|
||||
|
||||
pub fn set_pid(&self, pid: i32) -> Self {
|
||||
Self::new(
|
||||
self.state.id.as_str(),
|
||||
self.state.status,
|
||||
Some(pid),
|
||||
self.state.bundle.as_str(),
|
||||
&self.root,
|
||||
)
|
||||
.expect("unexpected error")
|
||||
let mut new_state = self.state.clone();
|
||||
new_state.pid = Some(pid);
|
||||
|
||||
Self {
|
||||
state: new_state,
|
||||
root: self.root.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_status(&self, status: ContainerStatus) -> Result<Self> {
|
||||
Self::new(
|
||||
self.state.id.as_str(),
|
||||
status,
|
||||
self.state.pid,
|
||||
self.state.bundle.as_str(),
|
||||
&self.root,
|
||||
)
|
||||
pub fn created(&self) -> Option<DateTime<Utc>> {
|
||||
self.state.created
|
||||
}
|
||||
|
||||
pub fn set_creator(mut self, uid: u32) -> Self {
|
||||
self.state.creator = Some(uid);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn creator(&self) -> Option<OsString> {
|
||||
if let Some(uid) = self.state.creator {
|
||||
let command = create_command();
|
||||
let user_name = command.get_pwuid(uid);
|
||||
if let Some(user_name) = user_name {
|
||||
return Some((&*user_name).to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn update_status(&self, status: ContainerStatus) -> Self {
|
||||
let created = match (status, self.state.created) {
|
||||
(ContainerStatus::Created, None) => Some(Utc::now()),
|
||||
_ => self.state.created,
|
||||
};
|
||||
|
||||
let mut new_state = self.state.clone();
|
||||
new_state.created = created;
|
||||
new_state.status = status;
|
||||
|
||||
Self {
|
||||
state: new_state,
|
||||
root: self.root.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(container_root: PathBuf) -> Result<Self> {
|
||||
|
@ -112,4 +152,8 @@ impl Container {
|
|||
root: container_root,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bundle(&self) -> String {
|
||||
self.state.bundle.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
//! Information about status and state of the container
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::fs;
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const STATE_FILE_PATH: &str = "state.json";
|
||||
|
@ -40,6 +42,19 @@ impl ContainerStatus {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for ContainerStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let print = match *self {
|
||||
Self::Creating => "Creating",
|
||||
Self::Created => "Created",
|
||||
Self::Running => "Running",
|
||||
Self::Stopped => "Stopped",
|
||||
};
|
||||
|
||||
write!(f, "{}", print)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the state information of the container
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -57,6 +72,12 @@ pub struct State {
|
|||
pub bundle: String,
|
||||
// Annotations are key values associated with the container.
|
||||
pub annotations: HashMap<String, String>,
|
||||
// Creation time of the container
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub created: Option<DateTime<Utc>>,
|
||||
// User that created the container
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub creator: Option<u32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -73,6 +94,8 @@ impl State {
|
|||
pid,
|
||||
bundle: bundle.to_string(),
|
||||
annotations: HashMap::default(),
|
||||
created: None,
|
||||
creator: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -196,7 +196,10 @@ fn run_container<P: AsRef<Path>>(
|
|||
// actually run the command / program to be run in container
|
||||
utils::do_exec(&spec_args[0], spec_args, envs)?;
|
||||
// the command / program is done executing
|
||||
container.update_status(ContainerStatus::Stopped)?.save()?;
|
||||
container
|
||||
.refresh_state()?
|
||||
.update_status(ContainerStatus::Stopped)
|
||||
.save()?;
|
||||
|
||||
Ok(Process::Init(init))
|
||||
}
|
||||
|
|
62
src/main.rs
62
src/main.rs
|
@ -2,14 +2,21 @@
|
|||
//! Container Runtime written in Rust, inspired by [railcar](https://github.com/oracle/railcar)
|
||||
//! This crate provides a container runtime which can be used by a high-level container runtime to run containers.
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
use clap::Clap;
|
||||
use nix::sys::signal as nix_signal;
|
||||
|
||||
use youki::command::linux::LinuxCommand;
|
||||
|
||||
use youki::container::{Container, ContainerStatus};
|
||||
use youki::create;
|
||||
use youki::info::{print_cgroups, print_hardware, print_kernel, print_os, print_youki};
|
||||
|
@ -17,6 +24,7 @@ use youki::rootless::should_use_rootless;
|
|||
use youki::signal;
|
||||
use youki::start;
|
||||
|
||||
use tabwriter::TabWriter;
|
||||
use youki::cgroups;
|
||||
use youki::utils;
|
||||
|
||||
|
@ -76,6 +84,8 @@ enum SubCommand {
|
|||
State(StateArgs),
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
Info,
|
||||
#[clap(version = "0.0.1", author = "utam0k <k0ma@utam0k.jp>")]
|
||||
List,
|
||||
}
|
||||
|
||||
/// This is the entry point in the container runtime. The binary is run by a high-level container runtime,
|
||||
|
@ -116,7 +126,7 @@ fn main() -> Result<()> {
|
|||
let sig = signal::from_str(kill.signal.as_str())?;
|
||||
log::debug!("kill signal {} to {}", sig, container.pid().unwrap());
|
||||
nix_signal::kill(container.pid().unwrap(), sig)?;
|
||||
container.update_status(ContainerStatus::Stopped)?.save()?;
|
||||
container.update_status(ContainerStatus::Stopped).save()?;
|
||||
std::process::exit(0)
|
||||
} else {
|
||||
bail!(
|
||||
|
@ -189,5 +199,55 @@ fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
SubCommand::List => {
|
||||
let root_path = fs::canonicalize(root_path)?;
|
||||
let mut content = String::new();
|
||||
|
||||
for container_dir in fs::read_dir(root_path)? {
|
||||
let container_dir = container_dir?.path();
|
||||
let state_file = container_dir.join("state.json");
|
||||
if !state_file.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let container = Container::load(container_dir)?.refresh_status()?;
|
||||
let pid = if let Some(pid) = container.pid() {
|
||||
pid.to_string()
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
let user_name = if let Some(creator) = container.creator() {
|
||||
creator
|
||||
} else {
|
||||
OsString::new()
|
||||
};
|
||||
|
||||
let created = if let Some(utc) = container.created() {
|
||||
let local: DateTime<Local> = DateTime::from(utc);
|
||||
local.to_rfc3339_opts(chrono::SecondsFormat::Secs, false)
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
content.push_str(&format!(
|
||||
"{}\t{}\t{}\t{}\t{}\t{}\n",
|
||||
container.id(),
|
||||
pid,
|
||||
container.status(),
|
||||
container.bundle(),
|
||||
created,
|
||||
user_name.to_string_lossy()
|
||||
));
|
||||
}
|
||||
|
||||
let mut tab_writer = TabWriter::new(io::stdout());
|
||||
writeln!(&mut tab_writer, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tCREATOR")?;
|
||||
write!(&mut tab_writer, "{}", content)?;
|
||||
tab_writer.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ use nix::{
|
|||
unistd::{self, Gid, Uid},
|
||||
};
|
||||
|
||||
use crate::command::{linux::LinuxCommand, test::TestHelperCommand, Command};
|
||||
use oci_spec::{LinuxNamespace, LinuxNamespaceType};
|
||||
use crate::command::{command::create_command, Command};
|
||||
use oci_spec::LinuxNamespace;
|
||||
|
||||
pub struct Namespaces {
|
||||
spaces: Vec<LinuxNamespace>,
|
||||
|
@ -33,11 +33,7 @@ impl From<Vec<LinuxNamespace>> for Namespaces {
|
|||
cf
|
||||
},
|
||||
);
|
||||
let command: Box<dyn Command> = if cfg!(test) {
|
||||
Box::new(TestHelperCommand::default())
|
||||
} else {
|
||||
Box::new(LinuxCommand)
|
||||
};
|
||||
let command: Box<dyn Command> = create_command();
|
||||
|
||||
Namespaces {
|
||||
spaces: namespaces,
|
||||
|
@ -80,10 +76,13 @@ impl Namespaces {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use oci_spec::LinuxNamespaceType;
|
||||
|
||||
use super::*;
|
||||
use crate::command::test::TestHelperCommand;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn gen_sample_linux_namespaces() -> Vec<LinuxNamespace> {
|
||||
vec![
|
||||
LinuxNamespace {
|
||||
|
|
|
@ -74,7 +74,8 @@ pub fn fork_first<P: AsRef<Path>>(
|
|||
|
||||
// update status and pid of the container process
|
||||
container
|
||||
.update_status(ContainerStatus::Created)?
|
||||
.update_status(ContainerStatus::Created)
|
||||
.set_creator(nix::unistd::geteuid().as_raw())
|
||||
.set_pid(init_pid)
|
||||
.save()?;
|
||||
// if file to write the pid to is specified, write pid of the child
|
||||
|
|
|
@ -36,7 +36,7 @@ impl Start {
|
|||
let mut notify_socket = NotifySocket::new(&container.root)?;
|
||||
notify_socket.notify_container_start()?;
|
||||
|
||||
container.update_status(ContainerStatus::Running)?.save()?;
|
||||
container.update_status(ContainerStatus::Running).save()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue