From 6347c87b5bc0df742d8014cda36a12de8b287dc9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 16 Jun 2025 16:48:08 +0200 Subject: [PATCH] Add buffered get_env variants to os2 --- core/os/env_windows.odin | 4 ++-- core/os/os2/env.odin | 32 +++++++++++++++++++++++++++----- core/os/os2/env_linux.odin | 19 ++++++++++++++++++- core/os/os2/env_posix.odin | 32 +++++++++++++++++++++++++++++++- core/os/os2/env_wasi.odin | 30 +++++++++++++++++++++++++++++- core/os/os2/env_windows.odin | 32 +++++++++++++++++++++++++++++++- core/os/os2/errors.odin | 4 ++-- 7 files changed, 140 insertions(+), 13 deletions(-) diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index a6b758bec..ef658b0a1 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -41,13 +41,13 @@ lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) } n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + if n2 == 0 { return "", .Env_Var_Not_Found } val_buf: [513]u16 n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) - if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + if n2 == 0 { return "", .Env_Var_Not_Found } else if int(n2) > len(buf) { return "", .Buffer_Full diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index 7d42b040d..310d45af1 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -3,25 +3,47 @@ package os2 import "base:runtime" import "core:strings" -// get_env retrieves the value of the environment variable named by the key +// `get_env` retrieves the value of the environment variable named by the key // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator @(require_results) -get_env :: proc(key: string, allocator: runtime.Allocator) -> string { +get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string { value, _ := lookup_env(key, allocator) return value } -// lookup_env gets the value of the environment variable named by the key +// `get_env` retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: this version takes a backing buffer for the string value +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> string { + value, _ := lookup_env(buf, key) + return value +} + +get_env :: proc{get_env_alloc, get_env_buf} + +// `lookup_env` gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator @(require_results) -lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - return _lookup_env(key, allocator) +lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + return _lookup_env_alloc(key, allocator) } +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return _lookup_env_buf(buf, key) +} + +lookup_env :: proc{lookup_env_alloc, lookup_env_buf} + // set_env sets the value of the environment variable named by the key // Returns Error on failure set_env :: proc(key, value: string) -> Error { diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index 2ed43dd91..e003b0717 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -41,7 +41,7 @@ _lookup :: proc(key: string) -> (value: string, idx: int) { return "", -1 } -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_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() } @@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string 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() diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 13682f76b..b11f07314 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -7,7 +7,7 @@ import "base:runtime" import "core:strings" import "core:sys/posix" -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } @@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string 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 := posix.getenv(ckey) + 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({}) diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin index faa54e36b..b126d014e 100644 --- a/core/os/os2/env_wasi.odin +++ b/core/os/os2/env_wasi.odin @@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) { } @(require_results) -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if err := build_env(); err != nil { return } @@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string 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) + } + + sync.shared_guard(&g_env_mutex) + + val, ok := g_env[key] + + if !ok { + return "", .Env_Var_Not_Found + } else { + if len(val) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, val) + return string(buf[:len(val)]), nil + } + } +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + @(require_results) _set_env :: proc(key, value: string) -> (err: Error) { build_env() or_return diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 6bfde34bb..55b2bb5ee 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -4,7 +4,7 @@ package os2 import win32 "core:sys/windows" import "base:runtime" -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } @@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + key_buf: [513]u16 + wkey := win32.utf8_to_wstring(key_buf[:], key) + if wkey == nil { + return "", .Buffer_Full + } + + n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n2 == 0 { + return "", .Env_Var_Not_Found + } + + val_buf: [513]u16 + n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) + if n2 == 0 { + return "", .Env_Var_Not_Found + } else if int(n2) > len(buf) { + return "", .Buffer_Full + } + + value = win32.utf16_to_utf8(buf, val_buf[:n2]) + + return value, nil +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + _set_env :: proc(key, value: string) -> Error { temp_allocator := TEMP_ALLOCATOR_GUARD({}) k := win32_utf8_to_wstring(key, temp_allocator) or_return diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index a73aee9a6..1cf7d765c 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -28,7 +28,7 @@ General_Error :: enum u32 { Pattern_Has_Separator, No_HOME_Variable, - Wordexp_Failed, + Env_Var_Not_Found, Unsupported, } @@ -77,7 +77,7 @@ error_string :: proc(ferr: Error) -> string { case .Unsupported: return "unsupported" case .Pattern_Has_Separator: return "pattern has separator" case .No_HOME_Variable: return "no $HOME variable" - case .Wordexp_Failed: return "posix.wordexp was unable to expand" + case .Env_Var_Not_Found: return "environment variable not found" } case io.Error: switch e {