Still looking for an answer? I've written one - as a learning exercise - that processes a file from end to beginning, counting the trailing zeros. It's pretty fast with a 1024 buffer over a 1 meg file of all zeros:
endee@penguin:~/rust/trailz$ time trailz test.zs
test.zs: 1048576
real 0m0.010s
user 0m0.003s
sys 0m0.008s
It reads the file one BUFF_SIZE at a time. Change BUFF_SIZE to suit yourself. I'm a rust-noob so there are probably more idiomatic ways do what it does. Useful tips welcomed ; )
//
// trailz -- utility to count number of trailing zeros in files
//
// usage: trailz file1 file2 ...
//
// outputs filename and number of trailing zeros
//
// Notes:
//
// Empty files and files with no trailing zeros both return 0
//
// It skips itself as long as its path ends with 'trailz'
//
// Developer Notes:
//
// If it is run with 'cargo test' then it will create several test files
//
// You can then run it with 'cargo run *.test' and it will process the
// created files as input
//
// If it cannot open a file, e.g. the file does not exist, then it will
// report "failed to open <filename> : <error message>" and continue
//
// BUFF_SIZE defines the number of bytes in the buffer. It is set to 4 for
// testing. Change it to something larger when you make a release version.
use std::io::prelude::*;
use std::fs::File;
use std::fs;
use std::env;
const BUFF_SIZE : usize = 4; // CHANGE ME!!!!!!
fn main() {
for arg in env::args() {
if arg.ends_with("trailz") { continue; }
match File::open(&arg) {
Ok(_) => (),
Err(err) => {
println!("failed to open {} : {}", &arg, err);
continue
},
}
let t = tail_size(&arg).unwrap();
println!("{}: {}", arg, t);
}
}
fn tail_size(name : &str) -> Option<u32> {
let mut f = File::open(name).unwrap();
let mut b = [0; BUFF_SIZE];
let f_len = fs::metadata(&name).unwrap().len();
if f_len == 0 {
return Some(0);
}
let mut pages = f_len / BUFF_SIZE as u64;
if f_len % BUFF_SIZE as u64 != 0 { pages += 1 };
let mut page : u64 = pages - 1;
let mut t = 0;
while page >= 0 as u64 {
match f.seek(std::io::SeekFrom::Start(page * BUFF_SIZE as u64)) {
Ok(_) => (),
Err(err) => {println!("seek error in {} : error {}", name, err); return None; },
};
let n = f.read(&mut b).unwrap();
let mut pos = n - 1 as usize;
while pos >= 0 as usize {
if b[pos] == 0 {
t += 1;
} else {
return Some(t);
}
if pos == 0 {break;}
else { pos -= 1; }
}
if page == 0 { break; }
else { page -= 1; }
}
return Some(t);
}
// utility used to load test files
#[allow(dead_code)]
fn load_file(name : &str, data : Vec<u8>) -> std::io::Result<()> {
let mut pos = 0;
let mut b = File::create(name)?;
while pos < data.len() {
let bytes_written = b.write(&data[pos..])?;
pos += bytes_written;
}
return Ok(());
}
#[test]
fn first_test() {
let data = vec![0,0,0,0, 0,0,0,0, 0,1,0,0, 0,0,0,];
load_file("first_test.test", data);
let t = tail_size("first_test.test").unwrap();
assert_eq!(t,5);
}
#[test]
fn no_trailing_zeros() {
let data = vec![1,1,1,0,0,0,1,1,1,0,1,0,0,0,0,1,];
load_file("no_trailing_zeros.test", data);
let t = tail_size("no_trailing_zeros.test").unwrap();
assert_eq!(t,0);
}
#[test]
fn empty_file() {
let data = vec![];
load_file("empty_file.test", data);
let t = tail_size("empty_file.test").unwrap();
assert_eq!(t,0);
}
#[test]
fn last_byte_is_zero() {
let data = vec![0,0,0,0, 0,0,0,0, 0,1,0,0, 0,0,1,0,];
load_file("last_byte_is_zero.test", data);
let t = tail_size("last_byte_is_zero.test").unwrap();
assert_eq!(t,1);
}