irradiate/src/main.rs

184 lines
5.5 KiB
Rust

// Irradiate v0.2
// Evelene Ultraviolet
// Ellie Photodiode
// Ariane Mechanism
// Amelia Rainfall
// July 2019
use std::io::prelude::*;
use std::io::stdout;
use std::io::SeekFrom;
use std::fs::OpenOptions;
use std::time::{Duration,Instant};
use std::process::exit;
extern crate rand;
use rand::Rng;
extern crate lapp;
// sets a bit in-place. depending on the bit's initial state, may or may not produce an error.
fn set_bit(byte:&mut u8, index:usize, state:bool) {
let norm_index = (index % 8) as u8;
match state {
true => *byte |= (0x01 << norm_index) ^ 0x00,
false => *byte &= (0x01 << norm_index) ^ 0xff,
};
}
// flips a bit in-place, ensuring exactly one error will be produced.
fn flip_bit(byte:&mut u8, index:usize) {
let norm_index = (index % 8) as u8;
*byte ^= 0x01 << norm_index;
}
fn main() {
let args = lapp::parse_args("
Irradiate file corruptor v0.2
<filename> (string) name of file on which to operate
-n, --count (default 10) integer number of bits to affect in the file
-0, --zero never set affected bits to 1, only set them to 0.
-1, --one never set affected bits to 0, only set them to 1.
-f, --flip invert affected bits, ensuring that the file contains an exact number of errors
-h, --holdoff (default 0) number of bytes at the start of the file to leave unaffected, so as not to corrupt the header and render the file unopenable
");
let filename = args.get_string("filename");
let reps = args.get_integer("count") as u64;
let zero_only = args.get_bool("zero");
let one_only = args.get_bool("one");
let flip = args.get_bool("flip");
let holdoff = args.get_integer("holdoff") as u64;
// make sure only one of the mode flags was given; gripe otherwise
if (zero_only as u8) + (one_only as u8) + (flip as u8) > 1 {
eprintln!("irradiate: the --flip, --zero, and --one flags (-f, -0, and -1) are mutually exclusive and cannot be combined");
exit(1);
}
// set up the progress meter by determining the numbers of affected bits that correspond to each percentage value.
let mut milestones:[u64;99] = [0;99];
let mut current_milestone:usize = 0;
for i in 1..100 {
milestones[i-1] = reps*(i as u64)/100;
}
let mut rng = rand::thread_rng();
let mut buffer:[u8;1] = [0x00;1];
let mut sout = stdout();
// we can't use lapp's nifty builtin file-fetcher because we need to open the file in read-write mode,
// allowing us to modify it in-place.
let mut infile = match OpenOptions::new()
.read(true)
.write(true)
.append(false)
.truncate(false)
.create(false)
.create_new(false)
.open(&filename)
{
Ok(file) => file,
Err(why) => {
eprintln!("irradiate: could not open {} - {}", filename, why);
exit(1);
},
};
// seek to the end of the file and figure out where that is - a better trick for determining length that,
// unlike fs metadata, actually works on block devices.
let infile_len:u64 = match infile.seek(SeekFrom::End(0)) {
Ok(n) => match n {
0 => {
eprintln!("irradiate: this file appears to be empty");
exit(1);
},
_ => n,
},
Err(why) => {
eprintln!("irradiate: could not determine the size of {} - {}", filename, why);
exit(1);
},
};
let mut total_seektime:Duration = Duration::new(0,0);
let mut total_readtime:Duration = Duration::new(0,0);
let mut total_writtime:Duration = Duration::new(0,0);
let mut seekstart:Instant;
let mut readstart:Instant;
let mut writstart:Instant;
print!("\rirradiating [ 0%]");
for rep in 0..reps {
// go to a randomly-selected location, between the holdoff position and the file end.
let position = rng.gen_range(holdoff, infile_len);
seekstart = Instant::now();
match infile.seek(SeekFrom::Start(position)) {
Ok(_) => (),
Err(why) => {
eprintln!("irradiate: seek error - {}", why);
exit(1);
},
};
total_seektime += seekstart.elapsed();
// read data into the buffer
readstart = Instant::now();
match infile.read(&mut buffer) {
Ok(n) => match n {
0 => break,
_ => (),
},
Err(why) => {
eprintln!("irradiate: read error - {}", why);
exit(1);
},
};
total_readtime += readstart.elapsed();
// go back to the same position from which we read the data
seekstart = Instant::now();
match infile.seek(SeekFrom::Start(position)) {
Ok(_) => (),
Err(why) => {
eprintln!("irradiate: seek error - {}", why);
exit(1);
},
};
total_seektime += seekstart.elapsed();
// set a random bit of this byte
let bit = rng.gen_range(0,8);
if one_only {
set_bit(&mut buffer[0], bit, true);
} else if zero_only {
set_bit(&mut buffer[0], bit, false);
} else if flip {
flip_bit(&mut buffer[0], bit);
} else {
set_bit(&mut buffer[0], bit, rng.gen::<bool>());
}
// write the modified data back to the file
writstart = Instant::now();
match infile.write_all(&buffer) {
Ok(_) => (),
Err(why) => {
eprintln!("irradiate: write error - {}", why);
exit(1);
},
};
let _ = infile.flush();
total_writtime += writstart.elapsed();
// update the progress meter
while milestones[current_milestone] <= rep && current_milestone < 98 {
print!("\rirradiating [{:3}%]", current_milestone);
current_milestone += 1;
}
// flush stdout, to make sure the progress meter actually updates
let _ = sout.flush();
}
print!("\rirradiating [100%]");
println!();
println!("irradiating done.");
println!("time seeking: {} s", (total_seektime.as_micros() as f32)/1000000.0);
println!("time reading: {} s", (total_readtime.as_micros() as f32)/1000000.0);
println!("time writing: {} s", (total_writtime.as_micros() as f32)/1000000.0);
}