aboutsummaryrefslogtreecommitdiff
path: root/core/os/path_linux.odin
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2026-02-09 15:50:21 +0100
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2026-02-09 15:50:21 +0100
commite7dbabf6681e4e6bcae33398e939c2c9c3cdc879 (patch)
tree91f25462cc2e9f3adf9884720b7f104d4d6d59f5 /core/os/path_linux.odin
parent8ed264680b1f3f94b6aa5176824d4ccadfc30322 (diff)
core:os -> core:os/old && core:os/os2 -> core:os
Diffstat (limited to 'core/os/path_linux.odin')
-rw-r--r--core/os/path_linux.odin227
1 files changed, 227 insertions, 0 deletions
diff --git a/core/os/path_linux.odin b/core/os/path_linux.odin
new file mode 100644
index 000000000..1c9927843
--- /dev/null
+++ b/core/os/path_linux.odin
@@ -0,0 +1,227 @@
+#+private
+package os2
+
+import "base:runtime"
+
+import "core:strings"
+import "core:strconv"
+import "core:sys/linux"
+
+_Path_Separator :: '/'
+_Path_Separator_String :: "/"
+_Path_List_Separator :: ':'
+
+_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
+
+_is_path_separator :: proc(c: byte) -> bool {
+ return c == _Path_Separator
+}
+
+_mkdir :: proc(path: string, perm: int) -> Error {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ path_cstr := clone_to_cstring(path, temp_allocator) or_return
+ return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
+}
+
+_mkdir_all :: proc(path: string, perm: int) -> Error {
+ mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error {
+ i: int
+ for ; i < len(path) - 1 && path[i] != '/'; i += 1 {}
+ if i == 0 {
+ return _get_platform_error(linux.close(dfd))
+ }
+ path[i] = 0
+ new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
+ #partial switch errno {
+ case .ENOENT:
+ if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE {
+ return _get_platform_error(errno)
+ }
+ has_created^ = true
+ if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE {
+ return _get_platform_error(errno)
+ }
+ fallthrough
+ case .NONE:
+ if errno = linux.close(dfd); errno != .NONE {
+ return _get_platform_error(errno)
+ }
+ // skip consecutive '/'
+ for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
+ return mkdirat(new_dfd, path[i:], perm, has_created)
+ }
+ return _get_platform_error(errno)
+ }
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ // need something we can edit, and use to generate cstrings
+ path_bytes := make([]u8, len(path) + 1, temp_allocator)
+
+ // zero terminate the byte slice to make it a valid cstring
+ copy(path_bytes, path)
+ path_bytes[len(path)] = 0
+
+ dfd: linux.Fd
+ errno: linux.Errno
+ if path_bytes[0] == '/' {
+ dfd, errno = linux.open("/", _OPENDIR_FLAGS)
+ path_bytes = path_bytes[1:]
+ } else {
+ dfd, errno = linux.open(".", _OPENDIR_FLAGS)
+ }
+ if errno != .NONE {
+ return _get_platform_error(errno)
+ }
+
+ has_created: bool
+ mkdirat(dfd, path_bytes, perm, &has_created) or_return
+ return nil if has_created else .Exist
+}
+
+_remove_all :: proc(path: string) -> Error {
+ remove_all_dir :: proc(dfd: linux.Fd) -> Error {
+ n := 64
+ buf := make([]u8, n)
+ defer delete(buf)
+
+ loop: for {
+ buflen, errno := linux.getdents(dfd, buf[:])
+ #partial switch errno {
+ case .EINVAL:
+ delete(buf)
+ n *= 2
+ buf = make([]u8, n)
+ continue loop
+ case .NONE:
+ if buflen == 0 { break loop }
+ case:
+ return _get_platform_error(errno)
+ }
+
+ offset: int
+ for d in linux.dirent_iterate_buf(buf[:buflen], &offset) {
+ d_name_str := linux.dirent_name(d)
+ d_name_cstr := strings.unsafe_string_to_cstring(d_name_str)
+
+ /* check for current or parent directory (. or ..) */
+ if d_name_str == "." || d_name_str == ".." {
+ continue
+ }
+
+ #partial switch d.type {
+ case .DIR:
+ new_dfd: linux.Fd
+ new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
+ if errno != .NONE {
+ return _get_platform_error(errno)
+ }
+ defer linux.close(new_dfd)
+ remove_all_dir(new_dfd) or_return
+ errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR})
+ case:
+ errno = linux.unlinkat(dfd, d_name_cstr, nil)
+ }
+
+ if errno != .NONE {
+ return _get_platform_error(errno)
+ }
+ }
+ }
+ return nil
+ }
+
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ path_cstr := clone_to_cstring(path, temp_allocator) or_return
+
+ fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
+ #partial switch errno {
+ case .NONE:
+ break
+ case .ENOTDIR:
+ return _get_platform_error(linux.unlink(path_cstr))
+ case:
+ return _get_platform_error(errno)
+ }
+
+ defer linux.close(fd)
+ remove_all_dir(fd) or_return
+ return _get_platform_error(linux.rmdir(path_cstr))
+}
+
+_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
+ // NOTE(tetra): I would use PATH_MAX here, but I was not able to find
+ // an authoritative value for it across all systems.
+ // The largest value I could find was 4096, so might as well use the page size.
+ // NOTE(jason): Avoiding libc, so just use 4096 directly
+ PATH_MAX :: 4096
+ buf := make([dynamic]u8, PATH_MAX, allocator)
+ for {
+ #no_bounds_check n, errno := linux.getcwd(buf[:])
+ if errno == .NONE {
+ return string(buf[:n-1]), nil
+ }
+ if errno != .ERANGE {
+ return "", _get_platform_error(errno)
+ }
+ resize(&buf, len(buf)+PATH_MAX)
+ }
+ unreachable()
+}
+
+_set_working_directory :: proc(dir: string) -> Error {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+ dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
+ return _get_platform_error(linux.chdir(dir_cstr))
+}
+
+_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+
+ 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/"
+
+ buf: [32]u8
+ copy(buf[:], PROC_FD_PATH)
+
+ strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10)
+
+ if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
+ delete(fullpath, allocator)
+ fullpath = ""
+ }
+ return
+}
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+ rel := path
+ if rel == "" {
+ rel = "."
+ }
+
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+
+ fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
+ if errno != nil {
+ err = _get_platform_error(errno)
+ return
+ }
+ defer linux.close(fd)
+
+ return _get_full_path(fd, allocator)
+}