Skip to content

Commit

Permalink
os/os2: add get_executable_path and get_executable_directory
Browse files Browse the repository at this point in the history
  • Loading branch information
laytan committed Jan 20, 2025
1 parent a5f3c1b commit 8a9d72d
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 3 deletions.
13 changes: 11 additions & 2 deletions core/os/os2/errors_posix.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
}
12 changes: 12 additions & 0 deletions core/os/os2/path.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
31 changes: 31 additions & 0 deletions core/os/os2/path_darwin.odin
Original file line number Diff line number Diff line change
@@ -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)
}
29 changes: 29 additions & 0 deletions core/os/os2/path_freebsd.odin
Original file line number Diff line number Diff line change
@@ -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
}
22 changes: 21 additions & 1 deletion core/os/os2/path_linux.odin
Original file line number Diff line number Diff line change
@@ -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 :: '/'
Expand Down Expand Up @@ -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/"

Expand Down
24 changes: 24 additions & 0 deletions core/os/os2/path_netbsd.odin
Original file line number Diff line number Diff line change
@@ -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
}
}
57 changes: 57 additions & 0 deletions core/os/os2/path_openbsd.odin
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 19 additions & 0 deletions core/os/os2/path_windows.odin
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ _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
}

return win32_utf16_to_utf8(buf[:ret], allocator)
}
}

can_use_long_paths: bool

@(init)
Expand Down
7 changes: 7 additions & 0 deletions core/sys/darwin/dyld.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package darwin

foreign import system "system:System.framework"

foreign system {
_NSGetExecutablePath :: proc(buf: [^]byte, bufsize: ^u32) -> i32 ---
}
22 changes: 22 additions & 0 deletions tests/core/os/os2/path.odin
Original file line number Diff line number Diff line change
@@ -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]))
}

0 comments on commit 8a9d72d

Please sign in to comment.