Skip to content

Commit dffc889

Browse files
authored
filesystem: fix some error handling (#36856)
walkdir: fix thrown error location and types and performance readdir: fix thrown error code on some OS seek: fix thrown error code on Windows
1 parent 7c0cb30 commit dffc889

File tree

4 files changed

+72
-70
lines changed

4 files changed

+72
-70
lines changed

base/file.jl

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
777777
# defined in sys.c, to call uv_fs_readdir, which sets errno on error.
778778
err = ccall(:uv_fs_scandir, Int32, (Ptr{Cvoid}, Ptr{UInt8}, Cstring, Cint, Ptr{Cvoid}),
779779
C_NULL, uv_readdir_req, dir, 0, C_NULL)
780-
err < 0 && throw(SystemError("unable to read directory $dir", -err))
780+
err < 0 && throw(_UVError("readdir", err, "with ", repr(dir)))
781781

782782
# iterate the listing into entries
783783
entries = String[]
@@ -804,10 +804,9 @@ readdir(; join::Bool=false, sort::Bool=true) =
804804
Return an iterator that walks the directory tree of a directory.
805805
The iterator returns a tuple containing `(rootpath, dirs, files)`.
806806
The directory tree can be traversed top-down or bottom-up.
807-
If `walkdir` encounters a [`SystemError`](@ref)
808-
it will rethrow the error by default.
807+
If `walkdir` or `stat` encounters a `IOError` it will rethrow the error by default.
809808
A custom error handling function can be provided through `onerror` keyword argument.
810-
`onerror` is called with a `SystemError` as argument.
809+
`onerror` is called with a `IOError` as argument.
811810
812811
# Examples
813812
```julia
@@ -839,48 +838,45 @@ julia> (root, dirs, files) = first(itr)
839838
```
840839
"""
841840
function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw)
842-
content = nothing
843-
try
844-
content = readdir(root)
845-
catch err
846-
isa(err, SystemError) || throw(err)
847-
onerror(err)
848-
# Need to return an empty closed channel to skip the current root folder
849-
chnl = Channel(0)
850-
close(chnl)
851-
return chnl
852-
end
853-
dirs = Vector{eltype(content)}()
854-
files = Vector{eltype(content)}()
855-
for name in content
856-
path = joinpath(root, name)
857-
858-
# If we're not following symlinks, then treat all symlinks as files
859-
if (!follow_symlinks && islink(path)) || !isdir(path)
860-
push!(files, name)
861-
else
862-
push!(dirs, name)
841+
function _walkdir(chnl, root)
842+
tryf(f, p) = try
843+
f(p)
844+
catch err
845+
isa(err, IOError) || rethrow()
846+
try
847+
onerror(err)
848+
catch err2
849+
close(chnl, err2)
850+
end
851+
return
852+
end
853+
content = tryf(readdir, root)
854+
content === nothing && return
855+
dirs = Vector{eltype(content)}()
856+
files = Vector{eltype(content)}()
857+
for name in content
858+
path = joinpath(root, name)
859+
860+
# If we're not following symlinks, then treat all symlinks as files
861+
if (!follow_symlinks && something(tryf(islink, path), true)) || !something(tryf(isdir, path), false)
862+
push!(files, name)
863+
else
864+
push!(dirs, name)
865+
end
863866
end
864-
end
865867

866-
function _it(chnl)
867868
if topdown
868-
put!(chnl, (root, dirs, files))
869+
push!(chnl, (root, dirs, files))
869870
end
870871
for dir in dirs
871-
path = joinpath(root,dir)
872-
if follow_symlinks || !islink(path)
873-
for (root_l, dirs_l, files_l) in walkdir(path, topdown=topdown, follow_symlinks=follow_symlinks, onerror=onerror)
874-
put!(chnl, (root_l, dirs_l, files_l))
875-
end
876-
end
872+
_walkdir(chnl, joinpath(root, dir))
877873
end
878874
if !topdown
879-
put!(chnl, (root, dirs, files))
875+
push!(chnl, (root, dirs, files))
880876
end
877+
nothing
881878
end
882-
883-
return Channel(_it)
879+
return Channel(chnl -> _walkdir(chnl, root))
884880
end
885881

886882
function unlink(p::AbstractString)

base/filesystem.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,26 +219,26 @@ const SEEK_END = Int32(2)
219219

220220
function seek(f::File, n::Integer)
221221
ret = ccall(:jl_lseek, Int64, (OS_HANDLE, Int64, Int32), f.handle, n, SEEK_SET)
222-
systemerror("seek", ret == -1)
222+
ret == -1 && (@static Sys.iswindows() ? windowserror : systemerror)("seek")
223223
return f
224224
end
225225

226226
function seekend(f::File)
227227
ret = ccall(:jl_lseek, Int64, (OS_HANDLE, Int64, Int32), f.handle, 0, SEEK_END)
228-
systemerror("seekend", ret == -1)
228+
ret == -1 && (@static Sys.iswindows() ? windowserror : systemerror)("seekend")
229229
return f
230230
end
231231

232232
function skip(f::File, n::Integer)
233233
ret = ccall(:jl_lseek, Int64, (OS_HANDLE, Int64, Int32), f.handle, n, SEEK_CUR)
234-
systemerror("skip", ret == -1)
234+
ret == -1 && (@static Sys.iswindows() ? windowserror : systemerror)("skip")
235235
return f
236236
end
237237

238238
function position(f::File)
239239
check_open(f)
240240
ret = ccall(:jl_lseek, Int64, (OS_HANDLE, Int64, Int32), f.handle, 0, SEEK_CUR)
241-
systemerror("lseek", ret == -1)
241+
ret == -1 && (@static Sys.iswindows() ? windowserror : systemerror)("lseek")
242242
return ret
243243
end
244244

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,10 @@ function complete_path(path::AbstractString, pos; use_envpath=false, shell_escap
250250
filesinpath = readdir(pathdir)
251251
catch e
252252
# Bash allows dirs in PATH that can't be read, so we should as well.
253-
if isa(e, SystemError)
253+
if isa(e, Base.IOError)
254254
continue
255255
else
256-
# We only handle SystemErrors here
256+
# We only handle IOError here
257257
rethrow()
258258
end
259259
end

test/file.jl

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ subdir = joinpath(dir, "adir")
1515
mkdir(subdir)
1616
subdir2 = joinpath(dir, "adir2")
1717
mkdir(subdir2)
18-
@test_throws Base.IOError mkdir(file)
18+
@test_throws Base._UVError("mkdir", Base.UV_EEXIST) mkdir(file)
1919
let err = nothing
2020
try
2121
mkdir(file)
@@ -445,9 +445,9 @@ close(f)
445445

446446
rm(c_tmpdir, recursive=true)
447447
@test !isdir(c_tmpdir)
448-
@test_throws Base.IOError rm(c_tmpdir)
448+
@test_throws Base._UVError(Sys.iswindows() ? "chmod" : "unlink", Base.UV_ENOENT) rm(c_tmpdir)
449449
@test rm(c_tmpdir, force=true) === nothing
450-
@test_throws Base.IOError rm(c_tmpdir, recursive=true)
450+
@test_throws Base._UVError(Sys.iswindows() ? "chmod" : "unlink", Base.UV_ENOENT) rm(c_tmpdir, recursive=true)
451451
@test rm(c_tmpdir, force=true, recursive=true) === nothing
452452

453453
if !Sys.iswindows()
@@ -462,8 +462,8 @@ if !Sys.iswindows()
462462
@test stat(file).gid == 0
463463
@test stat(file).uid == 0
464464
else
465-
@test_throws Base.IOError chown(file, -2, -1) # Non-root user cannot change ownership to another user
466-
@test_throws Base.IOError chown(file, -1, -2) # Non-root user cannot change group to a group they are not a member of (eg: nogroup)
465+
@test_throws Base._UVError("chown", Base.UV_EPERM) chown(file, -2, -1) # Non-root user cannot change ownership to another user
466+
@test_throws Base._UVError("chown", Base.UV_EPERM) chown(file, -1, -2) # Non-root user cannot change group to a group they are not a member of (eg: nogroup)
467467
end
468468
else
469469
# test that chown doesn't cause any errors for Windows
@@ -904,29 +904,31 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
904904
for src in dirs
905905
for dst in dirs
906906
# cptree
907-
@test_throws ArgumentError Base.cptree(src,dst; force=true, follow_symlinks=false)
908-
@test_throws ArgumentError Base.cptree(src,dst; force=true, follow_symlinks=true)
907+
@test_throws ArgumentError Base.cptree(src, dst; force=true, follow_symlinks=false)
908+
@test_throws ArgumentError Base.cptree(src, dst; force=true, follow_symlinks=true)
909909
# cp
910-
@test_throws ArgumentError cp(src,dst; force=true, follow_symlinks=false)
911-
@test_throws ArgumentError cp(src,dst; force=true, follow_symlinks=true)
910+
@test_throws ArgumentError cp(src, dst; force=true, follow_symlinks=false)
911+
@test_throws ArgumentError cp(src, dst; force=true, follow_symlinks=true)
912912
# mv
913-
@test_throws ArgumentError mv(src,dst; force=true)
913+
@test_throws ArgumentError mv(src, dst; force=true)
914914
end
915915
end
916916
end
917917
# None existing src
918918
mktempdir() do tmpdir
919-
none_existing_src = joinpath(tmpdir, "none_existing_src")
919+
nonexisting_src = joinpath(tmpdir, "nonexisting_src")
920920
dst = joinpath(tmpdir, "dst")
921-
@test !ispath(none_existing_src)
921+
@test !ispath(nonexisting_src)
922922
# cptree
923-
@test_throws ArgumentError Base.cptree(none_existing_src,dst; force=true, follow_symlinks=false)
924-
@test_throws ArgumentError Base.cptree(none_existing_src,dst; force=true, follow_symlinks=true)
923+
@test_throws(ArgumentError("'$nonexisting_src' is not a directory. Use `cp(src, dst)`"),
924+
Base.cptree(nonexisting_src, dst; force=true, follow_symlinks=false))
925+
@test_throws(ArgumentError("'$nonexisting_src' is not a directory. Use `cp(src, dst)`"),
926+
Base.cptree(nonexisting_src, dst; force=true, follow_symlinks=true))
925927
# cp
926-
@test_throws Base.IOError cp(none_existing_src,dst; force=true, follow_symlinks=false)
927-
@test_throws Base.IOError cp(none_existing_src,dst; force=true, follow_symlinks=true)
928+
@test_throws Base._UVError("open", Base.UV_ENOENT) cp(nonexisting_src, dst; force=true, follow_symlinks=false)
929+
@test_throws Base._UVError("open", Base.UV_ENOENT) cp(nonexisting_src, dst; force=true, follow_symlinks=true)
928930
# mv
929-
@test_throws Base.IOError mv(none_existing_src,dst; force=true)
931+
@test_throws Base._UVError("open", Base.UV_ENOENT) mv(nonexisting_src, dst; force=true)
930932
end
931933
end
932934

@@ -1130,13 +1132,13 @@ if !Sys.iswindows()
11301132
for src in files
11311133
for dst in files
11321134
# cptree
1133-
@test_throws ArgumentError Base.cptree(src,dst; force=true, follow_symlinks=false)
1134-
@test_throws ArgumentError Base.cptree(src,dst; force=true, follow_symlinks=true)
1135+
@test_throws ArgumentError Base.cptree(src, dst; force=true, follow_symlinks=false)
1136+
@test_throws ArgumentError Base.cptree(src, dst; force=true, follow_symlinks=true)
11351137
# cp
1136-
@test_throws ArgumentError cp(src,dst; force=true, follow_symlinks=false)
1137-
@test_throws ArgumentError cp(src,dst; force=true, follow_symlinks=true)
1138+
@test_throws ArgumentError cp(src, dst; force=true, follow_symlinks=false)
1139+
@test_throws ArgumentError cp(src, dst; force=true, follow_symlinks=true)
11381140
# mv
1139-
@test_throws ArgumentError mv(src,dst; force=true)
1141+
@test_throws ArgumentError mv(src, dst; force=true)
11401142
end
11411143
end
11421144
end
@@ -1298,7 +1300,8 @@ cd(dirwalk) do
12981300
@test files == ["file1", "file2"]
12991301

13001302
rm(joinpath("sub_dir1"), recursive=true)
1301-
@test_throws TaskFailedException take!(chnl_error) # throws an error because sub_dir1 do not exist
1303+
@test_throws(Base._UVError("readdir", Base.UV_ENOENT, "with ", repr(joinpath(".", "sub_dir1"))),
1304+
take!(chnl_error)) # throws an error because sub_dir1 do not exist
13021305

13031306
root, dirs, files = take!(chnl_noerror)
13041307
@test root == "."
@@ -1313,9 +1316,12 @@ cd(dirwalk) do
13131316
# Test that symlink loops don't cause errors
13141317
if has_symlinks
13151318
mkdir(joinpath(".", "sub_dir3"))
1316-
symlink("foo", joinpath(".", "sub_dir3", "foo"))
1319+
foo = joinpath(".", "sub_dir3", "foo")
1320+
symlink("foo", foo)
13171321

1318-
@test_throws Base.IOError walkdir(joinpath(".", "sub_dir3"); follow_symlinks=true)
1322+
let files = walkdir(joinpath(".", "sub_dir3"); follow_symlinks=true)
1323+
@test_throws Base._UVError("stat", Base.UV_ELOOP, "for file ", repr(foo)) take!(files)
1324+
end
13191325
root, dirs, files = take!(walkdir(joinpath(".", "sub_dir3"); follow_symlinks=false))
13201326
@test root == joinpath(".", "sub_dir3")
13211327
@test dirs == []
@@ -1504,8 +1510,8 @@ end
15041510
rm(d, recursive=true)
15051511
@test !ispath(d)
15061512
@test isempty(readdir())
1507-
@test_throws SystemError readdir(d)
1508-
@test_throws Base.IOError readdir(join=true)
1513+
@test_throws Base._UVError("readdir", Base.UV_ENOENT, "with ", repr(d)) readdir(d)
1514+
@test_throws Base._UVError("cwd", Base.UV_ENOENT) readdir(join=true)
15091515
end
15101516
end
15111517
end

0 commit comments

Comments
 (0)