From 5cf6725eb19d4461b6141d269353e9af2b798d21 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Fri, 6 Mar 2020 13:33:03 -0800 Subject: [PATCH] walkdir: avoid symlink loops when `follow_symlinks == false` (#35006) * walkdir: avoid symlink loops when `follow_symlinks == false` Because `isdir()` attempts to dereference symlinks, attempting to `walkdir()` trees that contain symlink loops errors out. This change modifies `walkdir()` to treat all symlinks as files when `follow_symlinks == false`. * rm: When checking `filemode()`, use `lstat()` to avoid following symlinks (cherry picked from commit 178ac974b5e9f649c0ffb3ed78752dc9241b7a5a) --- base/file.jl | 11 +++++++---- test/file.jl | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/base/file.jl b/base/file.jl index 8fec68f4d7e1c..9e91f433f2942 100644 --- a/base/file.jl +++ b/base/file.jl @@ -261,7 +261,7 @@ function rm(path::AbstractString; force::Bool=false, recursive::Bool=false) try @static if Sys.iswindows() # is writable on windows actually means "is deletable" - if (filemode(path) & 0o222) == 0 + if (filemode(lstat(path)) & 0o222) == 0 chmod(path, 0o777) end end @@ -853,10 +853,13 @@ function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw) dirs = Vector{eltype(content)}() files = Vector{eltype(content)}() for name in content - if isdir(joinpath(root, name)) - push!(dirs, name) - else + path = joinpath(root, name) + + # If we're not following symlinks, then treat all symlinks as files + if (!follow_symlinks && islink(path)) || !isdir(path) push!(files, name) + else + push!(dirs, name) end end diff --git a/test/file.jl b/test/file.jl index ae3ce717060e0..69036133f6125 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1215,8 +1215,18 @@ cd(dirwalk) do root, dirs, files = take!(chnl) @test root == joinpath(".", "sub_dir1") - @test dirs == (has_symlinks ? ["link", "subsub_dir1", "subsub_dir2"] : ["subsub_dir1", "subsub_dir2"]) - @test files == ["file1", "file2"] + if has_symlinks + if follow_symlinks + @test dirs == ["link", "subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2"] + else + @test dirs == ["subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2", "link"] + end + else + @test dirs == ["subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2"] + end root, dirs, files = take!(chnl) if follow_symlinks @@ -1253,8 +1263,18 @@ cd(dirwalk) do root, dirs, files = take!(chnl) end @test root == joinpath(".", "sub_dir1") - @test dirs == (has_symlinks ? ["link", "subsub_dir1", "subsub_dir2"] : ["subsub_dir1", "subsub_dir2"]) - @test files == ["file1", "file2"] + if has_symlinks + if follow_symlinks + @test dirs == ["link", "subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2"] + else + @test dirs == ["subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2", "link"] + end + else + @test dirs == ["subsub_dir1", "subsub_dir2"] + @test files == ["file1", "file2"] + end root, dirs, files = take!(chnl) @test root == joinpath(".", "sub_dir2") @@ -1286,6 +1306,18 @@ cd(dirwalk) do @test root == joinpath(".", "sub_dir2") @test dirs == [] @test files == ["file_dir2"] + + # Test that symlink loops don't cause errors + if has_symlinks + mkdir(joinpath(".", "sub_dir3")) + symlink("foo", joinpath(".", "sub_dir3", "foo")) + + @test_throws Base.IOError walkdir(joinpath(".", "sub_dir3"); follow_symlinks=true) + root, dirs, files = take!(walkdir(joinpath(".", "sub_dir3"); follow_symlinks=false)) + @test root == joinpath(".", "sub_dir3") + @test dirs == [] + @test files == ["foo"] + end end rm(dirwalk, recursive=true)