Merge branch 'master' into args-leak

This commit is contained in:
Jeroen van Rijn
2025-06-27 01:20:37 +02:00
committed by GitHub
876 changed files with 179630 additions and 17260 deletions
+3 -2
View File
@@ -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
+39 -2
View File
@@ -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)
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+17
View File
@@ -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
}
+230
View File
@@ -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
}
+124
View File
@@ -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)
}
+34 -21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+187
View File
@@ -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
}
+59 -18
View File
@@ -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
View File
@@ -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)] = ':'
+11 -2
View File
@@ -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,
}
+47
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, &times) != .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
}
+28
View File
@@ -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
}
+2 -2
View File
@@ -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[:]))
+1 -1
View File
@@ -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 }
+560
View File
@@ -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
}
}
+54 -33
View File
@@ -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
View File
@@ -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(
&region_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(
&region_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 := &region.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(&region.hdr.free_list[n])
alloc_idx := region.hdr.free_list[free_idx]
sync.atomic_store_explicit(&region.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
+6
View File
@@ -0,0 +1,6 @@
#+private
package os2
import "base:runtime"
_heap_allocator_proc :: runtime.wasm_allocator_proc
+9 -43
View File
@@ -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)
}
+421
View File
@@ -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
}
+17
View File
@@ -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
}
+29
View File
@@ -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
}
+49 -9
View File
@@ -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)
}
+24
View File
@@ -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
}
}
+57
View File
@@ -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
View File
@@ -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
}
+57
View File
@@ -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
}
+116
View File
@@ -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
View File
@@ -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
}
+2 -2
View File
@@ -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
}
+13
View File
@@ -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
}
+4 -14
View File
@@ -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 {
+58 -45
View File
@@ -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
}
+13 -16
View File
@@ -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])
+4 -3
View File
@@ -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()
+87
View File
@@ -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
}
+63 -32
View File
@@ -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)
}
+6 -7
View File
@@ -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
}
+5 -6
View File
@@ -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
View File
@@ -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
}
+100
View File
@@ -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
}
+90 -68
View File
@@ -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
View File
@@ -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)
}
+2 -2
View File
@@ -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"
}
+9
View File
@@ -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)
}
+2 -2
View File
@@ -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
View File
@@ -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)
}
+175
View File
@@ -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
}
+79
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+24
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+24
View File
@@ -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}