Browse Source

os2: initial implementation for Darwin&BSDs, process API is only thing incomplete

Laytan Laats 1 year ago
parent
commit
a4d459f651

+ 0 - 1
core/os/os2/allocators.odin

@@ -61,4 +61,3 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.
 	global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
 	global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
 	return tmp, loc
 	return tmp, loc
 }
 }
-

+ 71 - 0
core/os/os2/dir_posix.odin

@@ -0,0 +1,71 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+
+import "core:sys/posix"
+
+@(private)
+_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
+	if f == nil || f.impl == nil {
+		err = .Invalid_File
+		return
+	}
+
+	n := n
+	if n == 0 {
+		return
+	}
+
+	impl := (^File_Impl)(f)
+
+	dir := posix.fdopendir(impl.fd)
+	if dir == nil {
+		err = _get_platform_error()
+		return
+	}
+	defer posix.closedir(dir)
+
+	dfiles: [dynamic]File_Info
+	dfiles.allocator = allocator
+	defer if err != nil {
+		file_info_slice_delete(dfiles[:], allocator)
+	}
+
+	for {
+		posix.set_errno(.NONE)
+		entry := posix.readdir(dir)
+		if entry == nil {
+			if errno := posix.errno(); errno != .NONE {
+				err = _get_platform_error()
+				return
+			} else {
+				break
+			}
+		}
+
+		cname := cstring(raw_data(entry.d_name[:]))
+		if cname == "." || cname == ".." {
+			continue
+		}
+
+		stat: posix.stat_t
+		if posix.fstatat(posix.dirfd(dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK {
+			err = _get_platform_error()
+			return
+		}
+
+		fullpath := concatenate({impl.name, "/", string(cname)}, allocator) or_return
+		fi := internal_stat(stat, fullpath)
+		append(&dfiles, fi) or_return
+
+		n -= 1
+		if n == 0 {
+			break
+		}
+	}
+
+	files = dfiles[:]
+	return
+}

+ 78 - 0
core/os/os2/env_posix.odin

@@ -0,0 +1,78 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+
+import "core:strings"
+import "core:sys/posix"
+
+_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+	if key == "" {
+		return
+	}
+
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+
+	ckey := strings.clone_to_cstring(key, temp_allocator())
+	cval := posix.getenv(ckey)
+	if cval == nil {
+		return
+	}
+
+	found = true
+	value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails?
+
+	return
+}
+
+_set_env :: proc(key, value: string) -> (ok: bool) {
+	TEMP_ALLOCATOR_GUARD()
+
+	ckey := strings.clone_to_cstring(key, temp_allocator())
+	cval := strings.clone_to_cstring(key, temp_allocator())
+
+	ok = posix.setenv(ckey, cval, true) == .OK
+	return
+}
+
+_unset_env :: proc(key: string) -> (ok: bool) {
+	TEMP_ALLOCATOR_GUARD()
+
+	ckey := strings.clone_to_cstring(key, temp_allocator())
+
+	ok = posix.unsetenv(ckey) == .OK
+	return
+}
+
+// NOTE(laytan): clearing the env is weird, why would you ever do that?
+
+_clear_env :: proc() {
+	for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] {
+		key := strings.truncate_to_byte(string(entry), '=')
+		_unset_env(key)
+	}
+}
+
+_environ :: proc(allocator: runtime.Allocator) -> (environ: []string) {
+	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
+	}
+
+	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
+		}
+	}
+
+	return
+}
+
+

+ 30 - 0
core/os/os2/errors_posix.odin

@@ -0,0 +1,30 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "core:sys/posix"
+
+_error_string :: proc(errno: i32) -> string {
+	return string(posix.strerror(posix.Errno(errno)))
+}
+
+_get_platform_error :: proc() -> Error {
+	#partial switch errno := posix.errno(); errno {
+	case .EPERM:
+		return .Permission_Denied
+	case .EEXIST:
+		return .Exist
+	case .ENOENT:
+		return .Not_Exist
+	case .ETIMEDOUT:
+		return .Timeout
+	case .EPIPE:
+		return .Broken_Pipe
+	case .EBADF:
+		return .Invalid_File
+	case .ENOMEM:
+		return .Out_Of_Memory
+	case:
+		return Platform_Error(errno)
+	}
+}

+ 446 - 0
core/os/os2/file_posix.odin

