use safer_ring::{OwnedBuffer, Ring};
use std::collections::hash_map::DefaultHasher;
use std::env;
use std::fs::{File, OpenOptions};
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::os::unix::io::AsRawFd;
use std::time::{Duration, Instant};
#[derive(Debug)]
struct CopyConfig {
source_path: String,
dest_path: String,
buffer_size: usize,
parallel_ops: usize,
ring_size: u32,
}
impl CopyConfig {
fn from_args() -> Result<Self, Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
return Err(
"Usage: file_copy <source> <destination> [--buffer-size KB] [--parallel N]".into(),
);
}
let mut config = CopyConfig {
source_path: args[1].clone(),
dest_path: args[2].clone(),
buffer_size: 64 * 1024, parallel_ops: 2,
ring_size: 32,
};
let mut i = 3;
while i < args.len() {
match args[i].as_str() {
"--buffer-size" => {
if i + 1 < args.len() {
let kb: usize = args[i + 1].parse()?;
config.buffer_size = kb * 1024;
i += 2;
} else {
return Err("--buffer-size requires a value in KB".into());
}
}
"--parallel" => {
if i + 1 < args.len() {
config.parallel_ops = args[i + 1].parse()?;
config.ring_size = (config.parallel_ops * 2).max(32) as u32;
i += 2;
} else {
return Err("--parallel requires a number".into());
}
}
_ => {
return Err(format!("Unknown argument: {}", args[i]).into());
}
}
}
Ok(config)
}
}
#[derive(Debug, Default)]
struct CopyStats {
bytes_copied: u64,
operations_completed: u64,
start_time: Option<Instant>,
last_update: Option<Instant>,
}
impl CopyStats {
fn new() -> Self {
Self {
start_time: Some(Instant::now()),
last_update: Some(Instant::now()),
..Default::default()
}
}
fn update(&mut self, bytes: u64) {
self.bytes_copied += bytes;
self.operations_completed += 1;
self.last_update = Some(Instant::now());
}
fn throughput_mbps(&self) -> f64 {
if let Some(start) = self.start_time {
let elapsed = start.elapsed().as_secs_f64();
if elapsed > 0.0 {
(self.bytes_copied as f64) / (1024.0 * 1024.0) / elapsed
} else {
0.0
}
} else {
0.0
}
}
fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_idx = 0;
while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
size /= 1024.0;
unit_idx += 1;
}
format!("{:.2} {}", size, UNITS[unit_idx])
}
}
#[cfg(target_os = "linux")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 safer-ring File Copy Demo");
println!("============================");
println!("📚 This example demonstrates:");
println!(" • Sequential operation pattern (safer-ring's design)");
println!(" • Proper buffer lifetime management");
println!(" • The essential ?.await? error handling pattern");
println!(" • Zero-copy I/O with memory safety guarantees");
println!();
let config = CopyConfig::from_args()?;
println!("📁 Source: {}", config.source_path);
println!("📁 Destination: {}", config.dest_path);
println!(
"💾 Buffer size: {}",
CopyStats::format_bytes(config.buffer_size as u64)
);
println!("⚡ Operations: Sequential (safer-ring design)");
println!(
"🔧 Ring size: {} (small is fine for sequential ops)",
config.ring_size
);
println!();
let source_metadata = std::fs::metadata(&config.source_path)?;
let file_size = source_metadata.len();
println!("📏 File size: {}", CopyStats::format_bytes(file_size));
let source_file = File::open(&config.source_path)?;
let source_fd = source_file.as_raw_fd();
let dest_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&config.dest_path)?;
let dest_fd = dest_file.as_raw_fd();
let ring = Ring::new(config.ring_size)?;
println!("⚡ Created safer-ring with {} entries", config.ring_size);
println!("📚 Note: Sequential operations don't need many ring entries");
println!();
println!("🔄 Starting copy with safer-ring sequential pattern...");
let stats = copy_file_simple(&ring, source_fd, dest_fd, file_size, &config).await?;
println!();
println!("✅ Copy completed successfully!");
println!("📊 Final Statistics:");
println!(
" Bytes copied: {}",
CopyStats::format_bytes(stats.bytes_copied)
);
println!(" Operations: {}", stats.operations_completed);
println!(" Throughput: {:.2} MB/s", stats.throughput_mbps());
if let Some(start) = stats.start_time {
println!(" Total time: {:?}", start.elapsed());
}
println!();
println!("🔍 Verifying copy integrity...");
verify_copy(&config.source_path, &config.dest_path)?;
println!("✅ Copy verification successful!");
println!();
println!("📚 Key Takeaways from this Example:");
println!(" ✓ Ownership transfer (*_owned methods) ensures maximum safety");
println!(" ✓ ?.await? pattern provides robust error handling");
println!(" ✓ OwnedBuffer + hot potato pattern simplifies buffer management");
println!(" ✓ No explicit lifetime management or pinning required");
println!(" ✓ Predictable performance without internal complexity");
println!(" ✓ Single buffer efficiently reused across all operations");
println!();
println!("🎯 For higher parallelism, consider:");
println!(" • Multiple Ring instances (one per thread)");
println!(" • Batch operations for grouped I/O");
println!(" • This pattern scales perfectly with multiple threads");
Ok(())
}
#[cfg(target_os = "linux")]
async fn copy_file_simple(
ring: &Ring<'_>,
source_fd: i32,
dest_fd: i32,
file_size: u64,
config: &CopyConfig,
) -> Result<CopyStats, Box<dyn std::error::Error>> {
let mut stats = CopyStats::new();
let mut offset = 0u64;
let stats_clone = std::sync::Arc::new(tokio::sync::Mutex::new(CopyStats::new()));
let stats_reporter = std::sync::Arc::clone(&stats_clone);
let total_size = file_size;
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let stats = stats_reporter.lock().await;
let progress = (stats.bytes_copied as f64 / total_size as f64) * 100.0;
print!(
"\r📈 Progress: {:.1}% ({} / {}) at {:.2} MB/s",
progress,
CopyStats::format_bytes(stats.bytes_copied),
CopyStats::format_bytes(total_size),
stats.throughput_mbps()
);
io::stdout().flush().unwrap();
if stats.bytes_copied >= total_size {
break;
}
}
});
println!("🔄 Starting HOT POTATO copy operation...");
println!(
"📚 Educational Note: Using the 'hot potato' ownership transfer pattern for maximum safety and performance"
);
println!(
"💡 Performance Note: std::fs::copy may be faster for cached files due to kernel optimizations"
);
println!(
"🎯 safer-ring excels at: network I/O, database files, and scenarios requiring userspace bypass"
);
let mut buffer = OwnedBuffer::new(config.buffer_size);
while offset < file_size {
let chunk_size = std::cmp::min(config.buffer_size as u64, file_size - offset);
println!("📖 Reading {chunk_size} bytes at offset {offset} (hot potato: buffer → kernel)");
let (bytes_read, returned_buffer) = ring.read_at_owned(source_fd, buffer, offset).await?;
buffer = returned_buffer;
if bytes_read > 0 {
println!(
"✏️ Writing {bytes_read} bytes at offset {offset} (hot potato: buffer → kernel)"
);
let (bytes_written, returned_buffer_after_write) = ring
.write_at_owned(dest_fd, buffer, offset, bytes_read)
.await?;
buffer = returned_buffer_after_write;
if bytes_written != bytes_read {
return Err(
format!("Partial write: expected {bytes_read}, wrote {bytes_written}").into(),
);
}
stats.update(bytes_written as u64);
{
let mut shared_stats = stats_clone.lock().await;
shared_stats.update(bytes_written as u64);
}
offset += bytes_written as u64;
} else {
break;
}
}
println!("\n✅ Hot potato copy operation completed!");
println!("📚 Educational Summary:");
println!(" ✓ Used `read_at_owned`/`write_at_owned` for maximum safety");
println!(" ✓ Demonstrated the 'hot potato' ownership transfer pattern");
println!(" ✓ Reused single `OwnedBuffer` efficiently across all operations");
println!(" ✓ Applied ?.await? pattern for robust error handling");
println!(" ✓ No complex lifetime management or pinning required");
println!(" ✓ Buffer ownership: user → kernel → user → kernel → user (hot potato!)");
Ok(stats)
}
fn verify_copy(source_path: &str, dest_path: &str) -> Result<(), Box<dyn std::error::Error>> {
use std::fs;
let source_size = fs::metadata(source_path)?.len();
let dest_size = fs::metadata(dest_path)?.len();
if source_size != dest_size {
return Err(format!(
"File size mismatch: source {source_size} bytes, destination {dest_size} bytes"
)
.into());
}
if source_size <= 1024 * 1024 {
let source_content = fs::read(source_path)?;
let dest_content = fs::read(dest_path)?;
if source_content != dest_content {
return Err("File content mismatch".into());
}
} else {
let source_hash = calculate_file_hash(source_path)?;
let dest_hash = calculate_file_hash(dest_path)?;
if source_hash != dest_hash {
return Err("File checksum mismatch".into());
}
}
Ok(())
}
fn calculate_file_hash(path: &str) -> Result<u64, Box<dyn std::error::Error>> {
use std::fs::File;
use std::io::{BufReader, Read};
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut hasher = DefaultHasher::new();
let mut buffer = [0u8; 8192];
loop {
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
buffer[..bytes_read].hash(&mut hasher);
}
Ok(hasher.finish())
}
#[cfg(not(target_os = "linux"))]
fn main() {
println!("❌ This example requires Linux with io_uring support");
println!("💡 io_uring is not available on this platform");
println!();
println!("This example demonstrates:");
println!(" - Zero-copy file operations");
println!(" - Buffer pool management");
println!(" - Batch I/O processing");
println!(" - Performance optimization techniques");
println!();
println!("Supported platforms:");
println!(" - Linux 5.1+ (basic support)");
println!(" - Linux 5.19+ (recommended)");
println!(" - Linux 6.0+ (optimal performance)");
}