-
-
Save CamJN/febccca8003822c4003a9cae8fb22300 to your computer and use it in GitHub Desktop.
scan macOS for readable inodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate users; | |
extern crate num_cpus; | |
use std::thread; | |
use std::fs; | |
use std::io; | |
use std::cmp; | |
use std::str::Lines; | |
use std::process::Command; | |
use std::io::{Write, Error, ErrorKind}; | |
use std::os::unix::fs::{MetadataExt,PermissionsExt}; | |
use users::{get_current_gid,get_current_uid}; | |
const S_IRUSR:u32 = 0o00400; | |
const S_IRGRP:u32 = 0o00040; | |
const S_IROTH:u32 = 0o00004; | |
fn get_from_command<C,O>(command: &'static str, arg: &'static str, callback: C) -> io::Result<O> | |
where C: FnOnce(&str, Lines) -> io::Result<O> { | |
let output = Command::new(command) | |
.args(&[arg]) | |
.output()?; | |
if output.status.success() { | |
if let Ok(out) = String::from_utf8(output.stdout) { | |
let mut iter = out.lines(); | |
if let Some(line1) = iter.next() { | |
callback(line1, iter) | |
} else { | |
Err(Error::new(ErrorKind::InvalidData, format!("{} output malformed.",command))) | |
} | |
} else { | |
Err(Error::new(ErrorKind::InvalidData, format!("{} output malformed.",command))) | |
} | |
} else { | |
Err(Error::new(ErrorKind::Other, format!("Calling `{} {}` failed.", command, arg))) | |
} | |
} | |
fn inodes() -> io::Result<usize> { | |
get_from_command("df","/",|labels,mut iter|{ | |
if let Some(values) = iter.next() { | |
let pair = labels.split_whitespace() | |
.zip(values.split_whitespace()) | |
.find(|(e,_)|e==&"iused").unwrap(); | |
let res = pair.1.parse::<usize>().unwrap(); | |
Ok(res) | |
} else { | |
Err(Error::new(ErrorKind::InvalidData, "df output malformed.")) | |
} | |
}) | |
} | |
fn vol() -> io::Result<String> { | |
get_from_command("stat", "/.file", |out,_|{ | |
let mut iter = out.split_whitespace(); | |
if let Some(vol) = iter.next() { | |
Ok(vol.to_string()) | |
} else { | |
Err(Error::new(ErrorKind::InvalidData, "stat output malformed.")) | |
} | |
}) | |
} | |
fn main() -> io::Result<()> { | |
let vol = vol()?; | |
// my 4 core i7 can check 76,934 inodes per second, so I don't feel like exploring the whole available space, especially since it's unlikely to be highly populated at the higher end | |
let max = inodes()? * 10; // takes ~10 minutes | |
//let max = std::u32::MAX as usize; // true max for hfs+, takes about 12-16 hours | |
//let max = std::u64::MAX as usize; // true max for apfs, would take about 8 million years | |
let my_gid = get_current_gid(); | |
let my_uid = get_current_uid(); | |
let cpus = num_cpus::get(); | |
let threads: Vec<thread::JoinHandle<Vec<usize>>> = (1..=cpus).map(|core| { | |
let my_max = core * (max / cpus); | |
let my_min = cmp::max(1, (core - 1) * (max / cpus)); | |
let my_vol = vol.clone(); | |
thread::spawn(move || { | |
(my_min..my_max).filter(|i| { | |
if let Ok(meta) = fs::metadata(format!("/.vol/{}/{}",my_vol,i)) { | |
(meta.permissions().mode() & S_IROTH > 0) || | |
(meta.gid() == my_gid && (meta.permissions().mode() & S_IRGRP) > 0) || | |
(meta.uid() == my_uid && (meta.permissions().mode() & S_IRUSR) > 0) | |
} else { | |
false | |
} | |
}).collect() | |
}) | |
}).collect(); | |
let mut res = threads.into_iter() | |
.filter_map(|child| child.join().ok() ) | |
.flat_map(|x|x.into_iter()).peekable(); | |
if res.peek().is_none() { | |
Err(Error::new(ErrorKind::NotFound, "Could not find file.")) | |
} else { | |
let stdout = io::stdout(); | |
let mut lock = stdout.lock(); | |
res.for_each(|i| if let Err(e) = writeln!(lock,"inode {} has matching gid", i) { | |
eprintln!("{}",e); | |
}); | |
Ok(()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment