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..65aaf1e95 --- /dev/null +++ b/core/os/os2/path_darwin.odin @@ -0,0 +1,17 @@ +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + 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) + } + + err = _get_platform_error() + return +} 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_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) +} 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/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])) +}