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

Implement -ls and -fls #435

Merged
merged 14 commits into from
Sep 5, 2024
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 => "-",
_ => "?",

Check warning on line 19 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L19

Added line #L19 was not covered by tests
};

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

Check warning on line 28 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L28

Added line #L28 was not covered by tests
},
if mode & uucore::libc::S_IWUSR != 0 {
"w"
} else {
"-"

Check warning on line 33 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L33

Added line #L33 was not covered by tests
},
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 {
"-"

Check warning on line 48 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L48

Added line #L48 was not covered by tests
},
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 {
"-"

Check warning on line 68 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L68

Added line #L68 was not covered by tests
},
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))

Check warning on line 141 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L141

Added line #L141 was not covered by tests
}
};
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) => {

Check warning on line 177 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L177

Added line #L177 was not covered by tests
if print_error_message {
writeln!(
&mut stderr(),

Check warning on line 180 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L179-L180

Added lines #L179 - L180 were not covered by tests
"Error writing {:?} for {}",
file_info.path().to_string_lossy(),

Check warning on line 182 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L182

Added line #L182 was not covered by tests
e
)
.unwrap();
uucore::error::set_exit_code(1);

Check warning on line 186 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L185-L186

Added lines #L185 - L186 were not covered by tests
}
}

Check warning on line 188 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L188

Added line #L188 was not covered by tests
}
}

#[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);

Check warning on line 259 in src/find/matchers/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/ls.rs#L259

Added line #L259 was not covered by tests
} 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);
}
}
12 changes: 12 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 group;
mod lname;
mod logical_matchers;
mod ls;
mod name;
mod path;
mod perm;
Expand All @@ -33,6 +34,7 @@
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 @@ -462,6 +464,16 @@
let file = get_or_create_file(args[i])?;
Some(Printer::new(PrintDelimiter::Newline, Some(file)).into_box())
}
"-ls" => Some(Ls::new(None).into_box()),
"-fls" => {
if i >= args.len() - 1 {
return Err(From::from(format!("missing argument to {}", args[i])));
}
i += 1;

Check warning on line 472 in src/find/matchers/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/mod.rs#L472

Added line #L472 was not covered by tests

let file = get_or_create_file(args[i])?;
Some(Ls::new(Some(file)).into_box())
}

Check warning on line 476 in src/find/matchers/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/mod.rs#L475-L476

Added lines #L475 - L476 were not covered by tests
"-true" => Some(TrueMatcher.into_box()),
"-false" => Some(FalseMatcher.into_box()),
"-lname" | "-ilname" => {
Expand Down
9 changes: 9 additions & 0 deletions src/find/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1429,4 +1429,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 @@ -983,3 +983,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());
}
Loading