mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-28 18:30:06 +00:00
Merge branch 'master' into args-leak
This commit is contained in:
@@ -5,9 +5,10 @@ import "core:strings"
|
||||
|
||||
@(require_results)
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
|
||||
dupfd := _dup(fd) or_return
|
||||
context.allocator = allocator
|
||||
|
||||
dirp := _fdopendir(dupfd) or_return
|
||||
dupfd := _dup(fd) or_return
|
||||
dirp := _fdopendir(dupfd) or_return
|
||||
defer _closedir(dirp)
|
||||
|
||||
dirpath := absolute_path_from_handle(dupfd) or_return
|
||||
|
||||
@@ -8,7 +8,7 @@ import "base:runtime"
|
||||
// Otherwise the returned value will be empty and the boolean will be false
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
return
|
||||
}
|
||||
|
||||
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
|
||||
// Note that it is limited to environment names and values of 512 utf-16 values each
|
||||
// due to the necessary utf-8 <> utf-16 conversion.
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
key_buf: [513]u16
|
||||
wkey := win32.utf8_to_wstring(key_buf[:], key)
|
||||
if wkey == nil {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
|
||||
val_buf: [513]u16
|
||||
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else if int(n2) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
value = win32.utf16_to_utf8(buf, val_buf[:n2])
|
||||
|
||||
return value, nil
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
// get_env retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
|
||||
// set_env sets the value of the environment variable named by the key
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
k := win32.utf8_to_wstring(key)
|
||||
|
||||
@@ -35,6 +35,9 @@ General_Error :: enum u32 {
|
||||
|
||||
File_Is_Pipe,
|
||||
Not_Dir,
|
||||
|
||||
// Environment variable not found.
|
||||
Env_Var_Not_Found,
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string {
|
||||
case .Pattern_Has_Separator: return "pattern has separator"
|
||||
case .File_Is_Pipe: return "file is pipe"
|
||||
case .Not_Dir: return "file is not directory"
|
||||
case .Env_Var_Not_Found: return "environment variable not found"
|
||||
}
|
||||
case io.Error:
|
||||
switch e {
|
||||
|
||||
+54
-1
@@ -4,6 +4,7 @@ import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
|
||||
@@ -57,7 +58,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
|
||||
if r < 32 {
|
||||
if wrap(write_string(f, "\\x"), &n, &err) { return }
|
||||
b: [2]byte
|
||||
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
|
||||
s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
|
||||
switch len(s) {
|
||||
case 0: if wrap(write_string(f, "00"), &n, &err) { return }
|
||||
case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
|
||||
@@ -210,3 +211,55 @@ heap_free :: runtime.heap_free
|
||||
processor_core_count :: proc() -> int {
|
||||
return _processor_core_count()
|
||||
}
|
||||
|
||||
// Always allocates for consistency.
|
||||
replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) {
|
||||
path := path
|
||||
|
||||
sb: strings.Builder
|
||||
strings.builder_init_none(&sb, allocator)
|
||||
for len(path) > 0 {
|
||||
switch path[0] {
|
||||
case '%': // Windows
|
||||
when ODIN_OS == .Windows {
|
||||
for r, i in path[1:] {
|
||||
if r == '%' {
|
||||
env_key := path[1:i+1]
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[i+1:] // % is part of key, so skip 1 character extra
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case '$': // Posix
|
||||
when ODIN_OS != .Windows {
|
||||
env_key := ""
|
||||
dollar_loop: for r, i in path[1:] {
|
||||
switch r {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
|
||||
case:
|
||||
env_key = path[1:i+1]
|
||||
break dollar_loop
|
||||
}
|
||||
}
|
||||
if len(env_key) > 0 {
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[len(env_key):]
|
||||
}
|
||||
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case:
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
path = path[1:]
|
||||
}
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
+42
-41
@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
|
||||
return heap_allocator()
|
||||
}
|
||||
|
||||
temp_allocator_proc :: runtime.arena_allocator_proc
|
||||
|
||||
@(private="file")
|
||||
MAX_TEMP_ARENA_COUNT :: 2
|
||||
|
||||
@(private="file")
|
||||
MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
|
||||
@(private="file", thread_local)
|
||||
global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
|
||||
|
||||
@(private="file", thread_local)
|
||||
global_default_temp_allocator_index: uint
|
||||
|
||||
|
||||
@(require_results)
|
||||
temp_allocator :: proc() -> runtime.Allocator {
|
||||
arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
|
||||
if arena.backing_allocator.procedure == nil {
|
||||
arena.backing_allocator = heap_allocator()
|
||||
}
|
||||
|
||||
return runtime.Allocator{
|
||||
procedure = temp_allocator_proc,
|
||||
data = arena,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
|
||||
temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
|
||||
return
|
||||
}
|
||||
|
||||
temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
|
||||
runtime.arena_temp_end(temp, loc)
|
||||
}
|
||||
|
||||
@(fini, private)
|
||||
temp_allocator_fini :: proc() {
|
||||
for &arena in global_default_temp_allocator_arenas {
|
||||
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
|
||||
global_default_temp_allocator_arenas = {}
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
|
||||
runtime.arena_temp_end(temp, loc)
|
||||
if temp.arena != nil {
|
||||
global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT
|
||||
}
|
||||
Temp_Allocator :: struct {
|
||||
using arena: ^runtime.Arena,
|
||||
using allocator: runtime.Allocator,
|
||||
tmp: runtime.Arena_Temp,
|
||||
loc: runtime.Source_Code_Location,
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
|
||||
runtime.arena_temp_end(temp.tmp, temp.loc)
|
||||
}
|
||||
|
||||
@(deferred_out=TEMP_ALLOCATOR_GUARD_END)
|
||||
TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
|
||||
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
|
||||
tmp := temp_allocator_temp_begin(loc)
|
||||
return tmp, loc
|
||||
TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
|
||||
assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
|
||||
good_arena: ^runtime.Arena
|
||||
for i in 0..<MAX_TEMP_ARENA_COUNT {
|
||||
good_arena = &global_default_temp_allocator_arenas[i]
|
||||
for c in collisions {
|
||||
if good_arena == c.data {
|
||||
good_arena = nil
|
||||
}
|
||||
}
|
||||
if good_arena != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert(good_arena != nil)
|
||||
if good_arena.backing_allocator.procedure == nil {
|
||||
good_arena.backing_allocator = heap_allocator()
|
||||
}
|
||||
tmp := runtime.arena_temp_begin(good_arena, loc)
|
||||
return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
|
||||
}
|
||||
|
||||
temp_allocator_begin :: runtime.arena_temp_begin
|
||||
temp_allocator_end :: runtime.arena_temp_end
|
||||
@(deferred_out=_temp_allocator_end)
|
||||
temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
|
||||
return temp_allocator_begin(tmp.arena)
|
||||
}
|
||||
@(private="file")
|
||||
_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
|
||||
temp_allocator_end(tmp)
|
||||
}
|
||||
|
||||
@(init, private)
|
||||
|
||||
+174
-8
@@ -2,6 +2,7 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:slice"
|
||||
import "core:strings"
|
||||
|
||||
read_dir :: read_directory
|
||||
|
||||
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
|
||||
size = 100
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
it := read_directory_iterator_create(f) or_return
|
||||
it := read_directory_iterator_create(f)
|
||||
defer _read_directory_iterator_destroy(&it)
|
||||
|
||||
dfi := make([dynamic]File_Info, 0, size, temp_allocator())
|
||||
dfi := make([dynamic]File_Info, 0, size, temp_allocator)
|
||||
defer if err != nil {
|
||||
for fi in dfi {
|
||||
file_info_delete(fi, allocator)
|
||||
@@ -34,9 +35,14 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
|
||||
if n > 0 && index == n {
|
||||
break
|
||||
}
|
||||
|
||||
_ = read_directory_iterator_error(&it) or_break
|
||||
|
||||
append(&dfi, file_info_clone(fi, allocator) or_return)
|
||||
}
|
||||
|
||||
_ = read_directory_iterator_error(&it) or_return
|
||||
|
||||
return slice.clone(dfi[:], allocator)
|
||||
}
|
||||
|
||||
@@ -61,22 +67,182 @@ read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -
|
||||
|
||||
|
||||
Read_Directory_Iterator :: struct {
|
||||
f: ^File,
|
||||
f: ^File,
|
||||
err: struct {
|
||||
err: Error,
|
||||
path: [dynamic]byte,
|
||||
},
|
||||
index: int,
|
||||
impl: Read_Directory_Iterator_Impl,
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a directory iterator with the given directory.
|
||||
|
||||
@(require_results)
|
||||
read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
|
||||
return _read_directory_iterator_create(f)
|
||||
For an example on how to use the iterator, see `read_directory_iterator`.
|
||||
*/
|
||||
read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator) {
|
||||
read_directory_iterator_init(&it, f)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize a directory iterator with the given directory.
|
||||
|
||||
This procedure may be called on an existing iterator to reuse it for another directory.
|
||||
|
||||
For an example on how to use the iterator, see `read_directory_iterator`.
|
||||
*/
|
||||
read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
it.err.err = nil
|
||||
it.err.path.allocator = file_allocator()
|
||||
clear(&it.err.path)
|
||||
|
||||
it.f = f
|
||||
it.index = 0
|
||||
|
||||
_read_directory_iterator_init(it, f)
|
||||
}
|
||||
|
||||
/*
|
||||
Destroys a directory iterator.
|
||||
*/
|
||||
read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||
if it == nil {
|
||||
return
|
||||
}
|
||||
|
||||
delete(it.err.path)
|
||||
|
||||
_read_directory_iterator_destroy(it)
|
||||
}
|
||||
|
||||
// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone`
|
||||
/*
|
||||
Retrieve the last error that happened during iteration.
|
||||
*/
|
||||
@(require_results)
|
||||
read_directory_iterator_error :: proc(it: ^Read_Directory_Iterator) -> (path: string, err: Error) {
|
||||
return string(it.err.path[:]), it.err.err
|
||||
}
|
||||
|
||||
@(private)
|
||||
read_directory_iterator_set_error :: proc(it: ^Read_Directory_Iterator, path: string, err: Error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resize(&it.err.path, len(path))
|
||||
copy(it.err.path[:], path)
|
||||
|
||||
it.err.err = err
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the next file info entry for the iterator's directory.
|
||||
|
||||
The given `File_Info` is reused in subsequent calls so a copy (`file_info_clone`) has to be made to
|
||||
extend its lifetime.
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import os "core:os/os2"
|
||||
|
||||
main :: proc() {
|
||||
f, oerr := os.open("core")
|
||||
ensure(oerr == nil)
|
||||
defer os.close(f)
|
||||
|
||||
it := os.read_directory_iterator_create(f)
|
||||
defer os.read_directory_iterator_destroy(&it)
|
||||
|
||||
for info in os.read_directory_iterator(&it) {
|
||||
// Optionally break on the first error:
|
||||
// Supports not doing this, and keeping it going with remaining items.
|
||||
// _ = os.read_directory_iterator_error(&it) or_break
|
||||
|
||||
// Handle error as we go:
|
||||
// Again, no need to do this as it will keep going with remaining items.
|
||||
if path, err := os.read_directory_iterator_error(&it); err != nil {
|
||||
fmt.eprintfln("failed reading %s: %s", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Or, do not handle errors during iteration, and just check the error at the end.
|
||||
|
||||
|
||||
fmt.printfln("%#v", info)
|
||||
}
|
||||
|
||||
// Handle error if one happened during iteration at the end:
|
||||
if path, err := os.read_directory_iterator_error(&it); err != nil {
|
||||
fmt.eprintfln("read directory failed at %s: %s", path, err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
@(require_results)
|
||||
read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||
if it.f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if it.index == 0 && it.err.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return _read_directory_iterator(it)
|
||||
}
|
||||
|
||||
// Recursively copies a directory to `dst` from `src`
|
||||
copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
|
||||
when #defined(_copy_directory_all_native) {
|
||||
return _copy_directory_all_native(dst, src, dst_perm)
|
||||
} else {
|
||||
return _copy_directory_all(dst, src, dst_perm)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
|
||||
err := make_directory(dst, dst_perm)
|
||||
if err != nil && err != .Exist {
|
||||
return err
|
||||
}
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
abs_src := get_absolute_path(src, temp_allocator) or_return
|
||||
abs_dst := get_absolute_path(dst, temp_allocator) or_return
|
||||
|
||||
dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
|
||||
|
||||
w: Walker
|
||||
walker_init_path(&w, src)
|
||||
defer walker_destroy(&w)
|
||||
|
||||
for info in walker_walk(&w) {
|
||||
_ = walker_error(&w) or_break
|
||||
|
||||
rel := strings.trim_prefix(info.fullpath, abs_src)
|
||||
|
||||
non_zero_resize(&dst_buf, 0)
|
||||
reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
|
||||
append(&dst_buf, abs_dst)
|
||||
append(&dst_buf, Path_Separator_String)
|
||||
append(&dst_buf, rel)
|
||||
|
||||
if info.type == .Directory {
|
||||
err = make_directory(string(dst_buf[:]), dst_perm)
|
||||
if err != nil && err != .Exist {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
copy_file(string(dst_buf[:]), info.fullpath) or_return
|
||||
}
|
||||
}
|
||||
|
||||
_ = walker_error(&w) or_return
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+105
-5
@@ -1,20 +1,120 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
Read_Directory_Iterator_Impl :: struct {
|
||||
|
||||
prev_fi: File_Info,
|
||||
dirent_backing: []u8,
|
||||
dirent_buflen: int,
|
||||
dirent_off: int,
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||
scan_entries :: proc(it: ^Read_Directory_Iterator, dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) {
|
||||
for d in linux.dirent_iterate_buf(entries, offset) {
|
||||
file_name = linux.dirent_name(d)
|
||||
if file_name == "." || file_name == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
file_name_cstr := cstring(raw_data(file_name))
|
||||
entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
|
||||
if errno == .NONE {
|
||||
return entry_fd, file_name
|
||||
} else {
|
||||
read_directory_iterator_set_error(it, file_name, _get_platform_error(errno))
|
||||
}
|
||||
}
|
||||
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
index = it.index
|
||||
it.index += 1
|
||||
|
||||
dfd := linux.Fd(_fd(it.f))
|
||||
|
||||
entries := it.impl.dirent_backing[:it.impl.dirent_buflen]
|
||||
entry_fd, file_name := scan_entries(it, dfd, entries, &it.impl.dirent_off)
|
||||
|
||||
for entry_fd == -1 {
|
||||
if len(it.impl.dirent_backing) == 0 {
|
||||
it.impl.dirent_backing = make([]u8, 512, file_allocator())
|
||||
}
|
||||
|
||||
loop: for {
|
||||
buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:])
|
||||
#partial switch errno {
|
||||
case .EINVAL:
|
||||
delete(it.impl.dirent_backing, file_allocator())
|
||||
n := len(it.impl.dirent_backing) * 2
|
||||
it.impl.dirent_backing = make([]u8, n, file_allocator())
|
||||
continue
|
||||
case .NONE:
|
||||
if buflen == 0 {
|
||||
return
|
||||
}
|
||||
it.impl.dirent_off = 0
|
||||
it.impl.dirent_buflen = buflen
|
||||
entries = it.impl.dirent_backing[:buflen]
|
||||
break loop
|
||||
case:
|
||||
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
entry_fd, file_name = scan_entries(it, dfd, entries, &it.impl.dirent_off)
|
||||
}
|
||||
defer linux.close(entry_fd)
|
||||
|
||||
// PERF: reuse the fullpath string like on posix and wasi.
|
||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
||||
|
||||
err: Error
|
||||
fi, err = _fstat_internal(entry_fd, file_allocator())
|
||||
it.impl.prev_fi = fi
|
||||
|
||||
if err != nil {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
path, _ := _get_full_path(entry_fd, temp_allocator)
|
||||
read_directory_iterator_set_error(it, path, err)
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
|
||||
return {}, .Unsupported
|
||||
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
it.impl.dirent_buflen = 0
|
||||
it.impl.dirent_off = 0
|
||||
|
||||
if f == nil || f.impl == nil {
|
||||
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||
return
|
||||
}
|
||||
|
||||
stat: linux.Stat
|
||||
errno := linux.fstat(linux.Fd(fd(f)), &stat)
|
||||
if errno != .NONE {
|
||||
read_directory_iterator_set_error(it, name(f), _get_platform_error(errno))
|
||||
return
|
||||
}
|
||||
|
||||
if (stat.mode & linux.S_IFMT) != linux.S_IFDIR {
|
||||
read_directory_iterator_set_error(it, name(f), .Invalid_Dir)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||
if it == nil {
|
||||
return
|
||||
}
|
||||
|
||||
delete(it.impl.dirent_backing, file_allocator())
|
||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
||||
}
|
||||
|
||||
+39
-27
@@ -6,7 +6,6 @@ import "core:sys/posix"
|
||||
|
||||
Read_Directory_Iterator_Impl :: struct {
|
||||
dir: posix.DIR,
|
||||
idx: int,
|
||||
fullpath: [dynamic]byte,
|
||||
}
|
||||
|
||||
@@ -14,14 +13,16 @@ Read_Directory_Iterator_Impl :: struct {
|
||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||
fimpl := (^File_Impl)(it.f.impl)
|
||||
|
||||
index = it.impl.idx
|
||||
it.impl.idx += 1
|
||||
index = it.index
|
||||
it.index += 1
|
||||
|
||||
for {
|
||||
posix.set_errno(nil)
|
||||
entry := posix.readdir(it.impl.dir)
|
||||
if entry == nil {
|
||||
// NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator`
|
||||
// There isn't a way to now know if it failed or if we are at the end.
|
||||
if errno := posix.errno(); errno != nil {
|
||||
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,16 +32,20 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
||||
}
|
||||
sname := string(cname)
|
||||
|
||||
stat: posix.stat_t
|
||||
if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK {
|
||||
// NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator`
|
||||
// There isn't a way to now know if it failed or if we are at the end.
|
||||
n := len(fimpl.name)+1
|
||||
if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil {
|
||||
read_directory_iterator_set_error(it, sname, err)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
copy(it.impl.fullpath[n:], sname)
|
||||
|
||||
n := len(fimpl.name)+1
|
||||
non_zero_resize(&it.impl.fullpath, n+len(sname))
|
||||
n += copy(it.impl.fullpath[n:], sname)
|
||||
stat: posix.stat_t
|
||||
if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK {
|
||||
read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error())
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
fi = internal_stat(stat, string(it.impl.fullpath[:]))
|
||||
ok = true
|
||||
@@ -48,34 +53,41 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
|
||||
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
if f == nil || f.impl == nil {
|
||||
err = .Invalid_File
|
||||
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||
return
|
||||
}
|
||||
|
||||
impl := (^File_Impl)(f.impl)
|
||||
|
||||
iter.f = f
|
||||
iter.impl.idx = 0
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
it.impl.fullpath.allocator = file_allocator()
|
||||
clear(&it.impl.fullpath)
|
||||
if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil {
|
||||
read_directory_iterator_set_error(it, name(f), err)
|
||||
return
|
||||
}
|
||||
|
||||
iter.impl.fullpath.allocator = file_allocator()
|
||||
append(&iter.impl.fullpath, impl.name)
|
||||
append(&iter.impl.fullpath, "/")
|
||||
defer if err != nil { delete(iter.impl.fullpath) }
|
||||
append(&it.impl.fullpath, impl.name)
|
||||
append(&it.impl.fullpath, "/")
|
||||
|
||||
// `fdopendir` consumes the file descriptor so we need to `dup` it.
|
||||
dupfd := posix.dup(impl.fd)
|
||||
if dupfd == -1 {
|
||||
err = _get_platform_error()
|
||||
read_directory_iterator_set_error(it, name(f), _get_platform_error())
|
||||
return
|
||||
}
|
||||
defer if err != nil { posix.close(dupfd) }
|
||||
defer if it.err.err != nil { posix.close(dupfd) }
|
||||
|
||||
iter.impl.dir = posix.fdopendir(dupfd)
|
||||
if iter.impl.dir == nil {
|
||||
err = _get_platform_error()
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
if it.impl.dir != nil {
|
||||
posix.closedir(it.impl.dir)
|
||||
}
|
||||
|
||||
it.impl.dir = posix.fdopendir(dupfd)
|
||||
if it.impl.dir == nil {
|
||||
read_directory_iterator_set_error(it, name(f), _get_platform_error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,7 +95,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
|
||||
}
|
||||
|
||||
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||
if it == nil || it.impl.dir == nil {
|
||||
if it.impl.dir == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "core:sys/darwin"
|
||||
|
||||
_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
csrc := clone_to_cstring(src, temp_allocator) or_return
|
||||
cdst := clone_to_cstring(dst, temp_allocator) or_return
|
||||
|
||||
if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
|
||||
err = _get_platform_error()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package os2
|
||||
|
||||
import "core:container/queue"
|
||||
|
||||
/*
|
||||
A recursive directory walker.
|
||||
|
||||
Note that none of the fields should be accessed directly.
|
||||
*/
|
||||
Walker :: struct {
|
||||
todo: queue.Queue(string),
|
||||
skip_dir: bool,
|
||||
err: struct {
|
||||
path: [dynamic]byte,
|
||||
err: Error,
|
||||
},
|
||||
iter: Read_Directory_Iterator,
|
||||
}
|
||||
|
||||
walker_init_path :: proc(w: ^Walker, path: string) {
|
||||
cloned_path, err := clone_string(path, file_allocator())
|
||||
if err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
|
||||
walker_clear(w)
|
||||
|
||||
if _, err = queue.push(&w.todo, cloned_path); err != nil {
|
||||
walker_set_error(w, cloned_path, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
walker_init_file :: proc(w: ^Walker, f: ^File) {
|
||||
handle, err := clone(f)
|
||||
if err != nil {
|
||||
path, _ := clone_string(name(f), file_allocator())
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
|
||||
walker_clear(w)
|
||||
|
||||
read_directory_iterator_init(&w.iter, handle)
|
||||
}
|
||||
|
||||
/*
|
||||
Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
|
||||
|
||||
You are allowed to repeatedly call this to reuse it for later walks.
|
||||
|
||||
For an example on how to use the walker, see `walker_walk`.
|
||||
*/
|
||||
walker_init :: proc {
|
||||
walker_init_path,
|
||||
walker_init_file,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
walker_create_path :: proc(path: string) -> (w: Walker) {
|
||||
walker_init_path(&w, path)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
walker_create_file :: proc(f: ^File) -> (w: Walker) {
|
||||
walker_init_file(&w, f)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Creates a walker, either using a path or a file pointer to a directory the walker will start at.
|
||||
|
||||
For an example on how to use the walker, see `walker_walk`.
|
||||
*/
|
||||
walker_create :: proc {
|
||||
walker_create_path,
|
||||
walker_create_file,
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the last error that occurred during the walker's operations.
|
||||
|
||||
Can be called while iterating, or only at the end to check if anything failed.
|
||||
*/
|
||||
@(require_results)
|
||||
walker_error :: proc(w: ^Walker) -> (path: string, err: Error) {
|
||||
return string(w.err.path[:]), w.err.err
|
||||
}
|
||||
|
||||
@(private)
|
||||
walker_set_error :: proc(w: ^Walker, path: string, err: Error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resize(&w.err.path, len(path))
|
||||
copy(w.err.path[:], path)
|
||||
|
||||
w.err.err = err
|
||||
}
|
||||
|
||||
@(private)
|
||||
walker_clear :: proc(w: ^Walker) {
|
||||
w.iter.f = nil
|
||||
w.skip_dir = false
|
||||
|
||||
w.err.path.allocator = file_allocator()
|
||||
clear(&w.err.path)
|
||||
|
||||
w.todo.data.allocator = file_allocator()
|
||||
for path in queue.pop_front_safe(&w.todo) {
|
||||
delete(path, file_allocator())
|
||||
}
|
||||
}
|
||||
|
||||
walker_destroy :: proc(w: ^Walker) {
|
||||
walker_clear(w)
|
||||
queue.destroy(&w.todo)
|
||||
delete(w.err.path)
|
||||
read_directory_iterator_destroy(&w.iter)
|
||||
}
|
||||
|
||||
// Marks the current directory to be skipped (not entered into).
|
||||
walker_skip_dir :: proc(w: ^Walker) {
|
||||
w.skip_dir = true
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the next file info in the iterator, files are iterated in breadth-first order.
|
||||
|
||||
If an error occurred opening a directory, you may get zero'd info struct and
|
||||
`walker_error` will return the error.
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import os "core:os/os2"
|
||||
|
||||
main :: proc() {
|
||||
w := os.walker_create("core")
|
||||
defer os.walker_destroy(&w)
|
||||
|
||||
for info in os.walker_walk(&w) {
|
||||
// Optionally break on the first error:
|
||||
// _ = walker_error(&w) or_break
|
||||
|
||||
// Or, handle error as we go:
|
||||
if path, err := os.walker_error(&w); err != nil {
|
||||
fmt.eprintfln("failed walking %s: %s", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Or, do not handle errors during iteration, and just check the error at the end.
|
||||
|
||||
|
||||
|
||||
// Skip a directory:
|
||||
if strings.has_suffix(info.fullpath, ".git") {
|
||||
os.walker_skip_dir(&w)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.printfln("%#v", info)
|
||||
}
|
||||
|
||||
// Handle error if one happened during iteration at the end:
|
||||
if path, err := os.walker_error(&w); err != nil {
|
||||
fmt.eprintfln("failed walking %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
*/
|
||||
@(require_results)
|
||||
walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
|
||||
if w.skip_dir {
|
||||
w.skip_dir = false
|
||||
if skip, sok := queue.pop_back_safe(&w.todo); sok {
|
||||
delete(skip, file_allocator())
|
||||
}
|
||||
}
|
||||
|
||||
if w.iter.f == nil {
|
||||
if queue.len(w.todo) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
next := queue.pop_front(&w.todo)
|
||||
|
||||
handle, err := open(next)
|
||||
if err != nil {
|
||||
walker_set_error(w, next, err)
|
||||
return {}, true
|
||||
}
|
||||
|
||||
read_directory_iterator_init(&w.iter, handle)
|
||||
|
||||
delete(next, file_allocator())
|
||||
}
|
||||
|
||||
info, _, iter_ok := read_directory_iterator(&w.iter)
|
||||
|
||||
if path, err := read_directory_iterator_error(&w.iter); err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
}
|
||||
|
||||
if !iter_ok {
|
||||
close(w.iter.f)
|
||||
w.iter.f = nil
|
||||
return walker_walk(w)
|
||||
}
|
||||
|
||||
if info.type == .Directory {
|
||||
path, err := clone_string(info.fullpath, file_allocator())
|
||||
if err != nil {
|
||||
walker_set_error(w, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = queue.push_back(&w.todo, path)
|
||||
if err != nil {
|
||||
walker_set_error(w, path, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return info, iter_ok
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:slice"
|
||||
import "base:intrinsics"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
Read_Directory_Iterator_Impl :: struct {
|
||||
fullpath: [dynamic]byte,
|
||||
buf: []byte,
|
||||
off: int,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||
fimpl := (^File_Impl)(it.f.impl)
|
||||
|
||||
buf := it.impl.buf[it.impl.off:]
|
||||
|
||||
index = it.index
|
||||
it.index += 1
|
||||
|
||||
for {
|
||||
if len(buf) < size_of(wasi.dirent_t) {
|
||||
return
|
||||
}
|
||||
|
||||
entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf)))
|
||||
buf = buf[size_of(wasi.dirent_t):]
|
||||
|
||||
assert(len(buf) < int(entry.d_namlen))
|
||||
|
||||
name := string(buf[:entry.d_namlen])
|
||||
buf = buf[entry.d_namlen:]
|
||||
it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen)
|
||||
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
n := len(fimpl.name)+1
|
||||
if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil {
|
||||
read_directory_iterator_set_error(it, name, alloc_err)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
copy(it.impl.fullpath[n:], name)
|
||||
|
||||
stat, err := wasi.path_filestat_get(__fd(it.f), {}, name)
|
||||
if err != nil {
|
||||
// Can't stat, fill what we have from dirent.
|
||||
stat = {
|
||||
ino = entry.d_ino,
|
||||
filetype = entry.d_type,
|
||||
}
|
||||
read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error(err))
|
||||
}
|
||||
|
||||
fi = internal_stat(stat, string(it.impl.fullpath[:]))
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
it.impl.off = 0
|
||||
|
||||
if f == nil || f.impl == nil {
|
||||
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||
return
|
||||
}
|
||||
|
||||
impl := (^File_Impl)(f.impl)
|
||||
|
||||
buf: [dynamic]byte
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
if it.impl.buf != nil {
|
||||
buf = slice.into_dynamic(it.impl.buf)
|
||||
}
|
||||
buf.allocator = file_allocator()
|
||||
|
||||
defer if it.err.err != nil { delete(buf) }
|
||||
|
||||
for {
|
||||
if err := non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2); err != nil {
|
||||
read_directory_iterator_set_error(it, name(f), err)
|
||||
return
|
||||
}
|
||||
|
||||
n, err := wasi.fd_readdir(__fd(f), buf[:], 0)
|
||||
if err != nil {
|
||||
read_directory_iterator_set_error(it, name(f), _get_platform_error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if n < len(buf) {
|
||||
non_zero_resize(&buf, n)
|
||||
break
|
||||
}
|
||||
|
||||
assert(n == len(buf))
|
||||
}
|
||||
it.impl.buf = buf[:]
|
||||
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||
it.impl.fullpath.allocator = file_allocator()
|
||||
clear(&it.impl.fullpath)
|
||||
if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil {
|
||||
read_directory_iterator_set_error(it, name(f), err)
|
||||
return
|
||||
}
|
||||
|
||||
append(&it.impl.fullpath, impl.name)
|
||||
append(&it.impl.fullpath, "/")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||
delete(it.impl.buf, file_allocator())
|
||||
delete(it.impl.fullpath)
|
||||
}
|
||||
@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
|
||||
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
|
||||
return
|
||||
}
|
||||
path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
|
||||
|
||||
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
|
||||
defer win32.CloseHandle(handle)
|
||||
@@ -44,18 +46,11 @@ Read_Directory_Iterator_Impl :: struct {
|
||||
path: string,
|
||||
prev_fi: File_Info,
|
||||
no_more_files: bool,
|
||||
index: int,
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||
if it.f == nil {
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
|
||||
for !it.impl.no_more_files {
|
||||
err: Error
|
||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
||||
@@ -63,19 +58,21 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
||||
|
||||
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
|
||||
if err != nil {
|
||||
read_directory_iterator_set_error(it, it.impl.path, err)
|
||||
return
|
||||
}
|
||||
|
||||
if fi.name != "" {
|
||||
it.impl.prev_fi = fi
|
||||
ok = true
|
||||
index = it.impl.index
|
||||
it.impl.index += 1
|
||||
index = it.index
|
||||
it.index += 1
|
||||
}
|
||||
|
||||
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
|
||||
e := _get_platform_error()
|
||||
if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
|
||||
it.impl.no_more_files = true
|
||||
if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) {
|
||||
read_directory_iterator_set_error(it, it.impl.path, e)
|
||||
}
|
||||
it.impl.no_more_files = true
|
||||
}
|
||||
@@ -86,16 +83,27 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
|
||||
if f == nil {
|
||||
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||
it.impl.no_more_files = false
|
||||
|
||||
if f == nil || f.impl == nil {
|
||||
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||
return
|
||||
}
|
||||
|
||||
it.f = f
|
||||
impl := (^File_Impl)(f.impl)
|
||||
|
||||
// NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx.
|
||||
if it.impl.find_handle != nil {
|
||||
win32.FindClose(it.impl.find_handle)
|
||||
}
|
||||
if it.impl.path != "" {
|
||||
delete(it.impl.path, file_allocator())
|
||||
}
|
||||
|
||||
if !is_directory(impl.name) {
|
||||
err = .Invalid_Dir
|
||||
read_directory_iterator_set_error(it, impl.name, .Invalid_Dir)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,9 +116,9 @@ _read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterato
|
||||
wpath = impl.wname[:i]
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
wpath_search := make([]u16, len(wpath)+3, temp_allocator())
|
||||
wpath_search := make([]u16, len(wpath)+3, temp_allocator)
|
||||
copy(wpath_search, wpath)
|
||||
wpath_search[len(wpath)+0] = '\\'
|
||||
wpath_search[len(wpath)+1] = '*'
|
||||
@@ -118,14 +126,19 @@ _read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterato
|
||||
|
||||
it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data)
|
||||
if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
|
||||
err = _get_platform_error()
|
||||
read_directory_iterator_set_error(it, impl.name, _get_platform_error())
|
||||
return
|
||||
}
|
||||
defer if err != nil {
|
||||
defer if it.err.err != nil {
|
||||
win32.FindClose(it.impl.find_handle)
|
||||
}
|
||||
|
||||
it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return
|
||||
err: Error
|
||||
it.impl.path, err = _cleanpath_from_buf(wpath, file_allocator())
|
||||
if err != nil {
|
||||
read_directory_iterator_set_error(it, impl.name, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+82
-8
@@ -1,29 +1,52 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
|
||||
// get_env retrieves the value of the environment variable named by the key
|
||||
// `get_env` retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
|
||||
get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string {
|
||||
value, _ := lookup_env(key, allocator)
|
||||
return value
|
||||
}
|
||||
|
||||
// lookup_env gets the value of the environment variable named by the key
|
||||
// `get_env` retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: this version takes a backing buffer for the string value
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> string {
|
||||
value, _ := lookup_env(buf, key)
|
||||
return value
|
||||
}
|
||||
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
// `lookup_env` gets the value of the environment variable named by the key
|
||||
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
||||
// Otherwise the returned value will be empty and the boolean will be false
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
return _lookup_env(key, allocator)
|
||||
lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
return _lookup_env_alloc(key, allocator)
|
||||
}
|
||||
|
||||
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
|
||||
// Note that it is limited to environment names and values of 512 utf-16 values each
|
||||
// due to the necessary utf-8 <> utf-16 conversion.
|
||||
@(require_results)
|
||||
lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return _lookup_env_buf(buf, key)
|
||||
}
|
||||
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buf}
|
||||
|
||||
// set_env sets the value of the environment variable named by the key
|
||||
// Returns true on success, false on failure
|
||||
set_env :: proc(key, value: string) -> bool {
|
||||
// Returns Error on failure
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
return _set_env(key, value)
|
||||
}
|
||||
|
||||
@@ -41,8 +64,59 @@ clear_env :: proc() {
|
||||
// environ returns a copy of strings representing the environment, in the form "key=value"
|
||||
// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
|
||||
@(require_results)
|
||||
environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||
environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
|
||||
return _environ(allocator)
|
||||
}
|
||||
|
||||
// Always allocates for consistency.
|
||||
replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) {
|
||||
path := path
|
||||
|
||||
sb: strings.Builder
|
||||
strings.builder_init_none(&sb, allocator)
|
||||
|
||||
for len(path) > 0 {
|
||||
switch path[0] {
|
||||
case '%': // Windows
|
||||
when ODIN_OS == .Windows {
|
||||
for r, i in path[1:] {
|
||||
if r == '%' {
|
||||
env_key := path[1:i+1]
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[i+1:] // % is part of key, so skip 1 character extra
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case '$': // Posix
|
||||
when ODIN_OS != .Windows {
|
||||
env_key := ""
|
||||
dollar_loop: for r, i in path[1:] {
|
||||
switch r {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
|
||||
case:
|
||||
env_key = path[1:i+1]
|
||||
break dollar_loop
|
||||
}
|
||||
}
|
||||
if len(env_key) > 0 {
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[len(env_key):]
|
||||
}
|
||||
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case:
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
path = path[1:]
|
||||
}
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
+63
-46
@@ -20,19 +20,18 @@ NOT_FOUND :: -1
|
||||
// the environment is a 0 delimited list of <key>=<value> strings
|
||||
_env: [dynamic]string
|
||||
|
||||
_env_mutex: sync.Mutex
|
||||
_env_mutex: sync.Recursive_Mutex
|
||||
|
||||
// We need to be able to figure out if the environment variable
|
||||
// is contained in the original environment or not. This also
|
||||
// serves as a flag to determine if we have built _env.
|
||||
_org_env_begin: uintptr
|
||||
_org_env_end: uintptr
|
||||
_org_env_begin: uintptr // atomic
|
||||
_org_env_end: uintptr // guarded by _env_mutex
|
||||
|
||||
// Returns value + index location into _env
|
||||
// or -1 if not found
|
||||
_lookup :: proc(key: string) -> (value: string, idx: int) {
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
for entry, i in _env {
|
||||
if k, v := _kv_from_entry(entry); k == key {
|
||||
@@ -42,8 +41,8 @@ _lookup :: proc(key: string) -> (value: string, idx: int) {
|
||||
return "", -1
|
||||
}
|
||||
|
||||
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
if _org_env_begin == 0 {
|
||||
_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
_build_env()
|
||||
}
|
||||
|
||||
@@ -54,19 +53,35 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
|
||||
return
|
||||
}
|
||||
|
||||
_set_env :: proc(key, v_new: string) -> bool {
|
||||
if _org_env_begin == 0 {
|
||||
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
_build_env()
|
||||
}
|
||||
|
||||
if v, idx := _lookup(key); idx != -1 {
|
||||
if len(buf) >= len(v) {
|
||||
copy(buf, v)
|
||||
return string(buf[:len(v)]), nil
|
||||
}
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
|
||||
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
|
||||
|
||||
_set_env :: proc(key, v_new: string) -> Error {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
_build_env()
|
||||
}
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
// all key values are stored as "key=value\x00"
|
||||
kv_size := len(key) + len(v_new) + 2
|
||||
if v_curr, idx := _lookup(key); idx != NOT_FOUND {
|
||||
if v_curr == v_new {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
|
||||
unordered_remove(&_env, idx)
|
||||
|
||||
@@ -76,9 +91,9 @@ _set_env :: proc(key, v_new: string) -> bool {
|
||||
// wasn't in the environment in the first place.
|
||||
k_addr, v_addr := _kv_addr_from_val(v_curr, key)
|
||||
if len(v_new) > len(v_curr) {
|
||||
k_addr = ([^]u8)(heap_resize(k_addr, kv_size))
|
||||
k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size))
|
||||
if k_addr == nil {
|
||||
return false
|
||||
return .Out_Of_Memory
|
||||
}
|
||||
v_addr = &k_addr[len(key) + 1]
|
||||
}
|
||||
@@ -86,13 +101,13 @@ _set_env :: proc(key, v_new: string) -> bool {
|
||||
v_addr[len(v_new)] = 0
|
||||
|
||||
append(&_env, string(k_addr[:kv_size]))
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
k_addr := ([^]u8)(heap_alloc(kv_size))
|
||||
k_addr := ([^]u8)(runtime.heap_alloc(kv_size))
|
||||
if k_addr == nil {
|
||||
return false
|
||||
return .Out_Of_Memory
|
||||
}
|
||||
intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key))
|
||||
k_addr[len(key)] = '='
|
||||
@@ -101,16 +116,15 @@ _set_env :: proc(key, v_new: string) -> bool {
|
||||
intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new))
|
||||
val_slice[len(v_new)] = 0
|
||||
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
append(&_env, string(k_addr[:kv_size - 1]))
|
||||
sync.mutex_unlock(&_env_mutex)
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
_unset_env :: proc(key: string) -> bool {
|
||||
if _org_env_begin == 0 {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
_build_env()
|
||||
}
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
v: string
|
||||
i: int
|
||||
@@ -118,55 +132,60 @@ _unset_env :: proc(key: string) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
unordered_remove(&_env, i)
|
||||
sync.mutex_unlock(&_env_mutex)
|
||||
|
||||
if _is_in_org_env(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
// if we got this far, the envrionment variable
|
||||
// if we got this far, the environment variable
|
||||
// existed AND was allocated by us.
|
||||
k_addr, _ := _kv_addr_from_val(v, key)
|
||||
heap_free(k_addr)
|
||||
runtime.heap_free(k_addr)
|
||||
return true
|
||||
}
|
||||
|
||||
_clear_env :: proc() {
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
for kv in _env {
|
||||
if !_is_in_org_env(kv) {
|
||||
heap_free(raw_data(kv))
|
||||
runtime.heap_free(raw_data(kv))
|
||||
}
|
||||
}
|
||||
clear(&_env)
|
||||
|
||||
// nothing resides in the original environment either
|
||||
_org_env_begin = ~uintptr(0)
|
||||
intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release)
|
||||
_org_env_end = ~uintptr(0)
|
||||
}
|
||||
|
||||
_environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||
if _org_env_begin == 0 {
|
||||
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
_build_env()
|
||||
}
|
||||
env := make([]string, len(_env), allocator)
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
for entry, i in _env {
|
||||
env[i], _ = clone_string(entry, allocator)
|
||||
env := make([dynamic]string, 0, len(_env), allocator) or_return
|
||||
defer if err != nil {
|
||||
for e in env {
|
||||
delete(e, allocator)
|
||||
}
|
||||
delete(env)
|
||||
}
|
||||
return env
|
||||
|
||||
for entry in _env {
|
||||
s := clone_string(entry, allocator) or_return
|
||||
append(&env, s)
|
||||
}
|
||||
environ = env[:]
|
||||
return
|
||||
}
|
||||
|
||||
// The entire environment is stored as 0 terminated strings,
|
||||
// so there is no need to clone/free individual variables
|
||||
export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring {
|
||||
if _org_env_begin == 0 {
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
|
||||
// The environment has not been modified, so we can just
|
||||
// send the original environment
|
||||
org_env := _get_original_env()
|
||||
@@ -174,12 +193,11 @@ export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring {
|
||||
for ; org_env[n] != nil; n += 1 {}
|
||||
return slice.clone(org_env[:n + 1], allocator)
|
||||
}
|
||||
sync.guard(&_env_mutex)
|
||||
|
||||
// NOTE: already terminated by nil pointer via + 1
|
||||
env := make([]cstring, len(_env) + 1, allocator)
|
||||
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
for entry, i in _env {
|
||||
env[i] = cstring(raw_data(entry))
|
||||
}
|
||||
@@ -187,15 +205,14 @@ export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring {
|
||||
}
|
||||
|
||||
_build_env :: proc() {
|
||||
sync.mutex_lock(&_env_mutex)
|
||||
defer sync.mutex_unlock(&_env_mutex)
|
||||
if _org_env_begin != 0 {
|
||||
sync.guard(&_env_mutex)
|
||||
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
_env = make(type_of(_env), heap_allocator())
|
||||
_env = make(type_of(_env), runtime.heap_allocator())
|
||||
cstring_env := _get_original_env()
|
||||
_org_env_begin = uintptr(rawptr(cstring_env[0]))
|
||||
intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release)
|
||||
for i := 0; cstring_env[i] != nil; i += 1 {
|
||||
bytes := ([^]u8)(cstring_env[i])
|
||||
n := len(cstring_env[i])
|
||||
@@ -227,5 +244,5 @@ _kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]
|
||||
|
||||
_is_in_org_env :: #force_inline proc(env_data: string) -> bool {
|
||||
addr := uintptr(raw_data(env_data))
|
||||
return addr >= _org_env_begin && addr < _org_env_end
|
||||
return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end
|
||||
}
|
||||
|
||||
+52
-20
@@ -7,14 +7,14 @@ import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:sys/posix"
|
||||
|
||||
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator())
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator)
|
||||
cval := posix.getenv(ckey)
|
||||
if cval == nil {
|
||||
return
|
||||
@@ -26,20 +26,52 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
|
||||
return
|
||||
}
|
||||
|
||||
_set_env :: proc(key, value: string) -> (ok: bool) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator())
|
||||
cval := strings.clone_to_cstring(key, temp_allocator())
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
ok = posix.setenv(ckey, cval, true) == .OK
|
||||
cval := posix.getenv(cstring(raw_data(buf)))
|
||||
if cval == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if value = string(cval); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
|
||||
|
||||
_set_env :: proc(key, value: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator) or_return
|
||||
cval := strings.clone_to_cstring(value, temp_allocator) or_return
|
||||
|
||||
if posix.setenv(ckey, cval, true) != nil {
|
||||
err = _get_platform_error_from_errno()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_unset_env :: proc(key: string) -> (ok: bool) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator())
|
||||
ckey := strings.clone_to_cstring(key, temp_allocator)
|
||||
|
||||
ok = posix.unsetenv(ckey) == .OK
|
||||
return
|
||||
@@ -54,23 +86,23 @@ _clear_env :: proc() {
|
||||
}
|
||||
}
|
||||
|
||||
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string) {
|
||||
n := 0
|
||||
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
|
||||
n := 0
|
||||
for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {}
|
||||
|
||||
err: runtime.Allocator_Error
|
||||
if environ, err = make([]string, n, allocator); err != nil {
|
||||
// NOTE(laytan): is the environment empty or did allocation fail, how does the user know?
|
||||
return
|
||||
r := make([dynamic]string, 0, n, allocator) or_return
|
||||
defer if err != nil {
|
||||
for e in r {
|
||||
delete(e, allocator)
|
||||
}
|
||||
delete(r)
|
||||
}
|
||||
|
||||
for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] {
|
||||
if environ[i], err = strings.clone(string(entry), allocator); err != nil {
|
||||
// NOTE(laytan): is the entire environment returned or did allocation fail, how does the user know?
|
||||
return
|
||||
}
|
||||
append(&r, strings.clone(string(entry), allocator) or_return)
|
||||
}
|
||||
|
||||
environ = r[:]
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:strings"
|
||||
import "core:sync"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
g_env: map[string]string
|
||||
g_env_buf: []byte
|
||||
g_env_mutex: sync.RW_Mutex
|
||||
g_env_error: Error
|
||||
g_env_built: bool
|
||||
|
||||
build_env :: proc() -> (err: Error) {
|
||||
if g_env_built || g_env_error != nil {
|
||||
return g_env_error
|
||||
}
|
||||
|
||||
sync.guard(&g_env_mutex)
|
||||
|
||||
if g_env_built || g_env_error != nil {
|
||||
return g_env_error
|
||||
}
|
||||
|
||||
defer if err != nil {
|
||||
g_env_error = err
|
||||
}
|
||||
|
||||
num_envs, size_of_envs, _err := wasi.environ_sizes_get()
|
||||
if _err != nil {
|
||||
return _get_platform_error(_err)
|
||||
}
|
||||
|
||||
g_env = make(map[string]string, num_envs, file_allocator()) or_return
|
||||
defer if err != nil { delete(g_env) }
|
||||
|
||||
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
|
||||
defer if err != nil { delete(g_env_buf, file_allocator()) }
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
envs := make([]cstring, num_envs, temp_allocator) or_return
|
||||
|
||||
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
|
||||
if _err != nil {
|
||||
return _get_platform_error(_err)
|
||||
}
|
||||
|
||||
for env in envs {
|
||||
key, _, value := strings.partition(string(env), "=")
|
||||
g_env[key] = value
|
||||
}
|
||||
|
||||
g_env_built = true
|
||||
return
|
||||
}
|
||||
|
||||
delete_string_if_not_original :: proc(str: string) {
|
||||
start := uintptr(raw_data(g_env_buf))
|
||||
end := start + uintptr(len(g_env_buf))
|
||||
ptr := uintptr(raw_data(str))
|
||||
if ptr < start || ptr > end {
|
||||
delete(str, file_allocator())
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
if err := build_env(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sync.shared_guard(&g_env_mutex)
|
||||
|
||||
value = g_env[key] or_return
|
||||
value, _ = clone_string(value, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
sync.shared_guard(&g_env_mutex)
|
||||
|
||||
val, ok := g_env[key]
|
||||
|
||||
if !ok {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(val) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, val)
|
||||
return string(buf[:len(val)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
|
||||
|
||||
@(require_results)
|
||||
_set_env :: proc(key, value: string) -> (err: Error) {
|
||||
build_env() or_return
|
||||
|
||||
sync.guard(&g_env_mutex)
|
||||
|
||||
defer if err != nil {
|
||||
delete_key(&g_env, key)
|
||||
}
|
||||
|
||||
key_ptr, value_ptr, just_inserted := map_entry(&g_env, key) or_return
|
||||
|
||||
if just_inserted {
|
||||
key_ptr^ = clone_string(key, file_allocator()) or_return
|
||||
defer if err != nil {
|
||||
delete(key_ptr^, file_allocator())
|
||||
}
|
||||
value_ptr^ = clone_string(value, file_allocator()) or_return
|
||||
return
|
||||
}
|
||||
|
||||
delete_string_if_not_original(value_ptr^)
|
||||
|
||||
value_ptr^ = clone_string(value, file_allocator()) or_return
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_unset_env :: proc(key: string) -> bool {
|
||||
if err := build_env(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
sync.guard(&g_env_mutex)
|
||||
|
||||
dkey, dval := delete_key(&g_env, key)
|
||||
delete_string_if_not_original(dkey)
|
||||
delete_string_if_not_original(dval)
|
||||
return true
|
||||
}
|
||||
|
||||
_clear_env :: proc() {
|
||||
sync.guard(&g_env_mutex)
|
||||
|
||||
for k, v in g_env {
|
||||
delete_string_if_not_original(k)
|
||||
delete_string_if_not_original(v)
|
||||
}
|
||||
|
||||
delete(g_env_buf, file_allocator())
|
||||
g_env_buf = {}
|
||||
|
||||
clear(&g_env)
|
||||
|
||||
g_env_built = true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
|
||||
build_env() or_return
|
||||
|
||||
sync.shared_guard(&g_env_mutex)
|
||||
|
||||
envs := make([dynamic]string, 0, len(g_env), allocator) or_return
|
||||
defer if err != nil {
|
||||
for env in envs {
|
||||
delete(env, allocator)
|
||||
}
|
||||
delete(envs)
|
||||
}
|
||||
|
||||
for k, v in g_env {
|
||||
append(&envs, concatenate({k, "=", v}, allocator) or_return)
|
||||
}
|
||||
|
||||
environ = envs[:]
|
||||
return
|
||||
}
|
||||
@@ -4,12 +4,12 @@ package os2
|
||||
import win32 "core:sys/windows"
|
||||
import "base:runtime"
|
||||
|
||||
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
|
||||
|
||||
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
|
||||
if n == 0 {
|
||||
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
|
||||
return "", true
|
||||
}
|
||||
|
||||
b := make([]u16, n+1, temp_allocator())
|
||||
b := make([]u16, n+1, temp_allocator)
|
||||
|
||||
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
|
||||
if n == 0 {
|
||||
@@ -36,23 +36,56 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
|
||||
return
|
||||
}
|
||||
|
||||
_set_env :: proc(key, value: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
k, _ := win32_utf8_to_wstring(key, temp_allocator())
|
||||
v, _ := win32_utf8_to_wstring(value, temp_allocator())
|
||||
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
|
||||
// Note that it is limited to environment names and values of 512 utf-16 values each
|
||||
// due to the necessary utf-8 <> utf-16 conversion.
|
||||
@(require_results)
|
||||
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
key_buf: [513]u16
|
||||
wkey := win32.utf8_to_wstring(key_buf[:], key)
|
||||
if wkey == nil {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
return bool(win32.SetEnvironmentVariableW(k, v))
|
||||
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
|
||||
val_buf: [513]u16
|
||||
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else if int(n2) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
value = win32.utf16_to_utf8(buf, val_buf[:n2])
|
||||
|
||||
return value, nil
|
||||
}
|
||||
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
|
||||
|
||||
_set_env :: proc(key, value: string) -> Error {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
k := win32_utf8_to_wstring(key, temp_allocator) or_return
|
||||
v := win32_utf8_to_wstring(value, temp_allocator) or_return
|
||||
|
||||
if !win32.SetEnvironmentVariableW(k, v) {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_unset_env :: proc(key: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
k, _ := win32_utf8_to_wstring(key, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
k, _ := win32_utf8_to_wstring(key, temp_allocator)
|
||||
return bool(win32.SetEnvironmentVariableW(k, nil))
|
||||
}
|
||||
|
||||
_clear_env :: proc() {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
envs := environ(temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
envs, _ := environ(temp_allocator)
|
||||
for env in envs {
|
||||
for j in 1..<len(env) {
|
||||
if env[j] == '=' {
|
||||
@@ -63,10 +96,10 @@ _clear_env :: proc() {
|
||||
}
|
||||
}
|
||||
|
||||
_environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
|
||||
envs := win32.GetEnvironmentStringsW()
|
||||
if envs == nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
defer win32.FreeEnvironmentStringsW(envs)
|
||||
|
||||
@@ -82,7 +115,13 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||
}
|
||||
}
|
||||
|
||||
r := make([dynamic]string, 0, n, allocator)
|
||||
r := make([dynamic]string, 0, n, allocator) or_return
|
||||
defer if err != nil {
|
||||
for e in r {
|
||||
delete(e, allocator)
|
||||
}
|
||||
delete(r)
|
||||
}
|
||||
for from, i, p := 0, 0, envs; true; i += 1 {
|
||||
c := ([^]u16)(p)[i]
|
||||
if c == 0 {
|
||||
@@ -90,12 +129,14 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||
break
|
||||
}
|
||||
w := ([^]u16)(p)[from:i]
|
||||
append(&r, win32_utf16_to_utf8(w, allocator) or_else "")
|
||||
s := win32_utf16_to_utf8(w, allocator) or_return
|
||||
append(&r, s)
|
||||
from = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return r[:]
|
||||
environ = r[:]
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
+21
-16
@@ -27,6 +27,9 @@ General_Error :: enum u32 {
|
||||
|
||||
Pattern_Has_Separator,
|
||||
|
||||
No_HOME_Variable,
|
||||
Env_Var_Not_Found,
|
||||
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
|
||||
case General_Error:
|
||||
switch e {
|
||||
case .None: return ""
|
||||
case .Permission_Denied: return "permission denied"
|
||||
case .Exist: return "file already exists"
|
||||
case .Not_Exist: return "file does not exist"
|
||||
case .Closed: return "file already closed"
|
||||
case .Timeout: return "i/o timeout"
|
||||
case .Broken_Pipe: return "Broken pipe"
|
||||
case .No_Size: return "file has no definite size"
|
||||
case .Invalid_File: return "invalid file"
|
||||
case .Invalid_Dir: return "invalid directory"
|
||||
case .Invalid_Path: return "invalid path"
|
||||
case .Invalid_Callback: return "invalid callback"
|
||||
case .Invalid_Command: return "invalid command"
|
||||
case .Unsupported: return "unsupported"
|
||||
case .Pattern_Has_Separator: return "pattern has separator"
|
||||
case .Permission_Denied: return "permission denied"
|
||||
case .Exist: return "file already exists"
|
||||
case .Not_Exist: return "file does not exist"
|
||||
case .Closed: return "file already closed"
|
||||
case .Timeout: return "i/o timeout"
|
||||
case .Broken_Pipe: return "Broken pipe"
|
||||
case .No_Size: return "file has no definite size"
|
||||
case .Invalid_File: return "invalid file"
|
||||
case .Invalid_Dir: return "invalid directory"
|
||||
case .Invalid_Path: return "invalid path"
|
||||
case .Invalid_Callback: return "invalid callback"
|
||||
case .Invalid_Command: return "invalid command"
|
||||
case .Unsupported: return "unsupported"
|
||||
case .Pattern_Has_Separator: return "pattern has separator"
|
||||
case .No_HOME_Variable: return "no $HOME variable"
|
||||
case .Env_Var_Not_Found: return "environment variable not found"
|
||||
}
|
||||
case io.Error:
|
||||
switch e {
|
||||
@@ -108,12 +113,12 @@ error_string :: proc(ferr: Error) -> string {
|
||||
}
|
||||
|
||||
print_error :: proc(f: ^File, ferr: Error, msg: string) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
err_str := error_string(ferr)
|
||||
|
||||
// msg + ": " + err_str + '\n'
|
||||
length := len(msg) + 2 + len(err_str) + 1
|
||||
buf := make([]u8, length, temp_allocator())
|
||||
buf := make([]u8, length, temp_allocator)
|
||||
|
||||
copy(buf, msg)
|
||||
buf[len(msg)] = ':'
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:slice"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
_Platform_Error :: wasi.errno_t
|
||||
|
||||
_error_string :: proc(errno: i32) -> string {
|
||||
e := wasi.errno_t(errno)
|
||||
if e == .NONE {
|
||||
return ""
|
||||
}
|
||||
|
||||
err := runtime.Type_Info_Enum_Value(e)
|
||||
|
||||
ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum)
|
||||
if idx, ok := slice.binary_search(ti.values, err); ok {
|
||||
return ti.names[idx]
|
||||
}
|
||||
return "<unknown platform error>"
|
||||
}
|
||||
|
||||
_get_platform_error :: proc(errno: wasi.errno_t) -> Error {
|
||||
#partial switch errno {
|
||||
case .PERM:
|
||||
return .Permission_Denied
|
||||
case .EXIST:
|
||||
return .Exist
|
||||
case .NOENT:
|
||||
return .Not_Exist
|
||||
case .TIMEDOUT:
|
||||
return .Timeout
|
||||
case .PIPE:
|
||||
return .Broken_Pipe
|
||||
case .BADF:
|
||||
return .Invalid_File
|
||||
case .NOMEM:
|
||||
return .Out_Of_Memory
|
||||
case .NOSYS:
|
||||
return .Unsupported
|
||||
case:
|
||||
return Platform_Error(errno)
|
||||
}
|
||||
}
|
||||
+18
-4
@@ -122,6 +122,11 @@ new_file :: proc(handle: uintptr, name: string) -> ^File {
|
||||
return file
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
clone :: proc(f: ^File) -> (^File, Error) {
|
||||
return _clone(f)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fd :: proc(f: ^File) -> uintptr {
|
||||
return _fd(f)
|
||||
@@ -286,8 +291,8 @@ exists :: proc(path: string) -> bool {
|
||||
|
||||
@(require_results)
|
||||
is_file :: proc(path: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
fi, err := stat(path, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
fi, err := stat(path, temp_allocator)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -298,8 +303,8 @@ is_dir :: is_directory
|
||||
|
||||
@(require_results)
|
||||
is_directory :: proc(path: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
fi, err := stat(path, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
fi, err := stat(path, temp_allocator)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -308,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
|
||||
|
||||
|
||||
copy_file :: proc(dst_path, src_path: string) -> Error {
|
||||
when #defined(_copy_file_native) {
|
||||
return _copy_file_native(dst_path, src_path)
|
||||
} else {
|
||||
return _copy_file(dst_path, src_path)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_copy_file :: proc(dst_path, src_path: string) -> Error {
|
||||
src := open(src_path) or_return
|
||||
defer close(src)
|
||||
|
||||
|
||||
+88
-49
@@ -7,6 +7,13 @@ import "core:time"
|
||||
import "core:sync"
|
||||
import "core:sys/linux"
|
||||
|
||||
// Most implementations will EINVAL at some point when doing big writes.
|
||||
// In practice a read/write call would probably never read/write these big buffers all at once,
|
||||
// which is why the number of bytes is returned and why there are procs that will call this in a
|
||||
// loop for you.
|
||||
// We set a max of 1GB to keep alignment and to be safe.
|
||||
MAX_RW :: 1 << 30
|
||||
|
||||
File_Impl :: struct {
|
||||
file: File,
|
||||
name: string,
|
||||
@@ -59,8 +66,8 @@ _standard_stream_init :: proc() {
|
||||
}
|
||||
|
||||
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
// Just default to using O_NOCTTY because needing to open a controlling
|
||||
// terminal would be incredibly rare. This has no effect on files while
|
||||
@@ -106,6 +113,23 @@ _new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^
|
||||
return &impl.file, nil
|
||||
}
|
||||
|
||||
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
|
||||
if f == nil || f.impl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd := (^File_Impl)(f.impl).fd
|
||||
|
||||
clonefd, errno := linux.dup(fd)
|
||||
if errno != nil {
|
||||
err = _get_platform_error(errno)
|
||||
return
|
||||
}
|
||||
defer if err != nil { linux.close(clonefd) }
|
||||
|
||||
return _new_file(uintptr(clonefd), "", file_allocator())
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
|
||||
@@ -179,10 +203,11 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er
|
||||
}
|
||||
|
||||
_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
if len(p) <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n, errno := linux.read(f.fd, p[:])
|
||||
|
||||
n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)])
|
||||
if errno != .NONE {
|
||||
return -1, _get_platform_error(errno)
|
||||
}
|
||||
@@ -190,13 +215,13 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
|
||||
}
|
||||
|
||||
_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
if len(p) <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if offset < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
n, errno := linux.pread(f.fd, p[:], offset)
|
||||
n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset)
|
||||
if errno != .NONE {
|
||||
return -1, _get_platform_error(errno)
|
||||
}
|
||||
@@ -206,31 +231,45 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
|
||||
return i64(n), nil
|
||||
}
|
||||
|
||||
_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) {
|
||||
p := p
|
||||
for len(p) > 0 {
|
||||
n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)])
|
||||
if errno != .NONE {
|
||||
err = _get_platform_error(errno)
|
||||
return
|
||||
}
|
||||
|
||||
p = p[n:]
|
||||
nt += i64(n)
|
||||
}
|
||||
n, errno := linux.write(f.fd, p[:])
|
||||
if errno != .NONE {
|
||||
return -1, _get_platform_error(errno)
|
||||
}
|
||||
return i64(n), nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) {
|
||||
if offset < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
n, errno := linux.pwrite(f.fd, p[:], offset)
|
||||
if errno != .NONE {
|
||||
return -1, _get_platform_error(errno)
|
||||
|
||||
p := p
|
||||
offset := offset
|
||||
for len(p) > 0 {
|
||||
n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset)
|
||||
if errno != .NONE {
|
||||
err = _get_platform_error(errno)
|
||||
return
|
||||
}
|
||||
|
||||
p = p[n:]
|
||||
nt += i64(n)
|
||||
offset += i64(n)
|
||||
}
|
||||
return i64(n), nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(no_sanitize_memory)
|
||||
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
|
||||
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
|
||||
// eliminate the need for the _read_entire_pseudo_file procs.
|
||||
@@ -261,8 +300,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
|
||||
}
|
||||
|
||||
_remove :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
|
||||
linux.close(fd)
|
||||
@@ -273,25 +312,25 @@ _remove :: proc(name: string) -> Error {
|
||||
}
|
||||
|
||||
_rename :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
old_name_cstr := temp_cstring(old_name) or_return
|
||||
new_name_cstr := temp_cstring(new_name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
|
||||
new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
|
||||
|
||||
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
|
||||
}
|
||||
|
||||
_link :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
old_name_cstr := temp_cstring(old_name) or_return
|
||||
new_name_cstr := temp_cstring(new_name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
|
||||
new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
|
||||
|
||||
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
|
||||
}
|
||||
|
||||
_symlink :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
old_name_cstr := temp_cstring(old_name) or_return
|
||||
new_name_cstr := temp_cstring(new_name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
|
||||
new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
|
||||
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
|
||||
}
|
||||
|
||||
@@ -314,14 +353,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
|
||||
}
|
||||
|
||||
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _read_link_cstr(name_cstr, allocator)
|
||||
}
|
||||
|
||||
_chdir :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _get_platform_error(linux.chdir(name_cstr))
|
||||
}
|
||||
|
||||
@@ -331,8 +370,8 @@ _fchdir :: proc(f: ^File) -> Error {
|
||||
}
|
||||
|
||||
_chmod :: proc(name: string, mode: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
|
||||
}
|
||||
|
||||
@@ -343,15 +382,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
|
||||
|
||||
// NOTE: will throw error without super user priviledges
|
||||
_chown :: proc(name: string, uid, gid: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
|
||||
}
|
||||
|
||||
// NOTE: will throw error without super user priviledges
|
||||
_lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
|
||||
}
|
||||
|
||||
@@ -362,8 +401,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
|
||||
}
|
||||
|
||||
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
times := [2]linux.Time_Spec {
|
||||
{
|
||||
uint(atime._nsec) / uint(time.Second),
|
||||
@@ -393,8 +432,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
|
||||
}
|
||||
|
||||
_exists :: proc(name: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr, _ := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
name_cstr, _ := clone_to_cstring(name, temp_allocator)
|
||||
return linux.access(name_cstr, linux.F_OK) == .NONE
|
||||
}
|
||||
|
||||
@@ -402,8 +441,8 @@ _exists :: proc(name: string) -> bool {
|
||||
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
|
||||
|
||||
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := clone_to_cstring(name, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
return _read_entire_pseudo_file_cstring(name_cstr, allocator)
|
||||
}
|
||||
|
||||
|
||||
+59
-35
@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
|
||||
if .Trunc in flags { sys_flags += {.TRUNC} }
|
||||
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
|
||||
if fd < 0 {
|
||||
@@ -114,6 +114,29 @@ __new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File {
|
||||
return &impl.file
|
||||
}
|
||||
|
||||
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
|
||||
if f == nil || f.impl == nil {
|
||||
err = .Invalid_Pointer
|
||||
return
|
||||
}
|
||||
|
||||
impl := (^File_Impl)(f.impl)
|
||||
|
||||
fd := posix.dup(impl.fd)
|
||||
if fd <= 0 {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
defer if err != nil { posix.close(fd) }
|
||||
|
||||
clone = __new_file(fd, file_allocator())
|
||||
clone_impl := (^File_Impl)(clone.impl)
|
||||
clone_impl.cname = clone_to_cstring(impl.name, file_allocator()) or_return
|
||||
clone_impl.name = string(clone_impl.cname)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_close :: proc(f: ^File_Impl) -> (err: Error) {
|
||||
if f == nil { return nil }
|
||||
|
||||
@@ -160,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_remove :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
_remove :: proc(name: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.remove(cname) != 0 {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_rename :: proc(old_path, new_path: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cold := temp_cstring(old_path)
|
||||
cnew := temp_cstring(new_path)
|
||||
_rename :: proc(old_path, new_path: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cold := clone_to_cstring(old_path, temp_allocator) or_return
|
||||
cnew := clone_to_cstring(new_path, temp_allocator) or_return
|
||||
if posix.rename(cold, cnew) != 0 {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_link :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cold := temp_cstring(old_name)
|
||||
cnew := temp_cstring(new_name)
|
||||
_link :: proc(old_name, new_name: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cold := clone_to_cstring(old_name, temp_allocator) or_return
|
||||
cnew := clone_to_cstring(new_name, temp_allocator) or_return
|
||||
if posix.link(cold, cnew) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_symlink :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cold := temp_cstring(old_name)
|
||||
cnew := temp_cstring(new_name)
|
||||
_symlink :: proc(old_name, new_name: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cold := clone_to_cstring(old_name, temp_allocator) or_return
|
||||
cnew := clone_to_cstring(new_name, temp_allocator) or_return
|
||||
if posix.symlink(cold, cnew) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -200,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
|
||||
}
|
||||
|
||||
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
buf: [dynamic]byte
|
||||
buf.allocator = allocator
|
||||
@@ -245,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
|
||||
}
|
||||
}
|
||||
|
||||
_chdir :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
_chdir :: proc(name: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.chdir(cname) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -268,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_chmod :: proc(name: string, mode: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
_chmod :: proc(name: string, mode: int) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -284,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_chown :: proc(name: string, uid, gid: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
_chown :: proc(name: string, uid, gid: int) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -294,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
|
||||
}
|
||||
|
||||
_lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
|
||||
_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
|
||||
times := [2]posix.timeval{
|
||||
{
|
||||
tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */
|
||||
@@ -314,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
|
||||
},
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
if posix.utimes(cname, ×) != .OK {
|
||||
return _get_platform_error()
|
||||
@@ -342,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
|
||||
}
|
||||
|
||||
_exists :: proc(path: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cpath := temp_cstring(path)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cpath, err := clone_to_cstring(path, temp_allocator)
|
||||
if err != nil { return false }
|
||||
return posix.access(cpath) == .OK
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:sys/darwin"
|
||||
import "core:sys/posix"
|
||||
|
||||
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
|
||||
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
|
||||
|
||||
return clone_to_cstring(string(cstring(&buf[0])), allocator)
|
||||
}
|
||||
|
||||
_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
csrc := clone_to_cstring(src_path, temp_allocator) or_return
|
||||
cdst := clone_to_cstring(dst_path, temp_allocator) or_return
|
||||
|
||||
// Disallow directories, as specified by the generic implementation.
|
||||
|
||||
stat: posix.stat_t
|
||||
if posix.stat(csrc, &stat) != .OK {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
|
||||
if posix.S_ISDIR(stat.st_mode) {
|
||||
err = .Invalid_File
|
||||
return
|
||||
}
|
||||
|
||||
ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
|
||||
if ret < 0 {
|
||||
err = _get_platform_error()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import "base:runtime"
|
||||
import "core:sys/posix"
|
||||
|
||||
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
cname := clone_to_cstring(name, temp_allocator)
|
||||
|
||||
buf: [posix.PATH_MAX]byte
|
||||
path = posix.realpath(cname, raw_data(buf[:]))
|
||||
|
||||
@@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
|
||||
if r < 32 {
|
||||
if wrap(write_string(f, "\\x"), &n, &err) { return }
|
||||
b: [2]byte
|
||||
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
|
||||
s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
|
||||
switch len(s) {
|
||||
case 0: if wrap(write_string(f, "00"), &n, &err) { return }
|
||||
case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
|
||||
|
||||
@@ -0,0 +1,560 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:io"
|
||||
import "core:sys/wasm/wasi"
|
||||
import "core:time"
|
||||
|
||||
// NOTE: Don't know if there is a max in wasi.
|
||||
MAX_RW :: 1 << 30
|
||||
|
||||
File_Impl :: struct {
|
||||
file: File,
|
||||
name: string,
|
||||
fd: wasi.fd_t,
|
||||
allocator: runtime.Allocator,
|
||||
}
|
||||
|
||||
// WASI works with "preopened" directories, the environment retrieves directories
|
||||
// (for example with `wasmtime --dir=. module.wasm`) and those given directories
|
||||
// are the only ones accessible by the application.
|
||||
//
|
||||
// So in order to facilitate the `os` API (absolute paths etc.) we keep a list
|
||||
// of the given directories and match them when needed (notably `os.open`).
|
||||
Preopen :: struct {
|
||||
fd: wasi.fd_t,
|
||||
prefix: string,
|
||||
}
|
||||
preopens: []Preopen
|
||||
|
||||
@(init)
|
||||
init_std_files :: proc() {
|
||||
new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File {
|
||||
impl.file.impl = impl
|
||||
impl.allocator = runtime.nil_allocator()
|
||||
impl.fd = fd
|
||||
impl.name = string(name)
|
||||
impl.file.stream = {
|
||||
data = impl,
|
||||
procedure = _file_stream_proc,
|
||||
}
|
||||
impl.file.fstat = _fstat
|
||||
return &impl.file
|
||||
}
|
||||
|
||||
@(static) files: [3]File_Impl
|
||||
stdin = new_std(&files[0], 0, "/dev/stdin")
|
||||
stdout = new_std(&files[1], 1, "/dev/stdout")
|
||||
stderr = new_std(&files[2], 2, "/dev/stderr")
|
||||
}
|
||||
|
||||
@(init)
|
||||
init_preopens :: proc() {
|
||||
strip_prefixes :: proc(path: string) -> string {
|
||||
path := path
|
||||
loop: for len(path) > 0 {
|
||||
switch {
|
||||
case path[0] == '/':
|
||||
path = path[1:]
|
||||
case len(path) > 2 && path[0] == '.' && path[1] == '/':
|
||||
path = path[2:]
|
||||
case len(path) == 1 && path[0] == '.':
|
||||
path = path[1:]
|
||||
case:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
n: int
|
||||
n_loop: for fd := wasi.fd_t(3); ; fd += 1 {
|
||||
_, err := wasi.fd_prestat_get(fd)
|
||||
#partial switch err {
|
||||
case .BADF: break n_loop
|
||||
case .SUCCESS: n += 1
|
||||
case:
|
||||
print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get")
|
||||
break n_loop
|
||||
}
|
||||
}
|
||||
|
||||
alloc_err: runtime.Allocator_Error
|
||||
preopens, alloc_err = make([]Preopen, n, file_allocator())
|
||||
if alloc_err != nil {
|
||||
print_error(stderr, alloc_err, "could not allocate memory for wasi preopens")
|
||||
return
|
||||
}
|
||||
|
||||
loop: for &preopen, i in preopens {
|
||||
fd := wasi.fd_t(3 + i)
|
||||
|
||||
desc, err := wasi.fd_prestat_get(fd)
|
||||
assert(err == .SUCCESS)
|
||||
|
||||
switch desc.tag {
|
||||
case .DIR:
|
||||
buf: []byte
|
||||
buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator())
|
||||
if alloc_err != nil {
|
||||
print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name")
|
||||
continue loop
|
||||
}
|
||||
|
||||
if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
|
||||
print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name")
|
||||
continue loop
|
||||
}
|
||||
|
||||
preopen.fd = fd
|
||||
preopen.prefix = strip_prefixes(string(buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
|
||||
@(require_results)
|
||||
prefix_matches :: proc(prefix, path: string) -> bool {
|
||||
// Empty is valid for any relative path.
|
||||
if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(path) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
|
||||
if path[:len(prefix)] != prefix {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only match on full components.
|
||||
i := len(prefix)
|
||||
for i > 0 && prefix[i-1] == '/' {
|
||||
i -= 1
|
||||
}
|
||||
return path[i] == '/'
|
||||
}
|
||||
|
||||
path := path
|
||||
if path == "" {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
for len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
match: Preopen
|
||||
#reverse for preopen in preopens {
|
||||
if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
|
||||
match = preopen
|
||||
}
|
||||
}
|
||||
|
||||
if match.fd == 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
relative := path[len(match.prefix):]
|
||||
for len(relative) > 0 && relative[0] == '/' {
|
||||
relative = relative[1:]
|
||||
}
|
||||
|
||||
if len(relative) == 0 {
|
||||
relative = "."
|
||||
}
|
||||
|
||||
return match.fd, relative, true
|
||||
}
|
||||
|
||||
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
return nil, .Invalid_Path
|
||||
}
|
||||
|
||||
oflags: wasi.oflags_t
|
||||
if .Create in flags { oflags += {.CREATE} }
|
||||
if .Excl in flags { oflags += {.EXCL} }
|
||||
if .Trunc in flags { oflags += {.TRUNC} }
|
||||
|
||||
fdflags: wasi.fdflags_t
|
||||
if .Append in flags { fdflags += {.APPEND} }
|
||||
if .Sync in flags { fdflags += {.SYNC} }
|
||||
|
||||
// NOTE: rights are adjusted to what this package's functions might want to call.
|
||||
rights: wasi.rights_t
|
||||
if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} }
|
||||
if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} }
|
||||
|
||||
fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
|
||||
if fderr != nil {
|
||||
err = _get_platform_error(fderr)
|
||||
return
|
||||
}
|
||||
|
||||
return _new_file(uintptr(fd), name, file_allocator())
|
||||
}
|
||||
|
||||
_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) {
|
||||
if name == "" {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
impl := new(File_Impl, allocator) or_return
|
||||
defer if err != nil { free(impl, allocator) }
|
||||
|
||||
impl.allocator = allocator
|
||||
// NOTE: wasi doesn't really do full paths afact.
|
||||
impl.name = clone_string(name, allocator) or_return
|
||||
impl.fd = wasi.fd_t(handle)
|
||||
impl.file.impl = impl
|
||||
impl.file.stream = {
|
||||
data = impl,
|
||||
procedure = _file_stream_proc,
|
||||
}
|
||||
impl.file.fstat = _fstat
|
||||
|
||||
return &impl.file, nil
|
||||
}
|
||||
|
||||
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
|
||||
if f == nil || f.impl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dir_fd, relative, ok := match_preopen(name(f))
|
||||
if !ok {
|
||||
return nil, .Invalid_Path
|
||||
}
|
||||
|
||||
fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, {}, {}, {}, {})
|
||||
if fderr != nil {
|
||||
err = _get_platform_error(fderr)
|
||||
return
|
||||
}
|
||||
defer if err != nil { wasi.fd_close(fd) }
|
||||
|
||||
fderr = wasi.fd_renumber((^File_Impl)(f.impl).fd, fd)
|
||||
if fderr != nil {
|
||||
err = _get_platform_error(fderr)
|
||||
return
|
||||
}
|
||||
|
||||
return _new_file(uintptr(fd), name(f), file_allocator())
|
||||
}
|
||||
|
||||
_close :: proc(f: ^File_Impl) -> (err: Error) {
|
||||
if errno := wasi.fd_close(f.fd); errno != nil {
|
||||
err = _get_platform_error(errno)
|
||||
}
|
||||
|
||||
delete(f.name, f.allocator)
|
||||
free(f, f.allocator)
|
||||
return
|
||||
}
|
||||
|
||||
_fd :: proc(f: ^File) -> uintptr {
|
||||
return uintptr(__fd(f))
|
||||
}
|
||||
|
||||
__fd :: proc(f: ^File) -> wasi.fd_t {
|
||||
if f != nil && f.impl != nil {
|
||||
return (^File_Impl)(f.impl).fd
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
_name :: proc(f: ^File) -> string {
|
||||
if f != nil && f.impl != nil {
|
||||
return (^File_Impl)(f.impl).name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
_sync :: proc(f: ^File) -> Error {
|
||||
return _get_platform_error(wasi.fd_sync(__fd(f)))
|
||||
}
|
||||
|
||||
_truncate :: proc(f: ^File, size: i64) -> Error {
|
||||
return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size)))
|
||||
}
|
||||
|
||||
_remove :: proc(name: string) -> Error {
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
err := wasi.path_remove_directory(dir_fd, relative)
|
||||
if err == .NOTDIR {
|
||||
err = wasi.path_unlink_file(dir_fd, relative)
|
||||
}
|
||||
|
||||
return _get_platform_error(err)
|
||||
}
|
||||
|
||||
_rename :: proc(old_path, new_path: string) -> Error {
|
||||
src_dir_fd, src_relative, src_ok := match_preopen(old_path)
|
||||
if !src_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
new_dir_fd, new_relative, new_ok := match_preopen(new_path)
|
||||
if !new_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative))
|
||||
}
|
||||
|
||||
_link :: proc(old_name, new_name: string) -> Error {
|
||||
src_dir_fd, src_relative, src_ok := match_preopen(old_name)
|
||||
if !src_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
new_dir_fd, new_relative, new_ok := match_preopen(new_name)
|
||||
if !new_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative))
|
||||
}
|
||||
|
||||
_symlink :: proc(old_name, new_name: string) -> Error {
|
||||
src_dir_fd, src_relative, src_ok := match_preopen(old_name)
|
||||
if !src_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
new_dir_fd, new_relative, new_ok := match_preopen(new_name)
|
||||
if !new_ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
if src_dir_fd != new_dir_fd {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative))
|
||||
}
|
||||
|
||||
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
return "", .Invalid_Path
|
||||
}
|
||||
|
||||
n, _err := wasi.path_readlink(dir_fd, relative, nil)
|
||||
if _err != nil {
|
||||
err = _get_platform_error(_err)
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, n, allocator) or_return
|
||||
|
||||
_, _err = wasi.path_readlink(dir_fd, relative, buf)
|
||||
s = string(buf)
|
||||
err = _get_platform_error(_err)
|
||||
return
|
||||
}
|
||||
|
||||
_chdir :: proc(name: string) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_fchdir :: proc(f: ^File) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_fchmod :: proc(f: ^File, mode: int) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_chmod :: proc(name: string, mode: int) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_fchown :: proc(f: ^File, uid, gid: int) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_chown :: proc(name: string, uid, gid: int) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_lchown :: proc(name: string, uid, gid: int) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
_atime := wasi.timestamp_t(atime._nsec)
|
||||
_mtime := wasi.timestamp_t(mtime._nsec)
|
||||
|
||||
return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM}))
|
||||
}
|
||||
|
||||
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
|
||||
_atime := wasi.timestamp_t(atime._nsec)
|
||||
_mtime := wasi.timestamp_t(mtime._nsec)
|
||||
|
||||
return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM}))
|
||||
}
|
||||
|
||||
_exists :: proc(path: string) -> bool {
|
||||
dir_fd, relative, ok := match_preopen(path)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
|
||||
f := (^File_Impl)(stream_data)
|
||||
fd := f.fd
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
if len(p) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
to_read := min(len(p), MAX_RW)
|
||||
_n, _err := wasi.fd_read(fd, {p[:to_read]})
|
||||
n = i64(_n)
|
||||
|
||||
if _err != nil {
|
||||
err = .Unknown
|
||||
} else if n == 0 {
|
||||
err = .EOF
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case .Read_At:
|
||||
if len(p) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
err = .Invalid_Offset
|
||||
return
|
||||
}
|
||||
|
||||
to_read := min(len(p), MAX_RW)
|
||||
_n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset))
|
||||
n = i64(_n)
|
||||
|
||||
if _err != nil {
|
||||
err = .Unknown
|
||||
} else if n == 0 {
|
||||
err = .EOF
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case .Write:
|
||||
p := p
|
||||
for len(p) > 0 {
|
||||
to_write := min(len(p), MAX_RW)
|
||||
_n, _err := wasi.fd_write(fd, {p[:to_write]})
|
||||
if _err != nil {
|
||||
err = .Unknown
|
||||
return
|
||||
}
|
||||
p = p[_n:]
|
||||
n += i64(_n)
|
||||
}
|
||||
return
|
||||
|
||||
case .Write_At:
|
||||
p := p
|
||||
offset := offset
|
||||
|
||||
if offset < 0 {
|
||||
err = .Invalid_Offset
|
||||
return
|
||||
}
|
||||
|
||||
for len(p) > 0 {
|
||||
to_write := min(len(p), MAX_RW)
|
||||
_n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset))
|
||||
if _err != nil {
|
||||
err = .Unknown
|
||||
return
|
||||
}
|
||||
|
||||
p = p[_n:]
|
||||
n += i64(_n)
|
||||
offset += i64(_n)
|
||||
}
|
||||
return
|
||||
|
||||
case .Seek:
|
||||
#assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start))
|
||||
#assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current))
|
||||
#assert(int(wasi.whence_t.END) == int(io.Seek_From.End))
|
||||
|
||||
switch whence {
|
||||
case .Start, .Current, .End:
|
||||
break
|
||||
case:
|
||||
err = .Invalid_Whence
|
||||
return
|
||||
}
|
||||
|
||||
_n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence))
|
||||
#partial switch _err {
|
||||
case .INVAL:
|
||||
err = .Invalid_Offset
|
||||
case:
|
||||
err = .Unknown
|
||||
case .SUCCESS:
|
||||
n = i64(_n)
|
||||
}
|
||||
return
|
||||
|
||||
case .Size:
|
||||
stat, _err := wasi.fd_filestat_get(fd)
|
||||
if _err != nil {
|
||||
err = .Unknown
|
||||
return
|
||||
}
|
||||
|
||||
n = i64(stat.size)
|
||||
return
|
||||
|
||||
case .Flush:
|
||||
ferr := _sync(&f.file)
|
||||
err = error_to_io_error(ferr)
|
||||
return
|
||||
|
||||
case .Close, .Destroy:
|
||||
ferr := _close(f)
|
||||
err = error_to_io_error(ferr)
|
||||
return
|
||||
|
||||
case .Query:
|
||||
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
|
||||
|
||||
case:
|
||||
return 0, .Empty
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
|
||||
err = .Not_Exist
|
||||
return
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
path := _fix_long_path(name, temp_allocator()) or_return
|
||||
path := _fix_long_path(name, temp_allocator) or_return
|
||||
access: u32
|
||||
switch flags & {.Read, .Write} {
|
||||
case {.Read}: access = win32.FILE_GENERIC_READ
|
||||
@@ -210,6 +210,29 @@ _new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) ->
|
||||
return
|
||||
}
|
||||
|
||||
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
|
||||
if f == nil || f.impl == nil {
|
||||
return
|
||||
}
|
||||
|
||||
clonefd: win32.HANDLE
|
||||
process := win32.GetCurrentProcess()
|
||||
if !win32.DuplicateHandle(
|
||||
process,
|
||||
win32.HANDLE(_fd(f)),
|
||||
process,
|
||||
&clonefd,
|
||||
0,
|
||||
false,
|
||||
win32.DUPLICATE_SAME_ACCESS,
|
||||
) {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
defer if err != nil { win32.CloseHandle(clonefd) }
|
||||
|
||||
return _new_file(uintptr(clonefd), name(f), file_allocator())
|
||||
}
|
||||
|
||||
_fd :: proc(f: ^File) -> uintptr {
|
||||
if f == nil || f.impl == nil {
|
||||
@@ -483,10 +506,16 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error)
|
||||
|
||||
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
|
||||
length: win32.LARGE_INTEGER
|
||||
if f.kind == .Pipe {
|
||||
return 0, .No_Size
|
||||
}
|
||||
handle := _handle(&f.file)
|
||||
if f.kind == .Pipe {
|
||||
bytes_available: u32
|
||||
if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
|
||||
return i64(bytes_available), nil
|
||||
} else {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
}
|
||||
if !win32.GetFileSizeEx(handle, &length) {
|
||||
err = _get_platform_error()
|
||||
}
|
||||
@@ -528,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
|
||||
}
|
||||
|
||||
_remove :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
p := _fix_long_path(name, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
p := _fix_long_path(name, temp_allocator) or_return
|
||||
err, err1: Error
|
||||
if !win32.DeleteFileW(p) {
|
||||
err = _get_platform_error()
|
||||
@@ -566,9 +595,9 @@ _remove :: proc(name: string) -> Error {
|
||||
}
|
||||
|
||||
_rename :: proc(old_path, new_path: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
from := _fix_long_path(old_path, temp_allocator()) or_return
|
||||
to := _fix_long_path(new_path, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
from := _fix_long_path(old_path, temp_allocator) or_return
|
||||
to := _fix_long_path(new_path, temp_allocator) or_return
|
||||
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
|
||||
return nil
|
||||
}
|
||||
@@ -577,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
|
||||
}
|
||||
|
||||
_link :: proc(old_name, new_name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
o := _fix_long_path(old_name, temp_allocator()) or_return
|
||||
n := _fix_long_path(new_name, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
o := _fix_long_path(old_name, temp_allocator) or_return
|
||||
n := _fix_long_path(new_name, temp_allocator) or_return
|
||||
if win32.CreateHardLinkW(n, o, nil) {
|
||||
return nil
|
||||
}
|
||||
@@ -640,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
|
||||
return "", _get_platform_error()
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
buf := make([]u16, n+1, temp_allocator())
|
||||
buf := make([]u16, n+1, temp_allocator)
|
||||
n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
|
||||
if n == 0 {
|
||||
return "", _get_platform_error()
|
||||
@@ -666,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
|
||||
@thread_local
|
||||
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
p := _fix_long_path(name, temp_allocator()) or_return
|
||||
p := _fix_long_path(name, temp_allocator) or_return
|
||||
handle := _open_sym_link(p) or_return
|
||||
defer win32.CloseHandle(handle)
|
||||
|
||||
@@ -743,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
|
||||
}
|
||||
|
||||
_chdir :: proc(name: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
p := _fix_long_path(name, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
p := _fix_long_path(name, temp_allocator) or_return
|
||||
if !win32.SetCurrentDirectoryW(p) {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -771,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
|
||||
defer close(f)
|
||||
return _fchtimes(f, atime, mtime)
|
||||
}
|
||||
|
||||
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
|
||||
if f == nil || f.impl == nil {
|
||||
return nil
|
||||
}
|
||||
d: win32.BY_HANDLE_FILE_INFORMATION
|
||||
if !win32.GetFileInformationByHandle(_handle(f), &d) {
|
||||
return _get_platform_error()
|
||||
}
|
||||
|
||||
to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
|
||||
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
|
||||
return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
|
||||
}
|
||||
|
||||
atime, mtime := atime, mtime
|
||||
if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
|
||||
@@ -791,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
|
||||
}
|
||||
|
||||
info: win32.FILE_BASIC_INFO
|
||||
info.LastAccessTime = to_windows_time(atime)
|
||||
info.LastWriteTime = to_windows_time(mtime)
|
||||
if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
|
||||
info.LastAccessTime = time_as_filetime(atime)
|
||||
info.LastWriteTime = time_as_filetime(mtime)
|
||||
if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_exists :: proc(path: string) -> bool {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
wpath, _ := _fix_long_path(path, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
wpath, _ := _fix_long_path(path, temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
return attribs != win32.INVALID_FILE_ATTRIBUTES
|
||||
}
|
||||
|
||||
+2
-722
@@ -1,726 +1,6 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "core:sys/linux"
|
||||
import "core:sync"
|
||||
import "core:mem"
|
||||
|
||||
// NOTEs
|
||||
//
|
||||
// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region
|
||||
// consists of a Region_Header and the memory that will be divided into allocations to
|
||||
// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes.
|
||||
// Allocation_Headers are used to navigate the memory in the region. The "next" member of
|
||||
// the Allocation_Header points to the next header, and the space between the headers
|
||||
// can be used to send to the user. This space between is referred to as "blocks" in the
|
||||
// code. The indexes in the header refer to these blocks instead of bytes. This allows us
|
||||
// to index all the memory in the region with a u16.
|
||||
//
|
||||
// When an allocation request is made, it will use the first free block that can contain
|
||||
// the entire block. If there is an excess number of blocks (as specified by the constant
|
||||
// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list.
|
||||
//
|
||||
// To keep the implementation simple, there can never exist 2 free blocks adjacent to each
|
||||
// other. Any freeing will result in attempting to merge the blocks before and after the
|
||||
// newly free'd blocks.
|
||||
//
|
||||
// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation
|
||||
// getting its own individual mmap. Individual mmaps will still get an Allocation_Header
|
||||
// that contains the size with the last bit set to 1 to indicate it is indeed a direct
|
||||
// mmap allocation.
|
||||
|
||||
// Why not brk?
|
||||
// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation
|
||||
// does *not* utilize the brk system call to avoid possible conflicts with foreign C
|
||||
// code. Just because we aren't directly using libc, there is nothing stopping the user
|
||||
// from doing it.
|
||||
|
||||
// What's with all the #no_bounds_check?
|
||||
// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere
|
||||
// until that region is written to by *you*. So, when a new region is created, we call mmap
|
||||
// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the
|
||||
// region itself is never formally initialized by the compiler as this would result in writing
|
||||
// zeros to memory that we can already assume are 0. This would also have the effect of
|
||||
// actually commiting this data to memory whether it gets used or not.
|
||||
|
||||
|
||||
//
|
||||
// Some variables to play with
|
||||
//
|
||||
|
||||
// Minimum blocks used for any one allocation
|
||||
MINIMUM_BLOCK_COUNT :: 2
|
||||
|
||||
// Number of extra blocks beyond the requested amount where we would segment.
|
||||
// E.g. (blocks) |H0123456| 7 available
|
||||
// |H01H0123| Ask for 2, now 4 available
|
||||
BLOCK_SEGMENT_THRESHOLD :: 4
|
||||
|
||||
// Anything above this threshold will get its own memory map. Since regions
|
||||
// are indexed by 16 bit integers, this value should not surpass max(u16) * 6
|
||||
DIRECT_MMAP_THRESHOLD_USER :: int(max(u16))
|
||||
|
||||
// The point at which we convert direct mmap to region. This should be a decent
|
||||
// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions.
|
||||
MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4
|
||||
|
||||
// free_list is dynamic and is initialized in the begining of the region memory
|
||||
// when the region is initialized. Once resized, it can be moved anywhere.
|
||||
FREE_LIST_DEFAULT_CAP :: 32
|
||||
|
||||
|
||||
//
|
||||
// Other constants that should not be touched
|
||||
//
|
||||
|
||||
// This universally seems to be 4096 outside of uncommon archs.
|
||||
PAGE_SIZE :: 4096
|
||||
|
||||
// just rounding up to nearest PAGE_SIZE
|
||||
DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE
|
||||
|
||||
// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well
|
||||
// as end right on a page boundary as to not waste space.
|
||||
SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE)
|
||||
|
||||
// size of user memory blocks
|
||||
BLOCK_SIZE :: size_of(Allocation_Header)
|
||||
|
||||
// number of allocation sections (call them blocks) of the region used for allocations
|
||||
BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE)
|
||||
|
||||
// minimum amount of space that can used by any individual allocation (includes header)
|
||||
MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE
|
||||
|
||||
// This is used as a boolean value for Region_Header.local_addr.
|
||||
CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
|
||||
|
||||
FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
|
||||
|
||||
MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE}
|
||||
MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE}
|
||||
|
||||
@thread_local _local_region: ^Region
|
||||
global_regions: ^Region
|
||||
|
||||
|
||||
// There is no way of correctly setting the last bit of free_idx or
|
||||
// the last bit of requested, so we can safely use it as a flag to
|
||||
// determine if we are interacting with a direct mmap.
|
||||
REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF
|
||||
IS_DIRECT_MMAP :: 0x8000000000000000
|
||||
|
||||
// Special free_idx value that does not index the free_list.
|
||||
NOT_FREE :: 0x7FFF
|
||||
Allocation_Header :: struct #raw_union {
|
||||
using _: struct {
|
||||
// Block indicies
|
||||
idx: u16,
|
||||
prev: u16,
|
||||
next: u16,
|
||||
free_idx: u16,
|
||||
},
|
||||
requested: u64,
|
||||
}
|
||||
|
||||
Region_Header :: struct #align(16) {
|
||||
next_region: ^Region, // points to next region in global_heap (linked list)
|
||||
local_addr: ^^Region, // tracks region ownership via address of _local_region
|
||||
reset_addr: ^^Region, // tracks old local addr for reset
|
||||
free_list: []u16,
|
||||
free_list_len: u16,
|
||||
free_blocks: u16, // number of free blocks in region (includes headers)
|
||||
last_used: u16, // farthest back block that has been used (need zeroing?)
|
||||
_reserved: u16,
|
||||
}
|
||||
|
||||
Region :: struct {
|
||||
hdr: Region_Header,
|
||||
memory: [BLOCKS_PER_REGION]Allocation_Header,
|
||||
}
|
||||
|
||||
_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) {
|
||||
//
|
||||
// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
|
||||
// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
|
||||
// padding. We also store the original pointer returned by heap_alloc right before
|
||||
// the pointer we return to the user.
|
||||
//
|
||||
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
|
||||
a := max(alignment, align_of(rawptr))
|
||||
space := size + a - 1
|
||||
|
||||
allocated_mem: rawptr
|
||||
if old_ptr != nil {
|
||||
original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^
|
||||
allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
|
||||
} else {
|
||||
allocated_mem = heap_alloc(space+size_of(rawptr))
|
||||
}
|
||||
aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
|
||||
|
||||
ptr := uintptr(aligned_mem)
|
||||
aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
|
||||
diff := int(aligned_ptr - ptr)
|
||||
if (size + diff) > space || allocated_mem == nil {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
aligned_mem = rawptr(aligned_ptr)
|
||||
mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem
|
||||
|
||||
return mem.byte_slice(aligned_mem, size), nil
|
||||
}
|
||||
|
||||
aligned_free :: proc(p: rawptr) {
|
||||
if p != nil {
|
||||
heap_free(mem.ptr_offset((^rawptr)(p), -1)^)
|
||||
}
|
||||
}
|
||||
|
||||
aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) {
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return aligned_alloc(new_size, new_alignment, p)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return aligned_alloc(size, alignment)
|
||||
|
||||
case .Free:
|
||||
aligned_free(old_memory)
|
||||
|
||||
case .Free_All:
|
||||
return nil, .Mode_Not_Implemented
|
||||
|
||||
case .Resize, .Resize_Non_Zeroed:
|
||||
if old_memory == nil {
|
||||
return aligned_alloc(size, alignment)
|
||||
}
|
||||
return aligned_resize(old_memory, old_size, size, alignment)
|
||||
|
||||
case .Query_Features:
|
||||
set := (^mem.Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
case .Query_Info:
|
||||
return nil, .Mode_Not_Implemented
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
heap_alloc :: proc(size: int) -> rawptr {
|
||||
if size >= DIRECT_MMAP_THRESHOLD {
|
||||
return _direct_mmap_alloc(size)
|
||||
}
|
||||
|
||||
// atomically check if the local region has been stolen
|
||||
if _local_region != nil {
|
||||
res := sync.atomic_compare_exchange_strong_explicit(
|
||||
&_local_region.hdr.local_addr,
|
||||
&_local_region,
|
||||
CURRENTLY_ACTIVE,
|
||||
.Acquire,
|
||||
.Relaxed,
|
||||
)
|
||||
if res != &_local_region {
|
||||
// At this point, the region has been stolen and res contains the unexpected value
|
||||
expected := res
|
||||
if res != CURRENTLY_ACTIVE {
|
||||
expected = res
|
||||
res = sync.atomic_compare_exchange_strong_explicit(
|
||||
&_local_region.hdr.local_addr,
|
||||
expected,
|
||||
CURRENTLY_ACTIVE,
|
||||
.Acquire,
|
||||
.Relaxed,
|
||||
)
|
||||
}
|
||||
if res != expected {
|
||||
_local_region = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size := size
|
||||
size = _round_up_to_nearest(size, BLOCK_SIZE)
|
||||
blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE))
|
||||
|
||||
// retrieve a region if new thread or stolen
|
||||
if _local_region == nil {
|
||||
_local_region, _ = _region_retrieve_with_space(blocks_needed)
|
||||
if _local_region == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
|
||||
// At this point we have a usable region. Let's find the user some memory
|
||||
idx: u16
|
||||
local_region_idx := _region_get_local_idx()
|
||||
back_idx := -1
|
||||
infinite: for {
|
||||
for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 {
|
||||
idx = _local_region.hdr.free_list[i]
|
||||
#no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed {
|
||||
break infinite
|
||||
}
|
||||
}
|
||||
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
_local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx)
|
||||
}
|
||||
user_ptr, used := _region_get_block(_local_region, idx, blocks_needed)
|
||||
|
||||
sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release)
|
||||
|
||||
// If this memory was ever used before, it now needs to be zero'd.
|
||||
if idx < _local_region.hdr.last_used {
|
||||
mem.zero(user_ptr, int(used) * BLOCK_SIZE)
|
||||
} else {
|
||||
_local_region.hdr.last_used = idx + used
|
||||
}
|
||||
|
||||
return user_ptr
|
||||
}
|
||||
|
||||
heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check {
|
||||
alloc := _get_allocation_header(old_memory)
|
||||
if alloc.requested & IS_DIRECT_MMAP > 0 {
|
||||
return _direct_mmap_resize(alloc, new_size)
|
||||
}
|
||||
|
||||
if new_size > DIRECT_MMAP_THRESHOLD {
|
||||
return _direct_mmap_from_region(alloc, new_size)
|
||||
}
|
||||
|
||||
return _region_resize(alloc, new_size)
|
||||
}
|
||||
|
||||
heap_free :: proc(memory: rawptr) {
|
||||
alloc := _get_allocation_header(memory)
|
||||
if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP {
|
||||
_direct_mmap_free(alloc)
|
||||
return
|
||||
}
|
||||
|
||||
assert(alloc.free_idx == NOT_FREE)
|
||||
|
||||
_region_find_and_assign_local(alloc)
|
||||
_region_local_free(alloc)
|
||||
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
}
|
||||
|
||||
//
|
||||
// Regions
|
||||
//
|
||||
_new_region :: proc() -> ^Region #no_bounds_check {
|
||||
ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
|
||||
if errno != .NONE {
|
||||
return nil
|
||||
}
|
||||
new_region := (^Region)(ptr)
|
||||
|
||||
new_region.hdr.local_addr = CURRENTLY_ACTIVE
|
||||
new_region.hdr.reset_addr = &_local_region
|
||||
|
||||
free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK)
|
||||
_region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK)
|
||||
|
||||
// + 2 to account for free_list's allocation header
|
||||
first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2
|
||||
|
||||
// first allocation header (this is a free list)
|
||||
new_region.memory[0].next = u16(first_user_block)
|
||||
new_region.memory[0].free_idx = NOT_FREE
|
||||
new_region.memory[first_user_block].idx = u16(first_user_block)
|
||||
new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1
|
||||
|
||||
// add the first user block to the free list
|
||||
new_region.hdr.free_list[0] = u16(first_user_block)
|
||||
new_region.hdr.free_list_len = 1
|
||||
new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1
|
||||
|
||||
for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region);
|
||||
r != nil;
|
||||
r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {}
|
||||
|
||||
return new_region
|
||||
}
|
||||
|
||||
_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check {
|
||||
assert(alloc.free_idx == NOT_FREE)
|
||||
|
||||
old_memory := mem.ptr_offset(alloc, 1)
|
||||
|
||||
old_block_count := _get_block_count(alloc^)
|
||||
new_block_count := u16(
|
||||
max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE),
|
||||
)
|
||||
if new_block_count < old_block_count {
|
||||
if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT {
|
||||
_region_find_and_assign_local(alloc)
|
||||
_region_segment(_local_region, alloc, new_block_count, alloc.free_idx)
|
||||
new_block_count = _get_block_count(alloc^)
|
||||
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
}
|
||||
// need to zero anything within the new block that that lies beyond new_size
|
||||
extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size
|
||||
extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE)
|
||||
mem.zero(extra_bytes_ptr, extra_bytes)
|
||||
return old_memory
|
||||
}
|
||||
|
||||
if !alloc_is_free_list {
|
||||
_region_find_and_assign_local(alloc)
|
||||
}
|
||||
defer if !alloc_is_free_list {
|
||||
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
}
|
||||
|
||||
// First, let's see if we can grow in place.
|
||||
if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE {
|
||||
next_alloc := _local_region.memory[alloc.next]
|
||||
total_available := old_block_count + _get_block_count(next_alloc) + 1
|
||||
if total_available >= new_block_count {
|
||||
alloc.next = next_alloc.next
|
||||
_local_region.memory[alloc.next].prev = alloc.idx
|
||||
if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD {
|
||||
_region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx)
|
||||
} else {
|
||||
_region_free_list_remove(_local_region, next_alloc.free_idx)
|
||||
}
|
||||
mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE)
|
||||
_local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used)
|
||||
_local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count)
|
||||
if alloc_is_free_list {
|
||||
_region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^))
|
||||
}
|
||||
return old_memory
|
||||
}
|
||||
}
|
||||
|
||||
// If we made it this far, we need to resize, copy, zero and free.
|
||||
region_iter := _local_region
|
||||
local_region_idx := _region_get_local_idx()
|
||||
back_idx := -1
|
||||
idx: u16
|
||||
infinite: for {
|
||||
for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 {
|
||||
idx = region_iter.hdr.free_list[i]
|
||||
if _get_block_count(region_iter.memory[idx]) >= new_block_count {
|
||||
break infinite
|
||||
}
|
||||
}
|
||||
if region_iter != _local_region {
|
||||
sync.atomic_store_explicit(
|
||||
®ion_iter.hdr.local_addr,
|
||||
region_iter.hdr.reset_addr,
|
||||
.Release,
|
||||
)
|
||||
}
|
||||
region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx)
|
||||
}
|
||||
if region_iter != _local_region {
|
||||
sync.atomic_store_explicit(
|
||||
®ion_iter.hdr.local_addr,
|
||||
region_iter.hdr.reset_addr,
|
||||
.Release,
|
||||
)
|
||||
}
|
||||
|
||||
// copy from old memory
|
||||
new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count)
|
||||
mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE))
|
||||
|
||||
// zero any new memory
|
||||
addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count)
|
||||
new_blocks := used_blocks - old_block_count
|
||||
mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE)
|
||||
|
||||
region_iter.hdr.free_blocks -= (used_blocks + 1)
|
||||
|
||||
// Set free_list before freeing.
|
||||
if alloc_is_free_list {
|
||||
_region_assign_free_list(_local_region, new_memory, used_blocks)
|
||||
}
|
||||
|
||||
// free old memory
|
||||
_region_local_free(alloc)
|
||||
return new_memory
|
||||
}
|
||||
|
||||
_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check {
|
||||
alloc := alloc
|
||||
add_to_free_list := true
|
||||
|
||||
idx := sync.atomic_load(&alloc.idx)
|
||||
prev := sync.atomic_load(&alloc.prev)
|
||||
next := sync.atomic_load(&alloc.next)
|
||||
block_count := next - idx - 1
|
||||
free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1
|
||||
sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release)
|
||||
|
||||
// try to merge with prev
|
||||
if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE {
|
||||
sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release)
|
||||
_local_region.memory[next].prev = prev
|
||||
alloc = &_local_region.memory[prev]
|
||||
add_to_free_list = false
|
||||
}
|
||||
|
||||
// try to merge with next
|
||||
if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE {
|
||||
old_next := next
|
||||
sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release)
|
||||
|
||||
sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release)
|
||||
|
||||
if add_to_free_list {
|
||||
sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release)
|
||||
sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release)
|
||||
} else {
|
||||
// NOTE: We have aleady merged with prev, and now merged with next.
|
||||
// Now, we are actually going to remove from the free_list.
|
||||
_region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx)
|
||||
}
|
||||
add_to_free_list = false
|
||||
}
|
||||
|
||||
// This is the only place where anything is appended to the free list.
|
||||
if add_to_free_list {
|
||||
fl := _local_region.hdr.free_list
|
||||
fl_len := sync.atomic_load(&_local_region.hdr.free_list_len)
|
||||
sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release)
|
||||
fl[alloc.free_idx] = idx
|
||||
sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release)
|
||||
if int(fl_len + 1) == len(fl) {
|
||||
free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list))
|
||||
_region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) {
|
||||
raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list
|
||||
raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK
|
||||
raw_free_list.data = memory
|
||||
region.hdr.free_list = transmute([]u16)(raw_free_list)
|
||||
}
|
||||
|
||||
_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) {
|
||||
r: ^Region
|
||||
idx: int
|
||||
for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
|
||||
if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks {
|
||||
idx += 1
|
||||
continue
|
||||
}
|
||||
idx += 1
|
||||
local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr)
|
||||
if local_addr != CURRENTLY_ACTIVE {
|
||||
res := sync.atomic_compare_exchange_strong_explicit(
|
||||
&r.hdr.local_addr,
|
||||
local_addr,
|
||||
CURRENTLY_ACTIVE,
|
||||
.Acquire,
|
||||
.Relaxed,
|
||||
)
|
||||
if res == local_addr {
|
||||
r.hdr.reset_addr = local_addr
|
||||
return r, idx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _new_region(), idx
|
||||
}
|
||||
|
||||
_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region {
|
||||
r: ^Region
|
||||
for r = global_regions; r != nil; r = r.hdr.next_region {
|
||||
if _region_contains_mem(r, addr) {
|
||||
return r
|
||||
}
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check {
|
||||
alloc := ®ion.memory[idx]
|
||||
|
||||
assert(alloc.free_idx != NOT_FREE)
|
||||
assert(alloc.next > 0)
|
||||
|
||||
block_count := _get_block_count(alloc^)
|
||||
if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD {
|
||||
_region_segment(region, alloc, blocks_needed, alloc.free_idx)
|
||||
} else {
|
||||
_region_free_list_remove(region, alloc.free_idx)
|
||||
}
|
||||
|
||||
alloc.free_idx = NOT_FREE
|
||||
return mem.ptr_offset(alloc, 1), _get_block_count(alloc^)
|
||||
}
|
||||
|
||||
_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check {
|
||||
old_next := alloc.next
|
||||
alloc.next = alloc.idx + blocks + 1
|
||||
region.memory[old_next].prev = alloc.next
|
||||
|
||||
// Initialize alloc.next allocation header here.
|
||||
region.memory[alloc.next].prev = alloc.idx
|
||||
region.memory[alloc.next].next = old_next
|
||||
region.memory[alloc.next].idx = alloc.next
|
||||
region.memory[alloc.next].free_idx = new_free_idx
|
||||
|
||||
// Replace our original spot in the free_list with new segment.
|
||||
region.hdr.free_list[new_free_idx] = alloc.next
|
||||
}
|
||||
|
||||
_region_get_local_idx :: proc() -> int {
|
||||
idx: int
|
||||
for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region {
|
||||
if r == _local_region {
|
||||
return idx
|
||||
}
|
||||
idx += 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) {
|
||||
// Find the region that contains this memory
|
||||
if !_region_contains_mem(_local_region, alloc) {
|
||||
_local_region = _region_retrieve_from_addr(alloc)
|
||||
}
|
||||
|
||||
// At this point, _local_region is set correctly. Spin until acquire
|
||||
res := CURRENTLY_ACTIVE
|
||||
|
||||
for res == CURRENTLY_ACTIVE {
|
||||
res = sync.atomic_compare_exchange_strong_explicit(
|
||||
&_local_region.hdr.local_addr,
|
||||
&_local_region,
|
||||
CURRENTLY_ACTIVE,
|
||||
.Acquire,
|
||||
.Relaxed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
mem_int := uintptr(memory)
|
||||
return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1])
|
||||
}
|
||||
|
||||
_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check {
|
||||
// pop, swap and update allocation hdr
|
||||
if n := region.hdr.free_list_len - 1; free_idx != n {
|
||||
region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n])
|
||||
alloc_idx := region.hdr.free_list[free_idx]
|
||||
sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release)
|
||||
}
|
||||
region.hdr.free_list_len -= 1
|
||||
}
|
||||
|
||||
//
|
||||
// Direct mmap
|
||||
//
|
||||
_direct_mmap_alloc :: proc(size: int) -> rawptr {
|
||||
mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE)
|
||||
new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
|
||||
if errno != .NONE {
|
||||
return nil
|
||||
}
|
||||
|
||||
alloc := (^Allocation_Header)(uintptr(new_allocation))
|
||||
alloc.requested = u64(size) // NOTE: requested = requested size
|
||||
alloc.requested += IS_DIRECT_MMAP
|
||||
return rawptr(mem.ptr_offset(alloc, 1))
|
||||
}
|
||||
|
||||
_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
|
||||
old_requested := int(alloc.requested & REQUESTED_MASK)
|
||||
old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE)
|
||||
new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE)
|
||||
if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD {
|
||||
return _direct_mmap_to_region(alloc, new_size)
|
||||
} else if old_requested == new_size {
|
||||
return mem.ptr_offset(alloc, 1)
|
||||
}
|
||||
|
||||
new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE})
|
||||
if errno != .NONE {
|
||||
return nil
|
||||
}
|
||||
|
||||
new_header := (^Allocation_Header)(uintptr(new_allocation))
|
||||
new_header.requested = u64(new_size)
|
||||
new_header.requested += IS_DIRECT_MMAP
|
||||
|
||||
if new_mmap_size > old_mmap_size {
|
||||
// new section may not be pointer aligned, so cast to ^u8
|
||||
new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE)
|
||||
mem.zero(new_section, new_mmap_size - old_mmap_size)
|
||||
}
|
||||
return mem.ptr_offset(new_header, 1)
|
||||
|
||||
}
|
||||
|
||||
_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
|
||||
new_memory := _direct_mmap_alloc(new_size)
|
||||
if new_memory != nil {
|
||||
old_memory := mem.ptr_offset(alloc, 1)
|
||||
mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE)
|
||||
}
|
||||
_region_find_and_assign_local(alloc)
|
||||
_region_local_free(alloc)
|
||||
sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
|
||||
return new_memory
|
||||
}
|
||||
|
||||
_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
|
||||
new_memory := heap_alloc(new_size)
|
||||
if new_memory != nil {
|
||||
mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size)
|
||||
_direct_mmap_free(alloc)
|
||||
}
|
||||
return new_memory
|
||||
}
|
||||
|
||||
_direct_mmap_free :: proc(alloc: ^Allocation_Header) {
|
||||
requested := int(alloc.requested & REQUESTED_MASK)
|
||||
mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE)
|
||||
linux.munmap(alloc, uint(mmap_size))
|
||||
}
|
||||
|
||||
//
|
||||
// Util
|
||||
//
|
||||
|
||||
_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 {
|
||||
return alloc.next - alloc.idx - 1
|
||||
}
|
||||
|
||||
_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header {
|
||||
return mem.ptr_offset((^Allocation_Header)(raw_mem), -1)
|
||||
}
|
||||
|
||||
_round_up_to_nearest :: #force_inline proc(size, round: int) -> int {
|
||||
return (size-1) + round - (size-1) % round
|
||||
}
|
||||
import "base:runtime"
|
||||
|
||||
_heap_allocator_proc :: runtime.heap_allocator_proc
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_heap_allocator_proc :: runtime.wasm_allocator_proc
|
||||
@@ -3,6 +3,7 @@ package os2
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:math/rand"
|
||||
|
||||
|
||||
// Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix
|
||||
@@ -42,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
|
||||
return cstring(&buf[0]), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
|
||||
return clone_to_cstring(s, temp_allocator())
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
|
||||
s := string(b)
|
||||
@@ -84,45 +80,15 @@ concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: st
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(private="file")
|
||||
random_string_seed: [2]u64
|
||||
|
||||
@(init, private="file")
|
||||
init_random_string_seed :: proc() {
|
||||
seed := u64(intrinsics.read_cycle_counter())
|
||||
s := &random_string_seed
|
||||
s[0] = 0
|
||||
s[1] = (seed << 1) | 1
|
||||
_ = next_random(s)
|
||||
s[1] += seed
|
||||
_ = next_random(s)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
next_random :: proc(r: ^[2]u64) -> u64 {
|
||||
old_state := r[0]
|
||||
r[0] = old_state * 6364136223846793005 + (r[1]|1)
|
||||
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
|
||||
rot := (old_state >> 59)
|
||||
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
random_string :: proc(buf: []byte) -> string {
|
||||
@(static, rodata) digits := "0123456789"
|
||||
|
||||
u := next_random(&random_string_seed)
|
||||
|
||||
b :: 10
|
||||
i := len(buf)
|
||||
for u >= b {
|
||||
i -= 1
|
||||
buf[i] = digits[u % b]
|
||||
u /= b
|
||||
for i := 0; i < len(buf); i += 16 {
|
||||
n := rand.uint64()
|
||||
end := min(i + 16, len(buf))
|
||||
for j := i; j < end; j += 1 {
|
||||
buf[j] = '0' + u8(n) % 10
|
||||
n >>= 4
|
||||
}
|
||||
}
|
||||
i -= 1
|
||||
buf[i] = digits[u % b]
|
||||
return string(buf[i:])
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,18 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:strings"
|
||||
|
||||
Path_Separator :: _Path_Separator // OS-Specific
|
||||
Path_Separator_String :: _Path_Separator_String // OS-Specific
|
||||
Path_List_Separator :: _Path_List_Separator // OS-Specific
|
||||
|
||||
#assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.")
|
||||
|
||||
/*
|
||||
Return true if `c` is a character used to separate paths into directory and
|
||||
file hierarchies on the current system.
|
||||
*/
|
||||
@(require_results)
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return _is_path_separator(c)
|
||||
@@ -13,22 +21,42 @@ is_path_separator :: proc(c: byte) -> bool {
|
||||
|
||||
mkdir :: make_directory
|
||||
|
||||
/*
|
||||
Make a new directory.
|
||||
|
||||
If `path` is relative, it will be relative to the process's current working directory.
|
||||
*/
|
||||
make_directory :: proc(name: string, perm: int = 0o755) -> Error {
|
||||
return _mkdir(name, perm)
|
||||
}
|
||||
|
||||
mkdir_all :: make_directory_all
|
||||
|
||||
/*
|
||||
Make a new directory, creating new intervening directories when needed.
|
||||
|
||||
If `path` is relative, it will be relative to the process's current working directory.
|
||||
*/
|
||||
make_directory_all :: proc(path: string, perm: int = 0o755) -> Error {
|
||||
return _mkdir_all(path, perm)
|
||||
}
|
||||
|
||||
/*
|
||||
Delete `path` and all files and directories inside of `path` if it is a directory.
|
||||
|
||||
If `path` is relative, it will be relative to the process's current working directory.
|
||||
*/
|
||||
remove_all :: proc(path: string) -> Error {
|
||||
return _remove_all(path)
|
||||
}
|
||||
|
||||
getwd :: get_working_directory
|
||||
|
||||
/*
|
||||
Get the working directory of the current process.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
*/
|
||||
@(require_results)
|
||||
get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _get_working_directory(allocator)
|
||||
@@ -36,6 +64,399 @@ get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err
|
||||
|
||||
setwd :: set_working_directory
|
||||
|
||||
/*
|
||||
Change the working directory of the current process.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
*/
|
||||
set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
return _set_working_directory(dir)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the path for the currently running executable.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
*/
|
||||
@(require_results)
|
||||
get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
|
||||
return _get_executable_path(allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the directory for the currently running executable.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
*/
|
||||
@(require_results)
|
||||
get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
|
||||
path = _get_executable_path(allocator) or_return
|
||||
path, _ = split_path(path)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Compare two paths for exactness without normalization.
|
||||
|
||||
This procedure takes into account case-sensitivity on differing systems.
|
||||
*/
|
||||
@(require_results)
|
||||
are_paths_identical :: proc(a, b: string) -> (identical: bool) {
|
||||
return _are_paths_identical(a, b)
|
||||
}
|
||||
|
||||
/*
|
||||
Normalize a path.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
This will remove duplicate separators and unneeded references to the current or
|
||||
parent directory.
|
||||
*/
|
||||
@(require_results)
|
||||
clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) {
|
||||
if path == "" || path == "." {
|
||||
return strings.clone(".", allocator)
|
||||
}
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
// The extra byte is to simplify appending path elements by letting the
|
||||
// loop to end each with a separator. We'll trim the last one when we're done.
|
||||
buffer := make([]u8, len(path) + 1, temp_allocator) or_return
|
||||
|
||||
// This is the only point where Windows and POSIX differ, as Windows has
|
||||
// alphabet-based volumes for root paths.
|
||||
rooted, start := _clean_path_handle_start(path, buffer)
|
||||
|
||||
head, buffer_i := start, start
|
||||
for i, j := start, start; i <= len(path); i += 1 {
|
||||
if i == len(path) || _is_path_separator(path[i]) {
|
||||
elem := path[j:i]
|
||||
j = i + 1
|
||||
|
||||
switch elem {
|
||||
case "", ".":
|
||||
// Skip duplicate path separators and current directory references.
|
||||
case "..":
|
||||
if !rooted && buffer_i == head {
|
||||
// Only allow accessing further parent directories when the path is relative.
|
||||
buffer[buffer_i] = '.'
|
||||
buffer[buffer_i+1] = '.'
|
||||
buffer[buffer_i+2] = _Path_Separator
|
||||
buffer_i += 3
|
||||
head = buffer_i
|
||||
} else {
|
||||
// Roll back to the last separator or the head of the buffer.
|
||||
back_to := head
|
||||
// `buffer_i` will be equal to 1 + the last set byte, so
|
||||
// skipping two bytes avoids the final separator we just
|
||||
// added.
|
||||
for k := buffer_i-2; k >= head; k -= 1 {
|
||||
if _is_path_separator(buffer[k]) {
|
||||
back_to = k + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
buffer_i = back_to
|
||||
}
|
||||
case:
|
||||
// Copy the path element verbatim and add a separator.
|
||||
copy(buffer[buffer_i:], elem)
|
||||
buffer_i += len(elem)
|
||||
buffer[buffer_i] = _Path_Separator
|
||||
buffer_i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the final separator.
|
||||
// NOTE: No need to check if the last byte is a separator, as we always add it.
|
||||
if buffer_i > start {
|
||||
buffer_i -= 1
|
||||
}
|
||||
|
||||
if buffer_i == 0 {
|
||||
return strings.clone(".", allocator)
|
||||
}
|
||||
|
||||
compact := make([]u8, buffer_i, allocator) or_return
|
||||
copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here
|
||||
return string(compact), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Return true if `path` is an absolute path as opposed to a relative one.
|
||||
*/
|
||||
@(require_results)
|
||||
is_absolute_path :: proc(path: string) -> bool {
|
||||
return _is_absolute_path(path)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the absolute path to `path` with respect to the process's current directory.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
*/
|
||||
@(require_results)
|
||||
get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
|
||||
return _get_absolute_path(path, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the relative path needed to change directories from `base` to `target`.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`.
|
||||
|
||||
NOTE: This procedure expects both `base` and `target` to be normalized first,
|
||||
which can be done by calling `clean_path` on them if needed.
|
||||
|
||||
This procedure will return an `Invalid_Path` error if `base` begins with a
|
||||
reference to the parent directory (`".."`). Use `get_working_directory` with
|
||||
`join_path` to construct absolute paths for both arguments instead.
|
||||
*/
|
||||
@(require_results)
|
||||
get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
|
||||
if _are_paths_identical(base, target) {
|
||||
return strings.clone(".", allocator)
|
||||
}
|
||||
if base == "." {
|
||||
return strings.clone(target, allocator)
|
||||
}
|
||||
|
||||
// This is the first point where Windows and POSIX differ, as Windows has
|
||||
// alphabet-based volumes for root paths.
|
||||
if !_get_relative_path_handle_start(base, target) {
|
||||
return "", .Invalid_Path
|
||||
}
|
||||
if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) {
|
||||
// We could do the work for the user of getting absolute paths for both
|
||||
// arguments, but that could make something costly (repeatedly
|
||||
// normalizing paths) convenient, when it would be better for the user
|
||||
// to store already-finalized paths and operate on those instead.
|
||||
return "", .Invalid_Path
|
||||
}
|
||||
|
||||
// This is the other point where Windows and POSIX differ, as Windows is
|
||||
// case-insensitive.
|
||||
common := _get_common_path_len(base, target)
|
||||
|
||||
// Get the result of splitting `base` and `target` on _Path_Separator,
|
||||
// comparing them up to their most common elements, then count how many
|
||||
// unshared parts are in the split `base`.
|
||||
seps := 0
|
||||
size := 0
|
||||
if len(base)-common > 0 {
|
||||
seps = 1
|
||||
size = 2
|
||||
}
|
||||
// This range skips separators on the ends of the string.
|
||||
for i in common+1..<len(base)-1 {
|
||||
if _is_path_separator(base[i]) {
|
||||
seps += 1
|
||||
size += 3
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the rest of the size calculations.
|
||||
trailing := target[common:]
|
||||
if len(trailing) > 0 {
|
||||
// Account for leading separators on the target after cutting the common part.
|
||||
// (i.e. base == `/home`, target == `/home/a`)
|
||||
if _is_path_separator(trailing[0]) {
|
||||
trailing = trailing[1:]
|
||||
}
|
||||
size += len(trailing)
|
||||
if seps > 0 {
|
||||
size += 1
|
||||
}
|
||||
}
|
||||
if trailing == "." {
|
||||
trailing = ""
|
||||
size -= 2
|
||||
}
|
||||
|
||||
// Build the string.
|
||||
buf := make([]u8, size, allocator) or_return
|
||||
n := 0
|
||||
if seps > 0 {
|
||||
buf[0] = '.'
|
||||
buf[1] = '.'
|
||||
n = 2
|
||||
}
|
||||
for _ in 1..<seps {
|
||||
buf[n] = _Path_Separator
|
||||
buf[n+1] = '.'
|
||||
buf[n+2] = '.'
|
||||
n += 3
|
||||
}
|
||||
if len(trailing) > 0 {
|
||||
if seps > 0 {
|
||||
buf[n] = _Path_Separator
|
||||
n += 1
|
||||
}
|
||||
copy(buf[n:], trailing)
|
||||
}
|
||||
|
||||
path = string(buf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Split a path into a directory hierarchy and a filename.
|
||||
|
||||
For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`.
|
||||
*/
|
||||
@(require_results)
|
||||
split_path :: proc(path: string) -> (dir, filename: string) {
|
||||
return _split_path(path)
|
||||
}
|
||||
|
||||
/*
|
||||
Join all `elems` with the system's path separator and normalize the result.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`.
|
||||
*/
|
||||
@(require_results)
|
||||
join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
|
||||
for e, i in elems {
|
||||
if e != "" {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
|
||||
return clean_path(p, allocator)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
/*
|
||||
Split a filename from its extension.
|
||||
|
||||
This procedure splits on the last separator.
|
||||
|
||||
If the filename begins with a separator, such as `".readme.txt"`, the separator
|
||||
will be included in the filename, resulting in `".readme"` and `"txt"`.
|
||||
|
||||
For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`.
|
||||
*/
|
||||
@(require_results)
|
||||
split_filename :: proc(filename: string) -> (base, ext: string) {
|
||||
i := strings.last_index_byte(filename, '.')
|
||||
if i <= 0 {
|
||||
return filename, ""
|
||||
}
|
||||
return filename[:i], filename[i+1:]
|
||||
}
|
||||
|
||||
/*
|
||||
Split a filename from its extension.
|
||||
|
||||
This procedure splits on the first separator.
|
||||
|
||||
If the filename begins with a separator, such as `".readme.txt.gz"`, the separator
|
||||
will be included in the filename, resulting in `".readme"` and `"txt.gz"`.
|
||||
|
||||
For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`.
|
||||
*/
|
||||
@(require_results)
|
||||
split_filename_all :: proc(filename: string) -> (base, ext: string) {
|
||||
i := strings.index_byte(filename, '.')
|
||||
if i == 0 {
|
||||
j := strings.index_byte(filename[1:], '.')
|
||||
if j != -1 {
|
||||
j += 1
|
||||
}
|
||||
i = j
|
||||
}
|
||||
if i == -1 {
|
||||
return filename, ""
|
||||
}
|
||||
return filename[:i], filename[i+1:]
|
||||
}
|
||||
|
||||
/*
|
||||
Join `base` and `ext` with the system's filename extension separator.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`.
|
||||
*/
|
||||
@(require_results)
|
||||
join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
|
||||
if len(base) == 0 {
|
||||
return strings.clone(ext, allocator)
|
||||
} else if len(ext) == 0 {
|
||||
return strings.clone(base, allocator)
|
||||
}
|
||||
|
||||
buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return
|
||||
copy(buf, base)
|
||||
buf[len(base)] = '.'
|
||||
copy(buf[1+len(base):], ext)
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Split a string that is separated by a system-specific separator, typically used
|
||||
for environment variables specifying multiple directories.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
For example, there is the "PATH" environment variable on POSIX systems which
|
||||
this procedure can split into separate entries.
|
||||
*/
|
||||
@(require_results)
|
||||
split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: Error) {
|
||||
if path == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
start: int
|
||||
quote: bool
|
||||
|
||||
start, quote = 0, false
|
||||
count := 0
|
||||
|
||||
for i := 0; i < len(path); i += 1 {
|
||||
c := path[i]
|
||||
switch {
|
||||
case c == '"':
|
||||
quote = !quote
|
||||
case c == Path_List_Separator && !quote:
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
start, quote = 0, false
|
||||
list = make([]string, count + 1, allocator) or_return
|
||||
index := 0
|
||||
for i := 0; i < len(path); i += 1 {
|
||||
c := path[i]
|
||||
switch {
|
||||
case c == '"':
|
||||
quote = !quote
|
||||
case c == Path_List_Separator && !quote:
|
||||
list[index] = path[start:i]
|
||||
index += 1
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
assert(index == count)
|
||||
list[index] = path[start:]
|
||||
|
||||
for s0, i in list {
|
||||
s, new := strings.replace_all(s0, `"`, ``, allocator)
|
||||
if !new {
|
||||
s = strings.clone(s, allocator) or_return
|
||||
}
|
||||
list[i] = s
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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-1]), nil
|
||||
}
|
||||
@@ -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 :: '/'
|
||||
@@ -13,12 +14,12 @@ _Path_List_Separator :: ':'
|
||||
_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC}
|
||||
|
||||
_is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/'
|
||||
return c == _Path_Separator
|
||||
}
|
||||
|
||||
_mkdir :: proc(path: string, perm: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
path_cstr := temp_cstring(path) or_return
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -51,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
}
|
||||
return _get_platform_error(errno)
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
// need something we can edit, and use to generate cstrings
|
||||
path_bytes := make([]u8, len(path) + 1, temp_allocator())
|
||||
path_bytes := make([]u8, len(path) + 1, temp_allocator)
|
||||
|
||||
// zero terminate the byte slice to make it a valid cstring
|
||||
copy(path_bytes, path)
|
||||
@@ -128,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
path_cstr := temp_cstring(path) or_return
|
||||
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 {
|
||||
@@ -167,10 +168,31 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
|
||||
}
|
||||
|
||||
_set_working_directory :: proc(dir: string) -> Error {
|
||||
dir_cstr := temp_cstring(dir) or_return
|
||||
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/"
|
||||
|
||||
@@ -185,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.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)
|
||||
}
|
||||
|
||||
@@ -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 := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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 := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
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) or_return
|
||||
if posix.access(cpath, {.X_OK}) == .OK {
|
||||
return real(cpath, allocator)
|
||||
}
|
||||
}
|
||||
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
+32
-16
@@ -3,7 +3,6 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:path/filepath"
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
@@ -15,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == _Path_Separator
|
||||
}
|
||||
|
||||
_mkdir :: proc(name: string, perm: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name)
|
||||
_mkdir :: proc(name: string, perm: int) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
|
||||
return _get_platform_error()
|
||||
}
|
||||
@@ -29,17 +28,17 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
if exists(path) {
|
||||
return .Exist
|
||||
}
|
||||
|
||||
clean_path := filepath.clean(path, temp_allocator())
|
||||
clean_path := clean_path(path, temp_allocator) or_return
|
||||
return internal_mkdir_all(clean_path, perm)
|
||||
|
||||
internal_mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
dir, file := filepath.split(path)
|
||||
dir, file := split_path(path)
|
||||
if file != path && dir != "/" {
|
||||
if len(dir) > 1 && dir[len(dir) - 1] == '/' {
|
||||
dir = dir[:len(dir) - 1]
|
||||
@@ -53,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
_remove_all :: proc(path: string) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cpath := temp_cstring(path)
|
||||
_remove_all :: proc(path: string) -> (err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cpath := clone_to_cstring(path, temp_allocator) or_return
|
||||
|
||||
dir := posix.opendir(cpath)
|
||||
if dir == nil {
|
||||
@@ -79,9 +78,9 @@ _remove_all :: proc(path: string) -> Error {
|
||||
continue
|
||||
}
|
||||
|
||||
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
|
||||
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
|
||||
if entry.d_type == .DIR {
|
||||
_remove_all(fullpath[:len(fullpath)-1])
|
||||
_remove_all(fullpath[:len(fullpath)-1]) or_return
|
||||
} else {
|
||||
if posix.unlink(cstring(raw_data(fullpath))) != .OK {
|
||||
return _get_platform_error()
|
||||
@@ -96,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
|
||||
}
|
||||
|
||||
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
buf: [dynamic]byte
|
||||
buf.allocator = temp_allocator()
|
||||
buf.allocator = temp_allocator
|
||||
size := uint(posix.PATH_MAX)
|
||||
|
||||
cwd: cstring
|
||||
@@ -117,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
|
||||
}
|
||||
|
||||
_set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cdir := temp_cstring(dir)
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
cdir := clone_to_cstring(dir, temp_allocator) or_return
|
||||
if posix.chdir(cdir) != .OK {
|
||||
err = _get_platform_error()
|
||||
}
|
||||
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 })
|
||||
rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
|
||||
path_ptr := posix.realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", Platform_Error(posix.errno())
|
||||
}
|
||||
defer posix.free(path_ptr)
|
||||
|
||||
path_str := clone_string(string(path_ptr), allocator) or_return
|
||||
return path_str, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#+private
|
||||
#+build linux, darwin, netbsd, freebsd, openbsd, wasi
|
||||
package os2
|
||||
|
||||
// This implementation is for all systems that have POSIX-compliant filesystem paths.
|
||||
|
||||
_are_paths_identical :: proc(a, b: string) -> (identical: bool) {
|
||||
return a == b
|
||||
}
|
||||
|
||||
_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
|
||||
// Preserve rooted paths.
|
||||
if _is_path_separator(path[0]) {
|
||||
rooted = true
|
||||
buffer[0] = _Path_Separator
|
||||
start = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_is_absolute_path :: proc(path: string) -> bool {
|
||||
return len(path) > 0 && _is_path_separator(path[0])
|
||||
}
|
||||
|
||||
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
|
||||
base_rooted := len(base) > 0 && _is_path_separator(base[0])
|
||||
target_rooted := len(target) > 0 && _is_path_separator(target[0])
|
||||
return base_rooted == target_rooted
|
||||
}
|
||||
|
||||
_get_common_path_len :: proc(base, target: string) -> int {
|
||||
i := 0
|
||||
end := min(len(base), len(target))
|
||||
for j in 0..=end {
|
||||
if j == end || _is_path_separator(base[j]) {
|
||||
if base[i:j] == target[i:j] {
|
||||
i = j
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
_split_path :: proc(path: string) -> (dir, file: string) {
|
||||
i := len(path) - 1
|
||||
for i >= 0 && !_is_path_separator(path[i]) {
|
||||
i -= 1
|
||||
}
|
||||
if i == 0 {
|
||||
return path[:i+1], path[i+1:]
|
||||
} else if i > 0 {
|
||||
return path[:i], path[i+1:]
|
||||
}
|
||||
return "", path
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:sync"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
_Path_Separator :: '/'
|
||||
_Path_Separator_String :: "/"
|
||||
_Path_List_Separator :: ':'
|
||||
|
||||
_is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == _Path_Separator
|
||||
}
|
||||
|
||||
_mkdir :: proc(name: string, perm: int) -> Error {
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
return _get_platform_error(wasi.path_create_directory(dir_fd, relative))
|
||||
}
|
||||
|
||||
_mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
if path == "" {
|
||||
return .Invalid_Path
|
||||
}
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
if exists(path) {
|
||||
return .Exist
|
||||
}
|
||||
|
||||
clean_path := clean_path(path, temp_allocator)
|
||||
return internal_mkdir_all(clean_path)
|
||||
|
||||
internal_mkdir_all :: proc(path: string) -> Error {
|
||||
dir, file := split_path(path)
|
||||
if file != path && dir != "/" {
|
||||
if len(dir) > 1 && dir[len(dir) - 1] == '/' {
|
||||
dir = dir[:len(dir) - 1]
|
||||
}
|
||||
internal_mkdir_all(dir) or_return
|
||||
}
|
||||
|
||||
err := _mkdir(path, 0)
|
||||
if err == .Exist { err = nil }
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_remove_all :: proc(path: string) -> (err: Error) {
|
||||
// PERF: this works, but wastes a bunch of memory using the read_directory_iterator API
|
||||
// and using open instead of wasi fds directly.
|
||||
{
|
||||
dir := open(path) or_return
|
||||
defer close(dir)
|
||||
|
||||
iter := read_directory_iterator_create(dir)
|
||||
defer read_directory_iterator_destroy(&iter)
|
||||
|
||||
for fi in read_directory_iterator(&iter) {
|
||||
_ = read_directory_iterator_error(&iter) or_break
|
||||
|
||||
if fi.type == .Directory {
|
||||
_remove_all(fi.fullpath) or_return
|
||||
} else {
|
||||
remove(fi.fullpath) or_return
|
||||
}
|
||||
}
|
||||
|
||||
_ = read_directory_iterator_error(&iter) or_return
|
||||
}
|
||||
|
||||
return remove(path)
|
||||
}
|
||||
|
||||
g_wd: string
|
||||
g_wd_mutex: sync.Mutex
|
||||
|
||||
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
sync.guard(&g_wd_mutex)
|
||||
|
||||
return clone_string(g_wd if g_wd != "" else "/", allocator)
|
||||
}
|
||||
|
||||
_set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
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)
|
||||
}
|
||||
+131
-16
@@ -1,8 +1,9 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
_Path_Separator :: '\\'
|
||||
_Path_Separator_String :: "\\"
|
||||
@@ -13,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
|
||||
}
|
||||
|
||||
_mkdir :: proc(name: string, perm: int) -> Error {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
|
||||
return _get_platform_error()
|
||||
}
|
||||
return nil
|
||||
@@ -32,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
return p, false, nil
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
dir_stat, err := stat(path, temp_allocator())
|
||||
dir_stat, err := stat(path, temp_allocator)
|
||||
if err == nil {
|
||||
if dir_stat.type == .Directory {
|
||||
return nil
|
||||
@@ -62,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
|
||||
|
||||
err = mkdir(path, perm)
|
||||
if err != nil {
|
||||
new_dir_stat, err1 := lstat(path, temp_allocator())
|
||||
new_dir_stat, err1 := lstat(path, temp_allocator)
|
||||
if err1 == nil && new_dir_stat.type == .Directory {
|
||||
return nil
|
||||
}
|
||||
@@ -81,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
dir := win32_utf8_to_wstring(path, temp_allocator) or_return
|
||||
|
||||
empty: [1]u16
|
||||
|
||||
@@ -108,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
|
||||
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
|
||||
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
|
||||
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
|
||||
|
||||
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
|
||||
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
|
||||
@@ -122,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
|
||||
}
|
||||
|
||||
_set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
|
||||
|
||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||
|
||||
@@ -136,6 +137,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
|
||||
return
|
||||
}
|
||||
|
||||
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
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)
|
||||
@@ -166,7 +187,6 @@ init_long_path_support :: proc() {
|
||||
if value == 1 {
|
||||
can_use_long_paths = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
@@ -197,14 +217,14 @@ _fix_long_path_internal :: proc(path: string) -> string {
|
||||
return path
|
||||
}
|
||||
|
||||
if !_is_abs(path) { // relative path
|
||||
if !_is_absolute_path(path) { // relative path
|
||||
return path
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
PREFIX :: `\\?`
|
||||
path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator())
|
||||
path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
|
||||
copy(path_buf, PREFIX)
|
||||
n := len(path)
|
||||
r, w := 0, len(PREFIX)
|
||||
@@ -237,3 +257,98 @@ _fix_long_path_internal :: proc(path: string) -> string {
|
||||
|
||||
return string(path_buf[:w])
|
||||
}
|
||||
|
||||
_are_paths_identical :: strings.equal_fold
|
||||
|
||||
_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
|
||||
// Preserve rooted paths.
|
||||
start = _volume_name_len(path)
|
||||
if start > 0 {
|
||||
rooted = true
|
||||
if len(path) > start && _is_path_separator(path[start]) {
|
||||
// Take `C:` to `C:\`.
|
||||
start += 1
|
||||
}
|
||||
copy(buffer, path[:start])
|
||||
for n in 0..<start {
|
||||
if _is_path_separator(buffer[n]) {
|
||||
buffer[n] = _Path_Separator
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_is_absolute_path :: proc(path: string) -> bool {
|
||||
if _is_reserved_name(path) {
|
||||
return true
|
||||
}
|
||||
l := _volume_name_len(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
path := path
|
||||
path = path[l:]
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
return _is_path_separator(path[0])
|
||||
}
|
||||
|
||||
_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 })
|
||||
rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
|
||||
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
|
||||
if n == 0 {
|
||||
return "", Platform_Error(win32.GetLastError())
|
||||
}
|
||||
|
||||
buf := make([]u16, n, temp_allocator) or_return
|
||||
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
|
||||
if n == 0 {
|
||||
return "", Platform_Error(win32.GetLastError())
|
||||
}
|
||||
|
||||
return win32.utf16_to_utf8(buf, allocator)
|
||||
}
|
||||
|
||||
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
|
||||
base_root := base[:_volume_name_len(base)]
|
||||
target_root := target[:_volume_name_len(target)]
|
||||
return strings.equal_fold(base_root, target_root)
|
||||
}
|
||||
|
||||
_get_common_path_len :: proc(base, target: string) -> int {
|
||||
i := 0
|
||||
end := min(len(base), len(target))
|
||||
for j in 0..=end {
|
||||
if j == end || _is_path_separator(base[j]) {
|
||||
if strings.equal_fold(base[i:j], target[i:j]) {
|
||||
i = j
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
_split_path :: proc(path: string) -> (dir, file: string) {
|
||||
vol_len := _volume_name_len(path)
|
||||
|
||||
i := len(path) - 1
|
||||
for i >= vol_len && !_is_path_separator(path[i]) {
|
||||
i -= 1
|
||||
}
|
||||
if i == vol_len {
|
||||
return path[:i+1], path[i+1:]
|
||||
} else if i > vol_len {
|
||||
return path[:i], path[i+1:]
|
||||
}
|
||||
return "", path
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ _pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
strings.write_string(&rname, "/dev/fd/")
|
||||
strings.write_int(&rname, int(fds[0]))
|
||||
ri.name = strings.to_string(rname)
|
||||
ri.cname = strings.to_cstring(&rname)
|
||||
ri.cname = strings.to_cstring(&rname) or_return
|
||||
|
||||
w = __new_file(fds[1], file_allocator())
|
||||
wi := (^File_Impl)(w.impl)
|
||||
@@ -39,7 +39,7 @@ _pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
strings.write_string(&wname, "/dev/fd/")
|
||||
strings.write_int(&wname, int(fds[1]))
|
||||
wi.name = strings.to_string(wname)
|
||||
wi.cname = strings.to_cstring(&wname)
|
||||
wi.cname = strings.to_cstring(&wname) or_return
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
_pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
@@ -264,7 +264,7 @@ specific process, even after it has died.
|
||||
**Note(linux)**: The `handle` will be referring to pidfd.
|
||||
*/
|
||||
Process :: struct {
|
||||
pid: int,
|
||||
pid: int,
|
||||
handle: uintptr,
|
||||
}
|
||||
|
||||
@@ -290,21 +290,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
|
||||
return _process_open(pid, flags)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
OS-specific process attributes.
|
||||
*/
|
||||
Process_Attributes :: struct {
|
||||
sys_attr: _Sys_Process_Attributes,
|
||||
}
|
||||
|
||||
/*
|
||||
The description of how a process should be created.
|
||||
*/
|
||||
Process_Desc :: struct {
|
||||
// OS-specific attributes.
|
||||
sys_attr: Process_Attributes,
|
||||
|
||||
// The working directory of the process. If the string has length 0, the
|
||||
// working directory is assumed to be the current working directory of the
|
||||
// current process.
|
||||
@@ -407,11 +396,9 @@ process_exec :: proc(
|
||||
{
|
||||
stdout_b: [dynamic]byte
|
||||
stdout_b.allocator = allocator
|
||||
defer stdout = stdout_b[:]
|
||||
|
||||
stderr_b: [dynamic]byte
|
||||
stderr_b.allocator = allocator
|
||||
defer stderr = stderr_b[:]
|
||||
|
||||
buf: [1024]u8 = ---
|
||||
|
||||
@@ -450,6 +437,9 @@ process_exec :: proc(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout = stdout_b[:]
|
||||
stderr = stderr_b[:]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import "core:slice"
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:sys/linux"
|
||||
import "core:path/filepath"
|
||||
|
||||
PIDFD_UNASSIGNED :: ~uintptr(0)
|
||||
|
||||
@@ -51,7 +50,7 @@ _get_ppid :: proc() -> int {
|
||||
|
||||
@(private="package")
|
||||
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
|
||||
#partial switch errno {
|
||||
@@ -69,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
|
||||
}
|
||||
defer linux.close(dir_fd)
|
||||
|
||||
dynamic_list := make([dynamic]int, temp_allocator()) or_return
|
||||
dynamic_list := make([dynamic]int, temp_allocator) or_return
|
||||
|
||||
buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return
|
||||
buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
|
||||
loop: for {
|
||||
buflen: int
|
||||
buflen, errno = linux.getdents(dir_fd, buf[:])
|
||||
@@ -101,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
|
||||
|
||||
@(private="package")
|
||||
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
info.pid = pid
|
||||
|
||||
@@ -111,7 +110,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
|
||||
strings.write_string(&path_builder, "/proc/")
|
||||
strings.write_int(&path_builder, pid)
|
||||
proc_fd, errno := linux.open(strings.to_cstring(&path_builder), _OPENDIR_FLAGS)
|
||||
proc_fd, errno := linux.open(strings.to_cstring(&path_builder) or_return, _OPENDIR_FLAGS)
|
||||
if errno != .NONE {
|
||||
err = _get_platform_error(errno)
|
||||
return
|
||||
@@ -127,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
|
||||
passwd_bytes: []u8
|
||||
passwd_err: Error
|
||||
passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator())
|
||||
passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
|
||||
if passwd_err != nil {
|
||||
err = passwd_err
|
||||
break username_if
|
||||
@@ -163,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
}
|
||||
}
|
||||
|
||||
cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} {
|
||||
cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
|
||||
strings.builder_reset(&path_builder)
|
||||
strings.write_string(&path_builder, "/proc/")
|
||||
strings.write_int(&path_builder, pid)
|
||||
strings.write_string(&path_builder, "/cmdline")
|
||||
|
||||
cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
|
||||
cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
|
||||
if cmdline_err != nil || len(cmdline_bytes) == 0 {
|
||||
err = cmdline_err
|
||||
break cmdline_if
|
||||
@@ -179,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
terminator := strings.index_byte(cmdline, 0)
|
||||
assert(terminator > 0)
|
||||
|
||||
command_line_exec := cmdline[:terminator]
|
||||
// command_line_exec := cmdline[:terminator]
|
||||
|
||||
// Still need cwd if the execution on the command line is relative.
|
||||
cwd: string
|
||||
cwd_err: Error
|
||||
if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') {
|
||||
if .Working_Dir in selection {
|
||||
strings.builder_reset(&path_builder)
|
||||
strings.write_string(&path_builder, "/proc/")
|
||||
strings.write_int(&path_builder, pid)
|
||||
strings.write_string(&path_builder, "/cwd")
|
||||
|
||||
cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder), temp_allocator()) // allowed to fail
|
||||
cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
|
||||
if cwd_err == nil && .Working_Dir in selection {
|
||||
info.working_dir = strings.clone(cwd, allocator) or_return
|
||||
info.fields += {.Working_Dir}
|
||||
@@ -200,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
}
|
||||
}
|
||||
|
||||
if .Executable_Path in selection {
|
||||
if cmdline[0] == '/' {
|
||||
info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
|
||||
info.fields += {.Executable_Path}
|
||||
} else if cwd_err == nil {
|
||||
info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return
|
||||
info.fields += {.Executable_Path}
|
||||
} else {
|
||||
break cmdline_if
|
||||
}
|
||||
}
|
||||
|
||||
if selection & {.Command_Line, .Command_Args} != {} {
|
||||
// skip to first arg
|
||||
//cmdline = cmdline[terminator + 1:]
|
||||
@@ -258,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
strings.write_int(&path_builder, pid)
|
||||
strings.write_string(&path_builder, "/stat")
|
||||
|
||||
proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
|
||||
proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
|
||||
if stat_err != nil {
|
||||
err = stat_err
|
||||
break stat_if
|
||||
@@ -297,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
Nice,
|
||||
//... etc,
|
||||
}
|
||||
stat_fields := strings.split(stats, " ", temp_allocator()) or_return
|
||||
stat_fields := strings.split(stats, " ", temp_allocator) or_return
|
||||
|
||||
if len(stat_fields) <= int(Fields.Nice) {
|
||||
break stat_if
|
||||
@@ -324,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
}
|
||||
}
|
||||
|
||||
if .Executable_Path in selection {
|
||||
/*
|
||||
NOTE(Jeroen):
|
||||
|
||||
The old version returned the wrong executable path for things like `bash` or `sh`,
|
||||
for whom `/proc/<pid>/cmdline` will just report "bash" or "sh",
|
||||
resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
|
||||
|
||||
Thanks to Yawning for suggesting `/proc/self/exe`.
|
||||
*/
|
||||
|
||||
strings.builder_reset(&path_builder)
|
||||
strings.write_string(&path_builder, "/proc/")
|
||||
strings.write_int(&path_builder, pid)
|
||||
strings.write_string(&path_builder, "/exe")
|
||||
|
||||
if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
|
||||
info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
|
||||
info.fields += {.Executable_Path}
|
||||
} else {
|
||||
err = exe_err
|
||||
}
|
||||
}
|
||||
|
||||
if .Environment in selection {
|
||||
strings.builder_reset(&path_builder)
|
||||
strings.write_string(&path_builder, "/proc/")
|
||||
strings.write_int(&path_builder, pid)
|
||||
strings.write_string(&path_builder, "/environ")
|
||||
|
||||
if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()); env_err == nil {
|
||||
if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
|
||||
env := string(env_bytes)
|
||||
|
||||
env_list := make([dynamic]string, allocator) or_return
|
||||
@@ -379,12 +390,9 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
|
||||
return
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_Sys_Process_Attributes :: struct {}
|
||||
|
||||
@(private="package")
|
||||
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
if len(desc.command) == 0 {
|
||||
return process, .Invalid_Command
|
||||
@@ -393,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
dir_fd := linux.AT_FDCWD
|
||||
errno: linux.Errno
|
||||
if desc.working_dir != "" {
|
||||
dir_cstr := temp_cstring(desc.working_dir) or_return
|
||||
dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
|
||||
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
|
||||
return process, _get_platform_error(errno)
|
||||
}
|
||||
@@ -406,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
exe_path: cstring
|
||||
executable_name := desc.command[0]
|
||||
if strings.index_byte(executable_name, '/') < 0 {
|
||||
path_env := get_env("PATH", temp_allocator())
|
||||
path_dirs := filepath.split_list(path_env, temp_allocator()) or_return
|
||||
path_env := get_env("PATH", temp_allocator)
|
||||
path_dirs := split_path_list(path_env, temp_allocator) or_return
|
||||
|
||||
exe_builder := strings.builder_make(temp_allocator()) or_return
|
||||
exe_builder := strings.builder_make(temp_allocator) or_return
|
||||
|
||||
found: bool
|
||||
for dir in path_dirs {
|
||||
@@ -418,7 +426,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
strings.write_byte(&exe_builder, '/')
|
||||
strings.write_string(&exe_builder, executable_name)
|
||||
|
||||
exe_path = strings.to_cstring(&exe_builder)
|
||||
exe_path = strings.to_cstring(&exe_builder) or_return
|
||||
if linux.access(exe_path, linux.X_OK) == .NONE {
|
||||
found = true
|
||||
break
|
||||
@@ -430,13 +438,13 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
strings.write_string(&exe_builder, "./")
|
||||
strings.write_string(&exe_builder, executable_name)
|
||||
|
||||
exe_path = strings.to_cstring(&exe_builder)
|
||||
exe_path = strings.to_cstring(&exe_builder) or_return
|
||||
if linux.access(exe_path, linux.X_OK) != .NONE {
|
||||
return process, .Not_Exist
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exe_path = temp_cstring(executable_name) or_return
|
||||
exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
|
||||
if linux.access(exe_path, linux.X_OK) != .NONE {
|
||||
return process, .Not_Exist
|
||||
}
|
||||
@@ -444,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
|
||||
// args and environment need to be a list of cstrings
|
||||
// that are terminated by a nil pointer.
|
||||
cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
|
||||
cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
|
||||
for command, i in desc.command {
|
||||
cargs[i] = temp_cstring(command) or_return
|
||||
cargs[i] = clone_to_cstring(command, temp_allocator) or_return
|
||||
}
|
||||
|
||||
// Use current process' environment if description didn't provide it.
|
||||
env: [^]cstring
|
||||
if desc.env == nil {
|
||||
// take this process's current environment
|
||||
env = raw_data(export_cstring_environment(temp_allocator()))
|
||||
env = raw_data(export_cstring_environment(temp_allocator))
|
||||
} else {
|
||||
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return
|
||||
cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
|
||||
for env, i in desc.env {
|
||||
cenv[i] = temp_cstring(env) or_return
|
||||
cenv[i] = clone_to_cstring(env, temp_allocator) or_return
|
||||
}
|
||||
env = &cenv[0]
|
||||
}
|
||||
@@ -547,6 +555,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE {
|
||||
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
|
||||
}
|
||||
if dir_fd != linux.AT_FDCWD {
|
||||
if errno = linux.fchdir(dir_fd); errno != .NONE {
|
||||
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
|
||||
}
|
||||
}
|
||||
|
||||
errno = linux.execveat(dir_fd, exe_path, &cargs[0], env)
|
||||
assert(errno != nil)
|
||||
@@ -580,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
}
|
||||
|
||||
_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
stat_path_buf: [48]u8
|
||||
path_builder := strings.builder_from_bytes(stat_path_buf[:])
|
||||
@@ -589,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
|
||||
strings.write_string(&path_builder, "/stat")
|
||||
|
||||
stat_buf: []u8
|
||||
stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator())
|
||||
stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import "base:runtime"
|
||||
|
||||
import "core:time"
|
||||
import "core:strings"
|
||||
import "core:path/filepath"
|
||||
|
||||
import kq "core:sys/kqueue"
|
||||
import "core:sys/posix"
|
||||
@@ -47,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
|
||||
return _process_info_by_pid(_get_pid(), selection, allocator)
|
||||
}
|
||||
|
||||
_Sys_Process_Attributes :: struct {}
|
||||
|
||||
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
if len(desc.command) == 0 {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
// search PATH if just a plain name is provided.
|
||||
exe_builder := strings.builder_make(temp_allocator())
|
||||
exe_builder := strings.builder_make(temp_allocator)
|
||||
exe_name := desc.command[0]
|
||||
if strings.index_byte(exe_name, '/') < 0 {
|
||||
path_env := get_env("PATH", temp_allocator())
|
||||
path_dirs := filepath.split_list(path_env, temp_allocator())
|
||||
path_env := get_env("PATH", temp_allocator)
|
||||
path_dirs := split_path_list(path_env, temp_allocator) or_return
|
||||
|
||||
found: bool
|
||||
for dir in path_dirs {
|
||||
@@ -71,7 +68,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
strings.write_byte(&exe_builder, '/')
|
||||
strings.write_string(&exe_builder, exe_name)
|
||||
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
continue
|
||||
} else {
|
||||
posix.close(exe_fd)
|
||||
@@ -91,7 +88,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
|
||||
// "hello/./world" is fine right?
|
||||
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
err = .Not_Exist
|
||||
return
|
||||
} else {
|
||||
@@ -102,7 +99,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
strings.builder_reset(&exe_builder)
|
||||
strings.write_string(&exe_builder, exe_name)
|
||||
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 {
|
||||
err = .Not_Exist
|
||||
return
|
||||
} else {
|
||||
@@ -111,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
}
|
||||
|
||||
cwd: cstring; if desc.working_dir != "" {
|
||||
cwd = temp_cstring(desc.working_dir)
|
||||
cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
|
||||
}
|
||||
|
||||
cmd := make([]cstring, len(desc.command) + 1, temp_allocator())
|
||||
cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
|
||||
for part, i in desc.command {
|
||||
cmd[i] = temp_cstring(part)
|
||||
cmd[i] = clone_to_cstring(part, temp_allocator) or_return
|
||||
}
|
||||
|
||||
env: [^]cstring
|
||||
@@ -124,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
// take this process's current environment
|
||||
env = posix.environ
|
||||
} else {
|
||||
cenv := make([]cstring, len(desc.env) + 1, temp_allocator())
|
||||
cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
|
||||
for env, i in desc.env {
|
||||
cenv[i] = temp_cstring(env)
|
||||
cenv[i] = clone_to_cstring(env, temp_allocator) or_return
|
||||
}
|
||||
env = raw_data(cenv)
|
||||
}
|
||||
@@ -181,7 +178,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) }
|
||||
}
|
||||
|
||||
res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env)
|
||||
res := posix.execve(strings.to_cstring(&exe_builder) or_return, raw_data(cmd), env)
|
||||
assert(res == -1)
|
||||
abort(pipe[WRITE])
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
}
|
||||
|
||||
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
info.pid = pid
|
||||
|
||||
// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
|
||||
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
break args
|
||||
}
|
||||
|
||||
buf := runtime.make_aligned([]byte, length, 4, temp_allocator())
|
||||
buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
|
||||
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
|
||||
if err == nil {
|
||||
err = _get_platform_error()
|
||||
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
buffer := make([]i32, ret, temp_allocator())
|
||||
buffer := make([]i32, ret, temp_allocator)
|
||||
ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
|
||||
if ret < 0 {
|
||||
err = _get_platform_error()
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:time"
|
||||
import "core:sys/wasm/wasi"
|
||||
|
||||
_exit :: proc "contextless" (code: int) -> ! {
|
||||
wasi.proc_exit(wasi.exitcode_t(code))
|
||||
}
|
||||
|
||||
_get_uid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_euid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_gid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_egid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_pid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_ppid :: proc() -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_close :: proc(process: Process) -> Error {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_process_kill :: proc(process: Process) -> (err: Error) {
|
||||
return .Unsupported
|
||||
}
|
||||
|
||||
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
|
||||
process.pid = pid
|
||||
err = .Unsupported
|
||||
return
|
||||
}
|
||||
|
||||
_process_handle_still_valid :: proc(p: Process) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_process_state_update_times :: proc(p: Process, state: ^Process_State) {
|
||||
return
|
||||
}
|
||||
@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
if err != nil {
|
||||
break read_peb
|
||||
}
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
if selection >= {.Command_Line, .Command_Args} {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
|
||||
temp_allocator_scope(temp_allocator)
|
||||
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
}
|
||||
}
|
||||
if .Environment in selection {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator_scope(temp_allocator)
|
||||
env_len := process_params.EnvironmentSize / 2
|
||||
envs_w := make([]u16, env_len, temp_allocator()) or_return
|
||||
envs_w := make([]u16, env_len, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
|
||||
info.fields += {.Environment}
|
||||
}
|
||||
if .Working_Dir in selection {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
|
||||
temp_allocator_scope(temp_allocator)
|
||||
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
|
||||
if err != nil {
|
||||
break read_peb
|
||||
}
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
if selection >= {.Command_Line, .Command_Args} {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
|
||||
temp_allocator_scope(temp_allocator)
|
||||
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
|
||||
}
|
||||
}
|
||||
if .Environment in selection {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator_scope(temp_allocator)
|
||||
env_len := process_params.EnvironmentSize / 2
|
||||
envs_w := make([]u16, env_len, temp_allocator()) or_return
|
||||
envs_w := make([]u16, env_len, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
|
||||
info.fields += {.Environment}
|
||||
}
|
||||
if .Working_Dir in selection {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
|
||||
temp_allocator_scope(temp_allocator)
|
||||
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
|
||||
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
|
||||
if err != nil {
|
||||
break read_peb
|
||||
@@ -417,35 +419,64 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
|
||||
return
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_Sys_Process_Attributes :: struct {}
|
||||
|
||||
@(private="package")
|
||||
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
command_line := _build_command_line(desc.command, temp_allocator())
|
||||
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
command_line := _build_command_line(desc.command, temp_allocator)
|
||||
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
|
||||
environment := desc.env
|
||||
if desc.env == nil {
|
||||
environment = environ(temp_allocator())
|
||||
environment = environ(temp_allocator) or_return
|
||||
}
|
||||
environment_block := _build_environment_block(environment, temp_allocator())
|
||||
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
|
||||
stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
|
||||
stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
|
||||
stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
|
||||
environment_block := _build_environment_block(environment, temp_allocator)
|
||||
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
|
||||
|
||||
if desc.stdout != nil {
|
||||
stderr_handle: win32.HANDLE
|
||||
stdout_handle: win32.HANDLE
|
||||
stdin_handle: win32.HANDLE
|
||||
|
||||
null_handle: win32.HANDLE
|
||||
if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
|
||||
null_handle = win32.CreateFileW(
|
||||
win32.L("NUL"),
|
||||
win32.GENERIC_READ|win32.GENERIC_WRITE,
|
||||
win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
|
||||
&win32.SECURITY_ATTRIBUTES{
|
||||
nLength = size_of(win32.SECURITY_ATTRIBUTES),
|
||||
bInheritHandle = true,
|
||||
},
|
||||
win32.OPEN_EXISTING,
|
||||
win32.FILE_ATTRIBUTE_NORMAL,
|
||||
nil,
|
||||
)
|
||||
// Opening NUL should always succeed.
|
||||
assert(null_handle != nil)
|
||||
}
|
||||
// NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
|
||||
// and we don't have to hold onto this until the process exits.
|
||||
defer if null_handle != nil {
|
||||
win32.CloseHandle(null_handle)
|
||||
}
|
||||
|
||||
if desc.stdout == nil {
|
||||
stdout_handle = null_handle
|
||||
} else {
|
||||
stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
|
||||
}
|
||||
if desc.stderr != nil {
|
||||
|
||||
if desc.stderr == nil {
|
||||
stderr_handle = null_handle
|
||||
} else {
|
||||
stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
|
||||
}
|
||||
if desc.stdin != nil {
|
||||
|
||||
if desc.stdin == nil {
|
||||
stdin_handle = null_handle
|
||||
} else {
|
||||
stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
|
||||
}
|
||||
|
||||
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
|
||||
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
|
||||
process_info: win32.PROCESS_INFORMATION
|
||||
ok := win32.CreateProcessW(
|
||||
nil,
|
||||
@@ -583,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
|
||||
}
|
||||
|
||||
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
token_handle: win32.HANDLE
|
||||
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
|
||||
err = _get_platform_error()
|
||||
@@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
|
||||
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
|
||||
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
@@ -614,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
|
||||
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
|
||||
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
|
||||
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
|
||||
return strings.concatenate({domain, "\\", username}, allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
|
||||
@@ -24,8 +23,8 @@ File_Info :: struct {
|
||||
@(require_results)
|
||||
file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
|
||||
cloned = fi
|
||||
cloned.fullpath = strings.clone(fi.fullpath) or_return
|
||||
cloned.name = filepath.base(cloned.fullpath)
|
||||
cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return
|
||||
_, cloned.name = split_path(cloned.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -74,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
|
||||
|
||||
@(require_results)
|
||||
modification_time :: proc(f: ^File) -> (time.Time, Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
fi, err := fstat(f, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
fi, err := fstat(f, temp_allocator)
|
||||
return fi.modification_time, err
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
fi, err := stat(path, temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
fi, err := stat(path, temp_allocator)
|
||||
return fi.modification_time, err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package os2
|
||||
import "core:time"
|
||||
import "base:runtime"
|
||||
import "core:sys/linux"
|
||||
import "core:path/filepath"
|
||||
|
||||
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
|
||||
impl := (^File_Impl)(f.impl)
|
||||
@@ -42,14 +41,14 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
|
||||
creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
|
||||
}
|
||||
fi.creation_time = fi.modification_time
|
||||
fi.name = filepath.base(fi.fullpath)
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
|
||||
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
fd, errno := linux.open(name_cstr, {})
|
||||
if errno != .NONE {
|
||||
@@ -60,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
|
||||
}
|
||||
|
||||
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
name_cstr := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
name_cstr := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
|
||||
if errno != .NONE {
|
||||
|
||||
+10
-10
@@ -4,13 +4,12 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:path/filepath"
|
||||
import "core:sys/posix"
|
||||
import "core:time"
|
||||
|
||||
internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) {
|
||||
fi.fullpath = fullpath
|
||||
fi.name = filepath.base(fi.fullpath)
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
|
||||
fi.inode = u128(stat.st_ino)
|
||||
fi.size = i64(stat.st_size)
|
||||
@@ -70,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
cname := temp_cstring(name) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
cname := clone_to_cstring(name, temp_allocator) or_return
|
||||
|
||||
fd := posix.open(cname, {})
|
||||
if fd == -1 {
|
||||
@@ -97,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
|
||||
return
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
|
||||
|
||||
// NOTE: This might not be correct when given "/symlink/foo.txt",
|
||||
// you would want that to resolve "/symlink", but not resolve "foo.txt".
|
||||
|
||||
fullpath := filepath.clean(name, temp_allocator())
|
||||
fullpath := clean_path(name, temp_allocator) or_return
|
||||
assert(len(fullpath) > 0)
|
||||
switch {
|
||||
case fullpath[0] == '/':
|
||||
// nothing.
|
||||
case fullpath == ".":
|
||||
fullpath = getwd(temp_allocator()) or_return
|
||||
fullpath = getwd(temp_allocator) or_return
|
||||
case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
|
||||
fullpath = fullpath[2:]
|
||||
fallthrough
|
||||
case:
|
||||
fullpath = concatenate({
|
||||
getwd(temp_allocator()) or_return,
|
||||
getwd(temp_allocator) or_return,
|
||||
"/",
|
||||
fullpath,
|
||||
}, temp_allocator()) or_return
|
||||
}, temp_allocator) or_return
|
||||
}
|
||||
|
||||
stat: posix.stat_t
|
||||
if posix.lstat(temp_cstring(fullpath), &stat) != .OK {
|
||||
c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
|
||||
if posix.lstat(c_fullpath, &stat) != .OK {
|
||||
err = _get_platform_error()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
import "core:sys/wasm/wasi"
|
||||
import "core:time"
|
||||
|
||||
internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) {
|
||||
fi.fullpath = fullpath
|
||||
_, fi.name = split_path(fi.fullpath)
|
||||
|
||||
fi.inode = u128(stat.ino)
|
||||
fi.size = i64(stat.size)
|
||||
|
||||
switch stat.filetype {
|
||||
case .BLOCK_DEVICE: fi.type = .Block_Device
|
||||
case .CHARACTER_DEVICE: fi.type = .Character_Device
|
||||
case .DIRECTORY: fi.type = .Directory
|
||||
case .REGULAR_FILE: fi.type = .Regular
|
||||
case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket
|
||||
case .SYMBOLIC_LINK: fi.type = .Symlink
|
||||
case .UNKNOWN: fi.type = .Undetermined
|
||||
case: fi.type = .Undetermined
|
||||
}
|
||||
|
||||
fi.creation_time = time.Time{_nsec=i64(stat.ctim)}
|
||||
fi.modification_time = time.Time{_nsec=i64(stat.mtim)}
|
||||
fi.access_time = time.Time{_nsec=i64(stat.atim)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
|
||||
if f == nil || f.impl == nil {
|
||||
err = .Invalid_File
|
||||
return
|
||||
}
|
||||
|
||||
impl := (^File_Impl)(f.impl)
|
||||
|
||||
stat, _err := wasi.fd_filestat_get(__fd(f))
|
||||
if _err != nil {
|
||||
err = _get_platform_error(_err)
|
||||
return
|
||||
}
|
||||
|
||||
fullpath := clone_string(impl.name, allocator) or_return
|
||||
return internal_stat(stat, fullpath), nil
|
||||
}
|
||||
|
||||
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
|
||||
if name == "" {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative)
|
||||
if _err != nil {
|
||||
err = _get_platform_error(_err)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: wasi doesn't really do full paths afact.
|
||||
fullpath := clone_string(name, allocator) or_return
|
||||
return internal_stat(stat, fullpath), nil
|
||||
}
|
||||
|
||||
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
|
||||
if name == "" {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
dir_fd, relative, ok := match_preopen(name)
|
||||
if !ok {
|
||||
err = .Invalid_Path
|
||||
return
|
||||
}
|
||||
|
||||
stat, _err := wasi.path_filestat_get(dir_fd, {}, relative)
|
||||
if _err != nil {
|
||||
err = _get_platform_error(_err)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: wasi doesn't really do full paths afact.
|
||||
fullpath := clone_string(name, allocator) or_return
|
||||
return internal_stat(stat, fullpath), nil
|
||||
}
|
||||
|
||||
_same_file :: proc(fi1, fi2: File_Info) -> bool {
|
||||
return fi1.fullpath == fi2.fullpath
|
||||
}
|
||||
@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
|
||||
name = "."
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
p := win32_utf8_to_utf16(name, temp_allocator()) or_return
|
||||
p := win32_utf8_to_utf16(name, temp_allocator) or_return
|
||||
|
||||
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
|
||||
if n == 0 {
|
||||
return "", _get_platform_error()
|
||||
}
|
||||
buf := make([]u16, n+1, temp_allocator())
|
||||
buf := make([]u16, n+1, temp_allocator)
|
||||
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
|
||||
if n == 0 {
|
||||
return "", _get_platform_error()
|
||||
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
|
||||
if len(name) == 0 {
|
||||
return {}, .Not_Exist
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
wname := _fix_long_path(name, temp_allocator()) or_return
|
||||
wname := _fix_long_path(name, temp_allocator) or_return
|
||||
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
|
||||
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
|
||||
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
|
||||
return "", _get_platform_error()
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
buf := make([]u16, max(n, 260)+1, temp_allocator())
|
||||
buf := make([]u16, max(n, 260)+1, temp_allocator)
|
||||
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
|
||||
return _cleanpath_from_buf(buf[:n], allocator)
|
||||
}
|
||||
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
|
||||
return nil, _get_platform_error()
|
||||
}
|
||||
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
buf := make([]u16, max(n, 260)+1, temp_allocator())
|
||||
buf := make([]u16, max(n, 260)+1, temp_allocator)
|
||||
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
|
||||
return _cleanpath_strip_prefix(buf[:n]), nil
|
||||
}
|
||||
@@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
|
||||
return
|
||||
}
|
||||
|
||||
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
|
||||
time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
|
||||
win := u64(t._nsec / 100) + 116444736000000000
|
||||
return win32.LARGE_INTEGER(win)
|
||||
}
|
||||
|
||||
filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
|
||||
return {_nsec=(i64(ft) - 116444736000000000) * 100}
|
||||
}
|
||||
|
||||
filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
|
||||
return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
|
||||
}
|
||||
|
||||
filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
|
||||
|
||||
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
|
||||
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
|
||||
fi.type = type
|
||||
fi.mode |= mode
|
||||
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
|
||||
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
|
||||
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
|
||||
fi.creation_time = filetime_as_time(d.ftCreationTime)
|
||||
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
|
||||
fi.access_time = filetime_as_time(d.ftLastAccessTime)
|
||||
fi.fullpath, e = full_path_from_name(name, allocator)
|
||||
fi.name = basename(fi.fullpath)
|
||||
return
|
||||
@@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
|
||||
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
|
||||
fi.type = type
|
||||
fi.mode |= mode
|
||||
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
|
||||
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
|
||||
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
|
||||
fi.creation_time = filetime_as_time(d.ftCreationTime)
|
||||
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
|
||||
fi.access_time = filetime_as_time(d.ftLastAccessTime)
|
||||
fi.fullpath, e = full_path_from_name(name, allocator)
|
||||
fi.name = basename(fi.fullpath)
|
||||
return
|
||||
@@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
|
||||
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
|
||||
fi.type = type
|
||||
fi.mode |= mode
|
||||
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
|
||||
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
|
||||
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
|
||||
fi.creation_time = filetime_as_time(d.ftCreationTime)
|
||||
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
|
||||
fi.access_time = filetime_as_time(d.ftLastAccessTime)
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
@@ -310,62 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
_is_UNC :: proc(path: string) -> bool {
|
||||
return _volume_name_len(path) > 2
|
||||
}
|
||||
_volume_name_len :: proc(path: string) -> (length: int) {
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
_volume_name_len :: proc(path: string) -> int {
|
||||
if ODIN_OS == .Windows {
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
if path[1] == ':' {
|
||||
switch path[0] {
|
||||
case 'a'..='z', 'A'..='Z':
|
||||
return 2
|
||||
}
|
||||
c := path[0]
|
||||
if path[1] == ':' {
|
||||
switch c {
|
||||
}
|
||||
|
||||
/*
|
||||
See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
Further allowed paths can be of the form of:
|
||||
- \\server\share or \\server\share\more\path
|
||||
- \\?\C:\...
|
||||
- \\.\PhysicalDriveX
|
||||
*/
|
||||
// Any remaining kind of path has to start with two slashes.
|
||||
if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Device path. The volume name is the whole string
|
||||
if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
|
||||
return len(path)
|
||||
}
|
||||
|
||||
// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
|
||||
prefix := 2
|
||||
|
||||
// File namespace.
|
||||
if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
|
||||
if _is_path_separator(path[4]) {
|
||||
// `\\?\\` UNC path in file namespace
|
||||
prefix = 5
|
||||
}
|
||||
|
||||
if len(path) >= 6 && path[5] == ':' {
|
||||
switch path[4] {
|
||||
case 'a'..='z', 'A'..='Z':
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
|
||||
!_is_path_separator(path[2]) && path[2] != '.' {
|
||||
for n := 3; n < l-1; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
n += 1
|
||||
if !_is_path_separator(path[n]) {
|
||||
if path[n] == '.' {
|
||||
break
|
||||
}
|
||||
}
|
||||
for ; n < l; n += 1 {
|
||||
if _is_path_separator(path[n]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
break
|
||||
return 6
|
||||
case:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
_is_abs :: proc(path: string) -> bool {
|
||||
if _is_reserved_name(path) {
|
||||
return true
|
||||
}
|
||||
l := _volume_name_len(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
// UNC path, minimum version of the volume is `\\h\s` for host, share.
|
||||
// Can also contain an IP address in the host position.
|
||||
slash_count := 0
|
||||
for i in prefix..<len(path) {
|
||||
// Host needs to be at least 1 character
|
||||
if _is_path_separator(path[i]) && i > 0 {
|
||||
slash_count += 1
|
||||
|
||||
if slash_count == 2 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path := path
|
||||
path = path[l:]
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
return is_path_separator(path[0])
|
||||
}
|
||||
|
||||
return len(path)
|
||||
}
|
||||
+13
-11
@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
|
||||
// The caller must `close` the file once finished with.
|
||||
@(require_results)
|
||||
create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
dir := dir if dir != "" else temp_directory(temp_allocator) or_return
|
||||
prefix, suffix := _prefix_and_suffix(pattern) or_return
|
||||
prefix = temp_join_path(dir, prefix) or_return
|
||||
|
||||
rand_buf: [32]byte
|
||||
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
|
||||
rand_buf: [10]byte
|
||||
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
|
||||
|
||||
attempts := 0
|
||||
for {
|
||||
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
|
||||
// If `dir` is an empty tring, `temp_directory()` will be used.
|
||||
@(require_results)
|
||||
make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
dir := dir if dir != "" else temp_directory(temp_allocator) or_return
|
||||
prefix, suffix := _prefix_and_suffix(pattern) or_return
|
||||
prefix = temp_join_path(dir, prefix) or_return
|
||||
|
||||
rand_buf: [32]byte
|
||||
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
|
||||
rand_buf: [10]byte
|
||||
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
|
||||
|
||||
attempts := 0
|
||||
for {
|
||||
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
|
||||
return "", err
|
||||
}
|
||||
if err == .Not_Exist {
|
||||
if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist {
|
||||
if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
|
||||
return "", serr
|
||||
}
|
||||
}
|
||||
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
|
||||
|
||||
@(private="file")
|
||||
temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({})
|
||||
|
||||
if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
|
||||
return concatenate({dir, name}, temp_allocator(),)
|
||||
return concatenate({dir, name}, temp_allocator,)
|
||||
}
|
||||
|
||||
return concatenate({dir, Path_Separator_String, name}, temp_allocator())
|
||||
return concatenate({dir, Path_Separator_String, name}, temp_allocator)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ package os2
|
||||
import "base:runtime"
|
||||
|
||||
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
tmpdir := get_env("TMPDIR", temp_allocator())
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
tmpdir := get_env("TMPDIR", temp_allocator)
|
||||
if tmpdir == "" {
|
||||
tmpdir = "/tmp"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#+private
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
|
||||
// NOTE: requires user to add /tmp to their preopen dirs, no standard way exists.
|
||||
return clone_string("/tmp", allocator)
|
||||
}
|
||||
@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
|
||||
if n == 0 {
|
||||
return "", nil
|
||||
}
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
b := make([]u16, max(win32.MAX_PATH, n), temp_allocator())
|
||||
b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
|
||||
n = win32.GetTempPathW(u32(len(b)), raw_data(b))
|
||||
|
||||
if n == 3 && b[1] == ':' && b[2] == '\\' {
|
||||
|
||||
+141
-71
@@ -2,78 +2,148 @@ package os2
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
@(require_results)
|
||||
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
|
||||
#partial switch ODIN_OS {
|
||||
case .Windows:
|
||||
dir = get_env("LocalAppData", temp_allocator())
|
||||
if dir != "" {
|
||||
dir = clone_string(dir, allocator) or_return
|
||||
}
|
||||
case .Darwin:
|
||||
dir = get_env("HOME", temp_allocator())
|
||||
if dir != "" {
|
||||
dir = concatenate({dir, "/Library/Caches"}, allocator) or_return
|
||||
}
|
||||
case: // All other UNIX systems
|
||||
dir = get_env("XDG_CACHE_HOME", allocator)
|
||||
if dir == "" {
|
||||
dir = get_env("HOME", temp_allocator())
|
||||
if dir == "" {
|
||||
return
|
||||
}
|
||||
dir = concatenate({dir, "/.cache"}, allocator) or_return
|
||||
}
|
||||
}
|
||||
if dir == "" {
|
||||
err = .Invalid_Path
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
TEMP_ALLOCATOR_GUARD()
|
||||
|
||||
#partial switch ODIN_OS {
|
||||
case .Windows:
|
||||
dir = get_env("AppData", temp_allocator())
|
||||
if dir != "" {
|
||||
dir = clone_string(dir, allocator) or_return
|
||||
}
|
||||
case .Darwin:
|
||||
dir = get_env("HOME", temp_allocator())
|
||||
if dir != "" {
|
||||
dir = concatenate({dir, "/.config"}, allocator) or_return
|
||||
}
|
||||
case: // All other UNIX systems
|
||||
dir = get_env("XDG_CACHE_HOME", allocator)
|
||||
if dir == "" {
|
||||
dir = get_env("HOME", temp_allocator())
|
||||
if dir == "" {
|
||||
return
|
||||
}
|
||||
dir = concatenate({dir, "/.config"}, allocator) or_return
|
||||
}
|
||||
}
|
||||
if dir == "" {
|
||||
err = .Invalid_Path
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice
|
||||
// macOS: /Users/Alice
|
||||
// Linux: /home/alice
|
||||
// ```
|
||||
@(require_results)
|
||||
user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
env := "HOME"
|
||||
#partial switch ODIN_OS {
|
||||
case .Windows:
|
||||
env = "USERPROFILE"
|
||||
}
|
||||
if v := get_env(env, allocator); v != "" {
|
||||
return v, nil
|
||||
}
|
||||
return "", .Invalid_Path
|
||||
return _user_home_dir(allocator)
|
||||
}
|
||||
|
||||
// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches
|
||||
//
|
||||
// Sometimes deleted for system maintenance
|
||||
//
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\AppData\Local
|
||||
// macOS: /Users/Alice/Library/Caches
|
||||
// Linux: /home/alice/.cache
|
||||
// ```
|
||||
@(require_results)
|
||||
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_cache_dir(allocator)
|
||||
}
|
||||
|
||||
// User-hidden application data
|
||||
//
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
|
||||
// macOS: /Users/Alice/Library/Application Support
|
||||
// Linux: /home/alice/.local/share
|
||||
// ```
|
||||
//
|
||||
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
|
||||
@(require_results)
|
||||
user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
|
||||
return _user_data_dir(allocator, roaming)
|
||||
}
|
||||
|
||||
// Non-essential application data, e.g. history, ui layout state
|
||||
//
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\AppData\Local
|
||||
// macOS: /Users/Alice/Library/Application Support
|
||||
// Linux: /home/alice/.local/state
|
||||
// ```
|
||||
@(require_results)
|
||||
user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_state_dir(allocator)
|
||||
}
|
||||
|
||||
// Application log files
|
||||
//
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\AppData\Local
|
||||
// macOS: /Users/Alice/Library/Logs
|
||||
// Linux: /home/alice/.local/state
|
||||
// ```
|
||||
@(require_results)
|
||||
user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_log_dir(allocator)
|
||||
}
|
||||
|
||||
// Application settings/preferences
|
||||
//
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
|
||||
// macOS: /Users/Alice/Library/Application Support
|
||||
// Linux: /home/alice/.config
|
||||
// ```
|
||||
//
|
||||
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
|
||||
@(require_results)
|
||||
user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
|
||||
return _user_config_dir(allocator, roaming)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Music
|
||||
// macOS: /Users/Alice/Music
|
||||
// Linux: /home/alice/Music
|
||||
// ```
|
||||
@(require_results)
|
||||
user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_music_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Desktop
|
||||
// macOS: /Users/Alice/Desktop
|
||||
// Linux: /home/alice/Desktop
|
||||
// ```
|
||||
@(require_results)
|
||||
user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_desktop_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Documents
|
||||
// macOS: /Users/Alice/Documents
|
||||
// Linux: /home/alice/Documents
|
||||
// ```
|
||||
@(require_results)
|
||||
user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_documents_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Downloads
|
||||
// macOS: /Users/Alice/Downloads
|
||||
// Linux: /home/alice/Downloads
|
||||
// ```
|
||||
@(require_results)
|
||||
user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_downloads_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Pictures
|
||||
// macOS: /Users/Alice/Pictures
|
||||
// Linux: /home/alice/Pictures
|
||||
// ```
|
||||
@(require_results)
|
||||
user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_pictures_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Public
|
||||
// macOS: /Users/Alice/Public
|
||||
// Linux: /home/alice/Public
|
||||
// ```
|
||||
@(require_results)
|
||||
user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_public_dir(allocator)
|
||||
}
|
||||
|
||||
// ```
|
||||
// Windows: C:\Users\Alice\Videos
|
||||
// macOS: /Users/Alice/Movies
|
||||
// Linux: /home/alice/Videos
|
||||
// ```
|
||||
@(require_results)
|
||||
user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
return _user_videos_dir(allocator)
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#+build !windows
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:encoding/ini"
|
||||
import "core:strings"
|
||||
|
||||
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Library/Caches", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Library/Application Support", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Library/Application Support", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Library/Logs", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Library/Application Support", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Music", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Desktop", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Documents", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Downloads", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Pictures", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Public", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
#partial switch ODIN_OS {
|
||||
case .Darwin:
|
||||
return _xdg_lookup("", "/Movies", allocator)
|
||||
case: // Unix
|
||||
return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator)
|
||||
}
|
||||
}
|
||||
|
||||
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
if v := get_env("HOME", allocator); v != "" {
|
||||
return v, nil
|
||||
}
|
||||
err = .No_HOME_Variable
|
||||
return
|
||||
}
|
||||
|
||||
_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
|
||||
if xdg_key == "" { // Darwin doesn't have XDG paths.
|
||||
dir = get_env("HOME", temp_allocator)
|
||||
if dir == "" {
|
||||
err = .No_HOME_Variable
|
||||
return
|
||||
}
|
||||
return concatenate({dir, fallback_suffix}, allocator)
|
||||
} else {
|
||||
if strings.ends_with(xdg_key, "_DIR") {
|
||||
dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return
|
||||
} else {
|
||||
dir = get_env(xdg_key, allocator)
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
dir = get_env("HOME", temp_allocator)
|
||||
if dir == "" {
|
||||
err = .No_HOME_Variable
|
||||
return
|
||||
}
|
||||
dir = concatenate({dir, fallback_suffix}, allocator) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""`
|
||||
_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
|
||||
config_dir := user_config_dir(temp_allocator) or_return
|
||||
user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
|
||||
content := read_entire_file(user_dirs_path, temp_allocator) or_return
|
||||
|
||||
it := ini.Iterator{
|
||||
section = "",
|
||||
_src = string(content),
|
||||
options = ini.Options{
|
||||
comment = "#",
|
||||
key_lower_case = false,
|
||||
},
|
||||
}
|
||||
|
||||
for k, v in ini.iterate(&it) {
|
||||
if k == xdg_key {
|
||||
return replace_environment_placeholders(v, allocator), nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
@(require) import win32 "core:sys/windows"
|
||||
|
||||
_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_LocalAppData
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_LocalAppData
|
||||
if roaming {
|
||||
guid = win32.FOLDERID_RoamingAppData
|
||||
}
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_config_dir :: _local_appdata_or_roaming
|
||||
_user_data_dir :: _local_appdata_or_roaming
|
||||
|
||||
_user_state_dir :: _local_appdata
|
||||
_user_log_dir :: _local_appdata
|
||||
_user_cache_dir :: _local_appdata
|
||||
|
||||
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Profile
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Music
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Desktop
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Documents
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Downloads
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Pictures
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Public
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
guid := win32.FOLDERID_Videos
|
||||
return _get_known_folder_path(&guid, allocator)
|
||||
}
|
||||
|
||||
_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
|
||||
// See also `known_folders.odin` in `core:sys/windows` for the GUIDs.
|
||||
path_w: win32.LPWSTR
|
||||
res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w)
|
||||
defer win32.CoTaskMemFree(path_w)
|
||||
|
||||
if res != 0 {
|
||||
return "", .Invalid_Path
|
||||
}
|
||||
|
||||
dir, _ = win32.wstring_to_utf8(path_w, -1, allocator)
|
||||
return
|
||||
}
|
||||
+32
-3
@@ -1055,9 +1055,10 @@ flush :: proc(fd: Handle) -> Error {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
@@ -1292,7 +1321,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc
|
||||
}
|
||||
|
||||
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
|
||||
result := _unix_send(c.int(sd), raw_data(data), len(data), 0)
|
||||
result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags))
|
||||
if result < 0 {
|
||||
return 0, get_last_error()
|
||||
}
|
||||
|
||||
+34
-6
@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
s: OS_Stat = ---
|
||||
result := _unix_fstat(fd, &s)
|
||||
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
+35
-5
@@ -330,7 +330,7 @@ _delete_command_line_arguments :: proc() {
|
||||
delete(args)
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -344,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -358,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
@@ -468,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -479,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
info: haiku.system_info
|
||||
|
||||
@@ -249,3 +249,27 @@ exit :: proc "contextless" (code: int) -> ! {
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
+35
-7
@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
|
||||
return i64(res), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
@(require_results, no_sanitize_memory)
|
||||
file_size :: proc(fd: Handle) -> (i64, Error) {
|
||||
// deliberately uninitialized; the syscall fills this buffer for us
|
||||
s: OS_Stat = ---
|
||||
@@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
// deliberately uninitialized; the syscall fills this buffer for us
|
||||
s: OS_Stat = ---
|
||||
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
@@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
@@ -1160,7 +1188,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc
|
||||
}
|
||||
|
||||
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
|
||||
result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
|
||||
result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0)
|
||||
if result < 0 {
|
||||
return 0, _get_errno(int(result))
|
||||
}
|
||||
|
||||
+34
-6
@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
s: OS_Stat = ---
|
||||
result := _unix_fstat(fd, &s)
|
||||
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
+35
-6
@@ -343,7 +343,7 @@ AT_REMOVEDIR :: 0x08
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="__error") __error :: proc() -> ^c.int ---
|
||||
@(link_name="__errno") __error :: proc() -> ^c.int ---
|
||||
|
||||
@(link_name="fork") _unix_fork :: proc() -> pid_t ---
|
||||
@(link_name="getthrid") _unix_getthrid :: proc() -> int ---
|
||||
@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -244,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
|
||||
runtime._cleanup_runtime_contextless()
|
||||
wasi.proc_exit(wasi.exitcode_t(code))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
Reference in New Issue
Block a user