@@ -0,0 +1,446 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+
+import "core:io"
+import "core:c"
+import "core:time"
+import "core:sys/posix"
+
+// 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,
+	cname: cstring,
+	fd:    posix.FD,
+}
+
+@(init)
+init_std_files :: proc() {
+	// NOTE: is this (paths) also the case on non darwin?
+	stdin  = _new_file(posix.STDIN_FILENO,  "/dev/stdin") 
+	stdout = _new_file(posix.STDOUT_FILENO, "/dev/stdout")
+	stderr = _new_file(posix.STDERR_FILENO, "/dev/stdout")
+}
+
+_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
+	if name == "" {
+		err = .Invalid_Path
+		return
+	}
+
+	sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC}
+	
+	if .Write in flags {
+		if .Read in flags {
+			sys_flags += {.RDWR}
+		} else {
+			sys_flags += {.WRONLY}
+		}
+	}
+
+	if .Append      in flags { sys_flags += {.APPEND} }
+	if .Create      in flags { sys_flags += {.CREAT} }
+	if .Excl        in flags { sys_flags += {.EXCL} }
+	if .Sync        in flags { sys_flags += {.DSYNC} }
+	if .Trunc       in flags { sys_flags += {.TRUNC} }
+	if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
+
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+
+	fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
+	if fd < 0 {
+		err = _get_platform_error()
+		return
+	}
+
+	return _new_file(uintptr(fd), name), nil
+}
+
+_new_file :: proc(handle: uintptr, name: string) -> ^File {
+	if name == "" || handle == ~uintptr(0) {
+		return nil
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+
+	crname := posix.realpath(cname, nil)
+	assert(crname != nil)
+	rname := string(crname)
+
+	f    := __new_file(posix.FD(handle))
+	impl := (^File_Impl)(f.impl)
+	impl.name = rname
+	impl.cname = crname
+
+	return f
+}
+
+__new_file :: proc(handle: posix.FD) -> ^File {
+	impl := new(File_Impl, file_allocator())
+	impl.file.impl = impl
+	impl.fd = posix.FD(handle)
+	impl.file.stream = {
+		data = impl,
+		procedure = _file_stream_proc,
+	}
+	impl.file.fstat = _fstat
+	return &impl.file
+}
+
+_close :: proc(f: ^File_Impl) -> (err: Error) {
+	if f == nil { return nil }
+
+	if posix.close(f.fd) != .OK {
+		err = _get_platform_error()
+	}
+
+	delete(f.cname, file_allocator())
+	posix.free(f.cname)
+	return
+}
+
+_fd :: proc(f: ^File) -> uintptr {
+	return uintptr(__fd(f))
+}
+
+__fd :: proc(f: ^File) -> posix.FD {
+	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 {
+	if posix.fsync(__fd(f)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_truncate :: proc(f: ^File, size: i64) -> Error {
+	if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_remove :: proc(name: string) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	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)
+	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)
+	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)
+	if posix.symlink(cold, cnew) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+
+	buf: [dynamic]byte
+	buf.allocator = allocator
+	defer if err != nil { delete(buf) }
+
+	// Loop this because the file might've grown between lstat() and readlink().
+	for {
+		stat: posix.stat_t
+		if posix.lstat(cname, &stat) != .OK {
+			err = _get_platform_error()
+			return
+		}
+
+		bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX)
+
+		if bufsiz == len(buf) {
+			bufsiz *= 2
+		}
+
+		// Overflow.
+		if bufsiz <= 0 {
+			err = Platform_Error(posix.Errno.E2BIG)
+			return
+		}
+
+		resize(&buf, bufsiz) or_return
+
+		size := posix.readlink(cname, raw_data(buf), uint(bufsiz))
+		if size < 0 {
+			err = _get_platform_error()
+			return
+		}
+
+		// File has probably grown between lstat() and readlink().
+		if size == bufsiz {
+			continue
+		}
+
+		s = string(buf[:size])
+		return
+	}
+}
+
+_chdir :: proc(name: string) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	if posix.chdir(cname) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_fchdir :: proc(f: ^File) -> Error {
+	if posix.fchdir(__fd(f)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_fchmod :: proc(f: ^File, mode: int) -> Error {
+	if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_chmod :: proc(name: string, mode: int) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_fchown :: proc(f: ^File, uid, gid: int) -> Error {
+	if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_chown :: proc(name: string, uid, gid: int) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_lchown :: proc(name: string, uid, gid: int) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	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 {
+	times := [2]posix.timeval{
+		{
+			tv_sec  = posix.time_t(atime._nsec/1e9),           /* seconds */
+			tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */
+		},
+		{
+			tv_sec  = posix.time_t(mtime._nsec/1e9),           /* seconds */
+			tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */
+		},
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+
+	if posix.utimes(cname, &times) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
+	times := [2]posix.timespec{
+		{
+			tv_sec  = posix.time_t(atime._nsec/1e9), /* seconds */
+			tv_nsec = c.long(atime._nsec%1e9),       /* nanoseconds */
+		},
+		{
+			tv_sec  = posix.time_t(mtime._nsec/1e9), /* seconds */
+			tv_nsec = c.long(mtime._nsec%1e9),       /* nanoseconds */
+		},
+	}
+
+	if posix.futimens(__fd(f), &times) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_exists :: proc(path: string) -> bool {
+	TEMP_ALLOCATOR_GUARD()
+	cpath := temp_cstring(path)
+	return posix.access(cpath) == .OK
+}
+
+_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 := uint(min(len(p), MAX_RW))
+		n = i64(posix.read(fd, raw_data(p), to_read))
+		switch {
+		case n == 0:
+			err = .EOF
+		case n < 0:
+			err = .Unknown
+		}
+		return
+
+	case .Read_At:
+		if len(p) <= 0 {
+			return
+		}
+
+		if offset < 0 {
+			err = .Invalid_Offset
+			return
+		}
+
+		to_read := uint(min(len(p), MAX_RW))
+		n = i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset)))
+		switch {
+		case n == 0:
+			err = .EOF
+		case n < 0:
+			err = .Unknown
+		}
+		return
+
+	case .Write:
+		p := p
+		for len(p) > 0 {
+			to_write := uint(min(len(p), MAX_RW))
+			if _n := i64(posix.write(fd, raw_data(p), to_write)); n <= 0 {
+				err = .Unknown
+				return
+			} else {
+				p = p[_n:]
+				n += _n
+			}
+		}
+		return
+
+	case .Write_At:
+		p := p
+		offset := offset
+
+		if offset < 0 {
+			err = .Invalid_Offset
+			return
+		}
+
+		for len(p) > 0 {
+			to_write := uint(min(len(p), MAX_RW))
+			if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); n <= 0 {
+				err = .Unknown
+				return
+			} else {
+				p = p[_n:]
+				n += _n
+				offset += _n
+			}
+		}
+		return
+
+	case .Seek:
+		#assert(int(posix.Whence.SET) == int(io.Seek_From.Start))
+		#assert(int(posix.Whence.CUR) == int(io.Seek_From.Current))
+		#assert(int(posix.Whence.END) == int(io.Seek_From.End))
+
+		n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence)))
+		if n < 0 {
+			err = .Unknown
+		}
+		return
+
+	case .Size:
+		stat: posix.stat_t
+		if posix.fstat(fd, &stat) != .OK {
+			err = .Unknown
+			return
+		}
+
+		n = i64(stat.st_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, .Query})
+
+	case:
+		return 0, .Empty
+	}
+}

