1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-09 17:16:16 +02:00

Add list command

This commit is contained in:
Furisto 2021-06-23 18:10:54 +02:00
parent a105e11427
commit 9d785aa521
13 changed files with 247 additions and 37 deletions

11
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"] }

View File

@ -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)
}
}

View File

@ -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 theres 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)
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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()
}
}

View File

@ -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,
}
}

View File

@ -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))
}

View File

@ -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(())
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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(())
}
}