Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

find: Implement -[no]user and -[no]group predicates. #368

Merged
merged 24 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ regex = "1.7"
once_cell = "1.19"
onig = { version = "6.4", default-features = false }
uucore = { version = "0.0.26", features = ["entries", "fs", "fsext", "mode"] }
nix = { version = "0.29", features = ["user"] }

[dev-dependencies]
assert_cmd = "2"
Expand Down Expand Up @@ -54,6 +55,14 @@ ci = ["github"]
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc",
]
# Publish jobs to run in CI
pr-run-mode = "plan"
144 changes: 144 additions & 0 deletions src/find/matchers/group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// This file is part of the uutils findutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

use super::Matcher;
hanbings marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(unix)]
use nix::unistd::Group;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;

pub struct GroupMatcher {
gid: Option<u32>,
}

impl GroupMatcher {
#[cfg(unix)]
pub fn new(group: String) -> GroupMatcher {
// get gid from group name
let Ok(group) = Group::from_name(group.as_str()) else {
return GroupMatcher { gid: None };
};

Check warning on line 23 in src/find/matchers/group.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/group.rs#L22-L23

Added lines #L22 - L23 were not covered by tests

let Some(group) = group else {
// This if branch is to determine whether a certain group exists in the system.
// If a certain group does not exist in the system,
// the result will need to be returned according to
// the flag bit of whether to invert the result.
return GroupMatcher { gid: None };
};

GroupMatcher {
gid: Some(group.gid.as_raw()),
}
}

#[cfg(windows)]
pub fn new(_group: String) -> GroupMatcher {
GroupMatcher { gid: None }
}

pub fn gid(&self) -> &Option<u32> {
&self.gid
}
}

impl Matcher for GroupMatcher {
#[cfg(unix)]
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
let Ok(metadata) = file_info.path().metadata() else {
return false;
};

let file_gid = metadata.gid();

// When matching the -group parameter in find/matcher/mod.rs,
// it has been judged that the group does not exist and an error is returned.
// So use unwarp() directly here.
self.gid.unwrap() == file_gid
}

#[cfg(windows)]
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
// The user group acquisition function for Windows systems is not implemented in MetadataExt,
// so it is somewhat difficult to implement it. :(
false
}
}

pub struct NoGroupMatcher {}

impl Matcher for NoGroupMatcher {
#[cfg(unix)]
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
use nix::unistd::Gid;

if file_info.path().is_symlink() {
return false;
}

let Ok(metadata) = file_info.path().metadata() else {
return true;

Check warning on line 83 in src/find/matchers/group.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/group.rs#L83

Added line #L83 was not covered by tests
};

let Ok(gid) = Group::from_gid(Gid::from_raw(metadata.gid())) else {
return true;
};

Check warning on line 88 in src/find/matchers/group.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/group.rs#L87-L88

Added lines #L87 - L88 were not covered by tests

let Some(_group) = gid else {
return true;

Check warning on line 91 in src/find/matchers/group.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/group.rs#L91

Added line #L91 was not covered by tests
};

false
}

#[cfg(windows)]
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
false
}
}

#[cfg(test)]
mod tests {
#[test]
#[cfg(unix)]
fn test_group_matcher() {
use crate::find::matchers::{group::GroupMatcher, tests::get_dir_entry_for, Matcher};
use crate::find::tests::FakeDependencies;
use chrono::Local;
use nix::unistd::{Gid, Group};
use std::fs::File;
use std::os::unix::fs::MetadataExt;
use tempfile::Builder;

let deps = FakeDependencies::new();
let mut matcher_io = deps.new_matcher_io();

let temp_dir = Builder::new().prefix("group_matcher").tempdir().unwrap();
let foo_path = temp_dir.path().join("foo");
let _ = File::create(foo_path).expect("create temp file");
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
let file_gid = file_info.path().metadata().unwrap().gid();
let file_group = Group::from_gid(Gid::from_raw(file_gid))
.unwrap()
.unwrap()
.name;

let matcher = super::GroupMatcher::new(file_group.clone());
assert!(
matcher.matches(&file_info, &mut matcher_io),
"group should match"
);

// Testing a non-existent group name
let time_string = Local::now().format("%Y%m%d%H%M%S").to_string();
let matcher = GroupMatcher::new(time_string.clone());
assert!(
matcher.gid().is_none(),
"group name {} should not exist",
time_string
);
}
}
54 changes: 54 additions & 0 deletions src/find/matchers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod delete;
mod empty;
pub mod exec;
mod glob;
mod group;
mod lname;
mod logical_matchers;
mod name;
Expand All @@ -23,6 +24,7 @@ mod size;
mod stat;
mod time;
mod type_matcher;
mod user;

use ::regex::Regex;
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
Expand All @@ -35,6 +37,7 @@ use self::access::AccessMatcher;
use self::delete::DeleteMatcher;
use self::empty::EmptyMatcher;
use self::exec::SingleExecMatcher;
use self::group::{GroupMatcher, NoGroupMatcher};
use self::lname::LinkNameMatcher;
use self::logical_matchers::{
AndMatcherBuilder, FalseMatcher, ListMatcherBuilder, NotMatcher, TrueMatcher,
Expand All @@ -54,6 +57,7 @@ use self::time::{
NewerOptionType, NewerTimeMatcher,
};
use self::type_matcher::TypeMatcher;
use self::user::{NoUserMatcher, UserMatcher};

use super::{Config, Dependencies};

Expand Down Expand Up @@ -507,6 +511,56 @@ fn build_matcher_tree(
i += 1;
Some(LinksMatcher::new(inum)?.into_box())
}
"-user" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}

let user = args[i + 1];

if user.is_empty() {
return Err(From::from("The argument to -user should not be empty"));
}

i += 1;
let matcher = UserMatcher::new(user.to_string());
match matcher.uid() {
Some(_) => Some(matcher.into_box()),
None => {
return Err(From::from(format!(
"{} is not the name of a known user",
user
)))
}
}
}
"-nouser" => Some(NoUserMatcher {}.into_box()),
"-group" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}

let group = args[i + 1];

if group.is_empty() {
return Err(From::from(
"Argument to -group is empty, but should be a group name",
));
}

i += 1;
let matcher = GroupMatcher::new(group.to_string());
match matcher.gid() {
Some(_) => Some(matcher.into_box()),
None => {
return Err(From::from(format!(
"{} is not the name of an existing group",
group
)))
}
}
}
"-nogroup" => Some(NoGroupMatcher {}.into_box()),
"-executable" => Some(AccessMatcher::Executable.into_box()),
"-perm" => {
if i >= args.len() - 1 {
Expand Down
Loading
Loading