#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::match_wild_err_arm)]
#![allow(clippy::cast_ptr_alignment)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::ptr_as_ptr)]
use std::{fs::File, io::Write, os::unix::io::AsRawFd, process::exit};
use fork::{Fork, close_fd, fork, redirect_stdio, waitpid};
fn main() {
println!("\n╔═══════════════════════════════════════════════════════════════╗");
println!("║ DEMONSTRATION: File Descriptor Reuse Bug ║");
println!("╚═══════════════════════════════════════════════════════════════╝\n");
demonstrate_bug();
demonstrate_fix();
println!("\n╔═══════════════════════════════════════════════════════════════╗");
println!("║ SUMMARY ║");
println!("╚═══════════════════════════════════════════════════════════════╝");
println!("\nBUG: close_fd() frees fd 0,1,2 → files reuse them → corruption!");
println!("FIX: redirect_stdio() keeps fd 0,1,2 busy → files get fd >= 3\n");
}
fn demonstrate_bug() {
println!("═══════════════════════════════════════════════════════════════");
println!("PART 1: THE BUG (using close_fd)");
println!("═══════════════════════════════════════════════════════════════\n");
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).unwrap();
let content = std::fs::read_to_string("/tmp/demo_bug.txt").unwrap_or_default();
println!("\nResult with close_fd():");
println!(" File content: '{}'", content.trim());
if content.contains("This is debug output") {
println!(" ⚠️ BUG DETECTED: Debug output corrupted the file!");
} else {
println!(" File only contains: {}", content.trim());
}
let _ = std::fs::remove_file("/tmp/demo_bug.txt");
}
Ok(Fork::Child) => {
close_fd().unwrap();
let mut file = File::create("/tmp/demo_bug.txt").unwrap();
let fd = file.as_raw_fd();
println!("After close_fd():");
println!(" File got fd = {}", fd);
if fd < 3 {
println!(" ⚠️ WARNING: File got fd < 3!");
println!(" Any println! or panic will write to this file!\n");
let debug_msg = b"This is debug output that should NOT be in the file!\n";
unsafe {
libc::write(2, debug_msg.as_ptr() as *const _, debug_msg.len());
}
}
file.write_all(b"Expected data\n").unwrap();
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
fn demonstrate_fix() {
println!("\n═══════════════════════════════════════════════════════════════");
println!("PART 2: THE FIX (using redirect_stdio)");
println!("═══════════════════════════════════════════════════════════════\n");
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).unwrap();
let content = std::fs::read_to_string("/tmp/demo_fix.txt").unwrap_or_default();
println!("\nResult with redirect_stdio():");
println!(" File content: '{}'", content.trim());
if content.contains("debug output") {
println!(" ❌ UNEXPECTED: Debug leaked to file");
} else {
println!(" ✅ SUCCESS: File only contains intended data!");
println!(" Debug output went to /dev/null (discarded safely)");
}
let _ = std::fs::remove_file("/tmp/demo_fix.txt");
}
Ok(Fork::Child) => {
redirect_stdio().unwrap();
let mut file = File::create("/tmp/demo_fix.txt").unwrap();
let fd = file.as_raw_fd();
println!("After redirect_stdio():");
println!(" File got fd = {}", fd);
println!(" fd 0,1,2 are now occupied by /dev/null");
if fd >= 3 {
println!(" ✅ GOOD: File got fd >= 3");
println!(" Any println! or panic goes to /dev/null (safe)\n");
let debug_msg = b"This is debug output that goes to /dev/null\n";
unsafe {
libc::write(2, debug_msg.as_ptr() as *const _, debug_msg.len());
}
}
file.write_all(b"Expected data\n").unwrap();
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}