+ 97 - 0
core/os/os2/heap_posix.odin

@@ -0,0 +1,97 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:intrinsics"
+
+import "core:mem"
+import "core:sys/posix"
+
+
+_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, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
+		assert(size > 0)
+
+		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 = posix.realloc(original_old_ptr, uint(space)+size_of(rawptr))
+		} else if zero_memory {
+			allocated_mem = posix.calloc(1, uint(space)+size_of(rawptr))
+		} else {
+			allocated_mem = posix.malloc(uint(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 {
+			posix.free(mem.ptr_offset((^rawptr)(p), -1)^)
+		}
+	}
+
+	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory: bool) -> (new_memory: []byte, err: mem.Allocator_Error) {
+		assert(p != nil)
+
+		new_memory = aligned_alloc(new_size, new_alignment, false, p) or_return
+
+		if zero_memory && new_size > old_size {
+			new_region := raw_data(new_memory[old_size:])
+			intrinsics.mem_zero(new_region, new_size - old_size)
+		}
+		return
+	}
+
+	switch mode {
+	case .Alloc, .Alloc_Non_Zeroed:
+		return aligned_alloc(size, alignment, mode == .Alloc)
+
+	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, mode == .Resize)
+		}
+		return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize)
+
+	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
+}

+ 1 - 1
core/os/os2/internal_util.odin

@@ -43,7 +43,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
 }
 }
 
 
 @(require_results)
 @(require_results)
-temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) {
+temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
 	return clone_to_cstring(s, temp_allocator())
 	return clone_to_cstring(s, temp_allocator())
 }
 }
 
 

+ 127 - 0
core/os/os2/path_posix.odin

@@ -0,0 +1,127 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+import "core:path/filepath"
+
+import "core:sys/posix"
+
+_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 {
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name)
+	if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
+		return _get_platform_error()
+	}
+	return nil
+}
+
+_mkdir_all :: proc(path: string, perm: int) -> Error {
+	if path == "" {
+		return .Invalid_Path
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+
+	if exists(path) {
+		return .Exist
+	}
+
+	clean_path := filepath.clean(path, temp_allocator())
+	return internal_mkdir_all(clean_path, perm)
+
+	internal_mkdir_all :: proc(path: string, perm: int) -> Error {
+		a, _ := filepath.split(path)
+		if a != path {
+			if len(a) > 1 && a[len(a)-1] == '/' {
+				a = a[:len(a)-1]
+			}
+			internal_mkdir_all(a, perm) or_return
+		}
+
+		err := _mkdir(path, perm)
+		if err == .Exist { err = nil }
+		return err
+	}
+}
+
+_remove_all :: proc(path: string) -> Error {
+	TEMP_ALLOCATOR_GUARD()
+	cpath := temp_cstring(path)
+
+	dir := posix.opendir(cpath)
+	if dir == nil {
+		return _get_platform_error()
+	}
+
+	for {
+		posix.set_errno(.NONE)
+		entry := posix.readdir(dir)
+		if entry == nil {
+			if errno := posix.errno(); errno != .NONE {
+				return _get_platform_error()
+			} else {
+				break
+			}
+		}
+
+		cname := cstring(raw_data(entry.d_name[:]))
+		if cname == "." || cname == ".." {
+			continue
+		}
+
+		fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
+		if entry.d_type == .DIR {
+			_remove_all(fullpath[:len(fullpath)-1])
+		} else {
+			if posix.unlink(cstring(raw_data(fullpath))) != .OK {
+				return _get_platform_error()
+			}
+		}
+	}
+
+	if posix.rmdir(cpath) != .OK {
+		return _get_platform_error()
+	}
+	posix.closedir(dir)
+	return nil
+}
+
+_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+
+	buf: [dynamic]byte
+	buf.allocator = temp_allocator()
+	size := uint(posix.PATH_MAX)
+
+	cwd: cstring
+	for ; cwd == nil; size *= 2 {
+		resize(&buf, size)
+
+		cwd = posix.getcwd(raw_data(buf), len(buf))
+		if cwd == nil && posix.errno() != .ERANGE {
+			err = _get_platform_error()
+			return
+		}
+	}
+
+	return clone_string(string(cwd), allocator)
+}
+
+_set_working_directory :: proc(dir: string) -> (err: Error) {
+	TEMP_ALLOCATOR_GUARD()
+	cdir := temp_cstring(dir)
+	if posix.chdir(cdir) != .OK {
+		err = _get_platform_error()
+	}
+	return
+}

