Skip to content

Commit 4226456

Browse files
sambitdashvtjnash
andcommitted
mktempdir: support user-defined prefix
Also switch to using libuv, as it now provides a platform-independent implementation. Co-Authored-By: Jameson Nash <[email protected]>
1 parent 54fe55b commit 4226456

File tree

4 files changed

+96
-41
lines changed

4 files changed

+96
-41
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Standard library changes
4646
* `filter` now supports `SkipMissing`-wrapped arrays ([#31235]).
4747
* A no-argument construct to `Ptr{T}` has been added which constructs a null pointer ([#30919])
4848
* `strip` now accepts a function argument in the same manner as `lstrip` and `rstrip` ([#31211])
49+
* `mktempdir` now accepts a `prefix` keyword argument to customize the file name ([#31230], [#22922])
4950

5051
#### LinearAlgebra
5152

base/file.jl

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -416,24 +416,29 @@ function touch(path::AbstractString)
416416
path
417417
end
418418

419+
const temp_prefix = "jl_"
420+
419421
if Sys.iswindows()
420422

421423
function tempdir()
422424
temppath = Vector{UInt16}(undef, 32767)
423-
lentemppath = ccall(:GetTempPathW,stdcall,UInt32,(UInt32,Ptr{UInt16}),length(temppath),temppath)
425+
lentemppath = ccall(:GetTempPathW, stdcall, UInt32, (UInt32, Ptr{UInt16}), length(temppath), temppath)
424426
windowserror("GetTempPath", lentemppath >= length(temppath) || lentemppath == 0)
425-
resize!(temppath,lentemppath)
427+
resize!(temppath, lentemppath)
426428
return transcode(String, temppath)
427429
end
428430

429-
const temp_prefix = cwstring("jl_")
430431
function _win_tempname(temppath::AbstractString, uunique::UInt32)
431432
tempp = cwstring(temppath)
433+
temppfx = cwstring(temp_prefix)
432434
tname = Vector{UInt16}(undef, 32767)
433-
uunique = ccall(:GetTempFileNameW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32,Ptr{UInt16}), tempp,temp_prefix,uunique,tname)
434-
lentname = something(findfirst(iszero,tname), 0)-1
435-
windowserror("GetTempFileName", uunique == 0 || lentname <= 0)
436-
resize!(tname,lentname)
435+
uunique = ccall(:GetTempFileNameW, stdcall, UInt32,
436+
(Ptr{UInt16}, Ptr{UInt16}, UInt32, Ptr{UInt16}),
437+
tempp, temppfx, uunique, tname)
438+
windowserror("GetTempFileName", uunique == 0)
439+
lentname = something(findfirst(iszero, tname))
440+
@assert lentname > 0
441+
resize!(tname, lentname - 1)
437442
return transcode(String, tname)
438443
end
439444

@@ -442,22 +447,6 @@ function mktemp(parent=tempdir())
442447
return (filename, Base.open(filename, "r+"))
443448
end
444449

445-
function mktempdir(parent=tempdir())
446-
seed::UInt32 = Libc.rand(UInt32)
447-
while true
448-
if (seed & typemax(UInt16)) == 0
449-
seed += 1
450-
end
451-
filename = _win_tempname(parent, seed)
452-
ret = ccall(:_wmkdir, Int32, (Ptr{UInt16},), cwstring(filename))
453-
if ret == 0
454-
return filename
455-
end
456-
systemerror(:mktempdir, Libc.errno()!=Libc.EEXIST)
457-
seed += 1
458-
end
459-
end
460-
461450
function tempname()
462451
parent = tempdir()
463452
seed::UInt32 = rand(UInt32)
@@ -477,7 +466,7 @@ else # !windows
477466
# Obtain a temporary filename.
478467
function tempname()
479468
d = get(ENV, "TMPDIR", C_NULL) # tempnam ignores TMPDIR on darwin
480-
p = ccall(:tempnam, Cstring, (Cstring,Cstring), d, :julia)
469+
p = ccall(:tempnam, Cstring, (Cstring, Cstring), d, temp_prefix)
481470
systemerror(:tempnam, p == C_NULL)
482471
s = unsafe_string(p)
483472
Libc.free(p)
@@ -489,19 +478,12 @@ tempdir() = dirname(tempname())
489478

490479
# Create and return the name of a temporary file along with an IOStream
491480
function mktemp(parent=tempdir())
492-
b = joinpath(parent, "tmpXXXXXX")
481+
b = joinpath(parent, temp_prefix * "XXXXXX")
493482
p = ccall(:mkstemp, Int32, (Cstring,), b) # modifies b
494483
systemerror(:mktemp, p == -1)
495484
return (b, fdio(p, true))
496485
end
497486

498-
# Create and return the name of a temporary directory
499-
function mktempdir(parent=tempdir())
500-
b = joinpath(parent, "tmpXXXXXX")
501-
p = ccall(:mkdtemp, Cstring, (Cstring,), b)
502-
systemerror(:mktempdir, p == C_NULL)
503-
return unsafe_string(p)
504-
end
505487

506488
end # os-test
507489

@@ -536,12 +518,37 @@ is an open file object for this path.
536518
mktemp(parent)
537519

538520
"""
539-
mktempdir(parent=tempdir())
521+
mktempdir(parent=tempdir(); prefix=$(repr(temp_prefix)))
540522
541-
Create a temporary directory in the `parent` directory and return its path.
523+
Create a temporary directory in the `parent` directory with a name
524+
constructed from the given prefix and a random suffix, and return its path.
525+
Additionally, any trailing `X` characters may be replaced with random characters.
542526
If `parent` does not exist, throw an error.
543527
"""
544-
mktempdir(parent)
528+
function mktempdir(parent=tempdir(); prefix=temp_prefix)
529+
if isempty(parent) || occursin(path_separator_re, parent[end:end])
530+
# append a path_separator only if parent didn't already have one
531+
tpath = "$(parent)$(prefix)XXXXXX"
532+
else
533+
tpath = "$(parent)$(path_separator)$(prefix)XXXXXX"
534+
end
535+
536+
req = Libc.malloc(_sizeof_uv_fs)
537+
try
538+
ret = ccall(:uv_fs_mkdtemp, Int32,
539+
(Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}),
540+
eventloop(), req, tpath, C_NULL)
541+
if ret < 0
542+
ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req)
543+
uv_error("mktempdir", ret)
544+
end
545+
path = unsafe_string(ccall(:jl_uv_fs_t_path, Cstring, (Ptr{Cvoid},), req))
546+
ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req)
547+
return path
548+
finally
549+
Libc.free(req)
550+
end
551+
end
545552

546553

547554
"""
@@ -566,13 +573,13 @@ function mktemp(fn::Function, parent=tempdir())
566573
end
567574

568575
"""
569-
mktempdir(f::Function, parent=tempdir())
576+
mktempdir(f::Function, parent=tempdir(); prefix=$(repr(temp_prefix)))
570577
571-
Apply the function `f` to the result of [`mktempdir(parent)`](@ref) and remove the
572-
temporary directory upon completion.
578+
Apply the function `f` to the result of [`mktempdir(parent; prefix)`](@ref) and remove the
579+
temporary directory all of its contents upon completion.
573580
"""
574-
function mktempdir(fn::Function, parent=tempdir())
575-
tmpdir = mktempdir(parent)
581+
function mktempdir(fn::Function, parent=tempdir(); prefix=temp_prefix)
582+
tmpdir = mktempdir(parent; prefix=prefix)
576583
try
577584
fn(tmpdir)
578585
finally
@@ -809,7 +816,7 @@ function readlink(path::AbstractString)
809816
uv_error("readlink", ret)
810817
@assert false
811818
end
812-
tgt = unsafe_string(ccall(:jl_uv_fs_t_ptr, Ptr{Cchar}, (Ptr{Cvoid},), req))
819+
tgt = unsafe_string(ccall(:jl_uv_fs_t_ptr, Cstring, (Ptr{Cvoid},), req))
813820
ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req)
814821
return tgt
815822
finally

src/sys.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ JL_DLLEXPORT int32_t jl_nb_available(ios_t *s)
120120
JL_DLLEXPORT int jl_sizeof_uv_fs_t(void) { return sizeof(uv_fs_t); }
121121
JL_DLLEXPORT void jl_uv_fs_req_cleanup(uv_fs_t *req) { uv_fs_req_cleanup(req); }
122122
JL_DLLEXPORT char *jl_uv_fs_t_ptr(uv_fs_t *req) { return (char*)req->ptr; }
123+
JL_DLLEXPORT char *jl_uv_fs_t_path(uv_fs_t *req) { return (char*)req->path; }
123124
JL_DLLEXPORT ssize_t jl_uv_fs_result(uv_fs_t *f) { return f->result; }
124125

125126
// --- stat ---

test/file.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,3 +1069,49 @@ let n = tempname()
10691069
end
10701070

10711071
@test_throws ArgumentError mkpath("fakepath", mode = -1)
1072+
1073+
@testset "mktempdir 'prefix' argument" begin
1074+
tmpdirbase = joinpath(tempdir(), "")
1075+
def_prefix = "jl_"
1076+
mktempdir() do tmpdir
1077+
@test isdir(tmpdir)
1078+
@test startswith(tmpdir, tmpdirbase * def_prefix)
1079+
@test sizeof(tmpdir) == sizeof(tmpdirbase) + sizeof(def_prefix) + 6
1080+
@test sizeof(basename(tmpdir)) == sizeof(def_prefix) + 6
1081+
cd(tmpdir) do
1082+
Sys.iswindows() || mkdir(".\\")
1083+
for relpath in (".", "./", ".\\", "")
1084+
mktempdir(relpath) do tmpdir2
1085+
pfx = joinpath(relpath, def_prefix)
1086+
@test sizeof(tmpdir2) == sizeof(pfx) + 6
1087+
@test startswith(tmpdir2, pfx)
1088+
end
1089+
end
1090+
end
1091+
end
1092+
# Special character prefix tests
1093+
for tst_prefix in ("ABCDEF", "./pfx", ".\\pfx", "", "#!@%^&()-", "/", "\\", "////abc", "\\\\\\\\abc", "∃x∀y")
1094+
mktempdir(; prefix=tst_prefix) do tmpdir
1095+
@test isdir(tmpdir)
1096+
@test startswith(tmpdir, tmpdirbase * tst_prefix)
1097+
@test sizeof(basename(tmpdir)) == 6 + sizeof(basename(tst_prefix))
1098+
end
1099+
end
1100+
1101+
@test_throws Base.IOError mktempdir(; prefix="dir_notexisting/bar")
1102+
@test_throws Base.IOError mktempdir(; prefix="dir_notexisting/")
1103+
@test_throws Base.IOError mktempdir("dir_notexisting/")
1104+
1105+
# Behavioral differences across OS types
1106+
if Sys.iswindows()
1107+
# invalid file name
1108+
@test_throws Base.IOError mktempdir(; prefix="a*b")
1109+
@test_throws Base.IOError mktempdir("a*b")
1110+
end
1111+
1112+
mktempdir(""; prefix=tmpdirbase) do tmpdir
1113+
@test startswith(tmpdir, tmpdirbase)
1114+
@test sizeof(tmpdir) == 6 + sizeof(tmpdirbase)
1115+
@test sizeof(basename(tmpdir)) == 6
1116+
end
1117+
end

0 commit comments

Comments
 (0)