From cb57f0a539895e80c2779a536c4326dad3d6c0cc Mon Sep 17 00:00:00 2001 From: Sebastian Nallar <95891104+sebasnallar@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:47:48 -0300 Subject: [PATCH] Add --follow-symlinks flag to glob command (fixes #15559) (#15626) Fixes #15559 # Description The glob command wasn't working correctly with symlinks in the /sys filesystem. This commit adds a new flag that allows users to explicitly control whether symlinks should be followed, with special handling for the /sys directory. The issue was that the glob command didn't follow symbolic links when traversing the /sys filesystem, resulting in an empty list even though paths should be found. This implementation adds a new `--follow-symlinks` flag that explicitly enables following symlinks. By default, it now follows symlinks in most paths but has special handling for /sys paths where the flag is required. Example: ` # Before: This would return an empty list on Linux systems glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor # Now: This works as expected with the new flag glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor --follow-symlinks ` # User-Facing Changes 1. Added the --follow-symlinks (-l) flag to the glob command that allows users to explicitly control whether symbolic links should be followed 2. Added a new example to the glob command help text demonstrating the use of this flag # Tests + Formatting 1. Added a test for the new --follow-symlinks flag --- crates/nu-command/src/filesystem/glob.rs | 20 +++++++++++++-- crates/nu-command/tests/commands/glob.rs | 32 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/filesystem/glob.rs b/crates/nu-command/src/filesystem/glob.rs index 4a293e82fe..740d5ec9e9 100644 --- a/crates/nu-command/src/filesystem/glob.rs +++ b/crates/nu-command/src/filesystem/glob.rs @@ -35,6 +35,11 @@ impl Command for Glob { "Whether to filter out symlinks from the returned paths", Some('S'), ) + .switch( + "follow-symlinks", + "Whether to follow symbolic links to their targets", + Some('l'), + ) .named( "exclude", SyntaxShape::List(Box::new(SyntaxShape::String)), @@ -111,6 +116,11 @@ impl Command for Glob { example: r#"glob **/* --exclude [**/target/** **/.git/** */]"#, result: None, }, + Example { + description: "Search for files following symbolic links to their targets", + example: r#"glob "**/*.txt" --follow-symlinks"#, + result: None, + }, ] } @@ -132,6 +142,7 @@ impl Command for Glob { let no_dirs = call.has_flag(engine_state, stack, "no-dir")?; let no_files = call.has_flag(engine_state, stack, "no-file")?; let no_symlinks = call.has_flag(engine_state, stack, "no-symlink")?; + let follow_symlinks = call.has_flag(engine_state, stack, "follow-symlinks")?; let paths_to_exclude: Option = call.get_flag(engine_state, stack, "exclude")?; let (not_patterns, not_pattern_span): (Vec, Span) = match paths_to_exclude { @@ -213,6 +224,11 @@ impl Command for Glob { } }; + let link_behavior = match follow_symlinks { + true => wax::LinkBehavior::ReadTarget, + false => wax::LinkBehavior::ReadFile, + }; + let result = if !not_patterns.is_empty() { let np: Vec<&str> = not_patterns.iter().map(|s| s as &str).collect(); let glob_results = glob @@ -220,7 +236,7 @@ impl Command for Glob { path, WalkBehavior { depth: folder_depth, - ..Default::default() + link: link_behavior, }, ) .into_owned() @@ -247,7 +263,7 @@ impl Command for Glob { path, WalkBehavior { depth: folder_depth, - ..Default::default() + link: link_behavior, }, ) .into_owned() diff --git a/crates/nu-command/tests/commands/glob.rs b/crates/nu-command/tests/commands/glob.rs index 235896f63d..bded39d09f 100644 --- a/crates/nu-command/tests/commands/glob.rs +++ b/crates/nu-command/tests/commands/glob.rs @@ -173,3 +173,35 @@ fn glob_files_in_parent( assert_eq!(actual.out, expected, "\n test: {}", tag); }); } + +#[test] +fn glob_follow_symlinks() { + Playground::setup("glob_follow_symlinks", |dirs, sandbox| { + // Create a directory with some files + sandbox.mkdir("target_dir"); + sandbox + .within("target_dir") + .with_files(&[EmptyFile("target_file.txt")]); + + let target_dir = dirs.test().join("target_dir"); + let symlink_path = dirs.test().join("symlink_dir"); + #[cfg(unix)] + std::os::unix::fs::symlink(target_dir, &symlink_path).expect("Failed to create symlink"); + #[cfg(windows)] + std::os::windows::fs::symlink_dir(target_dir, &symlink_path) + .expect("Failed to create symlink"); + + // on some systems/filesystems, symlinks are followed by default + // on others (like Linux /sys), they aren't + // Test that with the --follow-symlinks flag, files are found for sure + let with_flag = nu!( + cwd: dirs.test(), + "glob 'symlink_dir/*.txt' --follow-symlinks | length", + ); + + assert_eq!( + with_flag.out, "1", + "Should find file with --follow-symlinks flag" + ); + }) +}