+ 37 - 0
core/os/os2/pipe_posix.odin

@@ -0,0 +1,37 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "core:sys/posix"
+import "core:strings"
+
+_pipe :: proc() -> (r, w: ^File, err: Error) {
+	fds: [2]posix.FD
+	if posix.pipe(&fds) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	r = __new_file(fds[0])
+	ri := (^File_Impl)(r.impl)
+
+	rname := strings.builder_make(file_allocator())
+	// TODO(laytan): is this on all the posix targets?
+	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)
+
+	w = __new_file(fds[1])
+	wi := (^File_Impl)(w.impl)
+	
+	wname := strings.builder_make(file_allocator())
+	// TODO(laytan): is this on all the posix targets?
+	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)
+
+	return
+}
+

+ 14 - 8
core/os/os2/process.odin

@@ -14,7 +14,7 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi
 */
 */
 args := get_args()
 args := get_args()
 
 
-@(private="file", require_results)
+@(private="file")
 get_args :: proc() -> []string {
 get_args :: proc() -> []string {
 	result := make([]string, len(runtime.args__), heap_allocator())
 	result := make([]string, len(runtime.args__), heap_allocator())
 	for rt_arg, i in runtime.args__ {
 	for rt_arg, i in runtime.args__ {
@@ -131,6 +131,8 @@ Process_Info_Field :: enum {
 	Working_Dir,
 	Working_Dir,
 }
 }
 
 
+ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir}
+
 /*
 /*
 	Contains information about the process as obtained by the `process_info()`
 	Contains information about the process as obtained by the `process_info()`
 	procedure.
 	procedure.
@@ -166,8 +168,8 @@ Process_Info :: struct {
 	a process given by `pid`.
 	a process given by `pid`.
 	
 	
 	Use `free_process_info` to free the memory allocated by this procedure. In
 	Use `free_process_info` to free the memory allocated by this procedure. In
-	case the function returns an error all temporary allocations would be freed
-	and as such, calling `free_process_info()` is not needed.
+	case the function returns an error it may only have been an error for one part
+	of the information and you would still need to call it to free the other parts.
 
 
 	**Note**: The resulting information may or may contain the fields specified
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	by the `selection` parameter. Always check whether the returned
@@ -187,8 +189,8 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator:
 	the `process` parameter.
 	the `process` parameter.
 
 
 	Use `free_process_info` to free the memory allocated by this procedure. In
 	Use `free_process_info` to free the memory allocated by this procedure. In
-	case the function returns an error, all temporary allocations would be freed
-	and as such, calling `free_process_info` is not needed.
+	case the function returns an error it may only have been an error for one part
+	of the information and you would still need to call it to free the other parts.
 
 
 	**Note**: The resulting information may or may contain the fields specified
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	by the `selection` parameter. Always check whether the returned
@@ -206,9 +208,9 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields,
 	This procedure obtains the information, specified by `selection` parameter
 	This procedure obtains the information, specified by `selection` parameter
 	about the currently running process.
 	about the currently running process.
 
 
-	Use `free_process_info` to free the memory allocated by this function. In
-	case this function returns an error, all temporary allocations would be
-	freed and as such calling `free_process_info()` is not needed.
+	Use `free_process_info` to free the memory allocated by this procedure. In
+	case the function returns an error it may only have been an error for one part
+	of the information and you would still need to call it to free the other parts.
 
 
 	**Note**: The resulting information may or may contain the fields specified
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	by the `selection` parameter. Always check whether the returned
@@ -239,12 +241,16 @@ process_info :: proc {
 free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
 free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) {
 	delete(pi.executable_path, allocator)
 	delete(pi.executable_path, allocator)
 	delete(pi.command_line, allocator)
 	delete(pi.command_line, allocator)
+	for a in pi.command_args {
+		delete(a, allocator)
+	}
 	delete(pi.command_args, allocator)
 	delete(pi.command_args, allocator)
 	for s in pi.environment {
 	for s in pi.environment {
 		delete(s, allocator)
 		delete(s, allocator)
 	}
 	}
 	delete(pi.environment, allocator)
 	delete(pi.environment, allocator)
 	delete(pi.working_dir, allocator)
 	delete(pi.working_dir, allocator)
+	delete(pi.username, allocator)
 }
 }
 
 
 /*
 /*

+ 69 - 0
core/os/os2/process_posix.odin

@@ -0,0 +1,69 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+import "core:time"
+
+import "core:sys/posix"
+
+_exit :: proc "contextless" (code: int) -> ! {
+	posix.exit(i32(code))
+}
+
+_get_uid :: proc() -> int {
+	return int(posix.getuid())
+}
+
+_get_euid :: proc() -> int {
+	return int(posix.geteuid())
+}
+
+_get_gid :: proc() -> int {
+	return int(posix.getgid())
+}
+
+_get_egid :: proc() -> int {
+	return int(posix.getegid())
+}
+
+_get_pid :: proc() -> int {
+	return int(posix.getpid())
+}
+
+_get_ppid :: proc() -> int {
+	return int(posix.getppid())
+}
+
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	return _process_info_by_pid(process.pid, selection, allocator)
+}
+
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	return _process_info_by_pid(_get_pid(), selection, allocator)
+}
+
+_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
+	err = .Unsupported
+	return
+}
+
+_Sys_Process_Attributes :: struct {}
+
+_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) -> Error {
+	return .Unsupported
+}

+ 251 - 0
core/os/os2/process_posix_darwin.odin

@@ -0,0 +1,251 @@
+//+private
+package os2
+
+import "base:runtime"
+import "base:intrinsics"
+
+import "core:bytes"
+import "core:sys/darwin"
+import "core:sys/posix"
+import "core:sys/unix"
+
+foreign import lib "system:System.framework"
+
+foreign lib {
+	sysctl :: proc(
+		name: [^]i32, namelen: u32,
+		oldp: rawptr, oldlenp: ^uint,
+		newp: rawptr, newlen: uint,
+	) -> posix.result ---
+}
+
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+	info.pid = pid
+
+	get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, nice: Maybe(i32), uid: posix.uid_t, ok: bool) {
+		// Short info is enough and requires less permissions if the priority isn't requested.
+		if .Priority in selection {
+			pinfo: darwin.proc_bsdinfo
+			ret := darwin.proc_pidinfo(posix.pid_t(pid), .BSDINFO, 0, &pinfo, size_of(pinfo))
+			if ret > 0 {
+				assert(ret == size_of(pinfo))
+				ppid = pinfo.pbi_ppid
+				nice = pinfo.pbi_nice
+				uid  = pinfo.pbi_uid
+				ok   = true
+				return
+			}
+		}
+
+		// Try short info, requires less permissions, but doesn't give a `nice`.
+		psinfo: darwin.proc_bsdshortinfo
+		ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo))
+		if ret > 0 {
+			assert(ret == size_of(psinfo))
+			ppid = psinfo.pbsi_ppid
+			uid  = psinfo.pbsi_uid
+			ok   = true
+		}
+
+		return
+	}
+
+	// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
+	// other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information.
+
+	pidinfo: {
+		if selection >= {.PPid, .Priority, .Username } {
+			ppid, mnice, uid, ok := get_pidinfo(pid, selection)
+			if !ok {
+				if err == nil {
+					err = _get_platform_error()
+				}
+				break pidinfo
+			}
+
+			if .PPid in selection {
+				info.ppid = int(ppid)
+				info.fields += {.PPid}
+			}
+
+			if nice, has_nice := mnice.?; has_nice && .Priority in selection {
+				info.priority = int(nice)
+				info.fields += {.Priority}
+			}
+
+			if .Username in selection {
+				pw := posix.getpwuid(uid)
+				if pw == nil {
+					if err == nil {
+						err = _get_platform_error()
+					}
+					break pidinfo
+				}
+
+				info.username = clone_string(string(pw.pw_name), allocator) or_return
+				info.fields += {.Username}
+			}
+		}
+	}
+
+	if .Working_Dir in selection {
+		pinfo: darwin.proc_vnodepathinfo
+		ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo))
+		if ret > 0 {
+			assert(ret == size_of(pinfo))
+			info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return
+			info.fields += {.Working_Dir}
+		} else if err == nil {
+			err = _get_platform_error()
+		}
+	}
+
+	if .Executable_Path in selection {
+		buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = ---
+		ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer))
+		if ret > 0 {
+			info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return
+			info.fields += {.Executable_Path}
+		} else if err == nil {
+			err = _get_platform_error()
+		}
+	}
+
+	args: if selection >= { .Command_Line, .Command_Args, .Environment } {
+		mib := []i32{
+			unix.CTL_KERN,
+			unix.KERN_PROCARGS2,
+			i32(pid),
+		}
+		length: uint
+		if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK {
+			if err == nil {
+				err = _get_platform_error()
+			}
+			break args
+		}
+
+		buf := make([]byte, length, temp_allocator())
+		if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
+			if err == nil {
+				err = _get_platform_error()
+			}
+			break args
+		}
+
+		buf = buf[:length]
+
+		if len(buf) < 4 {
+			break args
+		}
+
+		// Layout isn't really documented anywhere, I deduced it to be:
+		// i32        - argc
+		// cstring    - command name (skipped)
+		// [^]byte    - couple of 0 bytes (skipped)
+		// [^]cstring - argv (up to argc entries)
+		// [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too)
+
+		argc := (^i32)(raw_data(buf))^
+		buf = buf[size_of(i32):]
+
+		{
+			command_line: [dynamic]byte
+			command_line.allocator = allocator
+
+			argv: [dynamic]string
+			argv.allocator = allocator
+
+			defer if err != nil {
+				for arg in argv { delete(arg, allocator) }
+				delete(argv)
+				delete(command_line)
+			}
+
+			_, _ = bytes.split_iterator(&buf, {0})
+			buf = bytes.trim_left(buf, {0})
+
+			first_arg := true
+			for arg in bytes.split_iterator(&buf, {0}) {
+				if .Command_Line in selection {
+					if !first_arg {
+						append(&command_line, ' ') or_return
+					}
+					append(&command_line, ..arg) or_return
+				}
+
+				if .Command_Args in selection {
+					sarg := clone_string(string(arg), allocator) or_return
+					append(&argv, sarg) or_return
+				}
+
+				first_arg = false
+				argc -= 1
+				if argc == 0 {
+					break
+				}
+			}
+
+			if .Command_Line in selection {
+				info.command_line = string(command_line[:])
+				info.fields += {.Command_Line}
+			}
+			if .Command_Args in selection {
+				info.command_args = argv[:]
+				info.fields += {.Command_Args}
+			}
+		}
+
+		if .Environment in selection {
+			environment: [dynamic]string
+			environment.allocator = allocator
+
+			defer if err != nil {
+				for entry in environment { delete(entry, allocator) }
+				delete(environment)
+			}
+
+			for entry in bytes.split_iterator(&buf, {0}) {
+				if bytes.index_byte(entry, '=') > -1 {
+					sentry := clone_string(string(entry), allocator) or_return
+					append(&environment, sentry) or_return
+				}
+			}
+
+			info.environment = environment[:]
+			info.fields += {.Environment}
+		}
+	}
+
+	// Fields were requested that we didn't add.
+	if err == nil && selection - info.fields != {} {
+		err = .Unsupported
+	}
+
+	return
+}
+
+_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
+	ret := darwin.proc_listallpids(nil, 0)
+	if ret < 0 {
+		err = _get_platform_error()
+		return
+	}
+
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+
+	buffer := make([]i32, ret, temp_allocator())
+	ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
+	if ret < 0 {
+		err = _get_platform_error()
+		return
+	}
+
+	list = make([]int, ret, allocator) or_return
+	#no_bounds_check for &entry, i in list {
+		entry = int(buffer[i])
+	}
+
+	return
+}

+ 15 - 0
core/os/os2/process_posix_other.odin

@@ -0,0 +1,15 @@
+//+private
+//+build netbsd, openbsd, freebsd
+package os2
+
+import "base:runtime"
+
+_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
+}

+ 2 - 2
core/os/os2/stat.odin

@@ -30,8 +30,8 @@ file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned:
 }
 }
 
 
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
-	for i := len(infos)-1; i >= 0; i -= 1 {
-		file_info_delete(infos[i], allocator)
+	#reverse for info in infos {
+		file_info_delete(info, allocator)
 	}
 	}
 	delete(infos, allocator)
 	delete(infos, allocator)
 }
 }

+ 2 - 0
core/os/os2/stat_linux.odin

@@ -48,6 +48,7 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
 
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 // 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) {
 _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+	assert(!is_temp(allocator))
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	name_cstr := temp_cstring(name) or_return
 
 
@@ -60,6 +61,7 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
 }
 }
 
 
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+	assert(!is_temp(allocator))
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	name_cstr := temp_cstring(name) or_return
 
 

+ 113 - 0
core/os/os2/stat_posix.odin

@@ -0,0 +1,113 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+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.inode = u64(stat.st_ino)
+	fi.size = i64(stat.st_size)
+
+	fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix._S_IFMT))
+
+	fi.type = .Undetermined
+	switch {
+	case posix.S_ISBLK(stat.st_mode):
+		fi.type = .Block_Device
+	case posix.S_ISCHR(stat.st_mode):
+		fi.type = .Character_Device
+	case posix.S_ISDIR(stat.st_mode):
+		fi.type = .Directory
+	case posix.S_ISFIFO(stat.st_mode):
+		fi.type = .Named_Pipe
+	case posix.S_ISLNK(stat.st_mode):
+		fi.type = .Symlink
+	case posix.S_ISREG(stat.st_mode):
+		fi.type = .Regular
+	case posix.S_ISSOCK(stat.st_mode):
+		fi.type = .Socket
+	}
+
+	fi.creation_time = timespec_time(stat.st_birthtimespec)
+	fi.modification_time = timespec_time(stat.st_mtim)
+	fi.access_time = timespec_time(stat.st_atim)
+
+	timespec_time :: proc(t: posix.timespec) -> time.Time {
+		return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)}
+	}
+
+	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: posix.stat_t
+	if posix.fstat(impl.fd, &stat) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	fullpath := clone_string(impl.name, allocator) or_return
+	return internal_stat(stat, fullpath), nil
+}
+
+// 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) {
+	if name == "" {
+		err = .Invalid_Path
+		return
+	}
+
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name) or_return
+
+	rcname := posix.realpath(cname)
+
+	stat: posix.stat_t
+	if posix.stat(rcname, &stat) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	fullpath := clone_string(string(rcname), 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
+	}
+
+	assert(!is_temp(allocator))
+	TEMP_ALLOCATOR_GUARD()
+	cname := temp_cstring(name) or_return
+
+	rcname := posix.realpath(cname)
+
+	stat: posix.stat_t
+	if posix.lstat(rcname, &stat) != .OK {
+		err = _get_platform_error()
+		return
+	}
+
+	fullpath := clone_string(string(rcname), allocator) or_return
+	return internal_stat(stat, fullpath), nil
+}
+
+_same_file :: proc(fi1, fi2: File_Info) -> bool {
+	return fi1.fullpath == fi2.fullpath
+}

+ 8 - 0
core/os/os2/stat_windows.odin

@@ -44,6 +44,8 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
 	if name == "" {
 	if name == "" {
 		name = "."
 		name = "."
 	}
 	}
+
+	assert(!is_temp(allocator))
 	TEMP_ALLOCATOR_GUARD()
 	TEMP_ALLOCATOR_GUARD()
 
 
 	p := win32_utf8_to_utf16(name, temp_allocator()) or_return
 	p := win32_utf8_to_utf16(name, temp_allocator()) or_return
@@ -127,7 +129,10 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
 	if n == 0 {
 	if n == 0 {
 		return "", _get_platform_error()
 		return "", _get_platform_error()
 	}
 	}
+
+	assert(!is_temp(allocator))
 	TEMP_ALLOCATOR_GUARD()
 	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)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_from_buf(buf[:n], allocator)
 	return _cleanpath_from_buf(buf[:n], allocator)
@@ -143,7 +148,10 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
 	if n == 0 {
 	if n == 0 {
 		return nil, _get_platform_error()
 		return nil, _get_platform_error()
 	}
 	}
+
+	assert(!is_temp(allocator))
 	TEMP_ALLOCATOR_GUARD()
 	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)
 	n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
 	return _cleanpath_strip_prefix(buf[:n]), nil
 	return _cleanpath_strip_prefix(buf[:n]), nil

+ 1 - 1
core/os/os2/temp_file.odin

@@ -10,7 +10,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
 // Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`.
 // Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`.
 // The filename is generated by taking a pattern, and adding a randomized string to the end.
 // The filename is generated by taking a pattern, and adding a randomized string to the end.
 // If the pattern includes an "*", the random string replaces the last "*".
 // If the pattern includes an "*", the random string replaces the last "*".
-// If `dir` is an empty tring, `temp_directory()` will be used.
+// If `dir` is an empty string, `temp_directory()` will be used.
 //
 //
 // The caller must `close` the file once finished with.
 // The caller must `close` the file once finished with.
 @(require_results)
 @(require_results)

+ 20 - 0
core/os/os2/temp_file_posix.odin

@@ -0,0 +1,20 @@
+//+private
+//+build darwin, netbsd, freebsd, openbsd
+package os2
+
+import "base:runtime"
+
+@(require)
+import "core:sys/posix"
+
+_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
+	if tmp, ok := _lookup_env("TMPDIR", allocator); ok {
+		return tmp, nil
+	}
+
+	when #defined(posix.P_tmpdir) {
+		return clone_string(posix.P_tmpdir, allocator)
+	}
+
+	return clone_string("/tmp/", allocator)
+}

+ 142 - 0
core/sys/darwin/proc.odin

@@ -0,0 +1,142 @@
+package darwin
+
+import "base:intrinsics"
+
+import "core:sys/posix"
+
+foreign import lib "system:System.framework"
+
+// Incomplete bindings to the proc API on MacOS, add to when needed.
+
+foreign lib {
+	proc_pidinfo     :: proc(pid: posix.pid_t, flavor: PID_Info_Flavor, arg: i64, buffer: rawptr, buffersize: i32) -> i32 ---
+	proc_pidpath     :: proc(pid: posix.pid_t, buffer: [^]byte, buffersize: u32) -> i32 ---
+	proc_listallpids :: proc(buffer: [^]i32, buffersize: i32) -> i32 ---
+}
+
+MAXCOMLEN :: 16
+
+proc_bsdinfo :: struct {
+	pbi_flags:        PBI_Flags,
+	pbi_status:       u32,
+	pbi_xstatus:      u32,
+	pbi_pid:          u32,
+	pbi_ppid:         u32,
+	pbi_uid:          posix.uid_t,
+	pbi_gid:          posix.gid_t,
+	pbi_ruid:         posix.uid_t,
+	pbi_rgid:         posix.gid_t,
+	pbi_svuid:        posix.uid_t,
+	pbi_svgid:        posix.gid_t,
+	rfu_1:            u32,
+	pbi_comm:         [MAXCOMLEN]byte `fmt:"s,0"`,
+	pbi_name:         [2 * MAXCOMLEN]byte `fmt:"s,0"`,
+	pbi_nfiles:       u32,
+	pbi_pgid:         u32,
+	pbi_pjobc:        u32,
+	e_tdev:           u32,
+	e_tpgid:          u32,
+	pbi_nice:         i32,
+	pbi_start_tvsec:  u64,
+	pbi_start_tvusec: u64,
+}
+
+proc_bsdshortinfo :: struct {
+	pbsi_pid:    u32,
+	pbsi_ppid:   u32,
+	pbsi_pgid:   u32,
+	pbsi_status: u32,
+	pbsi_comm:   [MAXCOMLEN]byte `fmt:"s,0"`,
+	pbsi_flags:  PBI_Flags,
+	pbsi_uid:    posix.uid_t,
+	pbsi_gid:    posix.gid_t,
+	pbsi_ruid:   posix.uid_t,
+	pbsi_rgid:   posix.gid_t,
+	pbsi_svuid:  posix.uid_t,
+	pbsi_svgid:  posix.gid_t,
+	pbsi_rfu:    u32,
+}
+
+proc_vnodepathinfo :: struct {
+	pvi_cdir: vnode_info_path,
+	pvi_rdir: vnode_info_path,
+}
+
+vnode_info_path :: struct {
+	vip_vi:   vnode_info,
+	vip_path: [posix.PATH_MAX]byte,
+}
+
+vnode_info :: struct {
+	vi_stat: vinfo_stat,
+	vi_type: i32,
+	vi_pad:  i32,
+	vi_fsid: fsid_t,
+}
+
+vinfo_stat :: struct {
+	vst_dev:           u32,
+	vst_mode:          u16,
+	vst_nlink:         u16,
+	vst_ino:           u64,
+	vst_uid:           posix.uid_t,
+	vst_gid:           posix.gid_t,
+	vst_atime:         i64,
+	vst_atimensec:     i64,
+	vst_mtime:         i64,
+	vst_mtimensec:     i64,
+	vst_ctime:         i64,
+	vst_ctimensec:     i64,
+	vst_birthtime:     i64,
+	vst_birthtimensec: i64,
+	vst_size:          posix.off_t,
+	vst_blocks:        i64,
+	vst_blksize:       i32,
+	vst_flags:         u32,
+	vst_gen:           u32,
+	vst_rdev:          u32,
+	vst_qspare:        [2]i64,
+}
+
+fsid_t :: distinct [2]i32
+
+PBI_Flag_Bits :: enum u32 {
+	SYSTEM      = intrinsics.constant_log2(0x0001),
+	TRACED      = intrinsics.constant_log2(0x0002),
+	INEXIT      = intrinsics.constant_log2(0x0004),
+	PWAIT       = intrinsics.constant_log2(0x0008),
+	LP64        = intrinsics.constant_log2(0x0010),
+	SLEADER     = intrinsics.constant_log2(0x0020),
+	CTTY        = intrinsics.constant_log2(0x0040),
+	CONTROLT    = intrinsics.constant_log2(0x0080),
+	THCWD       = intrinsics.constant_log2(0x0100),
+	PC_THROTTLE = intrinsics.constant_log2(0x0200),
+	PC_SUSP     = intrinsics.constant_log2(0x0400),
+	PC_KILL     = intrinsics.constant_log2(0x0600),
+	PA_THROTTLE = intrinsics.constant_log2(0x0800),
+	PA_SUSP     = intrinsics.constant_log2(0x1000),
+	PA_PSUGID   = intrinsics.constant_log2(0x2000),
+	EXEC        = intrinsics.constant_log2(0x4000),
+}
+PBI_Flags :: bit_set[PBI_Flag_Bits; u32]
+
+PID_Info_Flavor :: enum i32 {
+	LISTFDS = 1,
+	TASKALLINFO,
+	BSDINFO,
+	TASKINFO,
+	THREADINFO,
+	LISTTHREADS,
+	REGIONINFO,
+	REGIONPATHINFO,
+	VNODEPATHINFO,
+	THREADPATHINFO,
+	PATHINFO,
+	WORKQUEUEINFO,
+	SHORTBSDINFO,
+	LISTFILEPORTS,
+	THREADID64INFO,
+	RUSAGE,
+}
+
+PIDPATHINFO_MAXSIZE :: 4*posix.PATH_MAX

+ 6 - 3
core/sys/posix/unistd.odin

@@ -211,7 +211,7 @@ foreign lib {
 
 
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]]
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]]
 	*/
 	*/
-	_exit :: proc(status: c.int) ---
+	_exit :: proc(status: c.int) -> ! ---
 
 
 	/*
 	/*
 	The exec family of functions shall replace the current process image with a new process image.
 	The exec family of functions shall replace the current process image with a new process image.
@@ -416,8 +416,11 @@ foreign lib {
 			}
 			}
 
 
 			cwd = posix.getcwd(raw_data(buf), len(buf))
 			cwd = posix.getcwd(raw_data(buf), len(buf))
-			if errno := posix.errno(); cwd == nil && errno != .ERANGE {
-				fmt.panicf("getcwd failure: %v", posix.strerror(errno))
+			if cwd == nil {
+				errno := posix.errno()
+				if errno != .ERANGE {
+					fmt.panicf("getcwd failure: %v", posix.strerror(errno))
+				}
 			}
 			}
 		}
 		}
 
 

+ 3 - 1
core/sys/unix/sysctl_darwin.odin

@@ -67,6 +67,8 @@ CTL_KERN    :: 1
 	KERN_VERSION   :: 4  // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64
 	KERN_VERSION   :: 4  // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64
 	KERN_OSRELDATE :: 26 // i32: OS release date
 	KERN_OSRELDATE :: 26 // i32: OS release date
 	KERN_OSVERSION :: 65 // Build number, e.g. 21F79
 	KERN_OSVERSION :: 65 // Build number, e.g. 21F79
+	KERN_PROCARGS  :: 38
+	KERN_PROCARGS2 :: 49
 CTL_VM      :: 2
 CTL_VM      :: 2
 CTL_VFS     :: 3
 CTL_VFS     :: 3
 CTL_NET     :: 4
 CTL_NET     :: 4
@@ -82,4 +84,4 @@ CTL_HW      :: 6
 	HW_AVAILCPU     :: 25 /* int: number of available CPUs */
 	HW_AVAILCPU     :: 25 /* int: number of available CPUs */
 
 
 CTL_MACHDEP :: 7
 CTL_MACHDEP :: 7
-CTL_USER    :: 8
+CTL_USER    :: 8

+ 2 - 0
core/sys/unix/sysctl_freebsd.odin

@@ -23,6 +23,8 @@ CTL_KERN   :: 1
 	KERN_OSRELEASE :: 2
 	KERN_OSRELEASE :: 2
 	KERN_OSREV     :: 3
 	KERN_OSREV     :: 3
 	KERN_VERSION   :: 4
 	KERN_VERSION   :: 4
+	KERN_PROC      :: 14
+		KERN_PROC_PATHNAME :: 12
 CTL_VM     :: 2
 CTL_VM     :: 2
 CTL_VFS    :: 3
 CTL_VFS    :: 3
 CTL_NET    :: 4
 CTL_NET    :: 4