mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Added support for -perm (#20)
* Deleted vertical whitespace
In an attempt to get around a mistake I made when merging from the
master fork
* Added a default implementation for has_side_effects
And removed all the "return false" implementations (as specified by most
of the Matchers).
* Added support for -exec and -execdir
* Fixed path_to_testing_commandline
* Minor tweaks from code review
* Added support for -perm
* Fixed string constant
* Fixed error caused by merging changes
* Tweaks after code review
* Fixed windows compatability. This required
- changing a lot of test code to supply/expect backslashes rather than
slashes when running on windows
- tweaking the way testing-commandline reported its args (debug format
escapes backslashes when printing strings)
- changing the NewerMatcher to get metadata from fs::metadata() rather
than from File's metadata method (the latter caused mysterious test
failures, which I didn't bother tracking down becasue the former is more
efficient anyway)
- hiding more of perm.rs behind #[cfg(unix)] to stop "unused X"
warnings
* Revert "Fixed windows compatability. This required"
This reverts commit 0e2c385237.
This commit is contained in:
+1
-1
@@ -18,4 +18,4 @@ path = "src/find/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "testing-commandline"
|
||||
path = "src/testing/commandline/main.rs"
|
||||
path = "src/testing/commandline/main.rs"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
pub mod exec;
|
||||
mod logical_matchers;
|
||||
mod name;
|
||||
mod perm;
|
||||
mod printer;
|
||||
mod prune;
|
||||
mod size;
|
||||
@@ -277,6 +278,13 @@ fn build_matcher_tree(args: &[&str],
|
||||
exec_args,
|
||||
expression == "-execdir")?)
|
||||
}
|
||||
"-perm" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(perm::PermMatcher::new_box(args[i])?)
|
||||
}
|
||||
"-prune" => Some(prune::PruneMatcher::new_box()),
|
||||
"-not" | "!" => {
|
||||
if !are_more_expressions(args, i) {
|
||||
@@ -796,4 +804,57 @@ mod tests {
|
||||
.expect("parsing argument list with exec that takes brackets and -os should work");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn build_top_level_matcher_perm() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
let mut config = Config::default();
|
||||
|
||||
// this should match: abbbc is readable
|
||||
let matcher_readable = build_top_level_matcher(&["-perm", "-u+r"], &mut config).unwrap();
|
||||
// this shouldn't match: abbbc isn't executable
|
||||
let matcher_executable = build_top_level_matcher(&["-perm", "-u+x"], &mut config).unwrap();
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher_readable.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(deps.get_output_as_string(), "./test_data/simple/abbbc\n");
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher_executable.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(deps.get_output_as_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn build_top_level_matcher_perm_bad() {
|
||||
let mut config = Config::default();
|
||||
if let Err(e) = build_top_level_matcher(&["-perm", "foo"], &mut config) {
|
||||
assert!(e.description().contains("invalid mode"));
|
||||
} else {
|
||||
panic!("-perm with bad mode pattern should fail");
|
||||
}
|
||||
|
||||
if let Err(e) = build_top_level_matcher(&["-perm"], &mut config) {
|
||||
assert!(e.description().contains("missing argument"));
|
||||
} else {
|
||||
panic!("-perm with no mode pattern should fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(unix))]
|
||||
fn build_top_level_matcher_perm_not_unix() {
|
||||
let mut config = Config::default();
|
||||
if let Err(e) = build_top_level_matcher(&["-perm", "444"], &mut config) {
|
||||
assert!(e.description().contains("not available"));
|
||||
} else {
|
||||
panic!("-perm on non-unix systems shouldn't be available");
|
||||
}
|
||||
|
||||
if let Err(e) = build_top_level_matcher(&["-perm"], &mut config) {
|
||||
assert!(e.description().contains("missing argument"));
|
||||
} else {
|
||||
panic!("-perm with no mode pattern should fail");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! find's permission matching uses a very unix-centric approach, that would
|
||||
//! be tricky to both implement and use on a windows platform. So we don't
|
||||
//! even try.
|
||||
|
||||
use std::error::Error;
|
||||
use std::io::{stderr, Write};
|
||||
use std::str::FromStr;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ComparisonType {
|
||||
/// mode bits have to match exactly
|
||||
Exact,
|
||||
/// all specified mode bits must be set. Others can be as well
|
||||
AtLeast,
|
||||
/// at least one of the specified bits must be set (or if no bits are
|
||||
/// specified then any mode will match)
|
||||
AnyOf,
|
||||
}
|
||||
|
||||
impl FromStr for ComparisonType {
|
||||
type Err = Box<Error>;
|
||||
fn from_str(s: &str) -> Result<ComparisonType, Box<Error>> {
|
||||
Ok(match s {
|
||||
"" => ComparisonType::Exact,
|
||||
"-" => ComparisonType::AtLeast,
|
||||
"/" => ComparisonType::AnyOf,
|
||||
_ => {
|
||||
return Err(From::from(format!("Invalid prefix {} for -perm. Only allowed \
|
||||
values are <nothing>, /, or -",
|
||||
s)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ComparisonType {
|
||||
fn mode_bits_match(&self, pattern: u32, value: u32) -> bool {
|
||||
match *self {
|
||||
ComparisonType::Exact => (0o7777 & value) == pattern,
|
||||
ComparisonType::AtLeast => (value & pattern) == pattern,
|
||||
ComparisonType::AnyOf => pattern == 0 || (value & pattern) > 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod parsing {
|
||||
use regex::Regex;
|
||||
use std::error::Error;
|
||||
use super::*;
|
||||
|
||||
// We need to be able to parse strings like /u+rw,g+w,o=w. Specifically
|
||||
// we have a prefix, as per ComparisonType. then combinations of (u, g or o
|
||||
// followed by a + or =, followed by combinations of r w and x) separated by
|
||||
// commas. Writing a hard-coded parser is easier to understand - and probably
|
||||
// shorter :-) - than the abomination required to do this as a regex
|
||||
enum ParserState {
|
||||
Beginning, // we're at the start, now waiting for /, -, a, u, g or o
|
||||
GatheringCategories, // expecting u, g or o to set categories, or =/+ to switch to...
|
||||
GatheringPermissions, // expecting r, w, or x
|
||||
}
|
||||
|
||||
struct Parser<'a> {
|
||||
state: ParserState,
|
||||
bit_pattern: u32,
|
||||
comparison_type: ComparisonType,
|
||||
string_pattern: &'a str,
|
||||
category_bit_pattern: u32,
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn new(string_pattern: &'a str) -> Parser<'a> {
|
||||
Parser {
|
||||
state: ParserState::Beginning,
|
||||
bit_pattern: 0,
|
||||
comparison_type: ComparisonType::Exact,
|
||||
string_pattern: string_pattern,
|
||||
category_bit_pattern: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn error(&self) -> Result<(), Box<Error>> {
|
||||
Err(From::from(format!("invalid mode '{}'", self.string_pattern)))
|
||||
}
|
||||
|
||||
fn handle_char(&mut self, char: &char) -> Result<(), Box<Error>> {
|
||||
if let ParserState::Beginning = self.state {};
|
||||
|
||||
|
||||
match *char {
|
||||
'-' => {
|
||||
if let ParserState::Beginning = self.state {
|
||||
self.comparison_type = ComparisonType::AtLeast;
|
||||
self.state = ParserState::GatheringCategories;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
if let ParserState::Beginning = self.state {
|
||||
self.comparison_type = ComparisonType::AnyOf;
|
||||
self.state = ParserState::GatheringCategories;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
'a' => {
|
||||
match self.state {
|
||||
ParserState::Beginning |
|
||||
ParserState::GatheringCategories => {
|
||||
self.state = ParserState::GatheringCategories;
|
||||
self.category_bit_pattern = 0o111;
|
||||
}
|
||||
_ => {
|
||||
return self.error();
|
||||
}
|
||||
};
|
||||
}
|
||||
'g' => {
|
||||
match self.state {
|
||||
ParserState::Beginning |
|
||||
ParserState::GatheringCategories => {
|
||||
self.state = ParserState::GatheringCategories;
|
||||
self.category_bit_pattern |= 0o010;
|
||||
}
|
||||
_ => {
|
||||
return self.error();
|
||||
}
|
||||
};
|
||||
}
|
||||
'u' => {
|
||||
match self.state {
|
||||
ParserState::Beginning |
|
||||
ParserState::GatheringCategories => {
|
||||
self.state = ParserState::GatheringCategories;
|
||||
self.category_bit_pattern |= 0o100;
|
||||
}
|
||||
_ => {
|
||||
return self.error();
|
||||
}
|
||||
};
|
||||
}
|
||||
'o' => {
|
||||
match self.state {
|
||||
ParserState::Beginning |
|
||||
ParserState::GatheringCategories => {
|
||||
self.state = ParserState::GatheringCategories;
|
||||
self.category_bit_pattern |= 0o001;
|
||||
}
|
||||
_ => {
|
||||
return self.error();
|
||||
}
|
||||
};
|
||||
}
|
||||
'=' | '+' => {
|
||||
if let ParserState::GatheringCategories = self.state {
|
||||
self.state = ParserState::GatheringPermissions;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
'r' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
self.bit_pattern |= self.category_bit_pattern << 2;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
'w' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
self.bit_pattern |= self.category_bit_pattern << 1;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
'x' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
self.bit_pattern |= self.category_bit_pattern;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
't' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
self.bit_pattern |= 0o1000;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
's' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
// if we're setting group bits, then set the set-group-id bit
|
||||
if (self.category_bit_pattern & 0o010) == 0o010 {
|
||||
self.bit_pattern |= 0o2000;
|
||||
}
|
||||
// if we're setting user bits, then set the set-user-id bit
|
||||
if (self.category_bit_pattern & 0o100) == 0o100 {
|
||||
self.bit_pattern |= 0o4000;
|
||||
}
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
',' => {
|
||||
if let ParserState::GatheringPermissions = self.state {
|
||||
self.state = ParserState::GatheringCategories;
|
||||
self.category_bit_pattern = 0;
|
||||
} else {
|
||||
return self.error();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return self.error();
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(string_value: &str) -> Result<(u32, ComparisonType), Box<Error>> {
|
||||
// safe to unwrap as the regex is a compile-time constant.
|
||||
let re = Regex::new("^([/-]?)([0-7]+)$").unwrap();
|
||||
|
||||
// have we been given a simple octal based string (e.g. /222)?
|
||||
if let Some(m) = re.captures(string_value) {
|
||||
// all these unwraps are safe because we checked the string in the regex above
|
||||
match u32::from_str_radix(m.get(2).unwrap().as_str(), 8) {
|
||||
Ok(val) => {
|
||||
return Ok((val, m.get(1).unwrap().as_str().parse().unwrap()));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(From::from(format!("Failed to parse -perm argument {}: {}",
|
||||
m.get(2).unwrap().as_str(),
|
||||
e)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// no: so we've got a /u=rw,g=r form instead (or an invalid string).
|
||||
let mut p = Parser::new(string_value);
|
||||
for c in string_value.chars() {
|
||||
p.handle_char(&c)?;
|
||||
}
|
||||
Ok((p.bit_pattern, p.comparison_type))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PermMatcher {
|
||||
pattern: u32,
|
||||
comparison_type: ComparisonType,
|
||||
}
|
||||
|
||||
impl PermMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn new(pattern: &str) -> Result<PermMatcher, Box<Error>> {
|
||||
let (bit_pattern, comparison_type) = parsing::parse(pattern)?;
|
||||
Ok(PermMatcher {
|
||||
pattern: bit_pattern,
|
||||
comparison_type: comparison_type,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new(pattern: &str) -> Result<PermMatcher, Box<Error>> {
|
||||
Err(From("Permission matching is not available on this platform"))
|
||||
}
|
||||
|
||||
pub fn new_box(pattern: &str) -> Result<Box<Matcher>, Box<Error>> {
|
||||
Ok(Box::new(PermMatcher::new(pattern)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for PermMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => {
|
||||
self.comparison_type.mode_bits_match(self.pattern, metadata.permissions().mode())
|
||||
}
|
||||
Err(e) => {
|
||||
writeln!(&mut stderr(),
|
||||
"Error getting permissions for {}: {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
stderr().write("Permission matching not available on this platform!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use find::matchers::Matcher;
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
use super::parsing;
|
||||
|
||||
#[test]
|
||||
fn parsing_prefix() {
|
||||
assert_eq!(parsing::parse("u=rwx").unwrap(),
|
||||
(0o700, ComparisonType::Exact));
|
||||
assert_eq!(parsing::parse("-u=rwx").unwrap(),
|
||||
(0o700, ComparisonType::AtLeast));
|
||||
assert_eq!(parsing::parse("/u=rwx").unwrap(),
|
||||
(0o700, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("700").unwrap(),
|
||||
(0o700, ComparisonType::Exact));
|
||||
assert_eq!(parsing::parse("-700").unwrap(),
|
||||
(0o700, ComparisonType::AtLeast));
|
||||
assert_eq!(parsing::parse("/700").unwrap(),
|
||||
(0o700, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_octal() {
|
||||
assert_eq!(parsing::parse("/1").unwrap(), (0o1, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/7777").unwrap(),
|
||||
(0o7777, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_human_readable_individual_bits() {
|
||||
assert_eq!(parsing::parse("/").unwrap(), (0o0, ComparisonType::AnyOf));
|
||||
|
||||
assert_eq!(parsing::parse("/u=r").unwrap(),
|
||||
(0o400, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/u=w").unwrap(),
|
||||
(0o200, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/u=x").unwrap(),
|
||||
(0o100, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/g=r").unwrap(),
|
||||
(0o40, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/g=w").unwrap(),
|
||||
(0o20, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/g=x").unwrap(),
|
||||
(0o10, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/o+r").unwrap(),
|
||||
(0o4, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/o+w").unwrap(),
|
||||
(0o2, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/o+x").unwrap(),
|
||||
(0o1, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/a+r").unwrap(),
|
||||
(0o444, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/a+w").unwrap(),
|
||||
(0o222, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/a+x").unwrap(),
|
||||
(0o111, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_human_readable_multiple_bits() {
|
||||
assert_eq!(parsing::parse("/u=rwx").unwrap(),
|
||||
(0o700, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/a=rwx").unwrap(),
|
||||
(0o777, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_human_readable_multiple_categories() {
|
||||
assert_eq!(parsing::parse("/u=rwx,g=rx,o+r").unwrap(),
|
||||
(0o754, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/u=rwx,g=rx,o+r,a+w").unwrap(),
|
||||
(0o776, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/ug=rwx,o+r").unwrap(),
|
||||
(0o774, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_human_readable_set_id_bits() {
|
||||
assert_eq!(parsing::parse("/u=s").unwrap(),
|
||||
(0o4000, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/g=s").unwrap(),
|
||||
(0o2000, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/ug=s").unwrap(),
|
||||
(0o6000, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/o=s").unwrap(),
|
||||
(0o0000, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_human_readable_sticky_bit() {
|
||||
assert_eq!(parsing::parse("/u=t").unwrap(),
|
||||
(0o1000, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/g=t").unwrap(),
|
||||
(0o1000, ComparisonType::AnyOf));
|
||||
assert_eq!(parsing::parse("/o=t").unwrap(),
|
||||
(0o1000, ComparisonType::AnyOf));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn parsing_fails() {
|
||||
assert!(parsing::parse("+u=rwx,g=rx,o+r").is_err(),
|
||||
"invalid prefix should fail");
|
||||
assert!(parsing::parse("urwx,g=rx,o+r").is_err(),
|
||||
"missing equals should fail");
|
||||
assert!(parsing::parse("d=rwx,g=rx,o+r").is_err(),
|
||||
"invalid category should fail");
|
||||
assert!(parsing::parse("u=dwx,g=rx,o+r").is_err(),
|
||||
"invalid permission bit should fail");
|
||||
assert!(parsing::parse("u=rwxg=rx,o+r").is_err(),
|
||||
"missing comma should fail");
|
||||
assert!(parsing::parse("u_rwx,g=rx,o+r").is_err(),
|
||||
"invalid category/permissoin spearator should fail");
|
||||
assert!(parsing::parse("77777777777777").is_err(),
|
||||
"overflowing octal value should fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comparison_type_matching() {
|
||||
let c = ComparisonType::Exact;
|
||||
assert!(c.mode_bits_match(0, 0),
|
||||
"Exact: only 0 should match if pattern is 0");
|
||||
assert!(!c.mode_bits_match(0, 0o444),
|
||||
"Exact: only 0 should match if pattern is 0");
|
||||
assert!(c.mode_bits_match(0o444, 0o444),
|
||||
"Exact: identical bits should match");
|
||||
assert!(!c.mode_bits_match(0o444, 0o777),
|
||||
"Exact: non-identical bits should fail");
|
||||
assert!(c.mode_bits_match(0o444, 0o70444),
|
||||
"Exact:high-end bits should be ignored");
|
||||
|
||||
|
||||
|
||||
let c = ComparisonType::AtLeast;
|
||||
assert!(c.mode_bits_match(0, 0),
|
||||
"AtLeast: anything should match if pattern is 0");
|
||||
assert!(c.mode_bits_match(0, 0o444),
|
||||
"AtLeast: anything should match if pattern is 0");
|
||||
assert!(c.mode_bits_match(0o444, 0o777),
|
||||
"AtLeast: identical bits should match");
|
||||
assert!(c.mode_bits_match(0o444, 0o777),
|
||||
"AtLeast: extra bits should match");
|
||||
assert!(!c.mode_bits_match(0o444, 0o700),
|
||||
"AtLeast: missing bits should fail");
|
||||
assert!(c.mode_bits_match(0o444, 0o70444),
|
||||
"AtLeast: high-end bits should be ignored");
|
||||
|
||||
let c = ComparisonType::AnyOf;
|
||||
assert!(c.mode_bits_match(0, 0),
|
||||
"AnyOf: anything should match if pattern is 0");
|
||||
assert!(c.mode_bits_match(0, 0o444),
|
||||
"AnyOf: anything should match if pattern is 0");
|
||||
assert!(c.mode_bits_match(0o444, 0o777),
|
||||
"AnyOf: identical bits should match");
|
||||
assert!(c.mode_bits_match(0o444, 0o777),
|
||||
"AnyOf: extra bits should match");
|
||||
assert!(c.mode_bits_match(0o777, 0o001),
|
||||
"AnyOf: anything should match as long as it has one bit in common");
|
||||
assert!(!c.mode_bits_match(0o010, 0o001),
|
||||
"AnyOf: no matching bits shouldn't match");
|
||||
assert!(c.mode_bits_match(0o444, 0o70444),
|
||||
"AnyOf: high-end bits should be ignored");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn perm_matches() {
|
||||
let file_info = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = PermMatcher::new("-u+r").unwrap();
|
||||
assert!(matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"user-readable pattern should match file");
|
||||
|
||||
let matcher = PermMatcher::new("-u+x").unwrap();
|
||||
assert!(!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"user-executable pattern should not match file");
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -145,7 +145,7 @@ fn do_find<'a>(args: &[&str], deps: &'a Dependencies<'a>) -> Result<u64, Box<Err
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!("Usage: find [path...] [expression]
|
||||
println!(r"Usage: find [path...] [expression]
|
||||
|
||||
If no path is supplied then the current working directory is used by default.
|
||||
|
||||
@@ -170,6 +170,7 @@ Early alpha implementation. Currently the only expressions supported are
|
||||
-ctime [+-]N
|
||||
-atime [+-]N
|
||||
-mtime [+-]N
|
||||
-perm [-/]{{octal|u=rwx,go=w}}
|
||||
-newer path_to_file
|
||||
-exec[dir] executable [args] [{{}}] [more args] ;
|
||||
-sorted
|
||||
|
||||
Reference in New Issue
Block a user