1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
#![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)]
/// Visual demonstration of file descriptor reuse bug
///
/// This creates output files showing what fd numbers files receive
/// Run with: cargo run --example visual_fd_demo
use std::{fs::File, io::Write, os::unix::io::AsRawFd, process::exit};
use fork::{Fork, close_fd, fork, redirect_stdio, waitpid};
fn main() {
println!("\nRunning demonstration...\n");
demo_close_fd();
demo_redirect_stdio();
println!("═══════════════════════════════════════════════════════════════");
println!("Results written to /tmp/fd_demo_*.txt");
println!("═══════════════════════════════════════════════════════════════\n");
// Show results
show_results();
}
fn demo_close_fd() {
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).unwrap();
}
Ok(Fork::Child) => {
// Write to file before closing stdio (so we can see output)
let mut report = File::create("/tmp/fd_demo_close_fd.txt").unwrap();
writeln!(report, "SCENARIO 1: Using close_fd()").unwrap();
writeln!(report, "══════════════════════════════════════").unwrap();
writeln!(report).unwrap();
// Close stdio
close_fd().unwrap();
writeln!(report, "After close_fd():").unwrap();
writeln!(report, " fd 0, 1, 2 are now FREE").unwrap();
writeln!(report).unwrap();
// Open test files
let f1 = File::create("/tmp/fd_demo_file1.txt").unwrap();
let f2 = File::create("/tmp/fd_demo_file2.txt").unwrap();
let f3 = File::create("/tmp/fd_demo_file3.txt").unwrap();
writeln!(report, "Opened files:").unwrap();
writeln!(
report,
" file1.txt → fd {} ⚠️ BUG: Reused stdin!",
f1.as_raw_fd()
)
.unwrap();
writeln!(
report,
" file2.txt → fd {} ⚠️ BUG: Reused stdout!",
f2.as_raw_fd()
)
.unwrap();
writeln!(
report,
" file3.txt → fd {} ⚠️ BUG: Reused stderr!",
f3.as_raw_fd()
)
.unwrap();
writeln!(report).unwrap();
writeln!(report, "DANGER:").unwrap();
writeln!(report, " - println!() would write to file2.txt").unwrap();
writeln!(report, " - panic!() would write to file3.txt").unwrap();
writeln!(report, " - Silent file corruption!").unwrap();
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
fn demo_redirect_stdio() {
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child).unwrap();
}
Ok(Fork::Child) => {
// Write to file before redirecting (so we can see output)
let mut report = File::create("/tmp/fd_demo_redirect_stdio.txt").unwrap();
writeln!(report, "SCENARIO 2: Using redirect_stdio()").unwrap();
writeln!(report, "══════════════════════════════════════").unwrap();
writeln!(report).unwrap();
// Redirect stdio to /dev/null
redirect_stdio().unwrap();
writeln!(report, "After redirect_stdio():").unwrap();
writeln!(report, " fd 0 → /dev/null").unwrap();
writeln!(report, " fd 1 → /dev/null").unwrap();
writeln!(report, " fd 2 → /dev/null").unwrap();
writeln!(report, " (fds 0,1,2 remain OCCUPIED)").unwrap();
writeln!(report).unwrap();
// Open test files
let f1 = File::create("/tmp/fd_demo_file1_safe.txt").unwrap();
let f2 = File::create("/tmp/fd_demo_file2_safe.txt").unwrap();
let f3 = File::create("/tmp/fd_demo_file3_safe.txt").unwrap();
writeln!(report, "Opened files:").unwrap();
writeln!(
report,
" file1.txt → fd {} ✅ SAFE: Higher fd!",
f1.as_raw_fd()
)
.unwrap();
writeln!(
report,
" file2.txt → fd {} ✅ SAFE: Higher fd!",
f2.as_raw_fd()
)
.unwrap();
writeln!(
report,
" file3.txt → fd {} ✅ SAFE: Higher fd!",
f3.as_raw_fd()
)
.unwrap();
writeln!(report).unwrap();
writeln!(report, "SAFETY:").unwrap();
writeln!(report, " - println!() goes to /dev/null (discarded)").unwrap();
writeln!(report, " - panic!() goes to /dev/null (discarded)").unwrap();
writeln!(report, " - Files are protected!").unwrap();
exit(0);
}
Err(_) => panic!("Fork failed"),
}
}
fn show_results() {
println!("BUG (close_fd):");
println!("───────────────────────────────────────────────────────────────");
if let Ok(content) = std::fs::read_to_string("/tmp/fd_demo_close_fd.txt") {
print!("{}", content);
}
println!("\n");
println!("FIX (redirect_stdio):");
println!("───────────────────────────────────────────────────────────────");
if let Ok(content) = std::fs::read_to_string("/tmp/fd_demo_redirect_stdio.txt") {
print!("{}", content);
}
// Cleanup
let _ = std::fs::remove_file("/tmp/fd_demo_close_fd.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_redirect_stdio.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file1.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file2.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file3.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file1_safe.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file2_safe.txt");
let _ = std::fs::remove_file("/tmp/fd_demo_file3_safe.txt");
}
|