use std::io::{self, Write}; use std::path::Path; use std::ffi::CString; use std::os::unix::ffi::OsStrExt; use std::fs::{read_dir, symlink_metadata, Metadata}; use std::os::unix::fs::MetadataExt; use libc::{AT_FDCWD, R_OK, X_OK, AT_SYMLINK_NOFOLLOW}; use libc; macro_rules! reportln { () => (writeln!(io::stderr()).unwrap_or(())); ($($arg:tt)*) => (writeln!(io::stderr(), $($arg)*).unwrap_or(())); } fn need_access_check() -> bool { let uid : libc::uid_t; let euid : libc::uid_t; let gid : libc::gid_t; let egid : libc::gid_t; unsafe { uid = libc::getuid(); euid = libc::geteuid(); gid = libc::getgid(); egid = libc::getegid(); } return uid != euid || gid != egid; } fn check_path(path: &Path, mode: libc::c_int) -> bool { let ospath = path.as_os_str(); if ospath.is_empty() { return true; } else { let cstring = CString::new(ospath.as_bytes()).unwrap(); let result : libc::c_int; unsafe { result = libc::faccessat(AT_FDCWD, cstring.as_ptr(), mode, AT_SYMLINK_NOFOLLOW); } println!("Checking {:?} -> {:?}", path, result); return result == 0; } } fn check_full_path(path: &Path, mode: libc::c_int) -> bool { let mut result = check_path(path, mode); let mut parent = path; loop { match parent.parent() { Some(p) => { result &= check_path(p, R_OK|X_OK); parent = p; }, _ => break } } println!("Checking {:?} {}", path, result); return result; } fn inner_visit(path: &Path, metadata: &Metadata, checkpath: bool, cb: &Fn(&Path, &Metadata)) -> io::Result<()> { if checkpath { if metadata.file_type().is_dir() { check_full_path(&path, R_OK|X_OK); } else { check_full_path(&path, R_OK); } } let access = if ! checkpath { true } else { if metadata.file_type().is_dir() { check_path(&path, R_OK|X_OK) } else { check_path(&path, R_OK) } }; if ! access { reportln!("Could not access {:?}", path); return Ok(()); } else { cb(&path, &metadata); if ! metadata.file_type().is_symlink() && metadata.is_dir() { match read_dir(path) { Ok(readdir) => { let mut paths: Vec<_> = readdir.map(|r| r.unwrap()).collect(); paths.sort_by_key(|entry| entry.file_name()); for entry in paths { let p = entry.path(); match symlink_metadata(&p) { Ok(m) => { if metadata.dev() != m.dev() { reportln!("Warning: Skipping mountpoint {:?}", p); continue; } inner_visit(&p, &m, checkpath, cb).unwrap(); }, Err(e) => { reportln!("Error: {:?} [{}]", p, e) } } } }, Err(e) => { reportln!("Error: {:?} {}", path, e); } } } } Ok(()) } pub fn visit(path: &Path, cb: &Fn(&Path, &Metadata)) -> io::Result<()> { let checkpath = need_access_check(); let metadata = symlink_metadata(path); let access = match metadata { Ok(ref m) => { if ! checkpath { true } else { if m.file_type().is_dir() { check_full_path(&path, R_OK|X_OK) } else { check_full_path(&path, R_OK) } } }, _ => false }; if ! access { reportln!("Could not access {:?}", path); return Ok(()); } else { let metadata = metadata.unwrap(); try!(inner_visit(&path, &metadata, checkpath, cb)); } Ok(()) }