Skip to content

Commit

Permalink
make Windows realpath consistent with Mac and Unix (#30611)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengj authored and StefanKarpinski committed Jan 10, 2019
1 parent bb636aa commit 09c31b4
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 12 deletions.
33 changes: 23 additions & 10 deletions base/path.jl
Original file line number Diff line number Diff line change
Expand Up @@ -333,17 +333,26 @@ current directory if necessary. Equivalent to `abspath(joinpath(path, paths...))
abspath(a::AbstractString, b::AbstractString...) = abspath(joinpath(a,b...))

if Sys.iswindows()

function realpath(path::AbstractString)
p = cwstring(path)
buf = zeros(UInt16, length(p))
while true
n = ccall((:GetFullPathNameW, "kernel32"), stdcall,
UInt32, (Ptr{UInt16}, UInt32, Ptr{UInt16}, Ptr{Cvoid}),
p, length(buf), buf, C_NULL)
windowserror(:realpath, n == 0)
x = n < length(buf) # is the buffer big enough?
resize!(buf, n) # shrink if x, grow if !x
x && return transcode(String, buf)
h = ccall(:CreateFileW, stdcall, Int, (Cwstring, UInt32, UInt32, Ptr{Cvoid}, UInt32, UInt32, Int),
path, 0, 0x03, C_NULL, 3, 0x02000000, 0)
windowserror(:realpath, h == -1)
try
buf = Array{UInt16}(undef, 256)
oldlen = len = length(buf)
while len >= oldlen
len = ccall(:GetFinalPathNameByHandleW, stdcall, UInt32, (Int, Ptr{UInt16}, UInt32, UInt32),
h, buf, (oldlen=len)-1, 0x0)
windowserror(:realpath, iszero(len))
resize!(buf, len) # strips NUL terminator on last call
end
if 4 < len < 264 && 0x005c == buf[1] == buf[2] == buf[4] && 0x003f == buf[3]

This comment has been minimized.

Copy link
@PallHaraldsson

PallHaraldsson Jan 11, 2019

Contributor

Is unexplained 264 constant for sure correct? Looking into Windows' MAXPATH that I thought shorter I found:

https://stackoverflow.com/questions/1880321/why-does-the-260-character-path-length-limit-exist-in-windows
"This is not strictly true as the NTFS filesystem supports paths up to 32k characters."

Off-topic while on-topic for Windows file system:
https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
Allowed chars, except: "Characters whose integer representations are in the range from 1 through 31, except for alternate data streams where these characters are allowed."

I'm a little curious if Julia supports alternate data streams, or needs to (I believe most software isn't aware of or ignores them).

Base._deletebeg!(buf, 4) # omit \\?\ prefix for paths < MAXPATH in length
end
return transcode(String, buf)
finally
windowserror(:realpath, iszero(ccall(:CloseHandle, stdcall, Cint, (Int,), h)))
end
end

Expand Down Expand Up @@ -376,6 +385,10 @@ end # os-test
realpath(path::AbstractString) -> AbstractString
Canonicalize a path by expanding symbolic links and removing "." and ".." entries.
On case-insensitive case-preserving filesystems (typically Mac and Windows), the
filesystem's stored case for the path is returned.
(This function throws an exception if `path` does not exist in the filesystem.)
"""
realpath(path::AbstractString)

Expand Down
17 changes: 15 additions & 2 deletions test/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1039,8 +1039,21 @@ mktempdir() do dir
@test isdir(namepath)
end



# issue #30588
@test realpath(".") == realpath(pwd())
mktempdir() do dir
cd(dir) do
path = touch("FooBar.txt")
@test ispath(realpath(path))
if ispath(uppercase(path)) # case-insensitive filesystem
@test realpath(path) == realpath(uppercase(path)) == realpath(lowercase(path)) ==
realpath(uppercase(realpath(path))) == realpath(lowercase(realpath(path)))
@test basename(realpath(uppercase(path))) == path
end
rm(path)
@test_throws SystemError realpath(path)
end
end

# issue #9687
let n = tempname()
Expand Down

0 comments on commit 09c31b4

Please sign in to comment.