Skip to content

Commit 6ac7a89

Browse files
committed
make checking for hardlinks an optional setting for every search root
1 parent 90108ef commit 6ac7a89

File tree

5 files changed

+55
-29
lines changed

5 files changed

+55
-29
lines changed

docs/usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ When you put a directory to search at, you can specify some options.
295295
| mindepth N | Minimum search depth. Default is unlimited. Depth 1 means skip one directory level and search further. |
296296
| maxdepth N | Maximum search depth. Default is unlimited. Depth 1 means search the mentioned directory only. Depth 2 means search mentioned directory and its subdirectories. Synonym is `depth`. |
297297
| symlinks | If specified, search process will follow symlinks. Default is not to follow. Synonym is `sym`. |
298+
| hardlinks | If specified, search process will track and ignore hardlinks. `SLOW!` Default is not to track. Synonym is `hard`. |
298299
| archives | Search within archives. Only zip archives are supported. Default is not to include archived content into the search results. Synonym is `arc`. |
299300
| gitignore | Search respects `.gitignore` files found. Synonym is `git`. |
300301
| hgignore | Search respects `.hgignore` files found. Synonym is `hg`. |

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ Path Options:
321321
mindepth N Minimum search depth. Default is unlimited. Depth 1 means skip one directory level and search further.
322322
maxdepth N | depth N Maximum search depth. Default is unlimited. Depth 1 means search the mentioned directory only. Depth 2 means search mentioned directory and its subdirectories.
323323
symlinks | sym If specified, search process will follow symlinks. Default is not to follow.
324+
hardlinks | hard If specified, search process will track hardlinks. Default is not to track.
324325
archives | arc Search within archives. Only zip archives are supported. Default is not to include archived content into the search results.
325326
gitignore | git Search respects .gitignore files found.
326327
hgignore | hg Search respects .hgignore files found.

