Directory Traversal - Rust Cookbook (original) (raw)
Rust Cookbook
Directory Traversal
File names that have been modified in the last 24 hours
Gets the current working directory by calling env::current_dir, then for each entries in fs::read_dir, extracts theDirEntry::path and gets the metadata via fs::Metadata. TheMetadata::modified returns the SystemTime::elapsed time since last modification. Duration::as_secs converts the time to seconds and compared with 24 hours (24 * 60 * 60 seconds). Metadata::is_file filters out directories.
use error_chain::error_chain;
use std::{env, fs};
error_chain! {
foreign_links {
Io(std::io::Error);
SystemTimeError(std::time::SystemTimeError);
}
}
fn main() -> Result<()> {
let current_dir = env::current_dir()?;
println!(
"Entries modified in the last 24 hours in {:?}:",
current_dir
);
for entry in fs::read_dir(current_dir)? {
let entry = entry?;
let path = entry.path();
let metadata = fs::metadata(&path)?;
let last_modified = metadata.modified()?.elapsed()?.as_secs();
if last_modified < 24 * 3600 && metadata.is_file() {
println!(
"Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
last_modified,
metadata.permissions().readonly(),
metadata.len(),
path.file_name().ok_or("No filename")?
);
}
}
Ok(())
}
Find loops for a given path
Use same_file::is_same_file to detect loops for a given path. For example, a loop could be created on a Unix system via symlinks:
mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
The following would assert that a loop exists.
use std::io;
use std::path::{Path, PathBuf};
use same_file::is_same_file;
fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> {
let path = path.as_ref();
let mut path_buf = path.to_path_buf();
while path_buf.pop() {
if is_same_file(&path_buf, path)? {
return Ok(Some((path_buf, path.to_path_buf())));
} else if let Some(looped_paths) = contains_loop(&path_buf)? {
return Ok(Some(looped_paths));
}
}
return Ok(None);
}
fn main() {
assert_eq!(
contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
Some((
PathBuf::from("/tmp/foo"),
PathBuf::from("/tmp/foo/bar/baz/qux")
))
);
}
Recursively find duplicate file names
Find recursively in the current directory duplicate filenames, printing them only once.
use std::collections::HashMap;
use walkdir::WalkDir;
fn main() {
let mut filenames = HashMap::new();
for entry in WalkDir::new(".")
.into_iter()
.filter_map(Result::ok)
.filter(|e| !e.file_type().is_dir()) {
let f_name = String::from(entry.file_name().to_string_lossy());
let counter = filenames.entry(f_name.clone()).or_insert(0);
*counter += 1;
if *counter == 2 {
println!("{}", f_name);
}
}
}
Recursively find all files with given predicate
Find JSON files modified within the last day in the current directory. Using follow_links ensures symbolic links are followed like they were normal directories and files.
use error_chain::error_chain;
use walkdir::WalkDir;
error_chain! {
foreign_links {
WalkDir(walkdir::Error);
Io(std::io::Error);
SystemTime(std::time::SystemTimeError);
}
}
fn main() -> Result<()> {
for entry in WalkDir::new(".")
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok()) {
let f_name = entry.file_name().to_string_lossy();
let sec = entry.metadata()?.modified()?;
if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 {
println!("{}", f_name);
}
}
Ok(())
}
Traverse directories while skipping dotfiles
Uses filter_entry to descend recursively into entries passing theis_not_hidden
predicate thus skipping hidden files and directories.Iterator::filter applies to each WalkDir::DirEntry even if the parent is a hidden directory.
Root dir "."
yields through WalkDir::depth usage in is_not_hidden
predicate.
use walkdir::{DirEntry, WalkDir};
fn is_not_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| entry.depth() == 0 || !s.starts_with("."))
.unwrap_or(false)
}
fn main() {
WalkDir::new(".")
.into_iter()
.filter_entry(|e| is_not_hidden(e))
.filter_map(|v| v.ok())
.for_each(|x| println!("{}", x.path().display()));
}
Recursively calculate file sizes at given depth
Recursion depth can be flexibly set by WalkDir::min_depth & WalkDir::max_depth methods. Calculates sum of all file sizes to 3 subfolders depth, ignoring files in the root folder.
use walkdir::WalkDir;
fn main() {
let total_size = WalkDir::new(".")
.min_depth(1)
.max_depth(3)
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len());
println!("Total size: {} bytes.", total_size);
}
Find all png files recursively
Recursively find all PNG files in the current directory. In this case, the **
pattern matches the current directory and all subdirectories.
Use the **
pattern in any path portion. For example, /media/**/*.png
matches all PNGs in media
and it's subdirectories.
use error_chain::error_chain;
use glob::glob;
error_chain! {
foreign_links {
Glob(glob::GlobError);
Pattern(glob::PatternError);
}
}
fn main() -> Result<()> {
for entry in glob("**/*.png")? {
println!("{}", entry?.display());
}
Ok(())
}
Find all files with given pattern ignoring filename case.
Find all image files in the /media/
directory matching the img_[0-9]*.png
pattern.
A custom MatchOptions struct is passed to the glob_with function making the glob pattern case insensitive while keeping the other options Default.
use error_chain::error_chain;
use glob::{glob_with, MatchOptions};
error_chain! {
foreign_links {
Glob(glob::GlobError);
Pattern(glob::PatternError);
}
}
fn main() -> Result<()> {
let options = MatchOptions {
case_sensitive: false,
..Default::default()
};
for entry in glob_with("/media/img_[0-9]*.png", options)? {
println!("{}", entry?.display());
}
Ok(())
}