mirror of
https://github.com/michael-yuji/xc.git
synced 2026-03-21 16:24:55 +01:00
refactor
This commit is contained in:
@@ -192,7 +192,9 @@ impl PatchActions {
|
||||
destination: mount_point.to_path_buf(),
|
||||
required: *required,
|
||||
};
|
||||
config.mounts.insert(key.to_string_lossy().to_string(), mountspec);
|
||||
config
|
||||
.mounts
|
||||
.insert(key.to_string_lossy().to_string(), mountspec);
|
||||
}
|
||||
PatchActions::ModAllow { allows } => {
|
||||
for allow in allows.iter() {
|
||||
|
||||
@@ -114,7 +114,9 @@ impl ConfigMod {
|
||||
}
|
||||
}
|
||||
Self::Volume(name, mount_spec) => {
|
||||
config.mounts.insert(name.to_string_lossy().to_string(), mount_spec.clone());
|
||||
config
|
||||
.mounts
|
||||
.insert(name.to_string_lossy().to_string(), mount_spec.clone());
|
||||
}
|
||||
Self::WorkDir(entry_point, dir) => {
|
||||
let work_dir = dir.to_string();
|
||||
|
||||
@@ -39,6 +39,7 @@ pub(crate) enum RdrAction {
|
||||
#[arg(short = 'H', action)]
|
||||
without_header: bool,
|
||||
},
|
||||
ShowRules,
|
||||
}
|
||||
|
||||
pub(crate) fn use_rdr_action(
|
||||
@@ -73,6 +74,14 @@ pub(crate) fn use_rdr_action(
|
||||
}
|
||||
}
|
||||
}
|
||||
RdrAction::ShowRules => {
|
||||
let response = do_list_rdr_rules(conn, ())?;
|
||||
if let Ok(response) = response {
|
||||
for rule in response.iter() {
|
||||
println!("{}", rule.to_pf_rule());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use oci_util::image_reference::ImageReference;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use xcd::ipc::*;
|
||||
use xcd::volume::VolumeDriverKind;
|
||||
use xcd::resources::volume::VolumeDriverKind;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
pub(crate) enum VolumeAction {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2023 Yan Ka, Chiu.
|
||||
// Copyright (c), , Yan Ka, Chiu.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -21,11 +21,20 @@
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
use freebsd::libc::*;
|
||||
use std::io::ErrorKind;
|
||||
use thiserror::Error;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! precondition_failure {
|
||||
macro_rules! errx {
|
||||
($errno:expr, $($t:tt)*) => {
|
||||
return Err(xc::container::error::PreconditionFailure::new($errno, anyhow::anyhow!($($t)*)).into())
|
||||
return Err(xc::container::error::Error::new($errno, anyhow::anyhow!($($t)*)).into())
|
||||
}
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! err {
|
||||
($errno:expr, $($t:tt)*) => {
|
||||
xc::container::error::Error::new($errno, anyhow::anyhow!($($t)*))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,27 +59,344 @@ pub enum ExecError {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub struct PreconditionFailure {
|
||||
pub struct Error {
|
||||
errno: i32,
|
||||
source: anyhow::Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PreconditionFailure {
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "{}", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl PreconditionFailure {
|
||||
pub fn new(errno: i32, source: anyhow::Error) -> PreconditionFailure {
|
||||
PreconditionFailure { errno, source }
|
||||
impl Error {
|
||||
pub fn new(errno: i32, source: anyhow::Error) -> Error {
|
||||
Error { errno, source }
|
||||
}
|
||||
|
||||
pub fn errno(&self) -> i32 {
|
||||
self.errno
|
||||
}
|
||||
|
||||
pub fn with_errno(self, errno: i32) -> Error {
|
||||
Self { errno, ..self }
|
||||
}
|
||||
|
||||
pub fn error_message(&self) -> String {
|
||||
format!("{:#}", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WithErrno {
|
||||
fn with_errno(self, errno: i32) -> Error;
|
||||
}
|
||||
|
||||
impl<E: std::error::Error + Send + Sync + 'static> WithErrno for E {
|
||||
fn with_errno(self, errno: i32) -> Error {
|
||||
Error {
|
||||
errno,
|
||||
source: anyhow::Error::from(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for Error {
|
||||
fn from(error: anyhow::Error) -> Error {
|
||||
Error {
|
||||
errno: 128,
|
||||
source: error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Error {
|
||||
let errno = match error.kind() {
|
||||
ErrorKind::NotFound => ENOENT,
|
||||
ErrorKind::OutOfMemory => ENOMEM,
|
||||
ErrorKind::Interrupted => EINTR,
|
||||
ErrorKind::Other => ETIMEDOUT,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::Deadlock => EDEADLK,
|
||||
ErrorKind::AddrInUse => EADDRINUSE,
|
||||
ErrorKind::BrokenPipe => EPIPE,
|
||||
ErrorKind::WouldBlock => EWOULDBLOCK,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::NetworkDown => ENETDOWN,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::InvalidData => EINVAL,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::StorageFull => ENOSPC,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::NotSeekable => ENXIO,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::Unsupported => ENOTSUP,
|
||||
ErrorKind::NotConnected => ENOTCONN,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::IsADirectory => EISDIR,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::InvalidInput => EINVAL,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::FileTooLarge => EFBIG,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::ResourceBusy => EBUSY,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::TooManyLinks => ELOOP,
|
||||
ErrorKind::AlreadyExists => EEXIST,
|
||||
ErrorKind::TimedOut => ETIMEDOUT,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::NotADirectory => ENOTDIR,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::FilesystemLoop => ELOOP,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::CrossesDevices => ENOTSUP,
|
||||
ErrorKind::ConnectionReset => ECONNRESET,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::HostUnreachable => EHOSTUNREACH,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::InvalidFilename => ENAMETOOLONG,
|
||||
ErrorKind::PermissionDenied => EPERM,
|
||||
ErrorKind::AddrNotAvailable => EADDRNOTAVAIL,
|
||||
ErrorKind::ConnectionRefused => ECONNREFUSED,
|
||||
ErrorKind::ConnectionAborted => ECONNABORTED,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::DirectoryNotEmpty => ENOTEMPTY,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::NetworkUnreachable => ENETUNREACH,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::ReadOnlyFilesystem => EROFS,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::ExecutableFileBusy => ETXTBSY,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::ArgumentListTooLong => E2BIG,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::StaleNetworkFileHandle => ESTALE,
|
||||
#[cfg(feature = "io_error_more")]
|
||||
ErrorKind::FilesystemQuotaExceeded => EDQUOT,
|
||||
_ => 128,
|
||||
};
|
||||
|
||||
Error {
|
||||
errno,
|
||||
source: error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod errno {
|
||||
|
||||
macro_rules! export {
|
||||
($($name:ident, $desc:expr),*) => {
|
||||
$(pub const $name: i32 = freebsd::libc::$name;)*
|
||||
|
||||
pub fn errno_desc(v: i32) -> &'static str {
|
||||
#[allow(unreachable_patterns)]
|
||||
match v {
|
||||
$($name => $desc,)*
|
||||
_ => "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export!(
|
||||
EPERM,
|
||||
"Operation not permitted",
|
||||
ENOENT,
|
||||
"No such file or directory",
|
||||
ESRCH,
|
||||
"No such process",
|
||||
EINTR,
|
||||
"Interrupted system call",
|
||||
EIO,
|
||||
"Input/output error",
|
||||
ENXIO,
|
||||
"Device not configured",
|
||||
E2BIG,
|
||||
"Argument list too long",
|
||||
ENOEXEC,
|
||||
"Exec format error",
|
||||
EBADF,
|
||||
"Bad file descriptor",
|
||||
ECHILD,
|
||||
"No child processes",
|
||||
EDEADLK,
|
||||
"Resource deadlock avoided",
|
||||
ENOMEM,
|
||||
"Cannot allocate memory",
|
||||
EACCES,
|
||||
"Permission denied",
|
||||
EFAULT,
|
||||
"Bad address",
|
||||
ENOTBLK,
|
||||
"Block device required",
|
||||
EBUSY,
|
||||
"Device busy",
|
||||
EEXIST,
|
||||
"File exists",
|
||||
EXDEV,
|
||||
"Cross-device link",
|
||||
ENODEV,
|
||||
"Operation not supported by device",
|
||||
ENOTDIR,
|
||||
"Not a directory",
|
||||
EISDIR,
|
||||
"Is a directory",
|
||||
EINVAL,
|
||||
"Invalid argument",
|
||||
ENFILE,
|
||||
"Too many open files in system",
|
||||
EMFILE,
|
||||
"Too many open files",
|
||||
ENOTTY,
|
||||
"Inappropriate ioctl for device",
|
||||
ETXTBSY,
|
||||
"Text file busy",
|
||||
EFBIG,
|
||||
"File too large",
|
||||
ENOSPC,
|
||||
"No space left on device",
|
||||
ESPIPE,
|
||||
"Illegal seek",
|
||||
EROFS,
|
||||
"Read-only filesystem",
|
||||
EMLINK,
|
||||
"Too many links",
|
||||
EPIPE,
|
||||
"Broken pipe",
|
||||
EDOM,
|
||||
"Numerical argument out of domain",
|
||||
ERANGE,
|
||||
"Result too large",
|
||||
EAGAIN,
|
||||
"Resource temporarily unavailable",
|
||||
EWOULDBLOCK,
|
||||
"Operation would block",
|
||||
EINPROGRESS,
|
||||
"Operation now in progress",
|
||||
EALREADY,
|
||||
"Operation already in progress",
|
||||
ENOTSOCK,
|
||||
"Socket operation on non-socket",
|
||||
EDESTADDRREQ,
|
||||
"Destination address required",
|
||||
EMSGSIZE,
|
||||
"Message too long",
|
||||
EPROTOTYPE,
|
||||
"Protocol wrong type for socket",
|
||||
ENOPROTOOPT,
|
||||
"Protocol not available",
|
||||
EPROTONOSUPPORT,
|
||||
"Protocol not supported",
|
||||
ESOCKTNOSUPPORT,
|
||||
"Socket type not supported",
|
||||
EOPNOTSUPP,
|
||||
"Operation not supported",
|
||||
ENOTSUP,
|
||||
"Operation not supported",
|
||||
EPFNOSUPPORT,
|
||||
"Protocol family not supported",
|
||||
EAFNOSUPPORT,
|
||||
"Address family not supported by protocol family",
|
||||
EADDRINUSE,
|
||||
"Address already in use",
|
||||
EADDRNOTAVAIL,
|
||||
"Can't assign requested address",
|
||||
ENETDOWN,
|
||||
"Network is down",
|
||||
ENETUNREACH,
|
||||
"Network is unreachable",
|
||||
ENETRESET,
|
||||
"Network dropped connection on reset",
|
||||
ECONNABORTED,
|
||||
"Software caused connection abort",
|
||||
ECONNRESET,
|
||||
"Connection reset by peer",
|
||||
ENOBUFS,
|
||||
"No buffer space available",
|
||||
EISCONN,
|
||||
"Socket is already connected",
|
||||
ENOTCONN,
|
||||
"Socket is not connected",
|
||||
ESHUTDOWN,
|
||||
"Can't send after socket shutdown",
|
||||
ETOOMANYREFS,
|
||||
"Too many references: can't splice",
|
||||
ETIMEDOUT,
|
||||
"Operation timed out",
|
||||
ECONNREFUSED,
|
||||
"Connection refused",
|
||||
ELOOP,
|
||||
"Too many levels of symbolic links",
|
||||
ENAMETOOLONG,
|
||||
"File name too long",
|
||||
EHOSTDOWN,
|
||||
"Host is down",
|
||||
EHOSTUNREACH,
|
||||
"No route to host",
|
||||
ENOTEMPTY,
|
||||
"Directory not empty",
|
||||
EPROCLIM,
|
||||
"Too many processes",
|
||||
EUSERS,
|
||||
"Too many users",
|
||||
EDQUOT,
|
||||
"Disc quota exceeded",
|
||||
ESTALE,
|
||||
"Stale NFS file handle",
|
||||
EREMOTE,
|
||||
"Too many levels of remote in path",
|
||||
EBADRPC,
|
||||
"RPC struct is bad",
|
||||
ERPCMISMATCH,
|
||||
"RPC version wrong",
|
||||
EPROGUNAVAIL,
|
||||
"RPC prog. not avail",
|
||||
EPROGMISMATCH,
|
||||
"Program version wrong",
|
||||
EPROCUNAVAIL,
|
||||
"Bad procedure for program",
|
||||
ENOLCK,
|
||||
"No locks available",
|
||||
ENOSYS,
|
||||
"Function not implemented",
|
||||
EFTYPE,
|
||||
"Inappropriate file type or format",
|
||||
EAUTH,
|
||||
"Authentication error",
|
||||
ENEEDAUTH,
|
||||
"Need authenticator",
|
||||
EIDRM,
|
||||
"Identifier removed",
|
||||
ENOMSG,
|
||||
"No message of desired type",
|
||||
EOVERFLOW,
|
||||
"Value too large to be stored in data type",
|
||||
ECANCELED,
|
||||
"Operation canceled",
|
||||
EILSEQ,
|
||||
"Illegal byte sequence",
|
||||
ENOATTR,
|
||||
"Attribute not found",
|
||||
EDOOFUS,
|
||||
"Programming error",
|
||||
EBADMSG,
|
||||
"Bad message",
|
||||
EMULTIHOP,
|
||||
"Multihop attempted",
|
||||
ENOLINK,
|
||||
"Link has been severed",
|
||||
EPROTO,
|
||||
"Protocol error",
|
||||
ENOTCAPABLE,
|
||||
"Capabilities insufficient",
|
||||
ECAPMODE,
|
||||
"Not permitted in capability mode",
|
||||
ENOTRECOVERABLE,
|
||||
"State not recoverable",
|
||||
EOWNERDEAD,
|
||||
"Previous owner died",
|
||||
EINTEGRITY,
|
||||
"Integrity check failed"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Exec {
|
||||
&self,
|
||||
envs: &HashMap<String, String>,
|
||||
args: &[String],
|
||||
) -> Result<Jexec, crate::container::error::PreconditionFailure> {
|
||||
) -> Result<Jexec, crate::container::error::Error> {
|
||||
let mut argv = Vec::new();
|
||||
let mut resolved_envs = if self.clear_env {
|
||||
HashMap::new()
|
||||
@@ -117,7 +117,7 @@ impl Exec {
|
||||
|
||||
for env in self.required_envs.iter() {
|
||||
if !resolved_envs.contains_key(env.as_str()) {
|
||||
return Err(crate::container::error::PreconditionFailure::new(
|
||||
return Err(crate::container::error::Error::new(
|
||||
freebsd::libc::ENOENT,
|
||||
anyhow::anyhow!("missing required environment variable {env}"),
|
||||
));
|
||||
|
||||
@@ -291,7 +291,8 @@ impl CompressionFormatExt for std::fs::File {
|
||||
|
||||
pub fn elf_abi_fallback_brand() -> String {
|
||||
Ctl::new("kern.elf64.fallback_brand")
|
||||
.expect("cannot sysctl").value_string()
|
||||
.expect("cannot sysctl")
|
||||
.value_string()
|
||||
.expect("cannot get sysctl output as string")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright (c) 2023 Yan Ka, Chiu.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions, and the following disclaimer,
|
||||
// without modification, immediately at the beginning of the file.
|
||||
// 2. The name of the author may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
use anyhow::Context;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::error;
|
||||
|
||||
use crate::config::inventory::Inventory;
|
||||
|
||||
pub(crate) struct InventoryManager {
|
||||
path: PathBuf,
|
||||
cached: Inventory,
|
||||
}
|
||||
|
||||
impl InventoryManager {
|
||||
pub(crate) fn load_from_path(
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<InventoryManager, anyhow::Error> {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let data = std::fs::read_to_string(&path).context("Cannot read config file")?;
|
||||
let cached: Inventory = match serde_json::from_str(&data).context("Cannot parse config") {
|
||||
Ok(inventory) => inventory,
|
||||
Err(_) => {
|
||||
let default = Inventory::default();
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&default).unwrap())
|
||||
.context("cannot write default inventory")?;
|
||||
default
|
||||
}
|
||||
};
|
||||
Ok(InventoryManager { path, cached })
|
||||
}
|
||||
|
||||
pub(crate) fn borrow(&self) -> &Inventory {
|
||||
&self.cached
|
||||
}
|
||||
|
||||
/// Modify the underlying config, and synchronize the changes to underlying config file
|
||||
pub(crate) fn modify<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Inventory),
|
||||
{
|
||||
let mut config = self.cached.clone();
|
||||
let old_config = config.clone();
|
||||
|
||||
f(&mut config);
|
||||
|
||||
if old_config != config {
|
||||
if let Ok(serialized) = serde_json::to_vec_pretty(&config) {
|
||||
if std::fs::write(&self.path, serialized).is_err() {
|
||||
error!(
|
||||
"failed to write new config to config file at {:?}",
|
||||
self.path
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error!("failed to serialize new config to bytes");
|
||||
}
|
||||
self.cached = config
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,14 @@
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::error;
|
||||
|
||||
use crate::network::Network;
|
||||
use crate::volume::Volume;
|
||||
use crate::resources::network::Network;
|
||||
use crate::resources::volume::Volume;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub(crate) struct Inventory {
|
||||
@@ -34,3 +37,61 @@ pub(crate) struct Inventory {
|
||||
#[serde(default)]
|
||||
pub volumes: HashMap<String, Volume>,
|
||||
}
|
||||
|
||||
/// Handle to access an on disk inventory storage.
|
||||
///
|
||||
/// This structure act as a middleman between the real, on disk inventory manifest and logics want
|
||||
/// to access and modify the inventories. On modification to the inventory via this structure, it
|
||||
/// automatically serialize the updated inventory and store on disk
|
||||
pub(crate) struct InventoryManager {
|
||||
path: PathBuf,
|
||||
cached: Inventory,
|
||||
}
|
||||
|
||||
impl InventoryManager {
|
||||
pub(crate) fn load_from_path(
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<InventoryManager, anyhow::Error> {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let data = std::fs::read_to_string(&path).context("Cannot read config file")?;
|
||||
let cached: Inventory = match serde_json::from_str(&data).context("Cannot parse config") {
|
||||
Ok(inventory) => inventory,
|
||||
Err(_) => {
|
||||
let default = Inventory::default();
|
||||
std::fs::write(&path, serde_json::to_vec_pretty(&default).unwrap())
|
||||
.context("cannot write default inventory")?;
|
||||
default
|
||||
}
|
||||
};
|
||||
Ok(InventoryManager { path, cached })
|
||||
}
|
||||
|
||||
pub(crate) fn borrow(&self) -> &Inventory {
|
||||
&self.cached
|
||||
}
|
||||
|
||||
/// Modify the underlying config, and synchronize the changes to underlying config file
|
||||
pub(crate) fn modify<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Inventory),
|
||||
{
|
||||
let mut config = self.cached.clone();
|
||||
let old_config = config.clone();
|
||||
|
||||
f(&mut config);
|
||||
|
||||
if old_config != config {
|
||||
if let Ok(serialized) = serde_json::to_vec_pretty(&config) {
|
||||
if std::fs::write(&self.path, serialized).is_err() {
|
||||
error!(
|
||||
"failed to write new config to config file at {:?}",
|
||||
self.path
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error!("failed to serialize new config to bytes");
|
||||
}
|
||||
self.cached = config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
pub mod config_manager;
|
||||
pub mod inventory;
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
|
||||
@@ -23,25 +23,23 @@
|
||||
// SUCH DAMAGE.
|
||||
|
||||
use crate::auth::Credential;
|
||||
use crate::config::config_manager::InventoryManager;
|
||||
use crate::config::inventory::Inventory;
|
||||
use crate::config::XcConfig;
|
||||
use crate::database::Database;
|
||||
use crate::dataset::JailedDatasetTracker;
|
||||
use crate::devfs_store::DevfsRulesetStore;
|
||||
use crate::image::pull::PullImageError;
|
||||
use crate::image::ImageManager;
|
||||
use crate::instantiate::{AppliedInstantiateRequest, InstantiateBlueprint};
|
||||
use crate::ipc::InstantiateRequest;
|
||||
use crate::network_manager::NetworkManager;
|
||||
use crate::port::PortForwardTable;
|
||||
use crate::registry::JsonRegistryProvider;
|
||||
use crate::resources::volume::{Volume, VolumeDriverKind};
|
||||
use crate::resources::Resources;
|
||||
use crate::site::Site;
|
||||
use crate::util::TwoWayMap;
|
||||
use crate::volume::{Volume, VolumeDriverKind, VolumeManager};
|
||||
|
||||
use anyhow::Context;
|
||||
use freebsd::fs::zfs::ZfsHandle;
|
||||
use freebsd::libc::EIO;
|
||||
use freebsd::net::pf;
|
||||
use oci_util::digest::OciDigest;
|
||||
use oci_util::image_reference::{ImageReference, ImageTag};
|
||||
@@ -52,8 +50,9 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use xc::container::error::PreconditionFailure;
|
||||
use xc::container::error::Error;
|
||||
use xc::container::ContainerManifest;
|
||||
use xc::errx;
|
||||
use xc::image_store::sqlite::SqliteImageStore;
|
||||
use xc::image_store::ImageRecord;
|
||||
use xc::models::jail_image::{JailConfig, JailImage};
|
||||
@@ -66,7 +65,6 @@ mod context_provider {
|
||||
}
|
||||
|
||||
pub struct ServerContext {
|
||||
pub(crate) network_manager: Arc<Mutex<NetworkManager>>,
|
||||
pub(crate) sites: HashMap<String, Arc<RwLock<Site>>>,
|
||||
pub(crate) terminated_sites: Vec<(String, Arc<RwLock<Site>>)>,
|
||||
|
||||
@@ -82,10 +80,7 @@ pub struct ServerContext {
|
||||
pub(crate) ng2jails: HashMap<String, Vec<String>>,
|
||||
pub(crate) jail2ngs: HashMap<String, Vec<String>>,
|
||||
|
||||
pub(crate) inventory: Arc<std::sync::Mutex<InventoryManager>>,
|
||||
|
||||
pub(crate) volume_manager: Arc<Mutex<VolumeManager>>,
|
||||
pub(crate) dataset_tracker: Arc<RwLock<JailedDatasetTracker>>,
|
||||
pub(crate) resources: Arc<RwLock<Resources>>,
|
||||
}
|
||||
|
||||
impl ServerContext {
|
||||
@@ -105,14 +100,9 @@ impl ServerContext {
|
||||
db.perform(xc::res::create_tables)
|
||||
.expect("cannot create tables");
|
||||
|
||||
let inventory = Arc::new(std::sync::Mutex::new(
|
||||
InventoryManager::load_from_path(&config.inventory).expect("cannot write inventory"),
|
||||
));
|
||||
|
||||
let network_manager = Arc::new(Mutex::new(NetworkManager::new(db, inventory.clone())));
|
||||
|
||||
let is = Arc::new(Mutex::new(image_store));
|
||||
let provider = JsonRegistryProvider::from_path(&config.registries).unwrap();
|
||||
let resources = Arc::new(RwLock::new(Resources::new(db, &config)));
|
||||
|
||||
let image_manager = ImageManager::new(
|
||||
is,
|
||||
@@ -121,14 +111,7 @@ impl ServerContext {
|
||||
Arc::new(Mutex::new(Box::new(provider))),
|
||||
);
|
||||
|
||||
let volume_manager = Arc::new(Mutex::new(VolumeManager::new(
|
||||
inventory.clone(),
|
||||
config.default_volume_dataset.clone(),
|
||||
None,
|
||||
)));
|
||||
|
||||
ServerContext {
|
||||
network_manager,
|
||||
alias_map: TwoWayMap::new(),
|
||||
devfs_store: DevfsRulesetStore::new(config.devfs_id_offset, config.force_devfs_ruleset),
|
||||
image_manager: Arc::new(RwLock::new(image_manager)),
|
||||
@@ -138,9 +121,7 @@ impl ServerContext {
|
||||
port_forward_table: PortForwardTable::new(),
|
||||
ng2jails: HashMap::new(),
|
||||
jail2ngs: HashMap::new(),
|
||||
inventory,
|
||||
volume_manager,
|
||||
dataset_tracker: Arc::new(RwLock::new(JailedDatasetTracker::default())),
|
||||
resources,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +129,13 @@ impl ServerContext {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn inventory(&self) -> Inventory {
|
||||
self.inventory.lock().unwrap().borrow().clone()
|
||||
pub(crate) async fn inventory(&self) -> crate::config::inventory::Inventory {
|
||||
self.resources
|
||||
.read()
|
||||
.await
|
||||
.inventory_manager
|
||||
.borrow()
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn create_channel(
|
||||
@@ -380,17 +366,21 @@ impl ServerContext {
|
||||
}
|
||||
|
||||
/// Reload the entry pf anchor "xc-rdr"
|
||||
pub fn reload_pf_rdr_anchor(&self) -> Result<(), std::io::Error> {
|
||||
pub fn reload_pf_rdr_anchor(&self) -> Result<(), Error> {
|
||||
let rules = self.port_forward_table.generate_rdr_rules();
|
||||
info!(rules, "reloading xc-rdr anchor");
|
||||
pf::is_pf_enabled().and_then(|enabled| {
|
||||
if let Err(error) = pf::is_pf_enabled().and_then(|enabled| {
|
||||
if enabled {
|
||||
pf::set_rules_unchecked(Some("xc-rdr".to_string()), &rules)
|
||||
} else {
|
||||
warn!("pf is not enabled");
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}) {
|
||||
errx!(EIO, "pfctl error: {error:?}")
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn terminate(&mut self, id: &str) -> Result<(), anyhow::Error> {
|
||||
@@ -409,9 +399,10 @@ impl ServerContext {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let mut nm = self.network_manager.lock().await;
|
||||
let mut resources = self.resources.write().await;
|
||||
// let mut nm = self.network_manager.lock().await;
|
||||
|
||||
let addresses = nm
|
||||
let addresses = resources
|
||||
.release_addresses(id)
|
||||
.context("sqlite failure on ip address release")?;
|
||||
|
||||
@@ -429,7 +420,9 @@ impl ServerContext {
|
||||
self.port_forward_table.remove_rules(id);
|
||||
self.reload_pf_rdr_anchor()?;
|
||||
self.alias_map.remove_all_referenced(id);
|
||||
self.dataset_tracker.write().await.release_container(id);
|
||||
|
||||
resources.dataset_tracker.release_container(id);
|
||||
// self.dataset_tracker.write().await.release_container(id);
|
||||
|
||||
if let Some(networks) = self.jail2ngs.get(id) {
|
||||
for network in networks.iter() {
|
||||
@@ -559,47 +552,25 @@ impl ServerContext {
|
||||
site.stage(image)?;
|
||||
let name = request.name.clone();
|
||||
|
||||
let applied = {
|
||||
let network_manager = this.network_manager.clone();
|
||||
let network_manager = network_manager.lock().await;
|
||||
let volume_manager = this.volume_manager.clone();
|
||||
let volume_manager = volume_manager.lock().await;
|
||||
AppliedInstantiateRequest::new(
|
||||
request,
|
||||
image,
|
||||
&cred,
|
||||
&network_manager,
|
||||
&volume_manager,
|
||||
)?
|
||||
};
|
||||
let resources_ref = this.resources.clone();
|
||||
let mut resources = resources_ref.write().await;
|
||||
|
||||
let applied =
|
||||
{ AppliedInstantiateRequest::new(request, image, &cred, &mut resources)? };
|
||||
|
||||
let blueprint = {
|
||||
let network_manager = this.network_manager.clone();
|
||||
let mut network_manager = network_manager.lock().await;
|
||||
let volume_manager = this.volume_manager.clone();
|
||||
let volume_manager = volume_manager.lock().await;
|
||||
let dataset_tracker = this.dataset_tracker.clone();
|
||||
let mut dataset_tracker = dataset_tracker.write().await;
|
||||
|
||||
InstantiateBlueprint::new(
|
||||
id,
|
||||
image,
|
||||
applied,
|
||||
&mut this.devfs_store,
|
||||
&cred,
|
||||
&mut network_manager,
|
||||
&volume_manager,
|
||||
&mut dataset_tracker,
|
||||
&mut resources,
|
||||
)?
|
||||
};
|
||||
|
||||
if pf::is_pf_enabled().unwrap_or_default() {
|
||||
if let Some(map) = this
|
||||
.network_manager
|
||||
.lock()
|
||||
.await
|
||||
.get_allocated_addresses(id)
|
||||
{
|
||||
if let Some(map) = resources.get_allocated_addresses(id) {
|
||||
for (network, addresses) in map.iter() {
|
||||
let table = format!("xc:network:{network}");
|
||||
let result = pf::table_add_addresses(None, &table, addresses);
|
||||
@@ -688,7 +659,7 @@ impl ServerContext {
|
||||
}
|
||||
|
||||
pub(crate) async fn list_volumes(&self) -> HashMap<String, Volume> {
|
||||
self.volume_manager.lock().await.list_volumes()
|
||||
self.resources.read().await.list_volumes()
|
||||
}
|
||||
|
||||
pub(crate) async fn create_volume(
|
||||
@@ -698,9 +669,9 @@ impl ServerContext {
|
||||
kind: VolumeDriverKind,
|
||||
source: Option<std::path::PathBuf>,
|
||||
zfs_props: HashMap<String, String>,
|
||||
) -> Result<(), PreconditionFailure> {
|
||||
self.volume_manager
|
||||
.lock()
|
||||
) -> Result<(), Error> {
|
||||
self.resources
|
||||
.write()
|
||||
.await
|
||||
.create_volume(kind, name, template, source, zfs_props)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use rusqlite::{Connection, OptionalExtension, Params, Row};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::network::AddressStore;
|
||||
use crate::resources::network::AddressStore;
|
||||
|
||||
pub struct Database {
|
||||
db: Mutex<Connection>,
|
||||
|
||||
@@ -23,11 +23,10 @@
|
||||
// SUCH DAMAGE.
|
||||
|
||||
use crate::auth::Credential;
|
||||
use crate::dataset::JailedDatasetTracker;
|
||||
use crate::devfs_store::DevfsRulesetStore;
|
||||
use crate::ipc::InstantiateRequest;
|
||||
use crate::network_manager::NetworkManager;
|
||||
use crate::volume::{Volume, VolumeManager};
|
||||
use crate::resources::volume::Volume;
|
||||
use crate::resources::Resources;
|
||||
|
||||
use anyhow::Context;
|
||||
use freebsd::event::EventFdNotify;
|
||||
@@ -39,12 +38,12 @@ use std::net::IpAddr;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
use varutil::string_interpolation::InterpolatedString;
|
||||
use xc::container::request::{CopyFileReq, Mount, NetworkAllocRequest};
|
||||
use xc::errx;
|
||||
use xc::format::devfs_rules::DevfsRule;
|
||||
use xc::models::exec::{Jexec, StdioMode};
|
||||
use xc::models::jail_image::JailImage;
|
||||
use xc::models::network::{DnsSetting, IpAssign};
|
||||
use xc::models::EnforceStatfs;
|
||||
use xc::precondition_failure;
|
||||
|
||||
pub struct AppliedInstantiateRequest {
|
||||
base: InstantiateRequest,
|
||||
@@ -63,8 +62,7 @@ impl AppliedInstantiateRequest {
|
||||
mut request: InstantiateRequest,
|
||||
oci_config: &JailImage,
|
||||
cred: &Credential,
|
||||
network_manager: &NetworkManager,
|
||||
volume_manager: &VolumeManager,
|
||||
resources: &mut Resources,
|
||||
) -> anyhow::Result<AppliedInstantiateRequest> {
|
||||
let existing_ifaces = freebsd::net::ifconfig::interfaces()?;
|
||||
let available_allows = xc::util::jail_allowables();
|
||||
@@ -82,7 +80,7 @@ impl AppliedInstantiateRequest {
|
||||
.as_ref()
|
||||
.map(|d| format!(" - {d}"))
|
||||
.unwrap_or_default();
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
ENOENT,
|
||||
"missing required environment variable: {key}{extra_info}"
|
||||
);
|
||||
@@ -142,7 +140,7 @@ impl AppliedInstantiateRequest {
|
||||
for assign in request.ips.iter() {
|
||||
let iface = &assign.interface;
|
||||
if !existing_ifaces.contains(iface) {
|
||||
precondition_failure!(ENOENT, "missing network interface {iface}");
|
||||
errx!(ENOENT, "missing network interface {iface}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +150,7 @@ impl AppliedInstantiateRequest {
|
||||
if available_allows.contains(allow) {
|
||||
allows.push(allow.to_string());
|
||||
} else {
|
||||
precondition_failure!(EIO, "allow.{allow} is not available on this system");
|
||||
errx!(EIO, "allow.{allow} is not available on this system");
|
||||
}
|
||||
}
|
||||
allows
|
||||
@@ -186,16 +184,13 @@ impl AppliedInstantiateRequest {
|
||||
|
||||
if !source_path.is_absolute() {
|
||||
let name = source_path.to_string_lossy().to_string();
|
||||
match volume_manager.query_volume(&name) {
|
||||
match resources.query_volume(&name) {
|
||||
None => {
|
||||
precondition_failure!(ENOENT, "no such volume {name}")
|
||||
errx!(ENOENT, "no such volume {name}")
|
||||
}
|
||||
Some(volume) => {
|
||||
if !volume.can_mount(cred.uid()) {
|
||||
precondition_failure!(
|
||||
EPERM,
|
||||
"this user is not allowed to mount the volume"
|
||||
)
|
||||
errx!(EPERM, "this user is not allowed to mount the volume")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,14 +201,14 @@ impl AppliedInstantiateRequest {
|
||||
|
||||
for (key, spec) in mount_specs.iter() {
|
||||
if spec.required {
|
||||
precondition_failure!(ENOENT, "Required volume {key:?} is not mounted");
|
||||
errx!(ENOENT, "Required volume {key:?} is not mounted");
|
||||
}
|
||||
}
|
||||
|
||||
for req in request.ipreq.iter() {
|
||||
let network = req.network();
|
||||
if !network_manager.has_network(&network) {
|
||||
precondition_failure!(ENOENT, "no such network: {network}");
|
||||
if !resources.has_network(&network) {
|
||||
errx!(ENOENT, "no such network: {network}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +231,7 @@ impl AppliedInstantiateRequest {
|
||||
let applied = rule.apply(&envs);
|
||||
match applied.parse::<xc::format::devfs_rules::DevfsRule>() {
|
||||
Err(error) => {
|
||||
precondition_failure!(EINVAL, "invaild devfs rule: [{applied}], {error}")
|
||||
errx!(EINVAL, "invaild devfs rule: [{applied}], {error}")
|
||||
}
|
||||
Ok(rule) => devfs_rules.push(rule),
|
||||
}
|
||||
@@ -302,9 +297,12 @@ impl InstantiateBlueprint {
|
||||
request: AppliedInstantiateRequest,
|
||||
devfs_store: &mut DevfsRulesetStore,
|
||||
cred: &Credential,
|
||||
/*
|
||||
network_manager: &mut NetworkManager,
|
||||
volume_manager: &VolumeManager,
|
||||
dataset_tracker: &mut JailedDatasetTracker,
|
||||
*/
|
||||
resources: &mut Resources,
|
||||
) -> anyhow::Result<InstantiateBlueprint> {
|
||||
let existing_ifaces = freebsd::net::ifconfig::interfaces()?;
|
||||
let config = oci_config.jail_config();
|
||||
@@ -312,9 +310,9 @@ impl InstantiateBlueprint {
|
||||
None => format!("xc-{id}"),
|
||||
Some(name) => {
|
||||
if name.parse::<isize>().is_ok() {
|
||||
precondition_failure!(EINVAL, "Jail name cannot be numeric")
|
||||
errx!(EINVAL, "Jail name cannot be numeric")
|
||||
} else if name.contains('.') {
|
||||
precondition_failure!(EINVAL, "Jail name cannot contain dot (.)")
|
||||
errx!(EINVAL, "Jail name cannot contain dot (.)")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
@@ -326,15 +324,12 @@ impl InstantiateBlueprint {
|
||||
|
||||
if config.linux {
|
||||
if !freebsd::exists_kld("linux64") {
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
EIO,
|
||||
"Linux image require linux64 kmod but it is missing from the system"
|
||||
);
|
||||
} else if xc::util::elf_abi_fallback_brand() != "3" {
|
||||
precondition_failure!(
|
||||
EIO,
|
||||
"kern.elf64.fallback_brand did not set to 3 (Linux)"
|
||||
);
|
||||
errx!(EIO, "kern.elf64.fallback_brand did not set to 3 (Linux)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,14 +343,10 @@ impl InstantiateBlueprint {
|
||||
let mut default_router = None;
|
||||
|
||||
for req in request.base.ipreq.iter() {
|
||||
match network_manager.allocate(vnet, req, id) {
|
||||
match resources.allocate(vnet, req, id) {
|
||||
Ok((alloc, router)) => {
|
||||
if !existing_ifaces.contains(&alloc.interface) {
|
||||
precondition_failure!(
|
||||
ENOENT,
|
||||
"missing network interface {}",
|
||||
&alloc.interface
|
||||
);
|
||||
errx!(ENOENT, "missing network interface {}", &alloc.interface);
|
||||
}
|
||||
if let Some(router) = router {
|
||||
if default_router.is_none() {
|
||||
@@ -365,28 +356,22 @@ impl InstantiateBlueprint {
|
||||
ip_alloc.push(alloc);
|
||||
}
|
||||
Err(error) => match error {
|
||||
crate::network_manager::Error::Sqlite(error) => {
|
||||
crate::resources::network::Error::Sqlite(error) => {
|
||||
Err(error).context("sqlite error on address allocation")?;
|
||||
}
|
||||
crate::network_manager::Error::AllocationFailure(network) => {
|
||||
precondition_failure!(
|
||||
ENOENT,
|
||||
"cannot allocate address from network {network}"
|
||||
)
|
||||
crate::resources::network::Error::AllocationFailure(network) => {
|
||||
errx!(ENOENT, "cannot allocate address from network {network}")
|
||||
}
|
||||
crate::network_manager::Error::AddressUsed(addr) => {
|
||||
precondition_failure!(ENOENT, "address {addr} already consumed")
|
||||
crate::resources::network::Error::AddressUsed(addr) => {
|
||||
errx!(ENOENT, "address {addr} already consumed")
|
||||
}
|
||||
crate::network_manager::Error::InvalidAddress(addr, network) => {
|
||||
precondition_failure!(EINVAL, "{addr} is not in the subnet of {network}")
|
||||
crate::resources::network::Error::InvalidAddress(addr, network) => {
|
||||
errx!(EINVAL, "{addr} is not in the subnet of {network}")
|
||||
}
|
||||
crate::network_manager::Error::NoSuchNetwork(network) => {
|
||||
precondition_failure!(
|
||||
ENOENT,
|
||||
"network {network} is missing from config file"
|
||||
)
|
||||
crate::resources::network::Error::NoSuchNetwork(network) => {
|
||||
errx!(ENOENT, "network {network} is missing from config file")
|
||||
}
|
||||
crate::network_manager::Error::Other(error) => {
|
||||
crate::resources::network::Error::Other(error) => {
|
||||
Err(error).context("error occured during address allocation")?;
|
||||
}
|
||||
},
|
||||
@@ -411,16 +396,13 @@ impl InstantiateBlueprint {
|
||||
|
||||
let volume = if !source_path.is_absolute() {
|
||||
let name = source_path.to_string_lossy().to_string();
|
||||
match volume_manager.query_volume(&name) {
|
||||
match resources.query_volume(&name) {
|
||||
None => {
|
||||
precondition_failure!(ENOENT, "no such volume {name}")
|
||||
errx!(ENOENT, "no such volume {name}")
|
||||
}
|
||||
Some(volume) => {
|
||||
if !volume.can_mount(cred.uid()) {
|
||||
precondition_failure!(
|
||||
EPERM,
|
||||
"this user is not allowed to mount the volume"
|
||||
)
|
||||
errx!(EPERM, "this user is not allowed to mount the volume")
|
||||
} else {
|
||||
volume
|
||||
}
|
||||
@@ -428,18 +410,18 @@ impl InstantiateBlueprint {
|
||||
}
|
||||
} else {
|
||||
match &req.evid {
|
||||
Maybe::None => precondition_failure!(ENOENT, "missing evidence"),
|
||||
Maybe::None => errx!(ENOENT, "missing evidence"),
|
||||
Maybe::Some(fd) => {
|
||||
println!("process to check evidence");
|
||||
let Ok(stat) = freebsd::nix::sys::stat::fstat(fd.as_raw_fd()) else {
|
||||
println!("cannot stat evidence");
|
||||
precondition_failure!(ENOENT, "cannot stat evidence")
|
||||
errx!(ENOENT, "cannot stat evidence")
|
||||
};
|
||||
let check_stat = freebsd::nix::sys::stat::stat(source_path).unwrap();
|
||||
println!("c: {}", stat.st_ino);
|
||||
println!("n: {}", check_stat.st_ino);
|
||||
if stat.st_ino != check_stat.st_ino {
|
||||
precondition_failure!(ENOENT, "evidence inode mismatch")
|
||||
errx!(ENOENT, "evidence inode mismatch")
|
||||
}
|
||||
|
||||
freebsd::nix::unistd::close(fd.as_raw_fd());
|
||||
@@ -455,18 +437,18 @@ impl InstantiateBlueprint {
|
||||
added_mount_specs.insert(&req.dest, mount_spec.clone().unwrap());
|
||||
}
|
||||
|
||||
let mount = volume_manager.mount(id, cred, req, mount_spec.as_ref(), &volume)?;
|
||||
let mount = resources.mount(id, cred, req, mount_spec.as_ref(), &volume)?;
|
||||
mount_req.push(mount);
|
||||
}
|
||||
|
||||
for dataset in request.base.jail_datasets.iter() {
|
||||
if dataset_tracker.is_jailed(dataset) {
|
||||
precondition_failure!(
|
||||
if resources.dataset_tracker.is_jailed(dataset) {
|
||||
errx!(
|
||||
EPERM,
|
||||
"another container is using this dataset: {dataset:?}"
|
||||
)
|
||||
} else {
|
||||
dataset_tracker.set_jailed(id, dataset)
|
||||
resources.dataset_tracker.set_jailed(id, dataset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ use crate::auth::Credential;
|
||||
use crate::context::ServerContext;
|
||||
use crate::image::pull::PullImageError;
|
||||
use crate::image::push::{PushImageError, PushImageStatusDesc};
|
||||
use crate::network::Network;
|
||||
use crate::volume::{Volume, VolumeDriverKind};
|
||||
use crate::resources::network::Network;
|
||||
use crate::resources::volume::{Volume, VolumeDriverKind};
|
||||
|
||||
use freebsd::event::EventFdNotify;
|
||||
use freebsd::libc::{EINVAL, EIO};
|
||||
@@ -333,8 +333,7 @@ async fn instantiate(
|
||||
.await;
|
||||
if let Err(error) = instantiate_result {
|
||||
tracing::error!("instantiate error: {error:#?}");
|
||||
if let Some(err) = error.downcast_ref::<xc::container::error::PreconditionFailure>()
|
||||
{
|
||||
if let Some(err) = error.downcast_ref::<xc::container::error::Error>() {
|
||||
ipc_err(err.errno(), &err.error_message())
|
||||
} else {
|
||||
enoent(error.to_string().as_str())
|
||||
@@ -525,7 +524,7 @@ pub struct ListNetworkRequest {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ListNetworkResponse {
|
||||
pub network_info: Vec<crate::network_manager::NetworkInfo>,
|
||||
pub network_info: Vec<crate::resources::network::NetworkInfo>,
|
||||
}
|
||||
|
||||
#[ipc_method(method = "list_networks")]
|
||||
@@ -535,9 +534,8 @@ async fn list_networks(
|
||||
request: ListNetworkRequest,
|
||||
) -> GenericResult<ListNetworkResponse> {
|
||||
let context = context.read().await;
|
||||
let network_manager = context.network_manager.clone();
|
||||
let network_manager = network_manager.lock().await;
|
||||
match network_manager.get_network_info() {
|
||||
let resources = context.resources.read().await;
|
||||
match resources.get_network_info() {
|
||||
Ok(network_info) => Ok(ListNetworkResponse { network_info }),
|
||||
Err(e) => ipc_err(EIO, e.to_string().as_str()),
|
||||
}
|
||||
@@ -546,7 +544,6 @@ async fn list_networks(
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateNetworkRequest {
|
||||
pub name: String,
|
||||
// pub ext_if: Option<String>,
|
||||
pub alias_iface: String,
|
||||
pub bridge_iface: String,
|
||||
pub subnet: ipcidr::IpCidr,
|
||||
@@ -562,13 +559,13 @@ async fn create_network(
|
||||
request: CreateNetworkRequest,
|
||||
) -> GenericResult<()> {
|
||||
let context = context.write().await;
|
||||
let config = context.inventory();
|
||||
let config = context.inventory().await;
|
||||
let existing_ifaces = freebsd::net::ifconfig::interfaces().unwrap();
|
||||
if config.networks.contains_key(&request.name) {
|
||||
ipc_err(EINVAL, "Network with such name already exists")
|
||||
} else {
|
||||
let nm = context.network_manager.clone();
|
||||
let nm = nm.lock().await;
|
||||
let nm = context.resources.clone();
|
||||
let mut nm = nm.write().await;
|
||||
|
||||
if !existing_ifaces.contains(&request.alias_iface) {
|
||||
return enoent(format!("interface {} not found", request.alias_iface).as_str());
|
||||
@@ -722,6 +719,17 @@ async fn list_site_rdr(
|
||||
}
|
||||
}
|
||||
|
||||
#[ipc_method(method = "list_rdr_rules")]
|
||||
async fn list_rdr_rules(
|
||||
context: Arc<RwLock<ServerContext>>,
|
||||
local_context: &mut ConnectionContext<Variables>,
|
||||
request: (),
|
||||
) -> GenericResult<Vec<PortRedirection>> {
|
||||
let context = context.read().await;
|
||||
let rdr_rules = context.port_forward_table.all_rules();
|
||||
Ok(rdr_rules)
|
||||
}
|
||||
|
||||
#[derive(FromPacket)]
|
||||
pub struct FdImport {
|
||||
pub fd: Fd,
|
||||
@@ -1239,6 +1247,7 @@ pub(crate) async fn register_to_service(
|
||||
service: &mut Service<tokio::sync::RwLock<ServerContext>, Variables>,
|
||||
) {
|
||||
service.register_event_delegate(on_channel_closed).await;
|
||||
service.register(list_rdr_rules).await;
|
||||
service.register(create_volume).await;
|
||||
service.register(list_volumes).await;
|
||||
service.register(commit_netgroup).await;
|
||||
|
||||
@@ -29,16 +29,13 @@ mod dataset;
|
||||
mod devfs_store;
|
||||
mod image;
|
||||
mod instantiate;
|
||||
mod network;
|
||||
mod network_manager;
|
||||
pub mod ipc;
|
||||
mod port;
|
||||
mod registry;
|
||||
pub mod resources;
|
||||
mod site;
|
||||
mod task;
|
||||
mod util;
|
||||
pub mod volume;
|
||||
|
||||
pub mod ipc;
|
||||
|
||||
use crate::config::XcConfig;
|
||||
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
// Copyright (c) 2023 Yan Ka, Chiu.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions, and the following disclaimer,
|
||||
// without modification, immediately at the beginning of the file.
|
||||
// 2. The name of the author may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
use anyhow::Context;
|
||||
use ipcidr::IpCidr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use thiserror::Error;
|
||||
use xc::container::request::NetworkAllocRequest;
|
||||
use xc::models::network::IpAssign;
|
||||
|
||||
use crate::config::config_manager::InventoryManager;
|
||||
use crate::database::Database;
|
||||
use crate::network::{AddressStore, Network};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NetworkInfo {
|
||||
pub name: String,
|
||||
pub subnet: IpCidr,
|
||||
pub start_addr: IpAddr,
|
||||
pub end_addr: IpAddr,
|
||||
pub last_addr: Option<IpAddr>,
|
||||
pub alias_iface: String,
|
||||
pub bridge_iface: String,
|
||||
}
|
||||
|
||||
impl NetworkInfo {
|
||||
fn new(db: &impl AddressStore, name: String, network: &Network) -> NetworkInfo {
|
||||
let netpool = network.parameterize(&name, db);
|
||||
NetworkInfo {
|
||||
name,
|
||||
subnet: netpool.network.subnet.clone(),
|
||||
start_addr: netpool.start_addr,
|
||||
end_addr: netpool.end_addr,
|
||||
last_addr: netpool.last_addr,
|
||||
alias_iface: network.alias_iface.clone(),
|
||||
bridge_iface: network.bridge_iface.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct NetworkManager {
|
||||
db: Arc<Database>,
|
||||
inventory_manager: Arc<Mutex<InventoryManager>>,
|
||||
table_cache: HashMap<String, HashMap<String, Vec<IpAddr>>>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum Error {
|
||||
#[error("sqlite error: {0}")]
|
||||
Sqlite(rusqlite::Error),
|
||||
#[error("cannot allocate address from network {0}")]
|
||||
AllocationFailure(String),
|
||||
#[error("address {0} is being used")]
|
||||
AddressUsed(std::net::IpAddr),
|
||||
#[error("address {0} and network {1} in different subnet")]
|
||||
InvalidAddress(IpAddr, String),
|
||||
#[error("no such network {0}")]
|
||||
NoSuchNetwork(String),
|
||||
#[error("{0}")]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for Error {
|
||||
fn from(error: rusqlite::Error) -> Error {
|
||||
Error::Sqlite(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for Error {
|
||||
fn from(error: anyhow::Error) -> Error {
|
||||
Error::Other(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkManager {
|
||||
pub(crate) fn new(
|
||||
db: Arc<Database>,
|
||||
inventory_manager: Arc<Mutex<InventoryManager>>,
|
||||
) -> NetworkManager {
|
||||
NetworkManager {
|
||||
db,
|
||||
inventory_manager,
|
||||
table_cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_network(&self, name: &str) -> bool {
|
||||
self.inventory_manager
|
||||
.lock()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.networks
|
||||
.contains_key(name)
|
||||
}
|
||||
|
||||
pub(crate) fn get_network_info(&self) -> Result<Vec<NetworkInfo>, anyhow::Error> {
|
||||
let config = self.inventory_manager.lock().unwrap().borrow().clone();
|
||||
let mut info = Vec::new();
|
||||
for (name, network) in config.networks.iter() {
|
||||
let db: &Database = &self.db;
|
||||
info.push(NetworkInfo::new(db, name.to_string(), network));
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub(crate) fn create_network(
|
||||
&self,
|
||||
name: &str,
|
||||
network: &Network,
|
||||
) -> Result<(), rusqlite::Error> {
|
||||
self.inventory_manager.lock().unwrap().modify(|inventory| {
|
||||
inventory.networks.insert(name.to_string(), network.clone());
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn release_addresses(
|
||||
&mut self,
|
||||
token: &str,
|
||||
) -> anyhow::Result<HashMap<String, Vec<IpAddr>>> {
|
||||
self.db
|
||||
.release_addresses(token)
|
||||
.context("fail to release addresses")?;
|
||||
let networks = self.table_cache.remove(token).unwrap_or_default();
|
||||
Ok(networks)
|
||||
}
|
||||
|
||||
pub(crate) fn get_allocated_addresses(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Option<&HashMap<String, Vec<IpAddr>>> {
|
||||
self.table_cache.get(token)
|
||||
}
|
||||
|
||||
/// Request allociation using `req`, and return (an_assignment, default_router)
|
||||
pub(crate) fn allocate(
|
||||
&mut self,
|
||||
vnet: bool,
|
||||
req: &NetworkAllocRequest,
|
||||
token: &str,
|
||||
) -> Result<(IpAssign, Option<IpAddr>), Error> {
|
||||
let network_name = req.network();
|
||||
let config = self.inventory_manager.lock().unwrap().borrow().clone();
|
||||
let network = config
|
||||
.networks
|
||||
.get(&network_name)
|
||||
.ok_or_else(|| Error::NoSuchNetwork(network_name.to_string()))?
|
||||
.clone();
|
||||
|
||||
let interface = if vnet {
|
||||
network.bridge_iface.to_string()
|
||||
} else {
|
||||
network.alias_iface.to_string()
|
||||
};
|
||||
|
||||
let db: &Database = &self.db;
|
||||
|
||||
let mut netpool = network.parameterize(&network_name, db);
|
||||
|
||||
match req {
|
||||
NetworkAllocRequest::Any { .. } => {
|
||||
let Some(address) = netpool.next_cidr(token)? else {
|
||||
return Err(Error::AllocationFailure(network_name));
|
||||
};
|
||||
|
||||
self.insert_to_cache(token, &network_name, &address.addr());
|
||||
|
||||
Ok((
|
||||
IpAssign {
|
||||
network: Some(network_name),
|
||||
interface,
|
||||
addresses: vec![address],
|
||||
},
|
||||
network.default_router,
|
||||
))
|
||||
}
|
||||
NetworkAllocRequest::Explicit { ip, .. } => {
|
||||
let address = IpCidr::from_addr(*ip, netpool.network.subnet.mask()).unwrap();
|
||||
if netpool.network.subnet.network_addr() == address.network_addr() {
|
||||
if netpool.is_address_consumed(ip)? {
|
||||
return Err(Error::AddressUsed(*ip));
|
||||
}
|
||||
netpool.register_address(ip, token)?;
|
||||
self.insert_to_cache(token, &network_name, ip);
|
||||
|
||||
Ok((
|
||||
IpAssign {
|
||||
network: Some(network_name),
|
||||
interface,
|
||||
addresses: vec![address],
|
||||
},
|
||||
network.default_router,
|
||||
))
|
||||
} else {
|
||||
Err(Error::InvalidAddress(*ip, network_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert_to_cache(&mut self, token: &str, network: &str, address: &IpAddr) {
|
||||
if let Some(network_address) = self.table_cache.get_mut(token) {
|
||||
if let Some(addresses) = network_address.get_mut(network) {
|
||||
addresses.push(*address);
|
||||
} else {
|
||||
network_address.insert(network.to_string(), vec![*address]);
|
||||
}
|
||||
} else {
|
||||
let mut hmap = HashMap::new();
|
||||
hmap.insert(network.to_string(), vec![*address]);
|
||||
self.table_cache.insert(token.to_string(), hmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
xcd/src/resources/mod.rs
Normal file
65
xcd/src/resources/mod.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2023 Yan Ka, Chiu.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions, and the following disclaimer,
|
||||
// without modification, immediately at the beginning of the file.
|
||||
// 2. The name of the author may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
use crate::config::inventory::InventoryManager;
|
||||
use crate::config::XcConfig;
|
||||
use crate::database::Database;
|
||||
use crate::dataset::JailedDatasetTracker;
|
||||
use crate::resources::volume::VolumeShareMode;
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod network;
|
||||
pub mod volume;
|
||||
|
||||
pub(crate) struct Resources {
|
||||
pub(crate) db: Arc<Database>,
|
||||
pub(crate) inventory_manager: InventoryManager,
|
||||
// network: { container: [address] }
|
||||
pub(crate) net_addr_alloc_cache: HashMap<String, HashMap<String, Vec<IpAddr>>>,
|
||||
|
||||
pub(crate) default_volume_dataset: Option<PathBuf>,
|
||||
pub(crate) default_volume_dir: Option<PathBuf>,
|
||||
|
||||
pub(crate) constrained_shares: HashMap<String, VolumeShareMode>,
|
||||
|
||||
pub(crate) dataset_tracker: JailedDatasetTracker,
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
pub(crate) fn new(database: Arc<Database>, config: &XcConfig) -> Resources {
|
||||
let inventory_manager =
|
||||
InventoryManager::load_from_path(&config.inventory).expect("cannot read inventory");
|
||||
Resources {
|
||||
db: database,
|
||||
inventory_manager,
|
||||
default_volume_dataset: config.default_volume_dataset.clone(),
|
||||
default_volume_dir: None,
|
||||
net_addr_alloc_cache: HashMap::new(),
|
||||
constrained_shares: HashMap::new(),
|
||||
dataset_tracker: JailedDatasetTracker::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,45 @@
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
use anyhow::Context;
|
||||
use ipcidr::IpCidr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use thiserror::Error;
|
||||
use xc::container::request::NetworkAllocRequest;
|
||||
use xc::models::network::IpAssign;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::resources::Resources;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum Error {
|
||||
#[error("sqlite error: {0}")]
|
||||
Sqlite(rusqlite::Error),
|
||||
#[error("cannot allocate address from network {0}")]
|
||||
AllocationFailure(String),
|
||||
#[error("address {0} is being used")]
|
||||
AddressUsed(std::net::IpAddr),
|
||||
#[error("address {0} and network {1} in different subnet")]
|
||||
InvalidAddress(IpAddr, String),
|
||||
#[error("no such network {0}")]
|
||||
NoSuchNetwork(String),
|
||||
#[error("{0}")]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for Error {
|
||||
fn from(error: rusqlite::Error) -> Error {
|
||||
Error::Sqlite(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for Error {
|
||||
fn from(error: anyhow::Error) -> Error {
|
||||
Error::Other(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Network {
|
||||
@@ -145,6 +181,158 @@ impl<'a, 'b, A: AddressStore> Netpool<'a, 'b, A> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct NetworkInfo {
|
||||
pub name: String,
|
||||
pub subnet: IpCidr,
|
||||
pub start_addr: IpAddr,
|
||||
pub end_addr: IpAddr,
|
||||
pub last_addr: Option<IpAddr>,
|
||||
pub alias_iface: String,
|
||||
pub bridge_iface: String,
|
||||
}
|
||||
|
||||
impl NetworkInfo {
|
||||
fn new(db: &impl AddressStore, name: String, network: &Network) -> NetworkInfo {
|
||||
let netpool = network.parameterize(&name, db);
|
||||
NetworkInfo {
|
||||
name,
|
||||
subnet: netpool.network.subnet.clone(),
|
||||
start_addr: netpool.start_addr,
|
||||
end_addr: netpool.end_addr,
|
||||
last_addr: netpool.last_addr,
|
||||
alias_iface: network.alias_iface.clone(),
|
||||
bridge_iface: network.bridge_iface.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
pub(crate) fn has_network(&self, name: &str) -> bool {
|
||||
self.inventory_manager.borrow().networks.contains_key(name)
|
||||
}
|
||||
|
||||
pub(crate) fn get_network_info(&self) -> Result<Vec<NetworkInfo>, anyhow::Error> {
|
||||
let config = self.inventory_manager.borrow().clone();
|
||||
let mut info = Vec::new();
|
||||
for (name, network) in config.networks.iter() {
|
||||
let db: &Database = &self.db;
|
||||
info.push(NetworkInfo::new(db, name.to_string(), network));
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub(crate) fn create_network(
|
||||
&mut self,
|
||||
name: &str,
|
||||
network: &Network,
|
||||
) -> Result<(), rusqlite::Error> {
|
||||
self.inventory_manager.modify(|inventory| {
|
||||
inventory.networks.insert(name.to_string(), network.clone());
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn release_addresses(
|
||||
&mut self,
|
||||
token: &str,
|
||||
) -> anyhow::Result<HashMap<String, Vec<IpAddr>>> {
|
||||
self.db
|
||||
.release_addresses(token)
|
||||
.context("fail to release addresses")?;
|
||||
let networks = self.net_addr_alloc_cache.remove(token).unwrap_or_default();
|
||||
Ok(networks)
|
||||
}
|
||||
|
||||
pub(crate) fn get_allocated_addresses(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Option<&HashMap<String, Vec<IpAddr>>> {
|
||||
self.net_addr_alloc_cache.get(token)
|
||||
}
|
||||
|
||||
/// Request allociation using `req`, and return (an_assignment, default_router)
|
||||
pub(crate) fn allocate(
|
||||
&mut self,
|
||||
vnet: bool,
|
||||
req: &NetworkAllocRequest,
|
||||
token: &str,
|
||||
) -> Result<(IpAssign, Option<IpAddr>), Error> {
|
||||
let network_name = req.network();
|
||||
let config = self.inventory_manager.borrow().clone();
|
||||
let network = config
|
||||
.networks
|
||||
.get(&network_name)
|
||||
.ok_or_else(|| Error::NoSuchNetwork(network_name.to_string()))?
|
||||
.clone();
|
||||
|
||||
let interface = if vnet {
|
||||
network.bridge_iface.to_string()
|
||||
} else {
|
||||
network.alias_iface.to_string()
|
||||
};
|
||||
|
||||
let db: &Database = &self.db;
|
||||
|
||||
let mut netpool = network.parameterize(&network_name, db);
|
||||
|
||||
match req {
|
||||
NetworkAllocRequest::Any { .. } => {
|
||||
let Some(address) = netpool.next_cidr(token)? else {
|
||||
return Err(Error::AllocationFailure(network_name));
|
||||
};
|
||||
|
||||
self.insert_to_cache(token, &network_name, &address.addr());
|
||||
|
||||
Ok((
|
||||
IpAssign {
|
||||
network: Some(network_name),
|
||||
interface,
|
||||
addresses: vec![address],
|
||||
},
|
||||
network.default_router,
|
||||
))
|
||||
}
|
||||
NetworkAllocRequest::Explicit { ip, .. } => {
|
||||
let address = IpCidr::from_addr(*ip, netpool.network.subnet.mask()).unwrap();
|
||||
if netpool.network.subnet.network_addr() == address.network_addr() {
|
||||
if netpool.is_address_consumed(ip)? {
|
||||
return Err(Error::AddressUsed(*ip));
|
||||
}
|
||||
netpool.register_address(ip, token)?;
|
||||
self.insert_to_cache(token, &network_name, ip);
|
||||
|
||||
Ok((
|
||||
IpAssign {
|
||||
network: Some(network_name),
|
||||
interface,
|
||||
addresses: vec![address],
|
||||
},
|
||||
network.default_router,
|
||||
))
|
||||
} else {
|
||||
Err(Error::InvalidAddress(*ip, network_name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert_to_cache(&mut self, token: &str, network: &str, address: &IpAddr) {
|
||||
if let Some(network_address) = self.net_addr_alloc_cache.get_mut(token) {
|
||||
if let Some(addresses) = network_address.get_mut(network) {
|
||||
addresses.push(*address);
|
||||
} else {
|
||||
network_address.insert(network.to_string(), vec![*address]);
|
||||
}
|
||||
} else {
|
||||
let mut hmap = HashMap::new();
|
||||
hmap.insert(network.to_string(), vec![*address]);
|
||||
self.net_addr_alloc_cache.insert(token.to_string(), hmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -27,13 +27,13 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use xc::models::MountSpec;
|
||||
use xc::{
|
||||
container::{error::PreconditionFailure, request::Mount},
|
||||
precondition_failure,
|
||||
container::{error::Error, request::Mount},
|
||||
errx,
|
||||
};
|
||||
|
||||
use crate::auth::Credential;
|
||||
use crate::ipc::MountReq;
|
||||
use crate::volume::VolumeDriverKind;
|
||||
use crate::{auth::Credential, volume::Volume};
|
||||
use crate::resources::volume::{Volume, VolumeDriverKind};
|
||||
|
||||
use super::VolumeDriver;
|
||||
|
||||
@@ -48,23 +48,23 @@ impl VolumeDriver for LocalDriver {
|
||||
_template: Option<MountSpec>,
|
||||
source: Option<std::path::PathBuf>,
|
||||
_props: HashMap<String, String>,
|
||||
) -> Result<Volume, PreconditionFailure> {
|
||||
) -> Result<Volume, Error> {
|
||||
let path = match source {
|
||||
None => {
|
||||
let Some(mut parent) = self.default_subdir.clone() else {
|
||||
precondition_failure!(ENOENT, "Default volume directory not found")
|
||||
errx!(ENOENT, "Default volume directory not found")
|
||||
};
|
||||
parent.push(name);
|
||||
if parent.exists() {
|
||||
precondition_failure!(ENOENT, "Target directory already exists")
|
||||
errx!(ENOENT, "Target directory already exists")
|
||||
}
|
||||
parent
|
||||
}
|
||||
Some(path) => {
|
||||
if !path.exists() {
|
||||
precondition_failure!(ENOENT, "No such directory")
|
||||
errx!(ENOENT, "No such directory")
|
||||
} else if !path.is_dir() {
|
||||
precondition_failure!(ENOTDIR, "Source path is not a directory")
|
||||
errx!(ENOTDIR, "Source path is not a directory")
|
||||
}
|
||||
path
|
||||
}
|
||||
@@ -86,23 +86,23 @@ impl VolumeDriver for LocalDriver {
|
||||
mount_req: &MountReq,
|
||||
mount_spec: Option<&MountSpec>,
|
||||
volume: &Volume,
|
||||
) -> Result<Mount, PreconditionFailure> {
|
||||
) -> Result<Mount, Error> {
|
||||
let source_path = &volume.device;
|
||||
if !&source_path.exists() {
|
||||
precondition_failure!(ENOENT, "source mount point does not exist: {source_path:?}");
|
||||
errx!(ENOENT, "source mount point does not exist: {source_path:?}");
|
||||
}
|
||||
if !&source_path.is_dir() && !source_path.is_file() {
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
ENOTDIR,
|
||||
"mount point source is not a file nor directory: {source_path:?}"
|
||||
)
|
||||
}
|
||||
let Ok(meta) = std::fs::metadata(&volume.device) else {
|
||||
precondition_failure!(ENOENT, "invalid nullfs mount source")
|
||||
errx!(ENOENT, "invalid nullfs mount source")
|
||||
};
|
||||
|
||||
if !cred.can_mount(&meta, false) {
|
||||
precondition_failure!(EPERM, "permission denied: {source_path:?}")
|
||||
errx!(EPERM, "permission denied: {source_path:?}")
|
||||
}
|
||||
|
||||
let mut mount_options = HashSet::new();
|
||||
@@ -28,7 +28,7 @@ use super::Volume;
|
||||
use crate::auth::Credential;
|
||||
use crate::ipc::MountReq;
|
||||
use std::collections::HashMap;
|
||||
use xc::container::{error::PreconditionFailure, request::Mount};
|
||||
use xc::container::{error::Error, request::Mount};
|
||||
use xc::models::MountSpec;
|
||||
|
||||
pub trait VolumeDriver {
|
||||
@@ -38,7 +38,7 @@ pub trait VolumeDriver {
|
||||
mount_req: &MountReq,
|
||||
mount_spec: Option<&MountSpec>,
|
||||
volume: &Volume,
|
||||
) -> Result<Mount, PreconditionFailure>;
|
||||
) -> Result<Mount, Error>;
|
||||
|
||||
fn create(
|
||||
&self,
|
||||
@@ -46,5 +46,5 @@ pub trait VolumeDriver {
|
||||
template: Option<MountSpec>,
|
||||
source: Option<std::path::PathBuf>,
|
||||
props: HashMap<String, String>,
|
||||
) -> Result<Volume, PreconditionFailure>;
|
||||
) -> Result<Volume, Error>;
|
||||
}
|
||||
@@ -22,17 +22,17 @@
|
||||
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
// SUCH DAMAGE.
|
||||
|
||||
use crate::auth::Credential;
|
||||
use crate::ipc::MountReq;
|
||||
use crate::volume::VolumeDriverKind;
|
||||
use crate::{auth::Credential, volume::Volume};
|
||||
use crate::resources::volume::{Volume, VolumeDriverKind};
|
||||
use freebsd::fs::zfs::{ZfsCreate, ZfsHandle};
|
||||
use freebsd::libc::{EEXIST, EIO, ENOENT, EPERM};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use xc::models::MountSpec;
|
||||
use xc::{
|
||||
container::{error::PreconditionFailure, request::Mount},
|
||||
precondition_failure,
|
||||
container::{error::Error, request::Mount},
|
||||
errx,
|
||||
};
|
||||
|
||||
use super::VolumeDriver;
|
||||
@@ -50,7 +50,7 @@ impl VolumeDriver for ZfsDriver {
|
||||
template: Option<MountSpec>,
|
||||
source: Option<std::path::PathBuf>,
|
||||
props: HashMap<String, String>,
|
||||
) -> Result<Volume, PreconditionFailure> {
|
||||
) -> Result<Volume, Error> {
|
||||
let mut zfs_props = props;
|
||||
|
||||
if let Some(template) = template {
|
||||
@@ -66,26 +66,26 @@ impl VolumeDriver for ZfsDriver {
|
||||
let dataset = match source {
|
||||
None => {
|
||||
let Some(mut dataset) = self.default_dataset.clone() else {
|
||||
precondition_failure!(ENOENT, "Default volume dataset not set")
|
||||
errx!(ENOENT, "Default volume dataset not set")
|
||||
};
|
||||
dataset.push(name);
|
||||
|
||||
if self.handle.exists(&dataset) {
|
||||
precondition_failure!(EEXIST, "Dataset already exist")
|
||||
errx!(EEXIST, "Dataset already exist")
|
||||
}
|
||||
|
||||
let mut zfs_create = ZfsCreate::new(&dataset, true, false);
|
||||
zfs_create.set_props(zfs_props.clone());
|
||||
|
||||
if let Err(error) = self.handle.create(zfs_create) {
|
||||
precondition_failure!(EIO, "Cannot create zfs dataset: {error:?}")
|
||||
errx!(EIO, "Cannot create zfs dataset: {error:?}")
|
||||
}
|
||||
|
||||
dataset
|
||||
}
|
||||
Some(dataset) => {
|
||||
if !self.handle.exists(&dataset) {
|
||||
precondition_failure!(ENOENT, "Requested dataset does not exist")
|
||||
errx!(ENOENT, "Requested dataset does not exist")
|
||||
}
|
||||
dataset
|
||||
}
|
||||
@@ -108,22 +108,22 @@ impl VolumeDriver for ZfsDriver {
|
||||
mount_req: &MountReq,
|
||||
mount_spec: Option<&MountSpec>,
|
||||
volume: &Volume,
|
||||
) -> Result<Mount, PreconditionFailure> {
|
||||
) -> Result<Mount, Error> {
|
||||
if !self.handle.exists(&volume.device) {
|
||||
precondition_failure!(ENOENT, "No such dataset: {:?}", volume.device);
|
||||
errx!(ENOENT, "No such dataset: {:?}", volume.device);
|
||||
}
|
||||
let Ok(Some(mount_point)) = self.handle.mount_point(&volume.device) else {
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
ENOENT,
|
||||
"Dataset {:?} does not have a mount point",
|
||||
volume.device
|
||||
);
|
||||
};
|
||||
let Ok(meta) = std::fs::metadata(&mount_point) else {
|
||||
precondition_failure!(EIO, "cannot get metadata of {mount_point:?}");
|
||||
errx!(EIO, "cannot get metadata of {mount_point:?}");
|
||||
};
|
||||
if !cred.can_mount(&meta, false) {
|
||||
precondition_failure!(EPERM, "permission denied: {mount_point:?}");
|
||||
errx!(EPERM, "permission denied: {mount_point:?}");
|
||||
}
|
||||
|
||||
let mut mount_options = HashSet::new();
|
||||
@@ -24,22 +24,20 @@
|
||||
pub mod drivers;
|
||||
|
||||
use crate::auth::Credential;
|
||||
use crate::config::config_manager::InventoryManager;
|
||||
use crate::ipc::MountReq;
|
||||
use crate::volume::drivers::local::LocalDriver;
|
||||
use crate::volume::drivers::zfs::ZfsDriver;
|
||||
use crate::volume::drivers::VolumeDriver;
|
||||
use crate::resources::volume::drivers::local::LocalDriver;
|
||||
use crate::resources::volume::drivers::zfs::ZfsDriver;
|
||||
use crate::resources::volume::drivers::VolumeDriver;
|
||||
|
||||
use freebsd::libc::EPERM;
|
||||
use serde::de::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use xc::container::error::PreconditionFailure;
|
||||
use xc::container::error::Error;
|
||||
use xc::container::request::Mount;
|
||||
use xc::errx;
|
||||
use xc::models::MountSpec;
|
||||
use xc::precondition_failure;
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Debug, Clone)]
|
||||
pub enum VolumeDriverKind {
|
||||
@@ -188,37 +186,17 @@ pub enum VolumeShareMode {
|
||||
SingleWriter,
|
||||
}
|
||||
|
||||
pub(crate) struct VolumeManager {
|
||||
inventory: Arc<Mutex<InventoryManager>>,
|
||||
default_volume_dataset: Option<PathBuf>,
|
||||
default_volume_dir: Option<PathBuf>,
|
||||
constrained_shares: HashMap<String, VolumeShareMode>,
|
||||
}
|
||||
|
||||
impl VolumeManager {
|
||||
pub(crate) fn new(
|
||||
inventory: Arc<Mutex<InventoryManager>>,
|
||||
default_volume_dataset: Option<PathBuf>,
|
||||
default_volume_dir: Option<PathBuf>,
|
||||
) -> VolumeManager {
|
||||
VolumeManager {
|
||||
inventory,
|
||||
default_volume_dataset,
|
||||
default_volume_dir,
|
||||
constrained_shares: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::resources::Resources {
|
||||
// insert or override a volume
|
||||
pub(crate) fn add_volume(&mut self, name: &str, volume: &Volume) {
|
||||
self.inventory.lock().unwrap().modify(|inventory| {
|
||||
self.inventory_manager.modify(|inventory| {
|
||||
inventory.volumes.insert(name.to_string(), volume.clone());
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn list_volumes(&self) -> HashMap<String, Volume> {
|
||||
let mut hm = HashMap::new();
|
||||
for (name, volume) in self.inventory.lock().unwrap().borrow().volumes.iter() {
|
||||
for (name, volume) in self.inventory_manager.borrow().volumes.iter() {
|
||||
let mut vol = volume.clone();
|
||||
vol.name = Some(name.to_string());
|
||||
hm.insert(name.to_string(), vol);
|
||||
@@ -227,9 +205,7 @@ impl VolumeManager {
|
||||
}
|
||||
|
||||
pub(crate) fn query_volume(&self, name: &str) -> Option<Volume> {
|
||||
self.inventory
|
||||
.lock()
|
||||
.unwrap()
|
||||
self.inventory_manager
|
||||
.borrow()
|
||||
.volumes
|
||||
.get(name)
|
||||
@@ -247,7 +223,7 @@ impl VolumeManager {
|
||||
template: Option<MountSpec>,
|
||||
source: Option<PathBuf>,
|
||||
props: HashMap<String, String>,
|
||||
) -> Result<(), PreconditionFailure> {
|
||||
) -> Result<(), Error> {
|
||||
let volume = match kind {
|
||||
VolumeDriverKind::Directory => {
|
||||
let local_driver = LocalDriver {
|
||||
@@ -276,16 +252,16 @@ impl VolumeManager {
|
||||
mount_req: &MountReq,
|
||||
mount_spec: Option<&MountSpec>,
|
||||
volume: &Volume,
|
||||
) -> Result<Mount, PreconditionFailure> {
|
||||
) -> Result<Mount, Error> {
|
||||
for (name, share) in self.constrained_shares.iter() {
|
||||
if volume.name.as_ref().unwrap() == name {
|
||||
if share == &VolumeShareMode::Exclusive {
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
EPERM,
|
||||
"The volume has been mounted exclusively by other container"
|
||||
)
|
||||
} else if share == &VolumeShareMode::SingleWriter && !mount_req.read_only {
|
||||
precondition_failure!(
|
||||
errx!(
|
||||
EPERM,
|
||||
"The volume has been mounted for exclusively write by other container"
|
||||
)
|
||||
Reference in New Issue
Block a user