Skip to content

Commit

Permalink
Merge branch 'main' into implement-380
Browse files Browse the repository at this point in the history
  • Loading branch information
sylvestre authored Sep 5, 2024
2 parents 2d7695a + f9d74ad commit 1d2cd88
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 4 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

294 changes: 294 additions & 0 deletions src/find/matchers/ls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
// 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 chrono::DateTime;
use std::{
fs::File,
io::{stderr, Write},
};

use super::{Matcher, MatcherIO, WalkEntry};

#[cfg(unix)]
fn format_permissions(mode: uucore::libc::mode_t) -> String {
let file_type = match mode & (uucore::libc::S_IFMT as uucore::libc::mode_t) {
uucore::libc::S_IFDIR => "d",
uucore::libc::S_IFREG => "-",
_ => "?",
};

// S_$$USR means "user permissions"
let user_perms = format!(
"{}{}{}",
if mode & uucore::libc::S_IRUSR != 0 {
"r"
} else {
"-"
},
if mode & uucore::libc::S_IWUSR != 0 {
"w"
} else {
"-"
},
if mode & uucore::libc::S_IXUSR != 0 {
"x"
} else {
"-"
}
);

// S_$$GRP means "group permissions"
let group_perms = format!(
"{}{}{}",
if mode & uucore::libc::S_IRGRP != 0 {
"r"
} else {
"-"
},
if mode & uucore::libc::S_IWGRP != 0 {
"w"
} else {
"-"
},
if mode & uucore::libc::S_IXGRP != 0 {
"x"
} else {
"-"
}
);

// S_$$OTH means "other permissions"
let other_perms = format!(
"{}{}{}",
if mode & uucore::libc::S_IROTH != 0 {
"r"
} else {
"-"
},
if mode & uucore::libc::S_IWOTH != 0 {
"w"
} else {
"-"
},
if mode & uucore::libc::S_IXOTH != 0 {
"x"
} else {
"-"
}
);

format!("{}{}{}{}", file_type, user_perms, group_perms, other_perms)
}

#[cfg(windows)]
fn format_permissions(file_attributes: u32) -> String {
let mut attributes = Vec::new();

// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
if file_attributes & 0x0001 != 0 {
attributes.push("read-only");
}
if file_attributes & 0x0002 != 0 {
attributes.push("hidden");
}
if file_attributes & 0x0004 != 0 {
attributes.push("system");
}
if file_attributes & 0x0020 != 0 {
attributes.push("archive");
}
if file_attributes & 0x0040 != 0 {
attributes.push("compressed");
}
if file_attributes & 0x0080 != 0 {
attributes.push("offline");
}

attributes.join(", ")
}

pub struct Ls {
output_file: Option<File>,
}

impl Ls {
pub fn new(output_file: Option<File>) -> Self {
Self { output_file }
}

#[cfg(unix)]
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
use nix::unistd::{Gid, Group, Uid, User};
use std::os::unix::fs::{MetadataExt, PermissionsExt};

let metadata = file_info.metadata().unwrap();

let inode_number = metadata.ino();
let number_of_blocks = {
let size = metadata.size();
let number_of_blocks = size / 1024;
let remainder = number_of_blocks % 4;

if remainder == 0 {
if number_of_blocks == 0 {
4
} else {
number_of_blocks
}
} else {
number_of_blocks + (4 - (remainder))
}
};
let permission =
{ format_permissions(metadata.permissions().mode() as uucore::libc::mode_t) };
let hard_links = metadata.nlink();
let user = {
let uid = metadata.uid();
User::from_uid(Uid::from_raw(uid)).unwrap().unwrap().name
};
let group = {
let gid = metadata.gid();
Group::from_gid(Gid::from_raw(gid)).unwrap().unwrap().name
};
let size = metadata.size();
let last_modified = {
let system_time = metadata.modified().unwrap();
let now_utc: DateTime<chrono::Utc> = system_time.into();
now_utc.format("%b %e %H:%M")
};
let path = file_info.path().to_string_lossy();

match writeln!(
out,
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}",
inode_number,
number_of_blocks,
permission,
hard_links,
user,
group,
size,
last_modified,
path,
) {
Ok(_) => {}
Err(e) => {
if print_error_message {
writeln!(
&mut stderr(),
"Error writing {:?} for {}",
file_info.path().to_string_lossy(),
e
)
.unwrap();
uucore::error::set_exit_code(1);
}
}
}
}

