diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index e003b0717..6ed193c1f 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -7,242 +7,371 @@ import "base:intrinsics" import "core:sync" import "core:slice" import "core:strings" +import "core:sys/linux" -// TODO: IF NO_CRT: -// Override the libc environment functions' weak linkage to -// allow us to interact with 3rd party code that DOES link -// to libc. Otherwise, our environment can be out of sync. -// ELSE: -// Just use the libc. +_ :: sync +_ :: slice +_ :: linux -NOT_FOUND :: -1 +when ODIN_NO_CRT { + // TODO: Override the libc environment functions' weak linkage to + // allow us to interact with 3rd party code that DOES link + // to libc. Otherwise, our environment can be out of sync. -// the environment is a 0 delimited list of = strings -_env: [dynamic]string + NOT_FOUND :: -1 -_env_mutex: sync.Recursive_Mutex + // the environment is a 0 delimited list of = strings + _env: [dynamic]string -// We need to be able to figure out if the environment variable -// is contained in the original environment or not. This also -// serves as a flag to determine if we have built _env. -_org_env_begin: uintptr // atomic -_org_env_end: uintptr // guarded by _env_mutex + _env_mutex: sync.Recursive_Mutex -// Returns value + index location into _env -// or -1 if not found -_lookup :: proc(key: string) -> (value: string, idx: int) { - sync.guard(&_env_mutex) + // We need to be able to figure out if the environment variable + // is contained in the original environment or not. This also + // serves as a flag to determine if we have built _env. + _org_env_begin: uintptr // atomic + _org_env_end: uintptr // guarded by _env_mutex - for entry, i in _env { - if k, v := _kv_from_entry(entry); k == key { - return v, i - } - } - return "", -1 -} + // Returns value + index location into _env + // or -1 if not found + _lookup :: proc(key: string) -> (value: string, idx: int) { + sync.guard(&_env_mutex) -_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - - if v, idx := _lookup(key); idx != -1 { - found = true - value, _ = clone_string(v, allocator) - } - return -} - -_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - - if v, idx := _lookup(key); idx != -1 { - if len(buf) >= len(v) { - copy(buf, v) - return string(buf[:len(v)]), nil - } - return "", .Buffer_Full - } - return "", .Env_Var_Not_Found -} - -_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - -_set_env :: proc(key, v_new: string) -> Error { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - // all key values are stored as "key=value\x00" - kv_size := len(key) + len(v_new) + 2 - if v_curr, idx := _lookup(key); idx != NOT_FOUND { - if v_curr == v_new { - return nil - } - - unordered_remove(&_env, idx) - - if !_is_in_org_env(v_curr) { - // We allocated this key-value. Possibly resize and - // overwrite the value only. Otherwise, treat as if it - // wasn't in the environment in the first place. - k_addr, v_addr := _kv_addr_from_val(v_curr, key) - if len(v_new) > len(v_curr) { - k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) - if k_addr == nil { - return .Out_Of_Memory - } - v_addr = &k_addr[len(key) + 1] + for entry, i in _env { + if k, v := _kv_from_entry(entry); k == key { + return v, i } - intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) - v_addr[len(v_new)] = 0 - - append(&_env, string(k_addr[:kv_size])) - return nil } + return "", -1 } - k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) - if k_addr == nil { - return .Out_Of_Memory - } - intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) - k_addr[len(key)] = '=' - - val_slice := k_addr[len(key) + 1:] - intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) - val_slice[len(v_new)] = 0 - - append(&_env, string(k_addr[:kv_size - 1])) - return nil -} - -_unset_env :: proc(key: string) -> bool { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - v: string - i: int - if v, i = _lookup(key); i == -1 { - return false - } - - unordered_remove(&_env, i) - - if _is_in_org_env(v) { - return true - } - - // if we got this far, the environment variable - // existed AND was allocated by us. - k_addr, _ := _kv_addr_from_val(v, key) - runtime.heap_free(k_addr) - return true -} - -_clear_env :: proc() { - sync.guard(&_env_mutex) - - for kv in _env { - if !_is_in_org_env(kv) { - runtime.heap_free(raw_data(kv)) + _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() } - } - clear(&_env) - // nothing resides in the original environment either - intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release) - _org_env_end = ~uintptr(0) -} - -_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - env := make([dynamic]string, 0, len(_env), allocator) or_return - defer if err != nil { - for e in env { - delete(e, allocator) + if v, idx := _lookup(key); idx != -1 { + found = true + value, _ = clone_string(v, allocator) } - delete(env) - } - - for entry in _env { - s := clone_string(entry, allocator) or_return - append(&env, s) - } - environ = env[:] - return -} - -// The entire environment is stored as 0 terminated strings, -// so there is no need to clone/free individual variables -export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - // The environment has not been modified, so we can just - // send the original environment - org_env := _get_original_env() - n: int - for ; org_env[n] != nil; n += 1 {} - return slice.clone(org_env[:n + 1], allocator) - } - sync.guard(&_env_mutex) - - // NOTE: already terminated by nil pointer via + 1 - env := make([]cstring, len(_env) + 1, allocator) - - for entry, i in _env { - env[i] = cstring(raw_data(entry)) - } - return env -} - -_build_env :: proc() { - sync.guard(&_env_mutex) - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 { return } - _env = make(type_of(_env), runtime.heap_allocator()) - cstring_env := _get_original_env() - intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release) - for i := 0; cstring_env[i] != nil; i += 1 { - bytes := ([^]u8)(cstring_env[i]) - n := len(cstring_env[i]) - _org_env_end = uintptr(&bytes[n]) - append(&_env, string(bytes[:n])) + _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + if len(buf) >= len(v) { + copy(buf, v) + return string(buf[:len(v)]), nil + } + return "", .Buffer_Full + } + return "", .Env_Var_Not_Found + } + + _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + + _set_env :: proc(key, v_new: string) -> Error { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + // all key values are stored as "key=value\x00" + kv_size := len(key) + len(v_new) + 2 + if v_curr, idx := _lookup(key); idx != NOT_FOUND { + if v_curr == v_new { + return nil + } + + unordered_remove(&_env, idx) + + if !_is_in_org_env(v_curr) { + // We allocated this key-value. Possibly resize and + // overwrite the value only. Otherwise, treat as if it + // wasn't in the environment in the first place. + k_addr, v_addr := _kv_addr_from_val(v_curr, key) + if len(v_new) > len(v_curr) { + k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) + if k_addr == nil { + return .Out_Of_Memory + } + v_addr = &k_addr[len(key) + 1] + } + intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) + v_addr[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size])) + return nil + } + } + + k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) + if k_addr == nil { + return .Out_Of_Memory + } + intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) + k_addr[len(key)] = '=' + + val_slice := k_addr[len(key) + 1:] + intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) + val_slice[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size - 1])) + return nil + } + + _unset_env :: proc(key: string) -> bool { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + v: string + i: int + if v, i = _lookup(key); i == -1 { + return true + } + + unordered_remove(&_env, i) + + if _is_in_org_env(v) { + return true + } + + // if we got this far, the environment variable + // existed AND was allocated by us. + k_addr, _ := _kv_addr_from_val(v, key) + runtime.heap_free(k_addr) + return true + } + + _clear_env :: proc() { + sync.guard(&_env_mutex) + + for kv in _env { + if !_is_in_org_env(kv) { + runtime.heap_free(raw_data(kv)) + } + } + clear(&_env) + + // nothing resides in the original environment either + intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release) + _org_env_end = ~uintptr(0) + } + + _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + env := make([dynamic]string, 0, len(_env), allocator) or_return + defer if err != nil { + for e in env { + delete(e, allocator) + } + delete(env) + } + + for entry in _env { + s := clone_string(entry, allocator) or_return + append(&env, s) + } + environ = env[:] + return + } + + // The entire environment is stored as 0 terminated strings, + // so there is no need to clone/free individual variables + export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + // The environment has not been modified, so we can just + // send the original environment + org_env := _get_original_env() + n: int + for ; org_env[n] != nil; n += 1 {} + return slice.clone(org_env[:n + 1], allocator) + } + sync.guard(&_env_mutex) + + // NOTE: already terminated by nil pointer via + 1 + env := make([]cstring, len(_env) + 1, allocator) + + for entry, i in _env { + env[i] = cstring(raw_data(entry)) + } + return env + } + + _build_env :: proc() { + sync.guard(&_env_mutex) + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 { + return + } + + _env = make(type_of(_env), runtime.heap_allocator()) + cstring_env := _get_original_env() + intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release) + for i := 0; cstring_env[i] != nil; i += 1 { + bytes := ([^]u8)(cstring_env[i]) + n := len(cstring_env[i]) + _org_env_end = uintptr(&bytes[n]) + append(&_env, string(bytes[:n])) + } + } + + _get_original_env :: #force_inline proc() -> [^]cstring { + // essentially &argv[argc] which should be a nil pointer! + #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] + assert(env[0] == nil) + return &env[1] + } + + _kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { + eq_idx := strings.index_byte(entry, '=') + if eq_idx == -1 { + return entry, "" + } + return entry[:eq_idx], entry[eq_idx + 1:] + } + + _kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { + v_addr := raw_data(val) + k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) + return k_addr, v_addr + } + + _is_in_org_env :: #force_inline proc(env_data: string) -> bool { + addr := uintptr(raw_data(env_data)) + return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end + } + +} else { + // We are linking with libc, so use libc env functions + foreign import libc "system:c" + + @(default_calling_convention="c") + foreign libc { + @(link_name="environ") + libc_environ: [^]cstring + + @(link_name="__errno_location") + libc_errno_location :: proc() -> ^int --- + + getenv :: proc(name: cstring) -> cstring --- + setenv :: proc(name: cstring, val: cstring, overwrite: b32) -> i32 --- + unsetenv :: proc(name: cstring) -> i32 --- + } + + _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + ckey := strings.clone_to_cstring(key, temp_allocator) + cval := getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return + } + + _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + cval := getenv(cstring(raw_data(buf))) + if cval == nil { + return + } + + if value = string(cval); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } + } + + _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + + _set_env :: proc(key, value: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) or_return + cval := strings.clone_to_cstring(value, temp_allocator) or_return + + if setenv(ckey, cval, true) != 0 { + errno := libc_errno_location()^ + err = _get_platform_error(cast(linux.Errno)errno) + //err = _get_platform_error_from_errno() + } + return + } + + _unset_env :: proc(key: string) -> (ok: bool) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) + + ok = unsetenv(ckey) == 0 + return + } + + _clear_env :: proc() { + for entry := libc_environ[0]; entry != nil; entry = libc_environ[0] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } + } + + _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + n := 0 + for entry := libc_environ[0]; entry != nil; n, entry = n+1, libc_environ[n] {} + + r := make([dynamic]string, 0, n, allocator) or_return + defer if err != nil { + for e in r { + delete(e, allocator) + } + delete(r) + } + + for i, entry := 0, libc_environ[0]; entry != nil; i, entry = i+1, libc_environ[i] { + append(&r, strings.clone(string(entry), allocator) or_return) + } + + environ = r[:] + return + } + + export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + env := make([dynamic]cstring, allocator) + for i, entry := 0, libc_environ[0]; entry != nil; i, entry = i+1, libc_environ[i] { + append(&env, entry) + } + append(&env, nil) + return env[:] } } - -_get_original_env :: #force_inline proc() -> [^]cstring { - // essentially &argv[argc] which should be a nil pointer! - #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] - assert(env[0] == nil) - return &env[1] -} - -_kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { - eq_idx := strings.index_byte(entry, '=') - if eq_idx == -1 { - return entry, "" - } - return entry[:eq_idx], entry[eq_idx + 1:] -} - -_kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { - v_addr := raw_data(val) - k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) - return k_addr, v_addr -} - -_is_in_org_env :: #force_inline proc(env_data: string) -> bool { - addr := uintptr(raw_data(env_data)) - return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end -} diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 84500d139..5d16390a2 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -80,7 +80,7 @@ _unset_env :: proc(key: string) -> (ok: bool) { // NOTE(laytan): clearing the env is weird, why would you ever do that? _clear_env :: proc() { - for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + for entry := libc_environ[0]; entry != nil; entry = libc_environ[0] { key := strings.truncate_to_byte(string(entry), '=') _unset_env(key) }