src/parser.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ impl Parser {
276276
let mut max_depth: u32 = 0;
277277
let mut archives = false;
278278
let mut symlinks = false;
279+
let mut hardlinks = false;
279280
let mut gitignore = None;
280281
let mut hgignore = None;
281282
let mut dockerignore = None;
@@ -299,6 +300,9 @@ impl Parser {
299300
} else if s.starts_with("sym") {
300301
symlinks = true;
301302
mode = RootParsingMode::Options;
303+
} else if s.starts_with("hard") {
304+
hardlinks = true;
305+
mode = RootParsingMode::Options;
302306
} else if s.starts_with("git") {
303307
#[cfg(feature = "git")]
304308
{
@@ -389,6 +393,7 @@ impl Parser {
389393
max_depth,
390394
archives,
391395
symlinks,
396+
hardlinks,
392397
gitignore,
393398
hgignore,
394399
dockerignore,
@@ -405,6 +410,7 @@ impl Parser {
405410
|| s == "maxdepth"
406411
|| s.starts_with("arc")
407412
|| s.starts_with("sym")
413+
|| s.starts_with("hard")
408414
|| s.starts_with("git")
409415
|| s.starts_with("hg")
410416
|| s.starts_with("dock")
@@ -1082,35 +1088,35 @@ mod tests {
10821088
vec![
10831089
Root::new(
10841090
String::from("/test"),
1085-
RootOptions::from(0, 2, false, false, None, None, None, Bfs, false)
1091+
RootOptions::from(0, 2, false, false, false, None, None, None, Bfs, false)
10861092
),
10871093
Root::new(
10881094
String::from("/test2"),
1089-
RootOptions::from(0, 0, true, false, None, None, None, Bfs, false)
1095+
RootOptions::from(0, 0, true, false, false, None, None, None, Bfs, false)
10901096
),
10911097
Root::new(
10921098
String::from("/test3"),
1093-
RootOptions::from(0, 3, true, false, None, None, None, Bfs, false)
1099+
RootOptions::from(0, 3, true, false, false, None, None, None, Bfs, false)
10941100
),
10951101
Root::new(
10961102
String::from("/test4"),
1097-
RootOptions::from(0, 0, false, false, None, None, None, Bfs, false)
1103+
RootOptions::from(0, 0, false, false, false, None, None, None, Bfs, false)
10981104
),
10991105
Root::new(
11001106
String::from("/test5"),
1101-
RootOptions::from(0, 0, false, false, Some(true), None, None, Bfs, false)
1107+
RootOptions::from(0, 0, false, false, false, Some(true), None, None, Bfs, false)
11021108
),
11031109
Root::new(
11041110
String::from("/test6"),
1105-
RootOptions::from(3, 0, false, false, None, None, None, Bfs, false)
1111+
RootOptions::from(3, 0, false, false, false, None, None, None, Bfs, false)
11061112
),
11071113
Root::new(
11081114
String::from("/test7"),
1109-
RootOptions::from(0, 0, true, false, None, None, None, Dfs, false)
1115+
RootOptions::from(0, 0, true, false, false, None, None, None, Dfs, false)
11101116
),
11111117
Root::new(
11121118
String::from("/test8"),
1113-
RootOptions::from(0, 0, false, false, None, None, None, Dfs, false)
1119+
RootOptions::from(0, 0, false, false, false, None, None, None, Dfs, false)
11141120
),
11151121
]
11161122
);
@@ -1166,7 +1172,7 @@ mod tests {
11661172
query.roots,
11671173
vec![Root::new(
11681174
String::from("/test"),
1169-
RootOptions::from(0, 0, false, false, None, None, None, Bfs, false)
1175+
RootOptions::from(0, 0, false, false, false, None, None, None, Bfs, false)
11701176
),]
11711177
);
11721178

@@ -1191,7 +1197,7 @@ mod tests {
11911197
query.roots,
11921198
vec![Root::new(
11931199
String::from("/test"),
1194-
RootOptions::from(0, 0, false, false, None, None, None, Bfs, false)
1200+
RootOptions::from(0, 0, false, false, false, None, None, None, Bfs, false)
11951201
),]
11961202
);
11971203

@@ -1297,7 +1303,7 @@ mod tests {
12971303
query.roots,
12981304
vec![Root::new(
12991305
String::from("/opt/Some Cool Dir/Test This"),
1300-
RootOptions::from(0, 0, false, false, None, None, None, Bfs, false)
1306+
RootOptions::from(0, 0, false, false, false, None, None, None, Bfs, false)
13011307
),]
13021308
);
13031309
}
@@ -1367,7 +1373,7 @@ mod tests {
13671373
query.roots,
13681374
vec![Root::new(
13691375
String::from("/test"),
1370-
RootOptions::from(2, 0, false, false, Some(true), None, None, Bfs, false)
1376+
RootOptions::from(2, 0, false, false, false, Some(true), None, None, Bfs, false)
13711377
),]
13721378
);
13731379

@@ -1402,7 +1408,7 @@ mod tests {
14021408
query.roots,
14031409
vec![Root::new(
14041410
String::from("."),
1405-
RootOptions::from(0, 2, false, false, None, None, None, Bfs, false)
1411+
RootOptions::from(0, 2, false, false, false, None, None, None, Bfs, false)
14061412
),]
14071413
);
14081414
}
@@ -1438,7 +1444,7 @@ mod tests {
14381444
query.roots,
14391445
vec![Root::new(
14401446
String::from("/test"),
1441-
RootOptions::from(0, 0, false, false, None, None, None, Bfs, false)
1447+
RootOptions::from(0, 0, false, false, false, None, None, None, Bfs, false)
14421448
),]
14431449
);
14441450

@@ -1471,7 +1477,7 @@ mod tests {
14711477
query.roots,
14721478
vec![Root::new(
14731479
String::from("/test"),
1474-
RootOptions::from(0, 0, false, false, None, None, None, Dfs, false)
1480+
RootOptions::from(0, 0, false, false, false, None, None, None, Dfs, false)
14751481
),]
14761482
);
14771483
}

src/query.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ pub struct RootOptions {
6868
pub archives: bool,
6969
/// Whether to follow symlinks
7070
pub symlinks: bool,
71+
/// Whether to track hardlinks
72+
pub hardlinks: bool,
7173
/// Whether to respect .gitignore files
7274
pub gitignore: Option<bool>,
7375
/// Whether to respect .hgignore files
@@ -87,6 +89,7 @@ impl RootOptions {
8789
max_depth: 0,
8890
archives: false,
8991
symlinks: false,
92+
hardlinks: false,
9093
gitignore: None,
9194
hgignore: None,
9295
dockerignore: None,
@@ -101,6 +104,7 @@ impl RootOptions {
101104
max_depth: u32,
102105
archives: bool,
103106
symlinks: bool,
107+
hardlinks: bool,
104108
gitignore: Option<bool>,
105109
hgignore: Option<bool>,
106110
dockerignore: Option<bool>,
@@ -112,6 +116,7 @@ impl RootOptions {
112116
max_depth,
113117
archives,
114118
symlinks,
119+
hardlinks,
115120
gitignore,
116121
hgignore,
117122
dockerignore,

src/searcher.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -370,13 +370,16 @@ impl<'a> Searcher<'a> {
370370

371371
#[cfg(unix)]
372372
{
373-
let metadata = match self.current_follow_symlinks {
374-
true => root_dir.metadata(),
375-
false => symlink_metadata(root_dir),
376-
};
377-
if let Ok(metadata) = metadata {
378-
self.visited_inodes.insert(metadata.ino());
379-
}
373+
let hardlinks = root.options.hardlinks;
374+
if hardlinks {
375+
let metadata = match self.current_follow_symlinks {
376+
true => root_dir.metadata(),
377+
false => symlink_metadata(root_dir),
378+
};
379+
if let Ok(metadata) = metadata {
380+
self.visited_inodes.insert(metadata.ino());
381+
}
382+
}
380383
}
381384

382385
let _result = self.visit_dir(
@@ -392,6 +395,8 @@ impl<'a> Searcher<'a> {
392395
apply_dockerignore,
393396
traversal_mode,
394397
true,
398+
#[cfg(unix)]
399+
hardlinks,
395400
);
396401
}
397402

@@ -572,6 +577,8 @@ impl<'a> Searcher<'a> {
572577
apply_dockerignore: bool,
573578
traversal_mode: TraversalMode,
574579
process_queue: bool,
580+
#[cfg(unix)]
581+
hardlinks: bool,
575582
) -> io::Result<()> {
576583
// Prevents infinite loops when following symlinks
577584
if self.current_follow_symlinks {
@@ -700,7 +707,7 @@ impl<'a> Searcher<'a> {
700707
ok = true;
701708
}
702709

703-
if ok && self.ok_to_visit_dir(&entry, file_type) {
710+
if ok && self.ok_to_visit_dir(&entry, file_type, #[cfg(unix)] hardlinks) {
704711
if traversal_mode == TraversalMode::Dfs {
705712
#[cfg(feature = "git")]
706713
let repo;
@@ -726,6 +733,8 @@ impl<'a> Searcher<'a> {
726733
apply_dockerignore,
727734
traversal_mode,
728735
false,
736+
#[cfg(unix)]
737+
hardlinks,
729738
);
730739

731740
if result.is_err() {
@@ -786,6 +795,8 @@ impl<'a> Searcher<'a> {
786795
apply_dockerignore,
787796
traversal_mode,
788797
false,
798+
#[cfg(unix)]
799+
hardlinks,
789800
);
790801

791802
if result.is_err() {
@@ -799,12 +810,14 @@ impl<'a> Searcher<'a> {
799810
}
800811

801812
#[cfg(unix)]
802-
fn ok_to_visit_dir(&mut self, entry: &DirEntry, file_type: FileType) -> bool {
803-
let ino = entry.ino();
804-
if self.visited_inodes.contains(&ino) {
805-
return false;
806-
} else {
807-
self.visited_inodes.insert(ino);
813+
fn ok_to_visit_dir(&mut self, entry: &DirEntry, file_type: FileType, hardlinks: bool) -> bool {
814+
if hardlinks {
815+
let ino = entry.ino();
816+
if self.visited_inodes.contains(&ino) {
817+
return false;
818+
} else {
819+
self.visited_inodes.insert(ino);
820+
}
808821
}
809822

810823
match self.current_follow_symlinks {

0 commit comments

Comments
 (0)