Skip to content

Commit 024ac97

Browse files
author
Joseph Rafael Ferrer
committed
Augment TreeNode with path depth
1 parent 9babe13 commit 024ac97

File tree

3 files changed

+226
-141
lines changed

3 files changed

+226
-141
lines changed

ftw/src/lib.rs

Lines changed: 97 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -329,15 +329,16 @@ struct TreeNode {
329329
dir: HybridDir,
330330
filename: Rc<[libc::c_char]>,
331331
metadata: Metadata,
332+
path_depth: usize,
332333
}
333334

334335
/// An entry in the directory tree.
335336
#[derive(Debug, Clone)]
336337
pub struct Entry<'a> {
337338
dir_file_descriptor: &'a FileDescriptor,
338339
path_stack: &'a [Rc<[libc::c_char]>],
339-
filename: &'a Rc<[libc::c_char]>,
340-
metadata: Option<&'a Metadata>,
340+
filename: Rc<[libc::c_char]>,
341+
metadata: Option<Metadata>,
341342
is_symlink: Option<bool>,
342343
read_link: Option<Rc<[libc::c_char]>>,
343344
}
@@ -346,8 +347,8 @@ impl<'a> Entry<'a> {
346347
fn new(
347348
dir_file_descriptor: &'a FileDescriptor,
348349
path_stack: &'a [Rc<[libc::c_char]>],
349-
filename: &'a Rc<[libc::c_char]>,
350-
metadata: Option<&'a Metadata>,
350+
filename: Rc<[libc::c_char]>,
351+
metadata: Option<Metadata>,
351352
) -> Self {
352353
Self {
353354
dir_file_descriptor,
@@ -375,7 +376,7 @@ impl<'a> Entry<'a> {
375376
///
376377
/// This is either the metadata of the file itself or the metadata of the file it points to.
377378
pub fn metadata(&self) -> Option<&Metadata> {
378-
self.metadata
379+
self.metadata.as_ref()
379380
}
380381

381382
/// Check if this entry is a symlink.
@@ -394,7 +395,7 @@ impl<'a> Entry<'a> {
394395
///
395396
/// This is either relative to the current working directory or an absolute path.
396397
pub fn path(&self) -> DisplayablePath {
397-
DisplayablePath(build_path(self.path_stack, self.filename))
398+
DisplayablePath(build_path(self.path_stack, &self.filename))
398399
}
399400

400401
/// Check if this `Entry` is an empty directory.
@@ -473,28 +474,22 @@ impl Deref for DisplayablePath {
473474
}
474475
}
475476

476-
enum NodeOrMetadata {
477-
TreeNode(TreeNode),
478-
Metadata(Metadata),
479-
}
480-
481-
enum ProcessFileResult {
482-
ProcessedDirectory(NodeOrMetadata),
477+
enum ProcessFileResult<'a> {
478+
ProcessedDirectory(Entry<'a>),
483479
ProcessedFile,
484480
NotProcessed,
485481
Skipped,
486482
}
487483

488-
fn process_file<F, H>(
489-
path_stack: &[Rc<[libc::c_char]>],
490-
dir_fd: &FileDescriptor,
484+
fn process_file<'a, F, H>(
485+
path_stack: &'a [Rc<[libc::c_char]>],
486+
dir_fd: &'a FileDescriptor,
491487
entry_filename: Rc<[libc::c_char]>,
492488
follow_symlinks: bool,
489+
is_dot_or_double_dot: bool,
493490
file_handler: &mut F,
494491
err_reporter: &mut H,
495-
is_dot_or_double_dot: bool,
496-
conserve_fds: bool,
497-
) -> ProcessFileResult
492+
) -> ProcessFileResult<'a>
498493
where
499494
F: FnMut(Entry<'_>) -> Result<bool, ()>,
500495
H: FnMut(Entry<'_>, Error),
@@ -504,7 +499,7 @@ where
504499
Ok(md) => md,
505500
Err(e) => {
506501
err_reporter(
507-
Entry::new(dir_fd, &path_stack, &entry_filename, None),
502+
Entry::new(dir_fd, &path_stack, entry_filename, None),
508503
Error::new(e, ErrorKind::Stat),
509504
);
510505
return ProcessFileResult::NotProcessed;
@@ -519,7 +514,7 @@ where
519514
Ok(p) => p,
520515
Err(e) => {
521516
err_reporter(
522-
Entry::new(dir_fd, path_stack, &entry_filename, None),
517+
Entry::new(dir_fd, path_stack, entry_filename, None),
523518
Error::new(e, ErrorKind::ReadLink),
524519
);
525520
return ProcessFileResult::NotProcessed;
@@ -534,7 +529,7 @@ where
534529
(Some(read_link), entry_symlink_metadata)
535530
} else {
536531
err_reporter(
537-
Entry::new(dir_fd, path_stack, &entry_filename, None),
532+
Entry::new(dir_fd, path_stack, entry_filename, None),
538533
Error::new(e, ErrorKind::Stat),
539534
);
540535
return ProcessFileResult::NotProcessed;
@@ -545,7 +540,7 @@ where
545540
(None, entry_symlink_metadata)
546541
};
547542

548-
let mut entry = Entry::new(dir_fd, path_stack, &entry_filename, Some(&entry_metadata));
543+
let mut entry = Entry::new(dir_fd, path_stack, entry_filename, Some(entry_metadata));
549544
entry.is_symlink = Some(is_symlink);
550545
entry.read_link = entry_readlink;
551546

@@ -558,28 +553,30 @@ where
558553

559554
match file_handler_result {
560555
Ok(true) => {
561-
if entry_metadata.file_type() == FileType::Directory {
556+
let entry_metadata = entry.metadata.as_ref().unwrap();
557+
if entry_metadata.is_dir() {
562558
// Is the directory searchable?
563559
if entry_metadata.is_executable() {
564-
if conserve_fds {
565-
ProcessFileResult::ProcessedDirectory(NodeOrMetadata::Metadata(
566-
entry_metadata,
567-
))
568-
} else {
569-
match OwnedDir::open_at(dir_fd, entry_filename.as_ptr()) {
570-
Ok(new_dir) => ProcessFileResult::ProcessedDirectory(
571-
NodeOrMetadata::TreeNode(TreeNode {
572-
dir: HybridDir::Owned(new_dir),
573-
filename: entry_filename,
574-
metadata: entry_metadata,
575-
}),
576-
),
577-
Err(error) => {
578-
err_reporter(entry, error);
579-
ProcessFileResult::NotProcessed
580-
}
581-
}
582-
}
560+
ProcessFileResult::ProcessedDirectory(entry)
561+
// if conserve_fds {
562+
// ProcessFileResult::ProcessedDirectory(NodeOrMetadata::Metadata(
563+
// entry_metadata,
564+
// ))
565+
// } else {
566+
// match OwnedDir::open_at(dir_fd, entry_filename.as_ptr()) {
567+
// Ok(new_dir) => ProcessFileResult::ProcessedDirectory(
568+
// NodeOrMetadata::TreeNode(TreeNode {
569+
// dir: HybridDir::Owned(new_dir),
570+
// filename: entry_filename,
571+
// metadata: entry_metadata,
572+
// }),
573+
// ),
574+
// Err(error) => {
575+
// err_reporter(entry, error);
576+
// ProcessFileResult::NotProcessed
577+
// }
578+
// }
579+
// }
583580
} else {
584581
// "Permission denied" error. `io::ErrorKind::PermissionDenied` uses
585582
// lowercase for "permission" in the error message so don't use that here.
@@ -651,7 +648,7 @@ where
651648
Err(e) => {
652649
if let Some(path_stack) = &path_stack {
653650
err_reporter(
654-
Entry::new(&starting_dir, path_stack, &filename, None),
651+
Entry::new(&starting_dir, path_stack, filename, None),
655652
Error::new(e, ErrorKind::Open),
656653
);
657654
}
@@ -670,7 +667,9 @@ where
670667
// TODO: Document
671668
#[derive(Debug, Clone, Default)] // Defaults to all `false`
672669
pub struct TraverseDirectoryOpts {
670+
/// Whether to dereference `path` if it's a symlink.
673671
pub follow_symlinks_on_args: bool,
672+
/// Dereference symlinks encountered (also including `path`).
674673
pub follow_symlinks: bool,
675674
pub include_dot_and_double_dot: bool,
676675
pub list_contents_first: bool,
@@ -695,9 +694,7 @@ pub struct TraverseDirectoryOpts {
695694
///
696695
/// * `err_reporter` - Callback for reporting the errors encountered during the directory traversal.
697696
///
698-
/// * `follow_symlinks_on_args` - Whether to dereference `path` if it's a symlink.
699-
///
700-
/// * `follow_symlinks` - Dereference symlinks encountered (also including `path`).
697+
/// * `opts` - TODO
701698
///
702699
/// # Return
703700
///
@@ -749,17 +746,29 @@ where
749746
match process_file(
750747
&path_stack,
751748
&starting_dir,
752-
dir_filename,
749+
dir_filename.clone(),
753750
follow_symlinks_on_args || follow_symlinks,
751+
false,
754752
&mut file_handler,
755753
&mut err_reporter,
756-
false,
757-
false,
758754
) {
759-
ProcessFileResult::ProcessedDirectory(node) => match node {
760-
NodeOrMetadata::TreeNode(node) => stack.push(node),
761-
NodeOrMetadata::Metadata(_) => unreachable!(),
762-
},
755+
ProcessFileResult::ProcessedDirectory(entry) => {
756+
match OwnedDir::open_at(&starting_dir, dir_filename.as_ptr()) {
757+
Ok(new_dir) => {
758+
let node = TreeNode {
759+
dir: HybridDir::Owned(new_dir),
760+
filename: dir_filename,
761+
metadata: entry.metadata.unwrap(),
762+
path_depth: path_stack.len(),
763+
};
764+
stack.push(node);
765+
}
766+
Err(error) => {
767+
err_reporter(entry, error);
768+
return false;
769+
}
770+
}
771+
}
763772
ProcessFileResult::ProcessedFile => {
764773
// `path` was not a directory
765774
return false;
@@ -802,10 +811,16 @@ where
802811
HybridDir::Deferred(dir) => &dir.open_file_descriptor(),
803812
};
804813

814+
// Resize `path_stack` to the appropriate depth.
815+
debug_assert!(path_stack.len() >= current.path_depth);
816+
path_stack.truncate(current.path_depth);
817+
805818
// Push the directory's filename. The contents' filename will be concatenated to the
806819
// directory's filename.
807820
path_stack.push(current.filename.clone());
808821

822+
let path_depth = path_stack.len();
823+
809824
{
810825
let mut dir_iter = dir.iter();
811826

@@ -830,8 +845,8 @@ where
830845
// Need to report the filename of the directory itself so exclude
831846
// the last one
832847
&path_stack[..(path_stack.len() - 2)],
833-
&current.filename,
834-
Some(&current.metadata),
848+
current.filename.clone(),
849+
Some(current.metadata.clone()),
835850
),
836851
Error::new(e, ErrorKind::ReadDir),
837852
);
@@ -866,15 +881,13 @@ where
866881
dir_fd,
867882
entry_filename.clone(),
868883
follow_symlinks,
884+
is_dot_or_double_dot,
869885
&mut file_handler,
870886
&mut err_reporter,
871-
is_dot_or_double_dot,
872-
conserve_fds,
873887
) {
874-
ProcessFileResult::ProcessedDirectory(node_or_metadata) => {
875-
let node = match node_or_metadata {
876-
NodeOrMetadata::TreeNode(node) => node,
877-
NodeOrMetadata::Metadata(metadata) => match dir {
888+
ProcessFileResult::ProcessedDirectory(entry) => {
889+
let node = if conserve_fds {
890+
match dir {
878891
HybridDir::Owned(current_dir) => {
879892
let path = build_path(&path_stack, &entry_filename);
880893
let slow_dir = DeferredDir::new(
@@ -887,7 +900,8 @@ where
887900
TreeNode {
888901
dir: HybridDir::Deferred(slow_dir),
889902
filename: entry_filename,
890-
metadata,
903+
metadata: entry.metadata.unwrap(),
904+
path_depth,
891905
}
892906
}
893907
HybridDir::Deferred(current_dir) => {
@@ -898,10 +912,25 @@ where
898912
TreeNode {
899913
dir: HybridDir::Deferred(slow_dir),
900914
filename: entry_filename,
901-
metadata,
915+
metadata: entry.metadata.unwrap(),
916+
path_depth,
902917
}
903918
}
904-
},
919+
}
920+
} else {
921+
match OwnedDir::open_at(dir_fd, entry_filename.as_ptr()) {
922+
Ok(new_dir) => TreeNode {
923+
dir: HybridDir::Owned(new_dir),
924+
filename: entry_filename,
925+
metadata: entry.metadata.unwrap(),
926+
path_depth,
927+
},
928+
Err(error) => {
929+
err_reporter(entry, error);
930+
success = false;
931+
continue;
932+
}
933+
}
905934
};
906935

907936
if list_contents_first {
@@ -951,15 +980,14 @@ where
951980
if let Err(_) = postprocess_dir(Entry::new(
952981
prev_dir,
953982
&path_stack,
954-
&current.filename,
955-
Some(&current.metadata),
983+
current.filename.clone(),
984+
Some(current.metadata.clone()),
956985
)) {
957986
success = false;
958987
// Don't `continue` here, falldown below
959988
}
960989

961-
// Go up a level in the tree
962-
path_stack.pop();
990+
// Process the next node
963991
stack.pop().unwrap();
964992
}
965993

ftw/tests/integration.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ fn test_ftw_simple() {
4848
filenames.sort();
4949
assert_eq!(expected_filenames.as_slice(), filenames.as_slice());
5050

51+
// Test listing files like in ls
52+
filenames.clear();
53+
ftw::traverse_directory(
54+
test_dir,
55+
|entry| {
56+
let s = format!("{}", entry.path());
57+
filenames.push(s);
58+
Ok(true)
59+
},
60+
|_| Ok(()),
61+
|_, e| panic!("{}", e.inner()),
62+
ftw::TraverseDirectoryOpts {
63+
list_contents_first: true,
64+
..Default::default()
65+
},
66+
);
67+
filenames.sort();
68+
assert_eq!(expected_filenames.as_slice(), filenames.as_slice());
69+
5170
fs::remove_dir_all(test_dir).unwrap();
5271
}
5372

0 commit comments

Comments
 (0)