mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 01:14:40 +00:00
228 lines
5.9 KiB
Odin
228 lines
5.9 KiB
Odin
#+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.itoa(buf[len(PROC_FD_PATH):], int(fd))
|
|
|
|
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)
|
|
}
|