use serde::{Deserialize, Serialize};
use std::os::unix::fs::MetadataExt;
use std::os::unix::prelude::PermissionsExt;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Metadata {
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub atime: i64,
pub mtime: i64,
pub atime_nsec: i64,
pub mtime_nsec: i64,
}
impl common::preserve::Metadata for Metadata {
fn uid(&self) -> u32 {
self.uid
}
fn gid(&self) -> u32 {
self.gid
}
fn atime(&self) -> i64 {
self.atime
}
fn atime_nsec(&self) -> i64 {
self.atime_nsec
}
fn mtime(&self) -> i64 {
self.mtime
}
fn mtime_nsec(&self) -> i64 {
self.mtime_nsec
}
fn permissions(&self) -> std::fs::Permissions {
std::fs::Permissions::from_mode(self.mode)
}
}
impl common::preserve::Metadata for &Metadata {
fn uid(&self) -> u32 {
(*self).uid()
}
fn gid(&self) -> u32 {
(*self).gid()
}
fn atime(&self) -> i64 {
(*self).atime()
}
fn atime_nsec(&self) -> i64 {
(*self).atime_nsec()
}
fn mtime(&self) -> i64 {
(*self).mtime()
}
fn mtime_nsec(&self) -> i64 {
(*self).mtime_nsec()
}
fn permissions(&self) -> std::fs::Permissions {
(*self).permissions()
}
}
impl From<&std::fs::Metadata> for Metadata {
fn from(metadata: &std::fs::Metadata) -> Self {
Metadata {
mode: metadata.mode(),
uid: metadata.uid(),
gid: metadata.gid(),
atime: metadata.atime(),
mtime: metadata.mtime(),
atime_nsec: metadata.atime_nsec(),
mtime_nsec: metadata.mtime_nsec(),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct File {
pub src: std::path::PathBuf,
pub dst: std::path::PathBuf,
pub size: u64,
pub metadata: Metadata,
pub is_root: bool,
}
#[derive(Debug)]
pub struct FileMetadata<'a> {
pub metadata: &'a Metadata,
pub size: u64,
}
impl<'a> common::preserve::Metadata for FileMetadata<'a> {
fn uid(&self) -> u32 {
self.metadata.uid()
}
fn gid(&self) -> u32 {
self.metadata.gid()
}
fn atime(&self) -> i64 {
self.metadata.atime()
}
fn atime_nsec(&self) -> i64 {
self.metadata.atime_nsec()
}
fn mtime(&self) -> i64 {
self.metadata.mtime()
}
fn mtime_nsec(&self) -> i64 {
self.metadata.mtime_nsec()
}
fn permissions(&self) -> std::fs::Permissions {
self.metadata.permissions()
}
fn size(&self) -> u64 {
self.size
}
}
#[derive(Debug, Deserialize, Serialize)]
pub enum SourceMessage {
Directory {
src: std::path::PathBuf,
dst: std::path::PathBuf,
metadata: Metadata,
is_root: bool,
entry_count: usize,
file_count: usize,
keep_if_empty: bool,
},
Symlink {
src: std::path::PathBuf,
dst: std::path::PathBuf,
target: std::path::PathBuf,
metadata: Metadata,
is_root: bool,
},
DirStructureComplete { has_root_item: bool },
FileSkipped {
src: std::path::PathBuf,
dst: std::path::PathBuf,
},
SymlinkSkipped { src_dst: SrcDst, is_root: bool },
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SrcDst {
pub src: std::path::PathBuf,
pub dst: std::path::PathBuf,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum DestinationMessage {
DirectoryCreated {
src: std::path::PathBuf,
dst: std::path::PathBuf,
file_count: usize,
},
DestinationDone,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RcpdConfig {
pub verbose: u8,
pub fail_early: bool,
pub max_workers: usize,
pub max_blocking_threads: usize,
pub max_open_files: Option<usize>,
pub ops_throttle: usize,
pub iops_throttle: usize,
pub chunk_size: usize,
pub dereference: bool,
pub overwrite: bool,
pub overwrite_compare: String,
pub overwrite_filter: Option<String>,
pub ignore_existing: bool,
pub debug_log_prefix: Option<String>,
pub port_ranges: Option<String>,
pub progress: bool,
pub progress_delay: Option<String>,
pub remote_copy_conn_timeout_sec: u64,
pub network_profile: crate::NetworkProfile,
pub buffer_size: Option<usize>,
pub max_connections: usize,
pub pending_writes_multiplier: usize,
pub chrome_trace_prefix: Option<String>,
pub flamegraph_prefix: Option<String>,
pub profile_level: Option<String>,
pub tokio_console: bool,
pub tokio_console_port: Option<u16>,
pub encryption: bool,
pub master_cert_fingerprint: Option<CertFingerprint>,
}
impl RcpdConfig {
pub fn to_args(&self) -> Vec<String> {
let mut args = vec![
format!("--max-workers={}", self.max_workers),
format!("--max-blocking-threads={}", self.max_blocking_threads),
format!("--ops-throttle={}", self.ops_throttle),
format!("--iops-throttle={}", self.iops_throttle),
format!("--chunk-size={}", self.chunk_size),
format!("--overwrite-compare={}", self.overwrite_compare),
];
if self.verbose > 0 {
args.push(format!("-{}", "v".repeat(self.verbose as usize)));
}
if self.fail_early {
args.push("--fail-early".to_string());
}
if let Some(v) = self.max_open_files {
args.push(format!("--max-open-files={v}"));
}
if self.dereference {
args.push("--dereference".to_string());
}
if self.overwrite {
args.push("--overwrite".to_string());
if let Some(ref filter) = self.overwrite_filter {
args.push(format!("--overwrite-filter={filter}"));
}
}
if self.ignore_existing {
args.push("--ignore-existing".to_string());
}
if let Some(ref prefix) = self.debug_log_prefix {
args.push(format!("--debug-log-prefix={prefix}"));
}
if let Some(ref ranges) = self.port_ranges {
args.push(format!("--port-ranges={ranges}"));
}
if self.progress {
args.push("--progress".to_string());
}
if let Some(ref delay) = self.progress_delay {
args.push(format!("--progress-delay={delay}"));
}
args.push(format!(
"--remote-copy-conn-timeout-sec={}",
self.remote_copy_conn_timeout_sec
));
args.push(format!("--network-profile={}", self.network_profile));
if let Some(v) = self.buffer_size {
args.push(format!("--buffer-size={v}"));
}
args.push(format!("--max-connections={}", self.max_connections));
args.push(format!(
"--pending-writes-multiplier={}",
self.pending_writes_multiplier
));
let profiling_enabled =
self.chrome_trace_prefix.is_some() || self.flamegraph_prefix.is_some();
if let Some(ref prefix) = self.chrome_trace_prefix {
args.push(format!("--chrome-trace={prefix}"));
}
if let Some(ref prefix) = self.flamegraph_prefix {
args.push(format!("--flamegraph={prefix}"));
}
if profiling_enabled {
if let Some(ref level) = self.profile_level {
args.push(format!("--profile-level={level}"));
}
}
if self.tokio_console {
args.push("--tokio-console".to_string());
}
if let Some(port) = self.tokio_console_port {
args.push(format!("--tokio-console-port={port}"));
}
if !self.encryption {
args.push("--no-encryption".to_string());
}
if let Some(fp) = self.master_cert_fingerprint {
args.push(format!(
"--master-cert-fp={}",
crate::tls::fingerprint_to_hex(&fp)
));
}
args
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub enum RcpdRole {
Source,
Destination,
}
impl std::fmt::Display for RcpdRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RcpdRole::Source => write!(f, "source"),
RcpdRole::Destination => write!(f, "destination"),
}
}
}
impl std::str::FromStr for RcpdRole {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"source" => Ok(RcpdRole::Source),
"destination" | "dest" => Ok(RcpdRole::Destination),
_ => Err(anyhow::anyhow!("invalid role: {}", s)),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TracingHello {
pub role: RcpdRole,
pub is_tracing: bool,
}
pub type CertFingerprint = [u8; 32];
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum MasterHello {
Source {
src: std::path::PathBuf,
dst: std::path::PathBuf,
dest_cert_fingerprint: Option<CertFingerprint>,
filter: Option<common::filter::FilterSettings>,
dry_run: Option<common::config::DryRunMode>,
},
Destination {
source_control_addr: std::net::SocketAddr,
source_data_addr: std::net::SocketAddr,
server_name: String,
preserve: common::preserve::Settings,
source_cert_fingerprint: Option<CertFingerprint>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SourceMasterHello {
pub control_addr: std::net::SocketAddr,
pub data_addr: std::net::SocketAddr,
pub server_name: String,
}
pub use common::RuntimeStats;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum RcpdResult {
Success {
message: String,
summary: common::copy::Summary,
runtime_stats: common::RuntimeStats,
},
Failure {
error: String,
summary: common::copy::Summary,
runtime_stats: common::RuntimeStats,
},
}