diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2026-02-09 15:50:21 +0100 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2026-02-09 15:50:21 +0100 |
| commit | e7dbabf6681e4e6bcae33398e939c2c9c3cdc879 (patch) | |
| tree | 91f25462cc2e9f3adf9884720b7f104d4d6d59f5 /core/os/path_linux.odin | |
| parent | 8ed264680b1f3f94b6aa5176824d4ccadfc30322 (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.odin | 227 |
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) +} |