diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2025-06-15 16:31:54 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2025-06-16 20:12:26 +0200 |
| commit | 1a2f83f1235039b6bad43d9e697cc4b2f0fb2b60 (patch) | |
| tree | 3f2acb923ea254a632170dfe30a75f2f31db5d2a /core/os | |
| parent | 1bd48df41febbcf0f903fdfc697f59527b03bbe9 (diff) | |
Add bring-your-own-buffer versions of `os.lookup_env` and `os.get_env`
And make `core:terminal` use it so that `core:log` can be imported with `-default-to-nil-allocator`,
in which the actual allocator is set up in `main()`.
Windows was tricky because of the utf-8 <> utf-16 conversion, so we use some temporary stack buffers for that purpose,
limiting the non-allocating version there to 512 utf-16 characters each for the key and environment value.
In general the value is (obviously) limited to the size of the supplied buffer, and a `.Buffer_Full` error is returned
if that buffer is insufficient. If the key is not found, the procedure returns `.Env_Var_Not_Found`.
TODO:
- Factor out buffer-backed utf8 + utf16 conversion to `core:sys/util` to more easily apply this pattern.
- Add similar `lookup_env` and `get_env` procedures to `core:os/os2`.
Fixes #5336
Diffstat (limited to 'core/os')
| -rw-r--r-- | core/os/env_windows.odin | 64 | ||||
| -rw-r--r-- | core/os/errors.odin | 4 | ||||
| -rw-r--r-- | core/os/os_darwin.odin | 33 | ||||
| -rw-r--r-- | core/os/os_freebsd.odin | 34 | ||||
| -rw-r--r-- | core/os/os_haiku.odin | 34 | ||||
| -rw-r--r-- | core/os/os_js.odin | 25 | ||||
| -rw-r--r-- | core/os/os_linux.odin | 32 | ||||
| -rw-r--r-- | core/os/os_netbsd.odin | 34 | ||||
| -rw-r--r-- | core/os/os_wasi.odin | 25 |
9 files changed, 261 insertions, 24 deletions
diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index efd002342..6c7759abe 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -8,7 +8,7 @@ import "base:runtime" // 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 := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { return } @@ -29,17 +29,77 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin 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_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + key_buf: [513]u16 + n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, raw_data(key), i32(len(key)), nil, 0) + if n1 == 0 { + return "", nil + } + + n1 = win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, raw_data(key), i32(len(key)), raw_data(key_buf[:]), n1) + if n1 == 0 { + return "", nil + } + + n2 := win32.GetEnvironmentVariableW(raw_data(key_buf[:]), nil, 0) + if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", .Env_Var_Not_Found + } + + val_buf: [513]u16 + n2 = win32.GetEnvironmentVariableW(raw_data(key_buf[:]), raw_data(val_buf[:]), u32(len(val_buf[:]))) + if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", .Env_Var_Not_Found + } else if int(n2) > len(buf) { + return "", .Buffer_Full + } + + n3 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(val_buf[:]), -1, nil, 0, nil, nil) + if n3 == 0 { + return + } else if int(n3) > len(buf) { + return "", .Buffer_Full + } + + n4 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(val_buf[:]), -1, raw_data(buf), n3, nil, nil) + if n4 == 0 { + return + } else if int(n4) > len(buf) { + return "", .Buffer_Full + } + + for i in 0..<n3 { + if buf[i] == 0 { + n3 = i + break + } + } + return string(buf[:n3]), nil +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} // 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 := context.allocator) -> (value: string) { +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + + // set_env sets the value of the environment variable named by the key set_env :: proc(key, value: string) -> Error { k := win32.utf8_to_wstring(key) diff --git a/core/os/errors.odin b/core/os/errors.odin index 691397f4b..bf4cf27ff 100644 --- a/core/os/errors.odin +++ b/core/os/errors.odin @@ -35,6 +35,9 @@ General_Error :: enum u32 { File_Is_Pipe, Not_Dir, + + // Environment variable not found. + Env_Var_Not_Found, } @@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string { case .Pattern_Has_Separator: return "pattern has separator" case .File_Is_Pipe: return "file is pipe" case .Not_Dir: return "file is not directory" + case .Env_Var_Not_Found: return "environment variable not found" } case io.Error: switch e { diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index bbffc46d7..b247b1000 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -1055,9 +1055,10 @@ flush :: proc(fd: Handle) -> Error { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); 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_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index f2ee6a496..a4c3c819e 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -839,12 +839,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); 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_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 4ce726965..82ead06ab 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -463,9 +463,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -474,11 +475,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); 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_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + + @(private, require_results) _processor_core_count :: proc() -> int { info: haiku.system_info diff --git a/core/os/os_js.odin b/core/os/os_js.odin index adb0f8061..1870218d3 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -250,11 +250,26 @@ current_thread_id :: proc "contextless" () -> int { return 0 } -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { return "", false } -get_env :: proc(key: string, allocator := context.allocator) -> string { - value, _ := lookup_env(key, allocator) - return value -}
\ No newline at end of file +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf}
\ No newline at end of file diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 84a7f7b32..c14f573bf 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. @@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); 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_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 40b41b133..d1e030e8e 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -886,12 +886,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); 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_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index a0938e860..4039d247b 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -240,11 +240,26 @@ exit :: proc "contextless" (code: int) -> ! { wasi.proc_exit(wasi.exitcode_t(code)) } -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { return "", false } -get_env :: proc(key: string, allocator := context.allocator) -> string { - value, _ := lookup_env(key, allocator) - return value -}
\ No newline at end of file +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf}
\ No newline at end of file |