use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum CopyDataSource {
File(PathBuf),
Data(Vec<u8>),
}
impl From<PathBuf> for CopyDataSource {
fn from(p: PathBuf) -> Self {
CopyDataSource::File(p)
}
}
impl From<Vec<u8>> for CopyDataSource {
fn from(b: Vec<u8>) -> Self {
CopyDataSource::Data(b)
}
}
#[derive(Debug, Clone)]
pub struct CopyTargetOptions {
pub(crate) path: String,
pub(crate) mode: u32,
pub(crate) uid: u32,
pub(crate) gid: u32,
}
impl CopyTargetOptions {
pub fn new(path: impl Into<String>) -> Self {
Self {
path: path.into(),
mode: 0o644,
uid: 0,
gid: 0,
}
}
pub fn with_mode(mut self, mode: u32) -> Self {
self.mode = mode;
self
}
pub fn with_uid(mut self, uid: u32) -> Self {
self.uid = uid;
self
}
pub fn with_gid(mut self, gid: u32) -> Self {
self.gid = gid;
self
}
pub fn path(&self) -> &str {
&self.path
}
pub fn mode(&self) -> Option<u32> {
Some(self.mode)
}
pub fn uid(&self) -> u32 {
self.uid
}
pub fn gid(&self) -> u32 {
self.gid
}
}
impl From<String> for CopyTargetOptions {
fn from(path: String) -> Self {
Self::new(path)
}
}
impl From<&str> for CopyTargetOptions {
fn from(path: &str) -> Self {
Self::new(path)
}
}
#[derive(Debug, Clone)]
pub struct CopyToContainer {
pub(crate) source: CopyDataSource,
pub(crate) target: CopyTargetOptions,
}
impl CopyToContainer {
pub fn new(source: impl Into<CopyDataSource>, target: impl Into<CopyTargetOptions>) -> Self {
Self {
source: source.into(),
target: target.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn copy_target_options_from_string_uses_defaults() {
let opts: CopyTargetOptions = "/data/hello.txt".to_string().into();
assert_eq!(opts.path, "/data/hello.txt");
assert_eq!(opts.mode, 0o644);
assert_eq!(opts.uid, 0);
assert_eq!(opts.gid, 0);
}
#[test]
fn copy_target_options_from_str_uses_defaults() {
let opts: CopyTargetOptions = "/data/hello.txt".into();
assert_eq!(opts.path, "/data/hello.txt");
assert_eq!(opts.mode, 0o644);
assert_eq!(opts.uid, 0);
assert_eq!(opts.gid, 0);
}
#[test]
fn copy_target_options_with_mode_sets_field_and_accessor() {
let opts = CopyTargetOptions::new("/data/secret.txt").with_mode(0o600);
assert_eq!(opts.mode, 0o600);
assert_eq!(opts.mode(), Some(0o600));
}
#[test]
fn copy_to_container_accepts_string_target() {
let from_string = CopyToContainer::new(
CopyDataSource::File("/host/file.txt".into()),
"/data/file.txt".to_string(),
);
assert_eq!(from_string.target.path, "/data/file.txt");
assert_eq!(from_string.target.mode, 0o644);
let from_str = CopyToContainer::new(
CopyDataSource::File("/host/file.txt".into()),
"/data/file.txt",
);
assert_eq!(from_str.target.path, "/data/file.txt");
assert_eq!(from_str.target.mode, 0o644);
}
}
#[derive(Debug)]
pub enum CopyToContainerError {
IoError(std::io::Error),
PathNameError(String),
SizeLimitExceeded {
limit: usize,
name: String,
},
}
impl std::fmt::Display for CopyToContainerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CopyToContainerError::IoError(e) => write!(f, "I/O error: {e}"),
CopyToContainerError::PathNameError(s) => {
write!(f, "copy path error: {s}")
}
CopyToContainerError::SizeLimitExceeded { limit, name } => {
write!(f, "copy size exceeds {limit} bytes: {name}")
}
}
}
}
impl std::error::Error for CopyToContainerError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CopyToContainerError::IoError(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for CopyToContainerError {
fn from(e: std::io::Error) -> Self {
Self::IoError(e)
}
}
pub trait CopyFileFromContainer: Sized + Send {
type Output: Send;
fn copy_from_reader<R: tokio::io::AsyncRead + Unpin + Send + 'static>(
self,
reader: R,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = std::result::Result<Self::Output, CopyFromContainerError>,
> + Send,
>,
>;
}
impl CopyFileFromContainer for PathBuf {
type Output = ();
fn copy_from_reader<R: tokio::io::AsyncRead + Unpin + Send + 'static>(
self,
mut reader: R,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = std::result::Result<Self::Output, CopyFromContainerError>,
> + Send,
>,
> {
Box::pin(async move {
let mut file = tokio::fs::File::create(&self).await?;
tokio::io::copy(&mut reader, &mut file).await?;
Ok(())
})
}
}
impl CopyFileFromContainer for Vec<u8> {
type Output = Vec<u8>;
fn copy_from_reader<R: tokio::io::AsyncRead + Unpin + Send + 'static>(
self,
mut reader: R,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = std::result::Result<Self::Output, CopyFromContainerError>,
> + Send,
>,
> {
Box::pin(async move {
let mut buf = self;
buf.clear();
tokio::io::copy(&mut reader, &mut buf).await?;
Ok(buf)
})
}
}
#[derive(Debug)]
pub enum CopyFromContainerError {
Io(std::io::Error),
IsDirectory,
EmptyArchive,
UnsupportedEntry(&'static str),
}
impl std::fmt::Display for CopyFromContainerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CopyFromContainerError::Io(e) => write!(f, "I/O error: {e}"),
CopyFromContainerError::IsDirectory => write!(f, "is a directory"),
CopyFromContainerError::EmptyArchive => write!(f, "empty archive"),
CopyFromContainerError::UnsupportedEntry(s) => write!(f, "unsupported entry type: {s}"),
}
}
}
impl std::error::Error for CopyFromContainerError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
CopyFromContainerError::Io(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for CopyFromContainerError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}