From 6245029445fc781fd22f508ef28c8835230c57c1 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 8 Oct 2022 15:02:56 -0500 Subject: [PATCH 01/25] rm: rm3 now passes --- src/uu/rm/src/rm.rs | 154 +++++++++++++++++++++++++++------------ tests/by-util/test_rm.rs | 103 ++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 46 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 5406979f7..f3a4f2f4e 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -13,10 +13,8 @@ extern crate uucore; use clap::{crate_version, Arg, Command}; use remove_dir_all::remove_dir_all; use std::collections::VecDeque; -use std::fs; -use std::fs::File; -use std::io::ErrorKind; -use std::io::{stderr, stdin, BufRead, Write}; +use std::fs::{self, File, Metadata}; +use std::io::{stderr, stdin, BufRead, ErrorKind, Write}; use std::ops::BitOr; use std::path::{Path, PathBuf}; use uucore::display::Quotable; @@ -365,12 +363,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } fn remove_dir(path: &Path, options: &Options) -> bool { - let response = if options.interactive == InteractiveMode::Always { - prompt_file(path, true) - } else { - true - }; - if response { + if prompt_file(path, options, true) { if let Ok(mut read_dir) = fs::read_dir(path) { if options.dir || options.recursive { if read_dir.next().is_none() { @@ -415,12 +408,7 @@ fn remove_dir(path: &Path, options: &Options) -> bool { } fn remove_file(path: &Path, options: &Options) -> bool { - let response = if options.interactive == InteractiveMode::Always { - prompt_file(path, false) - } else { - true - }; - if response && prompt_write_protected(path, false, options) { + if prompt_file(path, options, false) { match fs::remove_file(path) { Ok(_) => { if options.verbose { @@ -442,46 +430,122 @@ fn remove_file(path: &Path, options: &Options) -> bool { false } -fn prompt_write_protected(path: &Path, is_dir: bool, options: &Options) -> bool { +fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { + // If interactive is Never we never want to send prompts if options.interactive == InteractiveMode::Never { return true; } - match File::open(path) { - Ok(_) => true, - Err(err) => { - if err.kind() == ErrorKind::PermissionDenied { - if is_dir { - prompt(&(format!("rm: remove write-protected directory {}? ", path.quote()))) - } else { - if fs::metadata(path).unwrap().len() == 0 { - return prompt( - &(format!( - "rm: remove write-protected regular empty file {}? ", - path.quote() - )), - ); + // If interactive is Always we want to check if the file is symlink to prompt the right message + if options.interactive == InteractiveMode::Always { + if let Ok(metadata) = fs::symlink_metadata(path) { + if metadata.is_symlink() { + return prompt(&(format!("remove symbolic link {}? ", path.quote()))); + } + } + } + if is_dir { + // We can't use metadata.permissions.readonly for directories because it only works on files + // So we have to handle wether a directory is writable on not manually + if let Ok(metadata) = fs::metadata(path) { + handle_writable_directory(path, options, &metadata) + } else { + true + } + } else { + // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too + match File::options().read(true).write(true).open(path) { + Ok(file) => { + if let Ok(metadata) = file.metadata() { + if metadata.permissions().readonly() { + if metadata.len() == 0 { + prompt( + &(format!( + "remove write-protected regular empty file {}? ", + path.quote() + )), + ) + } else { + prompt( + &(format!( + "remove write-protected regular file {}? ", + path.quote() + )), + ) + } + } else if options.interactive == InteractiveMode::Always { + if metadata.len() == 0 { + prompt(&(format!("remove regular empty file {}? ", path.quote()))) + } else { + prompt(&(format!("remove file {}? ", path.quote()))) + } + } else { + true } - prompt(&(format!("rm: remove write-protected regular file {}? ", path.quote()))) + } else { + true + } + } + Err(err) => { + if err.kind() == ErrorKind::PermissionDenied { + if let Ok(metadata) = fs::metadata(path) { + if metadata.len() == 0 { + prompt( + &(format!( + "remove write-protected regular empty file {}? ", + path.quote() + )), + ) + } else { + prompt( + &(format!( + "remove write-protected regular file {}? ", + path.quote() + )), + ) + } + } else { + prompt(&(format!("remove write-protected regular file {}? ", path.quote()))) + } + } else { + true } - } else { - true } } } } -fn prompt_descend(path: &Path) -> bool { - prompt(&(format!("rm: descend into directory {}? ", path.quote()))) +// For directories finding if they are writable or not is a hassle. In Unix we can use the built-in rust crate to to check mode bits. But other os don't have something similar afaik +#[cfg(unix)] +fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { + use std::os::unix::fs::PermissionsExt; + let mode = metadata.permissions().mode(); + let user_write_permission = (mode & 0b1_1100_0000) >> 6; + let user_writable = !matches!(user_write_permission, 0o0 | 0o1 | 0o4 | 0o5); + if !user_writable { + prompt(&(format!("remove write-protected directory {}? ", path.quote()))) + } else if options.interactive == InteractiveMode::Always { + prompt(&(format!("remove directory {}? ", path.quote()))) + } else { + true + } } -fn prompt_file(path: &Path, is_dir: bool) -> bool { - if is_dir { - prompt(&(format!("rm: remove directory {}? ", path.quote()))) +// I have this here for completeness but it will always return "remove directory {}" because metadata.permissions().readonly() only works for file not directories +#[cfg(not(unix))] +fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { + if metadata.permissions().readonly() { + prompt(&(format!("remove write-protected directory {}? ", path.quote()))) + } else if options.interactive == InteractiveMode::Always { + prompt(&(format!("remove directory {}? ", path.quote()))) } else { - prompt(&(format!("rm: remove file {}? ", path.quote()))) + true } } +fn prompt_descend(path: &Path) -> bool { + prompt(&(format!("descend into directory {}? ", path.quote()))) +} + fn normalize(path: &Path) -> PathBuf { // copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90 // both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT @@ -491,7 +555,7 @@ fn normalize(path: &Path) -> PathBuf { } fn prompt(msg: &str) -> bool { - let _ = stderr().write_all(msg.as_bytes()); + let _ = stderr().write_all(format!("{}: {}", uucore::util_name(), msg).as_bytes()); let _ = stderr().flush(); let mut buf = Vec::new(); @@ -505,15 +569,13 @@ fn prompt(msg: &str) -> bool { } #[cfg(not(windows))] -fn is_symlink_dir(_metadata: &fs::Metadata) -> bool { +fn is_symlink_dir(_metadata: &Metadata) -> bool { false } #[cfg(windows)] -use std::os::windows::prelude::MetadataExt; - -#[cfg(windows)] -fn is_symlink_dir(metadata: &fs::Metadata) -> bool { +fn is_symlink_dir(metadata: &Metadata) -> bool { + use std::os::windows::prelude::MetadataExt; use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; metadata.file_type().is_symlink() diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index b600c2090..60b1f12b8 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -400,6 +400,109 @@ fn test_rm_descend_directory() { assert!(!at.file_exists(file_2)); } +#[cfg(feature = "chmod")] +#[test] +fn test_rm_prompts() { + use std::io::Write; + use std::process::Child; + + // Needed for talking with stdin on platforms where CRLF or LF matters + const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; + + let mut answers = vec![ + "rm: descend into directory 'a'?", + "rm: remove write-protected regular empty file 'a/empty-no-write'?", + "rm: remove symbolic link 'a/slink'?", + "rm: remove symbolic link 'a/slink-dot'?", + "rm: remove write-protected regular file 'a/f-no-write'?", + "rm: remove regular empty file 'a/empty'?", + "rm: remove directory 'a/b'?", + "rm: remove write-protected directory 'a/b-no-write'?", + "rm: remove directory 'a'?", + ]; + + answers.sort(); + + let yes = format!("y{}", END_OF_LINE); + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("a/"); + + let file_1 = "a/empty"; + let file_2 = "a/empty-no-write"; + let file_3 = "a/f-no-write"; + + at.touch(file_1); + at.touch(file_2); + at.make_file(file_3) + .write_all(b"not-empty") + .expect("Couldn't write to a/f-no-write"); + + at.symlink_dir("a/empty-f", "a/slink"); + at.symlink_dir(".", "a/slink-dot"); + + let dir_1 = "a/b/"; + let dir_2 = "a/b-no-write/"; + + at.mkdir(dir_1); + at.mkdir(dir_2); + + scene + .ccmd("chmod") + .arg("u-w") + .arg(file_3) + .arg(dir_2) + .arg(file_2) + .succeeds(); + + let mut child: Child = scene.ucmd().arg("-ri").arg("a").run_no_wait(); + + let mut child_stdin = child.stdin.take().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + + let output = child.wait_with_output().unwrap(); + + let mut trimmed_output = Vec::new(); + for string in String::from_utf8(output.stderr) + .expect("Couldn't convert output.stderr to string") + .split("rm: ") + { + if !string.is_empty() { + let trimmed_string = format!("rm: {}", string).trim().to_string(); + trimmed_output.push(trimmed_string); + } + } + + trimmed_output.sort(); + + assert!(trimmed_output.len() == answers.len()); + + for (i, checking_string) in trimmed_output.iter().enumerate() { + assert!(checking_string == answers[i]); + } + + assert!(!at.dir_exists("a")); +} + #[test] #[ignore = "issue #3722"] fn test_rm_directory_rights_rm1() { From 30adc8e037b3cdf6f5bce61c17a4be489b4e18f5 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 8 Oct 2022 15:22:09 -0500 Subject: [PATCH 02/25] Added windows version of handle_writable_directory --- src/uu/rm/src/rm.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f3a4f2f4e..fd3dd4a5c 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -530,7 +530,23 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata } } +// For windows we can use windows metadata trait and file attirubtes to see if a directory is readonly +#[cfg(windows)] +fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { + use std::os::windows::prelude::MetadataExt; + use winapi::um::winnt::FILE_ATTRIBUTE_READONLY; + let user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0; + if !user_writable { + prompt(&(format!("remove write-protected directory {}? ", path.quote()))) + } else if options.interactive == InteractiveMode::Always { + prompt(&(format!("remove directory {}? ", path.quote()))) + } else { + true + } +} + // I have this here for completeness but it will always return "remove directory {}" because metadata.permissions().readonly() only works for file not directories +#[cfg(not(windows))] #[cfg(not(unix))] fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { if metadata.permissions().readonly() { From 1f50df2af6f6c2726205cfdbfde96e0f9c40efc7 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 8 Oct 2022 16:47:27 -0500 Subject: [PATCH 03/25] Fixed spelling error --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index fd3dd4a5c..bd8cb7dcb 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -530,7 +530,7 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata } } -// For windows we can use windows metadata trait and file attirubtes to see if a directory is readonly +// For windows we can use windows metadata trait and file attributes to see if a directory is readonly #[cfg(windows)] fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { use std::os::windows::prelude::MetadataExt; From 6856ce0bf40f6c8376592d72294473d2494e361c Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 8 Oct 2022 16:51:14 -0500 Subject: [PATCH 04/25] Fixed handle_writable_directory on windows --- src/uu/rm/src/rm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index bd8cb7dcb..a72a88393 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -535,8 +535,8 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { use std::os::windows::prelude::MetadataExt; use winapi::um::winnt::FILE_ATTRIBUTE_READONLY; - let user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0; - if !user_writable { + let not_user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0; + if not_user_writable { prompt(&(format!("remove write-protected directory {}? ", path.quote()))) } else if options.interactive == InteractiveMode::Always { prompt(&(format!("remove directory {}? ", path.quote()))) From 50d2948aa1bc25cb8f463d5964517c23f8ecde83 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sun, 9 Oct 2022 17:24:36 -0500 Subject: [PATCH 05/25] Fixed rm --force argument not forcing prompt to not show up --- src/uu/rm/src/rm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a72a88393..b5adcf9e8 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -98,7 +98,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { force, interactive: { - if matches.contains_id(OPT_PROMPT) { + if force { + InteractiveMode::Never + } else if matches.contains_id(OPT_PROMPT) { InteractiveMode::Always } else if matches.contains_id(OPT_PROMPT_MORE) { InteractiveMode::Once From 3c39a57da0a32ec20beb4a03be31a09136e4b18f Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Mon, 10 Oct 2022 12:43:01 -0500 Subject: [PATCH 06/25] Check force position in rm --- src/uu/rm/src/rm.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index b5adcf9e8..576094d5d 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -88,17 +88,38 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let force = matches.contains_id(OPT_FORCE); + let force_index_option = matches.index_of(OPT_FORCE); - if files.is_empty() && !force { + // If -f(--force) is before any -i (or variants) we want prompts else no prompts + let force_first: bool = { + if let Some(force_index) = force_index_option { + let prompt_index_option = matches.index_of(OPT_PROMPT); + let prompt_more_index_option = matches.index_of(OPT_PROMPT_MORE); + let interactive_index_option = matches.index_of(OPT_INTERACTIVE); + + if let Some(prompt_index) = prompt_index_option { + prompt_index > force_index + } else if let Some(prompt_more_index_index) = prompt_more_index_option { + prompt_more_index_index > force_index + } else if let Some(interactive_index) = interactive_index_option { + interactive_index > force_index + } else { + true + } + } else { + true + } + }; + + if files.is_empty() && force_index_option.is_none() { // Still check by hand and not use clap // Because "rm -f" is a thing return Err(UUsageError::new(1, "missing operand")); } else { let options = Options { - force, + force: force_index_option.is_some(), interactive: { - if force { + if force_index_option.is_some() && !force_first { InteractiveMode::Never } else if matches.contains_id(OPT_PROMPT) { InteractiveMode::Always From 70bf4f36a04a3cb6200b787494a4857191015613 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Mon, 10 Oct 2022 13:48:34 -0500 Subject: [PATCH 07/25] Added test to check rm force prompts order --- tests/by-util/test_rm.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 60b1f12b8..dcf85f4d1 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -503,6 +503,48 @@ fn test_rm_prompts() { assert!(!at.dir_exists("a")); } +#[test] +fn test_rm_force_prompts_order() { + use std::io::Write; + use std::process::Child; + + // Needed for talking with stdin on platforms where CRLF or LF matters + const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; + + let yes = format!("y{}", END_OF_LINE); + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let empty_file = "empty"; + + at.touch(empty_file); + + // This should cause rm to prompt to remove regular empty file + let mut child: Child = scene.ucmd().arg("-fi").arg(empty_file).run_no_wait(); + + let mut child_stdin = child.stdin.take().unwrap(); + child_stdin.write_all(yes.as_bytes()).unwrap(); + child_stdin.flush().unwrap(); + + let output = child.wait_with_output().unwrap(); + let string_output = + String::from_utf8(output.stderr).expect("Couldn't convert output.stderr to string"); + assert!(string_output.trim() == String::from("rm: remove regular empty file 'empty'?")); + assert!(!at.file_exists(empty_file)); + + at.touch(empty_file); + + // This should not cause rm to prompt to remove regular empty file + scene + .ucmd() + .arg("-if") + .arg(empty_file) + .succeeds() + .no_stderr(); + assert!(!at.file_exists(empty_file)); +} + #[test] #[ignore = "issue #3722"] fn test_rm_directory_rights_rm1() { From 0507270f90d32698d5bfd86bcf2e109c6c6c0e1a Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Mon, 10 Oct 2022 13:52:05 -0500 Subject: [PATCH 08/25] Clippy is going to be the death of me --- tests/by-util/test_rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index dcf85f4d1..98273fedd 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -530,7 +530,7 @@ fn test_rm_force_prompts_order() { let output = child.wait_with_output().unwrap(); let string_output = String::from_utf8(output.stderr).expect("Couldn't convert output.stderr to string"); - assert!(string_output.trim() == String::from("rm: remove regular empty file 'empty'?")); + assert!(string_output.trim() == "rm: remove regular empty file 'empty'?"); assert!(!at.file_exists(empty_file)); at.touch(empty_file); From 3a1098489e6a7b1b71b77ca8c7e6b4a35af3363e Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Mon, 10 Oct 2022 15:01:17 -0500 Subject: [PATCH 09/25] Small fix --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 576094d5d..f83c09031 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -104,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else if let Some(interactive_index) = interactive_index_option { interactive_index > force_index } else { - true + false } } else { true From 355136936fc158dd0cfdad8c109d54bc28d0434a Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Tue, 11 Oct 2022 15:45:20 -0500 Subject: [PATCH 10/25] Fixed force rm --- src/uu/rm/src/rm.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f83c09031..2143bb8e3 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -91,23 +91,33 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let force_index_option = matches.index_of(OPT_FORCE); // If -f(--force) is before any -i (or variants) we want prompts else no prompts - let force_first: bool = { + let force_prompt_never: bool = { if let Some(force_index) = force_index_option { let prompt_index_option = matches.index_of(OPT_PROMPT); let prompt_more_index_option = matches.index_of(OPT_PROMPT_MORE); let interactive_index_option = matches.index_of(OPT_INTERACTIVE); + let mut result = true; + if let Some(prompt_index) = prompt_index_option { - prompt_index > force_index - } else if let Some(prompt_more_index_index) = prompt_more_index_option { - prompt_more_index_index > force_index - } else if let Some(interactive_index) = interactive_index_option { - interactive_index > force_index - } else { - false + if result { + result = prompt_index <= force_index; + } } + if let Some(prompt_more_index_index) = prompt_more_index_option { + if result { + result = prompt_more_index_index <= force_index; + } + } + if let Some(interactive_index) = interactive_index_option { + if result { + result = interactive_index <= force_index; + } + } + + result } else { - true + false } }; @@ -119,7 +129,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { force: force_index_option.is_some(), interactive: { - if force_index_option.is_some() && !force_first { + if force_index_option.is_some() && force_prompt_never { InteractiveMode::Never } else if matches.contains_id(OPT_PROMPT) { InteractiveMode::Always From e11dd50eb4f3572a52b5c92dae76b977349e8998 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Wed, 12 Oct 2022 16:54:38 -0500 Subject: [PATCH 11/25] Added comments --- src/uu/rm/src/rm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 2143bb8e3..a8932b7f5 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -99,16 +99,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut result = true; + // if we have rm -i -f if let Some(prompt_index) = prompt_index_option { if result { result = prompt_index <= force_index; } } + // if we have rm -I -f if let Some(prompt_more_index_index) = prompt_more_index_option { if result { result = prompt_more_index_index <= force_index; } } + // if we have rm --interactive -f if let Some(interactive_index) = interactive_index_option { if result { result = interactive_index <= force_index; From a3f35a726cb48cc65189ce968b6e64cd16b44c6b Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Wed, 12 Oct 2022 17:31:28 -0500 Subject: [PATCH 12/25] Add override for prompting --- src/uu/rm/src/rm.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a8932b7f5..ead7a63b6 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -194,12 +194,13 @@ pub fn uu_app<'a>() -> Command<'a> { .arg( Arg::new(OPT_PROMPT) .short('i') - .help("prompt before every removal"), + .help("prompt before every removal") + .overrides_with_all(&[OPT_PROMPT_MORE, OPT_INTERACTIVE]), ) .arg(Arg::new(OPT_PROMPT_MORE).short('I').help( "prompt once before removing more than three files, or when removing recursively. \ Less intrusive than -i, while still giving some protection against most mistakes", - )) + ).overrides_with_all(&[OPT_PROMPT, OPT_INTERACTIVE])) .arg( Arg::new(OPT_INTERACTIVE) .long(OPT_INTERACTIVE) @@ -208,7 +209,8 @@ pub fn uu_app<'a>() -> Command<'a> { prompts always", ) .value_name("WHEN") - .takes_value(true), + .takes_value(true) + .overrides_with_all(&[OPT_PROMPT, OPT_PROMPT_MORE]), ) .arg( Arg::new(OPT_ONE_FILE_SYSTEM) From fddc8c9d9d6896ceb91932cdb273013aa07b05d8 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Thu, 13 Oct 2022 17:22:59 -0500 Subject: [PATCH 13/25] More readable unix write permissions for directory using libc --- src/uu/rm/Cargo.toml | 3 +++ src/uu/rm/src/rm.rs | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index e992d86d1..3196c71ce 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -20,6 +20,9 @@ walkdir = "2.2" remove_dir_all = "0.7.0" uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["fs"] } +[target.'cfg(unix)'.dependencies] +libc = "0.2.135" + [target.'cfg(windows)'.dependencies] winapi = { version="0.3", features=[] } diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index ead7a63b6..fa4d8cc35 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -557,8 +557,8 @@ fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { use std::os::unix::fs::PermissionsExt; let mode = metadata.permissions().mode(); - let user_write_permission = (mode & 0b1_1100_0000) >> 6; - let user_writable = !matches!(user_write_permission, 0o0 | 0o1 | 0o4 | 0o5); + // Check if directory has user write permissions + let user_writable = (mode & libc::S_IWUSR) != 0; if !user_writable { prompt(&(format!("remove write-protected directory {}? ", path.quote()))) } else if options.interactive == InteractiveMode::Always { @@ -587,9 +587,7 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata #[cfg(not(windows))] #[cfg(not(unix))] fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { - if metadata.permissions().readonly() { - prompt(&(format!("remove write-protected directory {}? ", path.quote()))) - } else if options.interactive == InteractiveMode::Always { + if options.interactive == InteractiveMode::Always { prompt(&(format!("remove directory {}? ", path.quote()))) } else { true From e89d9d091da44a0e15653c0acf0b4341680004b1 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Thu, 13 Oct 2022 17:37:11 -0500 Subject: [PATCH 14/25] Forgot .lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 097df2a5e..8700897d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2691,6 +2691,7 @@ name = "uu_rm" version = "0.0.16" dependencies = [ "clap", + "libc", "remove_dir_all 0.7.0", "uucore", "walkdir", From 21b066a58e38b808953919636819aaf0f18ff740 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Thu, 13 Oct 2022 17:40:27 -0500 Subject: [PATCH 15/25] Why is S_IWUSR showing up as a u16 on macos --- src/uu/rm/src/rm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index fa4d8cc35..b17ea2d63 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -558,7 +558,8 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata use std::os::unix::fs::PermissionsExt; let mode = metadata.permissions().mode(); // Check if directory has user write permissions - let user_writable = (mode & libc::S_IWUSR) != 0; + // Why is S_IWUSR showing up as a u16 on macos? + let user_writable = (mode & (libc::S_IWUSR as u32)) != 0; if !user_writable { prompt(&(format!("remove write-protected directory {}? ", path.quote()))) } else if options.interactive == InteractiveMode::Always { From 430652193be939b9dbdd2487fa1c951da2d67af3 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 15 Oct 2022 15:08:28 -0500 Subject: [PATCH 16/25] Fixed merge conflicts --- Cargo.lock | 2 +- src/uu/rm/src/rm.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 763d24fa9..149099e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2713,7 +2713,7 @@ name = "uu_rm" version = "0.0.16" dependencies = [ "clap 4.0.14", - "libc" + "libc", "remove_dir_all 0.7.0", "uucore", "walkdir", diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index f94c4ce46..b45938a86 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -196,11 +196,14 @@ pub fn uu_app() -> Command { .overrides_with_all(&[OPT_PROMPT_MORE, OPT_INTERACTIVE]) .action(ArgAction::SetTrue), ) - .arg(Arg::new(OPT_PROMPT_MORE).short('I').help( - "prompt once before removing more than three files, or when removing recursively. \ - Less intrusive than -i, while still giving some protection against most mistakes", - ).overrides_with_all(&[OPT_PROMPT, OPT_INTERACTIVE]) - ).action(ArgAction::SetTrue)) + .arg( + Arg::new(OPT_PROMPT_MORE) + .short('I') + .help("prompt once before removing more than three files, or when removing recursively. \ + Less intrusive than -i, while still giving some protection against most mistakes") + .overrides_with_all(&[OPT_PROMPT, OPT_INTERACTIVE]) + .action(ArgAction::SetTrue), + ) .arg( Arg::new(OPT_INTERACTIVE) .long(OPT_INTERACTIVE) @@ -208,7 +211,7 @@ pub fn uu_app() -> Command { "prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, \ prompts always", ) - .value_name("WHEN"), + .value_name("WHEN") .overrides_with_all(&[OPT_PROMPT, OPT_PROMPT_MORE]), ) .arg( From 1c507c6739f7ce3bc3efa82ca23cc8203e185e86 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 15 Oct 2022 17:39:10 -0500 Subject: [PATCH 17/25] Updated to clap4 --- src/uu/rm/src/rm.rs | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index b45938a86..8898f546f 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -10,7 +10,7 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, Arg, ArgAction, Command}; +use clap::{crate_version, Arg, ArgAction, Command, parser::ValueSource}; use remove_dir_all::remove_dir_all; use std::collections::VecDeque; use std::fs::{self, File, Metadata}; @@ -85,51 +85,61 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let force_index_option = matches.index_of(OPT_FORCE); + let force_flag = matches.get_flag(OPT_FORCE); // If -f(--force) is before any -i (or variants) we want prompts else no prompts - let force_prompt_never: bool = { - if let Some(force_index) = force_index_option { - let prompt_index_option = matches.index_of(OPT_PROMPT); - let prompt_more_index_option = matches.index_of(OPT_PROMPT_MORE); - let interactive_index_option = matches.index_of(OPT_INTERACTIVE); - - let mut result = true; - - // if we have rm -i -f - if let Some(prompt_index) = prompt_index_option { - if result { - result = prompt_index <= force_index; + let force_prompt_never: bool = if force_flag { + if matches.value_source(OPT_FORCE) == Some(ValueSource::CommandLine) { + if let Some(force_index) = matches.index_of(OPT_FORCE) { + let mut result = true; + + // if we have rm -i -f + if matches.value_source(OPT_PROMPT) == Some(ValueSource::CommandLine) { + if let Some(prompt_index) = matches.index_of(OPT_PROMPT) { + if result { + result = prompt_index <= force_index; + } + } } - } - // if we have rm -I -f - if let Some(prompt_more_index_index) = prompt_more_index_option { - if result { - result = prompt_more_index_index <= force_index; + + // if we have rm -I -f + if matches.value_source(OPT_PROMPT_MORE) == Some(ValueSource::CommandLine) { + if let Some(prompt_more_index_index) = matches.index_of(OPT_PROMPT_MORE) { + if result { + result = prompt_more_index_index <= force_index; + } + } } - } - // if we have rm --interactive -f - if let Some(interactive_index) = interactive_index_option { - if result { - result = interactive_index <= force_index; - } - } - result + // if we have rm --interactive -f + if matches.value_source(OPT_INTERACTIVE) == Some(ValueSource::CommandLine) { + if let Some(interactive_index) = matches.index_of(OPT_INTERACTIVE) { + if result { + result = interactive_index <= force_index; + } + } + } + + result + } else { + false + } } else { false } + } else { + false }; - if files.is_empty() && force_index_option.is_none() { + if files.is_empty() && !force_flag { // Still check by hand and not use clap // Because "rm -f" is a thing return Err(UUsageError::new(1, "missing operand")); } else { let options = Options { - force: force_index_option.is_some(), + force: force_flag, interactive: { - if force_index_option.is_some() && force_prompt_never { + if force_flag && force_prompt_never { InteractiveMode::Never } else if matches.get_flag(OPT_PROMPT) { InteractiveMode::Always From b3b90e453cc9cc9e1b25cb6eac71562480da5fbe Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 15 Oct 2022 17:41:11 -0500 Subject: [PATCH 18/25] Forgot fmt again --- src/uu/rm/src/rm.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 8898f546f..db16cda11 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -10,7 +10,7 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, Arg, ArgAction, Command, parser::ValueSource}; +use clap::{crate_version, parser::ValueSource, Arg, ArgAction, Command}; use remove_dir_all::remove_dir_all; use std::collections::VecDeque; use std::fs::{self, File, Metadata}; @@ -92,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if matches.value_source(OPT_FORCE) == Some(ValueSource::CommandLine) { if let Some(force_index) = matches.index_of(OPT_FORCE) { let mut result = true; - + // if we have rm -i -f if matches.value_source(OPT_PROMPT) == Some(ValueSource::CommandLine) { if let Some(prompt_index) = matches.index_of(OPT_PROMPT) { @@ -101,14 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } } - + // if we have rm -I -f if matches.value_source(OPT_PROMPT_MORE) == Some(ValueSource::CommandLine) { if let Some(prompt_more_index_index) = matches.index_of(OPT_PROMPT_MORE) { if result { result = prompt_more_index_index <= force_index; } - } + } } // if we have rm --interactive -f @@ -117,9 +117,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if result { result = interactive_index <= force_index; } - } + } } - + result } else { false From b612ce5cd7e1ce2b6503dd2e1f4a757af708f608 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sun, 16 Oct 2022 10:57:12 -0500 Subject: [PATCH 19/25] Cleaner force_prompt_never --- src/uu/rm/src/rm.rs | 45 ++++++++------------------------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index db16cda11..190298aa6 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -88,47 +88,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let force_flag = matches.get_flag(OPT_FORCE); // If -f(--force) is before any -i (or variants) we want prompts else no prompts - let force_prompt_never: bool = if force_flag { + let force_prompt_never: bool = force_flag && { if matches.value_source(OPT_FORCE) == Some(ValueSource::CommandLine) { - if let Some(force_index) = matches.index_of(OPT_FORCE) { - let mut result = true; - - // if we have rm -i -f - if matches.value_source(OPT_PROMPT) == Some(ValueSource::CommandLine) { - if let Some(prompt_index) = matches.index_of(OPT_PROMPT) { - if result { - result = prompt_index <= force_index; - } - } - } - - // if we have rm -I -f - if matches.value_source(OPT_PROMPT_MORE) == Some(ValueSource::CommandLine) { - if let Some(prompt_more_index_index) = matches.index_of(OPT_PROMPT_MORE) { - if result { - result = prompt_more_index_index <= force_index; - } - } - } - - // if we have rm --interactive -f - if matches.value_source(OPT_INTERACTIVE) == Some(ValueSource::CommandLine) { - if let Some(interactive_index) = matches.index_of(OPT_INTERACTIVE) { - if result { - result = interactive_index <= force_index; - } - } - } - - result - } else { - false - } + let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); + [OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] + .iter() + .any(|opt| { + matches.value_source(opt) == Some(ValueSource::CommandLine) + && matches.index_of(opt).unwrap_or(0) < force_index + }) } else { false } - } else { - false }; if files.is_empty() && !force_flag { From 03578a7acae1c9434508c13a4a3ccd16bab82f46 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sun, 16 Oct 2022 13:14:17 -0500 Subject: [PATCH 20/25] Fixed invert issue --- src/uu/rm/src/rm.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 190298aa6..93f99eea3 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -91,11 +91,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let force_prompt_never: bool = force_flag && { if matches.value_source(OPT_FORCE) == Some(ValueSource::CommandLine) { let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); - [OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] + ![OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] .iter() - .any(|opt| { - matches.value_source(opt) == Some(ValueSource::CommandLine) - && matches.index_of(opt).unwrap_or(0) < force_index + .any(|flag| { + matches.value_source(flag) == Some(ValueSource::CommandLine) + && matches.index_of(flag).unwrap_or(0) > force_index }) } else { false @@ -110,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { force: force_flag, interactive: { - if force_flag && force_prompt_never { + if force_prompt_never { InteractiveMode::Never } else if matches.get_flag(OPT_PROMPT) { InteractiveMode::Always From 2e61580b9966a1fdff7798435b76cdbbe64cac03 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Mon, 17 Oct 2022 17:40:33 -0500 Subject: [PATCH 21/25] Cleaner force_prompt_never --- src/uu/rm/src/rm.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 93f99eea3..b8362adfc 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -89,17 +89,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // If -f(--force) is before any -i (or variants) we want prompts else no prompts let force_prompt_never: bool = force_flag && { - if matches.value_source(OPT_FORCE) == Some(ValueSource::CommandLine) { - let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); - ![OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] - .iter() - .any(|flag| { - matches.value_source(flag) == Some(ValueSource::CommandLine) - && matches.index_of(flag).unwrap_or(0) > force_index - }) - } else { - false - } + let force_index = matches.index_of(OPT_FORCE).unwrap_or(0); + ![OPT_PROMPT, OPT_PROMPT_MORE, OPT_INTERACTIVE] + .iter() + .any(|flag| { + matches.value_source(flag) == Some(ValueSource::CommandLine) + && matches.index_of(flag).unwrap_or(0) > force_index + }) }; if files.is_empty() && !force_flag { From 5968f53ef434e64772e3f424b2fc6181fe390933 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Thu, 20 Oct 2022 15:42:55 -0500 Subject: [PATCH 22/25] Fixed merge conflict --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 07c50c2d3..4741d948b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -550,7 +550,7 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata #[cfg(windows)] fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { use std::os::windows::prelude::MetadataExt; - use winapi::um::winnt::FILE_ATTRIBUTE_READONLY; + use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_READONLY; let not_user_writable = (metadata.file_attributes() & FILE_ATTRIBUTE_READONLY) != 0; if not_user_writable { prompt(&(format!("remove write-protected directory {}? ", path.quote()))) From 744481c8008b70e99f6e2dd4148f7636d51baa8d Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 22 Oct 2022 18:00:17 -0500 Subject: [PATCH 23/25] Updated handle_writable_directory comment --- src/uu/rm/src/rm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 4741d948b..f65ebb2fa 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -530,6 +530,7 @@ fn prompt_file(path: &Path, options: &Options, is_dir: bool) -> bool { } // For directories finding if they are writable or not is a hassle. In Unix we can use the built-in rust crate to to check mode bits. But other os don't have something similar afaik +// Most cases are covered by keep eye out for edge cases #[cfg(unix)] fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata) -> bool { use std::os::unix::fs::PermissionsExt; From c7af9a6a80adfd458edea7ce1eadcdec57f97b14 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Thu, 27 Oct 2022 13:41:55 -0500 Subject: [PATCH 24/25] Bump libc version from "0.2.135" to "0.2.136" --- src/uu/rm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 1467f7568..75bde3d3e 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -21,7 +21,7 @@ remove_dir_all = "0.7.0" uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["fs"] } [target.'cfg(unix)'.dependencies] -libc = "0.2.135" +libc = "0.2.136" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem"] } From 4b4222ffe357e452d23d43a0359d007465f6ea60 Mon Sep 17 00:00:00 2001 From: Pat Laster Date: Sat, 29 Oct 2022 11:09:52 -0500 Subject: [PATCH 25/25] Bump libc version from "0.2.136" to "0.2.137" --- src/uu/rm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 75bde3d3e..3cd49b11b 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -21,7 +21,7 @@ remove_dir_all = "0.7.0" uucore = { version=">=0.0.16", package="uucore", path="../../uucore", features=["fs"] } [target.'cfg(unix)'.dependencies] -libc = "0.2.136" +libc = "0.2.137" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.42.0", default-features = false, features = ["Win32_Storage_FileSystem"] }