Fix Bug: Handle -L with broken symlink #457 (#754)

This commit is contained in:
r3dArch 2022-11-27 00:26:21 -04:00 committed by GitHub
parent c48f0f48f5
commit f22ad5b2ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 32 deletions

View file

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#712](https://github.com/Peltoche/lsd/issues/712). Executables will be marked [#712](https://github.com/Peltoche/lsd/issues/712). Executables will be marked
based on the file extension: `exe`, `msi`, `bat` and `ps1`. based on the file extension: `exe`, `msi`, `bat` and `ps1`.
[`LS_COLORS`](README.md#Colors) can be used to customize. [`LS_COLORS`](README.md#Colors) can be used to customize.
- Handle dereference (-L) with broken symlink from [r3dArch](https://github.com/r3dArch)
## [0.23.1] - 2022-09-13 ## [0.23.1] - 2022-09-13

View file

@ -289,6 +289,8 @@ fn get_output(
tree: (usize, &str), tree: (usize, &str),
) -> Vec<String> { ) -> Vec<String> {
let mut strings: Vec<String> = Vec::new(); let mut strings: Vec<String> = Vec::new();
let colorize_missing = |string: &str| colors.colorize(string, &Elem::NoAccess);
for (i, block) in flags.blocks.0.iter().enumerate() { for (i, block) in flags.blocks.0.iter().enumerate() {
let mut block_vec = if Layout::Tree == flags.layout && tree.0 == i { let mut block_vec = if Layout::Tree == flags.layout && tree.0 == i {
vec![colors.colorize(tree.1, &Elem::TreeEdge)] vec![colors.colorize(tree.1, &Elem::TreeEdge)]
@ -297,28 +299,58 @@ fn get_output(
}; };
match block { match block {
Block::INode => block_vec.push(meta.inode.render(colors)), Block::INode => block_vec.push(match &meta.inode {
Block::Links => block_vec.push(meta.links.render(colors)), Some(inode) => inode.render(colors),
None => colorize_missing("?"),
}),
Block::Links => block_vec.push(match &meta.links {
Some(links) => links.render(colors),
None => colorize_missing("?"),
}),
Block::Permission => { Block::Permission => {
block_vec.extend([ block_vec.extend([
meta.file_type.render(colors), meta.file_type.render(colors),
meta.permissions.render(colors, flags), match meta.permissions {
meta.access_control.render_method(colors), Some(permissions) => permissions.render(colors, flags),
None => colorize_missing("?????????"),
},
match &meta.access_control {
Some(access_control) => access_control.render_method(colors),
None => colorize_missing(""),
},
]); ]);
} }
Block::User => block_vec.push(meta.owner.render_user(colors)), Block::User => block_vec.push(match &meta.owner {
Block::Group => block_vec.push(meta.owner.render_group(colors)), Some(owner) => owner.render_user(colors),
Block::Context => block_vec.push(meta.access_control.render_context(colors)), None => colorize_missing("?"),
}),
Block::Group => block_vec.push(match &meta.owner {
Some(owner) => owner.render_group(colors),
None => colorize_missing("?"),
}),
Block::Context => block_vec.push(match &meta.access_control {
Some(access_control) => access_control.render_context(colors),
None => colorize_missing("?"),
}),
Block::Size => { Block::Size => {
let pad = if Layout::Tree == flags.layout && 0 == tree.0 && 0 == i { let pad = if Layout::Tree == flags.layout && 0 == tree.0 && 0 == i {
None None
} else { } else {
Some(padding_rules[&Block::SizeValue]) Some(padding_rules[&Block::SizeValue])
}; };
block_vec.push(meta.size.render(colors, flags, pad)) block_vec.push(match &meta.size {
Some(size) => size.render(colors, flags, pad),
None => colorize_missing("?"),
})
} }
Block::SizeValue => block_vec.push(meta.size.render_value(colors, flags)), Block::SizeValue => block_vec.push(match &meta.size {
Block::Date => block_vec.push(meta.date.render(colors, flags)), Some(size) => size.render_value(colors, flags),
None => colorize_missing("?"),
}),
Block::Date => block_vec.push(match &meta.date {
Some(date) => date.render(colors, flags),
None => colorize_missing("?"),
}),
Block::Name => { Block::Name => {
block_vec.extend([ block_vec.extend([
meta.name.render( meta.name.render(
@ -377,7 +409,10 @@ fn detect_size_lengths(metas: &[Meta], flags: &Flags) -> usize {
let mut max_value_length: usize = 0; let mut max_value_length: usize = 0;
for meta in metas { for meta in metas {
let value_len = meta.size.value_string(flags).len(); let value_len = match &meta.size {
Some(size) => size.value_string(flags).len(),
None => 0,
};
if value_len > max_value_length { if value_len > max_value_length {
max_value_length = value_len; max_value_length = value_len;

View file

@ -149,8 +149,9 @@ mod test {
let metadata = tmp_dir.path().metadata().expect("failed to get metas"); let metadata = tmp_dir.path().metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors); let colors = Colors::new(ThemeOption::NoLscolors);
#[cfg(not(windows))] #[cfg(not(windows))]
let file_type = FileType::new(&metadata, None, &meta.permissions); let file_type = FileType::new(&metadata, None, &meta.permissions.unwrap());
#[cfg(windows)] #[cfg(windows)]
let file_type = FileType::new(&metadata, None, tmp_dir.path()); let file_type = FileType::new(&metadata, None, tmp_dir.path());

View file

@ -36,17 +36,17 @@ use std::path::{Component, Path, PathBuf};
pub struct Meta { pub struct Meta {
pub name: Name, pub name: Name,
pub path: PathBuf, pub path: PathBuf,
pub permissions: Permissions, pub permissions: Option<Permissions>,
pub date: Date, pub date: Option<Date>,
pub owner: Owner, pub owner: Option<Owner>,
pub file_type: FileType, pub file_type: FileType,
pub size: Size, pub size: Option<Size>,
pub symlink: SymLink, pub symlink: SymLink,
pub indicator: Indicator, pub indicator: Indicator,
pub inode: INode, pub inode: Option<INode>,
pub links: Links, pub links: Option<Links>,
pub content: Option<Vec<Meta>>, pub content: Option<Vec<Meta>>,
pub access_control: AccessControl, pub access_control: Option<AccessControl>,
} }
impl Meta { impl Meta {
@ -169,17 +169,27 @@ impl Meta {
} }
pub fn calculate_total_size(&mut self) { pub fn calculate_total_size(&mut self) {
if self.size.is_none() {
return;
}
if let FileType::Directory { .. } = self.file_type { if let FileType::Directory { .. } = self.file_type {
if let Some(metas) = &mut self.content { if let Some(metas) = &mut self.content {
let mut size_accumulated = self.size.get_bytes(); let mut size_accumulated = match &self.size {
Some(size) => size.get_bytes(),
None => 0,
};
for x in &mut metas.iter_mut() { for x in &mut metas.iter_mut() {
x.calculate_total_size(); x.calculate_total_size();
size_accumulated += x.size.get_bytes(); size_accumulated += match &x.size {
Some(size) => size.get_bytes(),
None => 0,
};
} }
self.size = Size::new(size_accumulated); self.size = Some(Size::new(size_accumulated));
} else { } else {
// possibility that 'depth' limited the recursion in 'recurse_into' // possibility that 'depth' limited the recursion in 'recurse_into'
self.size = Size::new(Meta::calculate_total_file_size(&self.path)); self.size = Some(Size::new(Meta::calculate_total_file_size(&self.path)));
} }
} }
} }
@ -225,6 +235,7 @@ impl Meta {
pub fn from_path(path: &Path, dereference: bool) -> io::Result<Self> { pub fn from_path(path: &Path, dereference: bool) -> io::Result<Self> {
let mut metadata = path.symlink_metadata()?; let mut metadata = path.symlink_metadata()?;
let mut symlink_meta = None; let mut symlink_meta = None;
let mut broken_link = false;
if metadata.file_type().is_symlink() { if metadata.file_type().is_symlink() {
match path.metadata() { match path.metadata() {
Ok(m) => { Ok(m) => {
@ -238,7 +249,8 @@ impl Meta {
// This case, it is definitely a symlink or // This case, it is definitely a symlink or
// path.symlink_metadata would have errored out // path.symlink_metadata would have errored out
if dereference { if dereference {
return Err(e); broken_link = true;
eprintln!("lsd: {}: {}\n", path.to_str().unwrap_or(""), e);
} }
} }
} }
@ -252,8 +264,6 @@ impl Meta {
#[cfg(windows)] #[cfg(windows)]
let (owner, permissions) = windows_utils::get_file_data(path)?; let (owner, permissions) = windows_utils::get_file_data(path)?;
let access_control = AccessControl::for_path(path);
#[cfg(not(windows))] #[cfg(not(windows))]
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), &permissions); let file_type = FileType::new(&metadata, symlink_meta.as_ref(), &permissions);
@ -261,16 +271,27 @@ impl Meta {
let file_type = FileType::new(&metadata, symlink_meta.as_ref(), path); let file_type = FileType::new(&metadata, symlink_meta.as_ref(), path);
let name = Name::new(path, file_type); let name = Name::new(path, file_type);
let inode = INode::from(&metadata);
let links = Links::from(&metadata); let (inode, links, size, date, owner, permissions, access_control) = match broken_link {
true => (None, None, None, None, None, None, None),
false => (
Some(INode::from(&metadata)),
Some(Links::from(&metadata)),
Some(Size::from(&metadata)),
Some(Date::from(&metadata)),
Some(owner),
Some(permissions),
Some(AccessControl::for_path(path)),
),
};
Ok(Self { Ok(Self {
inode, inode,
links, links,
path: path.to_path_buf(), path: path.to_path_buf(),
symlink: SymLink::from(path), symlink: SymLink::from(path),
size: Size::from(&metadata), size,
date: Date::from(&metadata), date,
indicator: Indicator::from(file_type), indicator: Indicator::from(file_type),
owner, owner,
permissions, permissions,
@ -282,15 +303,61 @@ impl Meta {
} }
} }
#[cfg(unix)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Meta; use super::Meta;
use std::fs::File;
use tempfile::tempdir;
#[cfg(unix)]
#[test] #[test]
fn test_from_path_path() { fn test_from_path_path() {
let dir = assert_fs::TempDir::new().unwrap(); let dir = assert_fs::TempDir::new().unwrap();
let meta = Meta::from_path(dir.path(), false).unwrap(); let meta = Meta::from_path(dir.path(), false).unwrap();
assert_eq!(meta.path, dir.path()) assert_eq!(meta.path, dir.path())
} }
#[test]
fn test_from_path() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let path_a = tmp_dir.path().join("aaa.aa");
File::create(&path_a).expect("failed to create file");
let meta_a = Meta::from_path(&path_a, false).expect("failed to get meta");
let path_b = tmp_dir.path().join("bbb.bb");
let path_c = tmp_dir.path().join("ccc.cc");
#[cfg(unix)]
std::os::unix::fs::symlink(&path_c, &path_b).expect("failed to create broken symlink");
// this needs to be tested on Windows
// likely to fail because of permission issue
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
#[cfg(windows)]
std::os::windows::fs::symlink_file(&path_c, &path_b)
.expect("failed to create broken symlink");
let meta_b = Meta::from_path(&path_b, true).expect("failed to get meta");
assert!(
meta_a.inode.is_some()
&& meta_a.links.is_some()
&& meta_a.size.is_some()
&& meta_a.date.is_some()
&& meta_a.owner.is_some()
&& meta_a.permissions.is_some()
&& meta_a.access_control.is_some()
);
assert!(
meta_b.inode.is_none()
&& meta_b.links.is_none()
&& meta_b.size.is_none()
&& meta_b.date.is_none()
&& meta_b.owner.is_none()
&& meta_b.permissions.is_none()
&& meta_b.access_control.is_none()
);
}
} }

View file

@ -48,7 +48,12 @@ fn with_dirs_first(a: &Meta, b: &Meta) -> Ordering {
} }
fn by_size(a: &Meta, b: &Meta) -> Ordering { fn by_size(a: &Meta, b: &Meta) -> Ordering {
b.size.get_bytes().cmp(&a.size.get_bytes()) match (&a.size, &b.size) {
(Some(a_size), Some(b_size)) => b_size.get_bytes().cmp(&a_size.get_bytes()),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(None, None) => Ordering::Equal,
}
} }
fn by_name(a: &Meta, b: &Meta) -> Ordering { fn by_name(a: &Meta, b: &Meta) -> Ordering {
@ -72,6 +77,7 @@ mod tests {
use super::*; use super::*;
use crate::flags::Flags; use crate::flags::Flags;
use std::fs::{create_dir, File}; use std::fs::{create_dir, File};
use std::io::prelude::*;
use std::process::Command; use std::process::Command;
use tempfile::tempdir; use tempfile::tempdir;
@ -338,4 +344,50 @@ mod tests {
let sorter = assemble_sorters(&flags); let sorter = assemble_sorters(&flags);
assert_eq!(by_meta(&sorter, &meta_c, &meta_d), Ordering::Equal); assert_eq!(by_meta(&sorter, &meta_c, &meta_d), Ordering::Equal);
} }
#[test]
fn test_sort_by_size() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let path_a = tmp_dir.path().join("aaa.aa");
File::create(&path_a)
.expect("failed to create file")
.write_all(b"1, 2, 3")
.expect("failed to write to file");
let meta_a = Meta::from_path(&path_a, false).expect("failed to get meta");
let path_b = tmp_dir.path().join("bbb.bb");
File::create(&path_b)
.expect("failed to create file")
.write_all(b"1, 2, 3, 4, 5, 6, 7, 8, 9, 10")
.expect("failed to write file");
let meta_b = Meta::from_path(&path_b, false).expect("failed to get meta");
let path_c = tmp_dir.path().join("ccc.cc");
let path_d = tmp_dir.path().join("ddd.dd");
#[cfg(unix)]
std::os::unix::fs::symlink(&path_d, &path_c).expect("failed to create broken symlink");
// this needs to be tested on Windows
// likely to fail because of permission issue
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
#[cfg(windows)]
std::os::windows::fs::symlink_file(&path_d, &path_c)
.expect("failed to create broken symlink");
let meta_c = Meta::from_path(&path_c, true).expect("failed to get meta");
assert_eq!(by_size(&meta_a, &meta_a), Ordering::Equal);
assert_eq!(by_size(&meta_a, &meta_b), Ordering::Greater);
assert_eq!(by_size(&meta_a, &meta_c), Ordering::Greater);
assert_eq!(by_size(&meta_b, &meta_a), Ordering::Less);
assert_eq!(by_size(&meta_b, &meta_b), Ordering::Equal);
assert_eq!(by_size(&meta_b, &meta_c), Ordering::Greater);
assert_eq!(by_size(&meta_c, &meta_a), Ordering::Less);
assert_eq!(by_size(&meta_c, &meta_b), Ordering::Less);
assert_eq!(by_size(&meta_c, &meta_c), Ordering::Equal);
}
} }

View file

@ -289,6 +289,39 @@ fn test_dereference_link_broken_link() {
.stderr(predicate::str::contains("No such file or directory")); .stderr(predicate::str::contains("No such file or directory"));
} }
#[test]
fn test_dereference_link_broken_link_output() {
let dir = tempdir();
let link = dir.path().join("link");
let target = dir.path().join("target");
#[cfg(unix)]
fs::symlink(&target, &link).unwrap();
// this needs to be tested on Windows
// likely to fail because of permission issue
// see https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html
#[cfg(windows)]
std::os::windows::fs::symlink_file(&target, &link).expect("failed to create broken symlink");
cmd()
.arg("-l")
.arg("--dereference")
.arg("--ignore-config")
.arg(&link)
.assert()
.stdout(predicate::str::starts_with("l????????? ? ? ? ?"));
cmd()
.arg("-l")
.arg("-L")
.arg("--ignore-config")
.arg(link)
.assert()
.stdout(predicate::str::starts_with("l????????? ? ? ? ?"));
}
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn test_show_folder_content_of_symlink() { fn test_show_folder_content_of_symlink() {