#[cfg(windows)]
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
use std::os::windows::fs::MetadataExt;

let metadata = file_info.metadata().unwrap();

let inode_number = 0;
let number_of_blocks = {
let size = metadata.file_size();
let number_of_blocks = size / 1024;
let remainder = number_of_blocks % 4;

if remainder == 0 {
if number_of_blocks == 0 {
4
} else {
number_of_blocks
}
} else {
number_of_blocks + (4 - (remainder))
}
};
let permission = { format_permissions(metadata.file_attributes()) };
let hard_links = 0;
let user = 0;
let group = 0;
let size = metadata.file_size();
let last_modified = {
let system_time = metadata.modified().unwrap();
let now_utc: DateTime<chrono::Utc> = system_time.into();
now_utc.format("%b %e %H:%M")
};
let path = file_info.path().to_string_lossy();

match write!(
out,
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}\n",
inode_number,
number_of_blocks,
permission,
hard_links,
user,
group,
size,
last_modified,
path,
) {
Ok(_) => {}
Err(e) => {
if print_error_message {
writeln!(
&mut stderr(),
"Error writing {:?} for {}",
file_info.path().to_string_lossy(),
e
)
.unwrap();
uucore::error::set_exit_code(1);
}
}
}
}
}

impl Matcher for Ls {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
if let Some(file) = &self.output_file {
self.print(file_info, file, true);
} else {
self.print(
file_info,
&mut *matcher_io.deps.get_output().borrow_mut(),
false,
);
}
true
}

fn has_side_effects(&self) -> bool {
true
}
}

#[cfg(test)]
mod tests {
#[test]
#[cfg(unix)]
fn test_format_permissions() {
use super::format_permissions;

let mode: uucore::libc::mode_t = 0o100644;
let expected = "-rw-r--r--";
assert_eq!(format_permissions(mode), expected);

let mode: uucore::libc::mode_t = 0o040755;
let expected = "drwxr-xr-x";
assert_eq!(format_permissions(mode), expected);

let mode: uucore::libc::mode_t = 0o100777;
let expected = "-rwxrwxrwx";
assert_eq!(format_permissions(mode), expected);
}
}
5 changes: 5 additions & 0 deletions src/find/matchers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod glob;
mod group;
mod lname;
mod logical_matchers;
mod ls;
mod name;
mod path;
mod perm;
Expand All @@ -33,6 +34,7 @@ mod user;
use ::regex::Regex;
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use fs::FileSystemMatcher;
use ls::Ls;
use std::fs::{File, Metadata};
use std::path::Path;
use std::time::SystemTime;
Expand Down Expand Up @@ -470,6 +472,9 @@ fn build_matcher_tree(

let file = get_or_create_file(args[i])?;
Some(Printer::new(PrintDelimiter::Null, Some(file)).into_box())
"-ls" => Some(Ls::new(None).into_box()),
"-fls" => {
Some(Ls::new(Some(file)).into_box())
}
"-true" => Some(TrueMatcher.into_box()),
"-false" => Some(FalseMatcher.into_box()),
Expand Down
9 changes: 9 additions & 0 deletions src/find/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1433,4 +1433,13 @@ mod tests {
fix_up_slashes("./test_data/links/link-d\n")
);
}

#[test]
#[cfg(unix)]
fn test_ls() {
let deps = FakeDependencies::new();
let rc = find_main(&["find", "./test_data/simple/subdir", "-ls"], &deps);

assert_eq!(rc, 0);
}
}
11 changes: 11 additions & 0 deletions tests/find_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -991,3 +991,14 @@ fn find_follow() {
.stdout(predicate::str::contains("test_data/links/link-f"))
.stderr(predicate::str::is_empty());
}

#[test]
#[serial(working_dir)]
fn find_ls() {
Command::cargo_bin("find")
.expect("found binary")
.args(["./test_data/simple/subdir", "-ls"])
.assert()
.success()
.stderr(predicate::str::is_empty());
}

0 comments on commit 1d2cd88

Please sign in to comment.