Skip to content

Commit c72f110

Browse files
ENV as a normal Dict: populate at boot, copy when spawning children
1 parent 99661c9 commit c72f110

File tree

4 files changed

+78
-153
lines changed

4 files changed

+78
-153
lines changed

base/Base.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ function __init__()
397397
reinit_stdio()
398398
Multimedia.reinit_displays() # since Multimedia.displays uses stdout as fallback
399399
# initialize loading
400+
init_env()
400401
init_depot_path()
401402
init_load_path()
402403
nothing

base/env.jl

Lines changed: 17 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,32 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
if Sys.iswindows()
4-
const ERROR_ENVVAR_NOT_FOUND = UInt32(203)
5-
6-
_getenvlen(var::Vector{UInt16}) = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,C_NULL,0)
7-
_hasenv(s::Vector{UInt16}) = _getenvlen(s) != 0 || Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND
8-
_hasenv(s::AbstractString) = _hasenv(cwstring(s))
9-
10-
function access_env(onError::Function, str::AbstractString)
11-
var = cwstring(str)
12-
len = _getenvlen(var)
13-
if len == 0
14-
return Libc.GetLastError() != ERROR_ENVVAR_NOT_FOUND ? "" : onError(str)
15-
end
16-
val = zeros(UInt16,len)
17-
ret = ccall(:GetEnvironmentVariableW,stdcall,UInt32,(Ptr{UInt16},Ptr{UInt16},UInt32),var,val,len)
18-
windowserror(:getenv, (ret == 0 && len != 1) || ret != len-1 || val[end] != 0)
19-
pop!(val) # NUL
20-
return transcode(String, val)
21-
end
22-
23-
function _setenv(svar::AbstractString, sval::AbstractString, overwrite::Bool=true)
24-
var = cwstring(svar)
25-
val = cwstring(sval)
26-
if overwrite || !_hasenv(var)
27-
ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,val)
28-
windowserror(:setenv, ret == 0)
29-
end
30-
end
31-
32-
function _unsetenv(svar::AbstractString)
33-
var = cwstring(svar)
34-
ret = ccall(:SetEnvironmentVariableW,stdcall,Int32,(Ptr{UInt16},Ptr{UInt16}),var,C_NULL)
35-
windowserror(:setenv, ret == 0)
36-
end
37-
else # !windows
38-
_getenv(var::AbstractString) = ccall(:getenv, Cstring, (Cstring,), var)
39-
_hasenv(s::AbstractString) = _getenv(s) != C_NULL
40-
41-
function access_env(onError::Function, var::AbstractString)
42-
val = _getenv(var)
43-
val == C_NULL ? onError(var) : unsafe_string(val)
44-
end
45-
46-
function _setenv(var::AbstractString, val::AbstractString, overwrite::Bool=true)
47-
ret = ccall(:setenv, Int32, (Cstring,Cstring,Int32), var, val, overwrite)
48-
systemerror(:setenv, ret != 0)
49-
end
50-
51-
function _unsetenv(var::AbstractString)
52-
ret = ccall(:unsetenv, Int32, (Cstring,), var)
53-
systemerror(:unsetenv, ret != 0)
54-
end
55-
end # os test
56-
57-
## ENV: hash interface ##
58-
59-
"""
60-
EnvDict() -> EnvDict
61-
62-
A singleton of this type provides a hash table interface to environment variables.
63-
"""
64-
struct EnvDict <: AbstractDict{String,String}; end
65-
66-
"""
67-
ENV
68-
69-
Reference to the singleton `EnvDict`, providing a dictionary interface to system environment
70-
variables.
71-
72-
(On Windows, system environment variables are case-insensitive, and `ENV` correspondingly converts
73-
all keys to uppercase for display, iteration, and copying. Portable code should not rely on the
74-
ability to distinguish variables by case, and should beware that setting an ostensibly lowercase
75-
variable may result in an uppercase `ENV` key.)
76-
"""
77-
const ENV = EnvDict()
78-
79-
getindex(::EnvDict, k::AbstractString) = access_env(k->throw(KeyError(k)), k)
80-
get(::EnvDict, k::AbstractString, def) = access_env(k->def, k)
81-
get(f::Callable, ::EnvDict, k::AbstractString) = access_env(k->f(), k)
82-
in(k::AbstractString, ::KeySet{String, EnvDict}) = _hasenv(k)
83-
pop!(::EnvDict, k::AbstractString) = (v = ENV[k]; _unsetenv(k); v)
84-
pop!(::EnvDict, k::AbstractString, def) = haskey(ENV,k) ? pop!(ENV,k) : def
85-
delete!(::EnvDict, k::AbstractString) = (_unsetenv(k); ENV)
86-
setindex!(::EnvDict, v, k::AbstractString) = _setenv(k,string(v))
87-
push!(::EnvDict, kv::Pair{<:AbstractString}) = setindex!(ENV, kv.second, kv.first)
88-
89-
if Sys.iswindows()
90-
GESW() = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos))
91-
function winuppercase(s::AbstractString)
92-
isempty(s) && return s
93-
LOCALE_INVARIANT = 0x0000007f
94-
LCMAP_UPPERCASE = 0x00000200
95-
ws = transcode(UInt16, String(s))
96-
result = ccall(:LCMapStringW, stdcall, Cint, (UInt32, UInt32, Ptr{UInt16}, Cint, Ptr{UInt16}, Cint),
97-
LOCALE_INVARIANT, LCMAP_UPPERCASE, ws, length(ws), ws, length(ws))
98-
systemerror(:LCMapStringW, iszero(result))
99-
return transcode(String, ws)
100-
end
101-
function iterate(hash::EnvDict, block::Tuple{Ptr{UInt16},Ptr{UInt16}} = GESW())
102-
if unsafe_load(block[1]) == 0
103-
ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2])
104-
return nothing
105-
end
106-
pos = block[1]
107-
blk = block[2]
108-
len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos)
109-
buf = Vector{UInt16}(undef, len)
110-
GC.@preserve buf unsafe_copyto!(pointer(buf), pos, len)
111-
env = transcode(String, buf)
112-
m = match(r"^(=?[^=]+)=(.*)$"s, env)
113-
if m === nothing
114-
error("malformed environment entry: $env")
115-
end
116-
return (Pair{String,String}(winuppercase(m.captures[1]), m.captures[2]), (pos+(len+1)*2, blk))
117-
end
118-
else # !windows
119-
function iterate(::EnvDict, i=0)
120-
env = ccall(:jl_environ, Any, (Int32,), i)
121-
env === nothing && return nothing
122-
env = env::String
123-
m = match(r"^(.*?)=(.*)$"s, env)
124-
if m === nothing
125-
error("malformed environment entry: $env")
126-
end
127-
return (Pair{String,String}(m.captures[1], m.captures[2]), i+1)
128-
end
129-
end # os-test
130-
131-
#TODO: Make these more efficient
132-
function length(::EnvDict)
133-
i = 0
134-
for (k,v) in ENV
135-
i += 1
136-
end
137-
return i
138-
end
139-
140-
function show(io::IO, ::EnvDict)
141-
for (k,v) = ENV
142-
println(io, "$k=$v")
143-
end
144-
end
145-
1463
"""
1474
withenv(f::Function, kv::Pair...)
1485
149-
Execute `f` in an environment that is temporarily modified (not replaced as in `setenv`)
6+
Execute `f` in an environment that is temporarily modified (not replaced as in`setenv`)
1507
by zero or more `"var"=>val` arguments `kv`. `withenv` is generally used via the
151-
`withenv(kv...) do ... end` syntax. A value of `nothing` can be used to temporarily unset an
152-
environment variable (if it is set). When `withenv` returns, the original environment has
153-
been restored.
8+
`withenv(kv...) do ... end` syntax. A value of `nothing` can be used to temporarily
9+
unset an environment variable (if it is set). When `withenv` returns, the original
10+
environment has been restored.
15411
"""
15512
function withenv(f::Function, keyvals::Pair{T}...) where T<:AbstractString
15613
old = Dict{T,Any}()
157-
for (key,val) in keyvals
158-
old[key] = get(ENV,key,nothing)
159-
val !== nothing ? (ENV[key]=val) : delete!(ENV, key)
14+
for (key, val) in keyvals
15+
old[key] = get(ENV, key, nothing)
16+
if val === nothing
17+
delete!(ENV, key)
18+
else
19+
ENV[key] = val
20+
end
16021
end
16122
try f()
16223
finally
163-
for (key,val) in old
164-
val !== nothing ? (ENV[key]=val) : delete!(ENV, key)
24+
for (key, val) in old
25+
if val === nothing
26+
delete!(ENV, key)
27+
else
28+
ENV[key] = val
29+
end
16530
end
16631
end
16732
end

