diff --git a/base/file.jl b/base/file.jl index 74cf10f6ca8fa..16d264a119f0e 100644 --- a/base/file.jl +++ b/base/file.jl @@ -554,15 +554,52 @@ end const temp_prefix = "jl_" -if Sys.iswindows() +# Use `Libc.rand()` to generate random strings +function _rand_filename(len = 10) + slug = Base.StringVector(len) + chars = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + for i = 1:len + slug[i] = chars[(Libc.rand() % length(chars)) + 1] + end + return String(slug) +end + + +# Obtain a temporary filename. +function tempname(parent::AbstractString=tempdir(); max_tries::Int = 100, cleanup::Bool=true) + isdir(parent) || throw(ArgumentError("$(repr(parent)) is not a directory")) + + prefix = joinpath(parent, temp_prefix) + filename = nothing + for i in 1:max_tries + filename = string(prefix, _rand_filename()) + if ispath(filename) + filename = nothing + else + break + end + end + + if filename === nothing + error("tempname: max_tries exhausted") + end -function _win_tempname(temppath::AbstractString, uunique::UInt32) + cleanup && temp_cleanup_later(filename) + return filename +end + +if Sys.iswindows() +# While this isn't a true analog of `mkstemp`, it _does_ create an +# empty file for us, ensuring that other simultaneous calls to +# `_win_mkstemp()` won't collide, so it's a better name for the +# function than `tempname()`. +function _win_mkstemp(temppath::AbstractString) tempp = cwstring(temppath) temppfx = cwstring(temp_prefix) tname = Vector{UInt16}(undef, 32767) uunique = ccall(:GetTempFileNameW, stdcall, UInt32, (Ptr{UInt16}, Ptr{UInt16}, UInt32, Ptr{UInt16}), - tempp, temppfx, uunique, tname) + tempp, temppfx, UInt32(0), tname) windowserror("GetTempFileName", uunique == 0) lentname = something(findfirst(iszero, tname)) @assert lentname > 0 @@ -571,49 +608,13 @@ function _win_tempname(temppath::AbstractString, uunique::UInt32) end function mktemp(parent::AbstractString=tempdir(); cleanup::Bool=true) - filename = _win_tempname(parent, UInt32(0)) + filename = _win_mkstemp(parent) cleanup && temp_cleanup_later(filename) return (filename, Base.open(filename, "r+")) end -# generate a random string from random bytes -function _rand_string() - nchars = 10 - A = Vector{UInt8}(undef, nchars) - windowserror("SystemFunction036 (RtlGenRandom)", 0 == ccall( - (:SystemFunction036, :Advapi32), stdcall, UInt8, (Ptr{Cvoid}, UInt32), - A, sizeof(A))) - - slug = Base.StringVector(10) - chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - for i = 1:nchars - slug[i] = chars[(A[i] % length(chars)) + 1] - end - return name = String(slug) -end - -function tempname(parent::AbstractString=tempdir(); cleanup::Bool=true) - isdir(parent) || throw(ArgumentError("$(repr(parent)) is not a directory")) - name = _rand_string() - filename = joinpath(parent, temp_prefix * name) - @assert !ispath(filename) - cleanup && temp_cleanup_later(filename) - return filename -end - else # !windows -# Obtain a temporary filename. -function tempname(parent::AbstractString=tempdir(); cleanup::Bool=true) - isdir(parent) || throw(ArgumentError("$(repr(parent)) is not a directory")) - p = ccall(:tempnam, Cstring, (Cstring, Cstring), parent, temp_prefix) - systemerror(:tempnam, p == C_NULL) - s = unsafe_string(p) - Libc.free(p) - cleanup && temp_cleanup_later(s) - return s -end - # Create and return the name of a temporary file along with an IOStream function mktemp(parent::AbstractString=tempdir(); cleanup::Bool=true) b = joinpath(parent, temp_prefix * "XXXXXX") @@ -623,7 +624,6 @@ function mktemp(parent::AbstractString=tempdir(); cleanup::Bool=true) return (b, fdio(p, true)) end - end # os-test diff --git a/test/file.jl b/test/file.jl index 15dc5ef65bd5a..a7c0b6dca125d 100644 --- a/test/file.jl +++ b/test/file.jl @@ -106,15 +106,23 @@ using Random end @testset "tempname with parent" begin - withenv("TMPDIR" => nothing) do - t = tempname() - @test dirname(t) == tempdir() - mktempdir() do d + t = tempname() + @test dirname(t) == tempdir() + mktempdir() do d + t = tempname(d) + @test dirname(t) == d + end + @test_throws ArgumentError tempname(randstring()) + + # 38873: check that `TMPDIR` being set does not + # override the parent argument to `tempname`. + mktempdir() do d + withenv("TMPDIR"=>tempdir()) do t = tempname(d) @test dirname(t) == d end - @test_throws ArgumentError tempname(randstring()) end + @test_throws ArgumentError tempname(randstring()) end child_eval(code::String) = eval(Meta.parse(readchomp(`$(Base.julia_cmd()) -E $code`)))