aboutsummaryrefslogtreecommitdiff
path: root/core/os
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2025-06-15 16:31:54 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2025-06-16 20:12:26 +0200
commit1a2f83f1235039b6bad43d9e697cc4b2f0fb2b60 (patch)
tree3f2acb923ea254a632170dfe30a75f2f31db5d2a /core/os
parent1bd48df41febbcf0f903fdfc697f59527b03bbe9 (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.odin64
-rw-r--r--core/os/errors.odin4
-rw-r--r--core/os/os_darwin.odin33
-rw-r--r--core/os/os_freebsd.odin34
-rw-r--r--core/os/os_haiku.odin34
-rw-r--r--core/os/os_js.odin25
-rw-r--r--core/os/os_linux.odin32
-rw-r--r--core/os/os_netbsd.odin34
-rw-r--r--core/os/os_wasi.odin25
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