base/initdefs.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,65 @@ An array of the command line arguments passed to Julia, as strings.
1818
"""
1919
const ARGS = String[]
2020

21+
"""
22+
ENV
23+
24+
A dictionary that is populated from the initial process environment and whose contents
25+
are used to populate the environments of spawned subprocesses.
26+
27+
!!! note
28+
As of Julia 1.6, this is a plain `Dict` no longer a special type that directly and
29+
synchronously modifies the process environment, since doing so is inherently thread
30+
unsafe in a way that cannot be mitigated due to how most `libc` libraries work.
31+
"""
32+
const ENV = Dict{String,String}()
33+
34+
if !Sys.iswindows()
35+
function init_env()
36+
empty!(ENV)
37+
idx = 0
38+
while true
39+
entry = ccall(:jl_environ, Any, (Int32,), idx)
40+
entry === nothing && break
41+
entry = entry::String
42+
m = match(r"^(.*?)=(.*)$"s, entry)
43+
m === nothing && error("malformed environment entry: $entry")
44+
ENV[m.captures[1]] = m.captures[2]
45+
idx += 1
46+
end
47+
end
48+
else
49+
function winuppercase(s::AbstractString)
50+
isempty(s) && return s
51+
LOCALE_INVARIANT = 0x0000007f
52+
LCMAP_UPPERCASE = 0x00000200
53+
ws = transcode(UInt16, String(s))
54+
result = ccall(:LCMapStringW, stdcall, Cint,
55+
(UInt32, UInt32, Ptr{UInt16}, Cint, Ptr{UInt16}, Cint),
56+
LOCALE_INVARIANT, LCMAP_UPPERCASE, ws, length(ws), ws, length(ws))
57+
systemerror(:LCMapStringW, iszero(result))
58+
return transcode(String, ws)
59+
end
60+
61+
function init_env()
62+
env = ccall(:GetEnvironmentStringsW, stdcall, Ptr{UInt16}, ())
63+
try pos = env
64+
while !iszero(unsafe_load(pos))
65+
len = ccall(:wcslen, UInt, (Ptr{UInt16},), pos)
66+
buf = Vector{UInt16}(undef, len)
67+
GC.@preserve buf unsafe_copyto!(pointer(buf), pos, len)
68+
entry = transcode(String, buf)
69+
m = match(r"^(.*?)=(.*)$"s, entry)
70+
m === nothing && error("malformed environment entry: $entry")
71+
ENV[winuppercase(m.captures[1])] = m.captures[2]
72+
pos += 2(len+1) # 2-byte code units
73+
end
74+
finally
75+
ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), env)
76+
end
77+
end
78+
end
79+
2180
"""
2281
exit(code=0)
2382

base/process.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const SpawnIOs = Vector{Any} # convenience name for readability
9191
file, cmd.exec, loop, handle,
9292
iohandles, length(iohandles),
9393
cmd.flags,
94-
cmd.env === nothing ? C_NULL : cmd.env,
94+
cmd.env === nothing ? ["$k=$v" for (k, v) in ENV] : cmd.env,
9595
isempty(cmd.dir) ? C_NULL : cmd.dir,
9696
uv_jl_return_spawn::Ptr{Cvoid})
9797
if err != 0

0 commit comments

Comments
 (0)