From b6736424125f0ad7dd633369a875050788c7df65 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 20 Jan 2025 22:35:35 +0100 Subject: [PATCH 1/3] os/os2: add get_executable_path and get_executable_directory --- core/os/os2/errors_posix.odin | 13 ++++++-- core/os/os2/path.odin | 12 ++++++++ core/os/os2/path_darwin.odin | 31 +++++++++++++++++++ core/os/os2/path_freebsd.odin | 29 ++++++++++++++++++ core/os/os2/path_linux.odin | 22 +++++++++++++- core/os/os2/path_netbsd.odin | 24 +++++++++++++++ core/os/os2/path_openbsd.odin | 57 +++++++++++++++++++++++++++++++++++ core/os/os2/path_windows.odin | 20 ++++++++++++ core/sys/darwin/dyld.odin | 7 +++++ tests/core/os/os2/path.odin | 22 ++++++++++++++ 10 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 core/os/os2/path_darwin.odin create mode 100644 core/os/os2/path_freebsd.odin create mode 100644 core/os/os2/path_netbsd.odin create mode 100644 core/os/os2/path_openbsd.odin create mode 100644 core/sys/darwin/dyld.odin create mode 100644 tests/core/os/os2/path.odin diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 0b5876c0b..8a9ca07df 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -10,8 +10,12 @@ _error_string :: proc(errno: i32) -> string { return string(posix.strerror(posix.Errno(errno))) } -_get_platform_error :: proc() -> Error { - #partial switch errno := posix.errno(); errno { +_get_platform_error_from_errno :: proc() -> Error { + return _get_platform_error_existing(posix.errno()) +} + +_get_platform_error_existing :: proc(errno: posix.Errno) -> Error { + #partial switch errno { case .EPERM: return .Permission_Denied case .EEXIST: @@ -32,3 +36,8 @@ _get_platform_error :: proc() -> Error { return Platform_Error(errno) } } + +_get_platform_error :: proc{ + _get_platform_error_existing, + _get_platform_error_from_errno, +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 254950d68..9231307f5 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,6 +2,8 @@ package os2 import "base:runtime" +import "core:path/filepath" + Path_Separator :: _Path_Separator // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific @@ -39,3 +41,13 @@ setwd :: set_working_directory set_working_directory :: proc(dir: string) -> (err: Error) { return _set_working_directory(dir) } + +get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + return _get_executable_path(allocator) +} + +get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + path = _get_executable_path(allocator) or_return + path, _ = filepath.split(path) + return +} diff --git a/core/os/os2/path_darwin.odin b/core/os/os2/path_darwin.odin new file mode 100644 index 000000000..2e7bbc7b9 --- /dev/null +++ b/core/os/os2/path_darwin.odin @@ -0,0 +1,31 @@ +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + size: u32 + + ret := darwin._NSGetExecutablePath(nil, &size) + assert(ret == -1) + assert(size > 0) + + TEMP_ALLOCATOR_GUARD() + + buf := make([]byte, size, temp_allocator()) or_return + assert(u32(len(buf)) == size) + + ret = darwin._NSGetExecutablePath(raw_data(buf), &size) + assert(ret == 0) + + real := posix.realpath(cstring(raw_data(buf))) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + + return clone_string(string(real), allocator) +} diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin new file mode 100644 index 000000000..577108eca --- /dev/null +++ b/core/os/os2/path_freebsd.odin @@ -0,0 +1,29 @@ +package os2 + +import "base:runtime" + +import "core:sys/freebsd" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)} + + size: uint + if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + assert(size > 0) + + buf := make([]byte, size, allocator) or_return + defer if err != nil { delete(buf, allocator) } + + assert(uint(len(buf)) == size) + + if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + + return string(buf[:size]), nil +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index bfdb645ef..e3e7f8a7c 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,9 +1,10 @@ #+private package os2 +import "base:runtime" + import "core:strings" import "core:strconv" -import "base:runtime" import "core:sys/linux" _Path_Separator :: '/' @@ -171,6 +172,25 @@ _set_working_directory :: proc(dir: string) -> Error { return _get_platform_error(linux.chdir(dir_cstr)) } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n, errno := linux.readlink("/proc/self/exe", buf[:]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} + _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin new file mode 100644 index 000000000..f56a91fd6 --- /dev/null +++ b/core/os/os2/path_netbsd.odin @@ -0,0 +1,24 @@ +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) + if n < 0 { + err = _get_platform_error() + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin new file mode 100644 index 000000000..f56c1a61b --- /dev/null +++ b/core/os/os2/path_openbsd.odin @@ -0,0 +1,57 @@ +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + // OpenBSD does not have an API for this, we do our best below. + + if len(runtime.args__) <= 0 { + err = .Invalid_Path + return + } + + real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) { + real := posix.realpath(path) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + return clone_string(string(real), allocator) + } + + arg := runtime.args__[0] + sarg := string(arg) + + if len(sarg) == 0 { + err = .Invalid_Path + return + } + + if sarg[0] == '.' || sarg[0] == '/' { + return real(arg, allocator) + } + + TEMP_ALLOCATOR_GUARD() + + buf := strings.builder_make(temp_allocator()) + + paths := get_env("PATH", temp_allocator()) + for dir in strings.split_iterator(&paths, ":") { + strings.builder_reset(&buf) + strings.write_string(&buf, dir) + strings.write_string(&buf, "/") + strings.write_string(&buf, sarg) + + cpath := strings.to_cstring(&buf) + if posix.access(cpath, {.X_OK}) == .OK { + return real(cpath, allocator) + } + } + + err = .Invalid_Path + return +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 3e92cb6f3..041a4d1e3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -136,6 +136,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { return } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]u16, 512, temp_allocator()) or_return + for { + ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) + if ret == 0 { + err = _get_platform_error() + return + } + + if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER { + resize(&buf, len(buf)*2) or_return + continue + } + + return win32_utf16_to_utf8(buf[:ret], allocator) + } +} + can_use_long_paths: bool @(init) diff --git a/core/sys/darwin/dyld.odin b/core/sys/darwin/dyld.odin new file mode 100644 index 000000000..0a6a2cfa6 --- /dev/null +++ b/core/sys/darwin/dyld.odin @@ -0,0 +1,7 @@ +package darwin + +foreign import system "system:System.framework" + +foreign system { + _NSGetExecutablePath :: proc(buf: [^]byte, bufsize: ^u32) -> i32 --- +} diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin new file mode 100644 index 000000000..b91f43368 --- /dev/null +++ b/tests/core/os/os2/path.odin @@ -0,0 +1,22 @@ +package tests_core_os_os2 + +import os "core:os/os2" +import "core:log" +import "core:path/filepath" +import "core:testing" +import "core:strings" + +@(test) +test_executable :: proc(t: ^testing.T) { + path, err := os.get_executable_path(context.allocator) + defer delete(path) + + log.infof("executable path: %q", path) + + // NOTE: some sanity checks that should always be the case, at least in the CI. + + testing.expect_value(t, err, nil) + testing.expect(t, len(path) > 0) + testing.expect(t, filepath.is_abs(path)) + testing.expectf(t, strings.contains(path, filepath.base(os.args[0])), "expected the executable path to contain the base of os.args[0] which is %q", filepath.base(os.args[0])) +} From f1b0b197109a2ffea9435a2ff8eb7bd037298292 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 21 Jan 2025 19:14:15 +0100 Subject: [PATCH 2/3] os/os2: get_executable_path and working directory on wasi --- core/os/os2/path_wasi.odin | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin index 17efbff62..2f8a3c8c6 100644 --- a/core/os/os2/path_wasi.odin +++ b/core/os/os2/path_wasi.odin @@ -4,6 +4,7 @@ package os2 import "base:runtime" import "core:path/filepath" +import "core:sync" import "core:sys/wasm/wasi" _Path_Separator :: '/' @@ -74,11 +75,39 @@ _remove_all :: proc(path: string) -> (err: Error) { return remove(path) } +g_wd: string +g_wd_mutex: sync.Mutex + _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return ".", .Unsupported + sync.guard(&g_wd_mutex) + + return clone_string(g_wd if g_wd != "" else "/", allocator) } _set_working_directory :: proc(dir: string) -> (err: Error) { - err = .Unsupported + sync.guard(&g_wd_mutex) + + if dir == g_wd { + return + } + + if g_wd != "" { + delete(g_wd, file_allocator()) + } + + g_wd = clone_string(dir, file_allocator()) or_return return } + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + if len(args) <= 0 { + return clone_string("/", allocator) + } + + arg := args[0] + if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') { + return clone_string(arg, allocator) + } + + return concatenate({"/", arg}, allocator) +} From d54de6704a0acb95e8123480121f0ddba62f1516 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 22 Jan 2025 18:39:33 +0100 Subject: [PATCH 3/3] os/os2: use proc_pidpath for executable path on darwin --- core/os/os2/path_darwin.odin | 26 ++++++-------------------- core/sys/darwin/dyld.odin | 7 ------- 2 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 core/sys/darwin/dyld.odin diff --git a/core/os/os2/path_darwin.odin b/core/os/os2/path_darwin.odin index 2e7bbc7b9..65aaf1e95 100644 --- a/core/os/os2/path_darwin.odin +++ b/core/os/os2/path_darwin.odin @@ -6,26 +6,12 @@ import "core:sys/darwin" import "core:sys/posix" _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - size: u32 - - ret := darwin._NSGetExecutablePath(nil, &size) - assert(ret == -1) - assert(size > 0) - - TEMP_ALLOCATOR_GUARD() - - buf := make([]byte, size, temp_allocator()) or_return - assert(u32(len(buf)) == size) - - ret = darwin._NSGetExecutablePath(raw_data(buf), &size) - assert(ret == 0) - - real := posix.realpath(cstring(raw_data(buf))) - if real == nil { - err = _get_platform_error() - return + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + return clone_string(string(buffer[:ret]), allocator) } - defer posix.free(real) - return clone_string(string(real), allocator) + err = _get_platform_error() + return } diff --git a/core/sys/darwin/dyld.odin b/core/sys/darwin/dyld.odin deleted file mode 100644 index 0a6a2cfa6..000000000 --- a/core/sys/darwin/dyld.odin +++ /dev/null @@ -1,7 +0,0 @@ -package darwin - -foreign import system "system:System.framework" - -foreign system { - _NSGetExecutablePath :: proc(buf: [^]byte, bufsize: ^u32) -> i32 --- -}