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

This commit is contained in:
Laytan Laats
2024-08-01 21:31:30 +02:00
parent ff0ca0bd53
commit a4d459f651
23 changed files with 1535 additions and 17 deletions

View File

@@ -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
return tmp, loc
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
core/os/os2/file_posix.odin Normal file
View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -43,7 +43,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
}
@(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())
}

127
core/os/os2/path_posix.odin Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -14,7 +14,7 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi
*/
args := get_args()
@(private="file", require_results)
@(private="file")
get_args :: proc() -> []string {
result := make([]string, len(runtime.args__), heap_allocator())
for rt_arg, i in runtime.args__ {
@@ -131,6 +131,8 @@ Process_Info_Field :: enum {
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()`
procedure.
@@ -166,8 +168,8 @@ Process_Info :: struct {
a process given by `pid`.
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
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.
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
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
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
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) {
delete(pi.executable_path, allocator)
delete(pi.command_line, allocator)
for a in pi.command_args {
delete(a, allocator)
}
delete(pi.command_args, allocator)
for s in pi.environment {
delete(s, allocator)
}
delete(pi.environment, allocator)
delete(pi.working_dir, allocator)
delete(pi.username, allocator)
}
/*

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {
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)
}

View File

@@ -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
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
assert(!is_temp(allocator))
TEMP_ALLOCATOR_GUARD()
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) {
assert(!is_temp(allocator))
TEMP_ALLOCATOR_GUARD()
name_cstr := temp_cstring(name) or_return

113
core/os/os2/stat_posix.odin Normal file
View File

@@ -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
}

View File

@@ -44,6 +44,8 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
if name == "" {
name = "."
}
assert(!is_temp(allocator))
TEMP_ALLOCATOR_GUARD()
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 {
return "", _get_platform_error()
}
assert(!is_temp(allocator))
TEMP_ALLOCATOR_GUARD()
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)
@@ -143,7 +148,10 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
if n == 0 {
return nil, _get_platform_error()
}
assert(!is_temp(allocator))
TEMP_ALLOCATOR_GUARD()
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

View File

@@ -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`.
// 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 `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.
@(require_results)

View File

@@ -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
core/sys/darwin/proc.odin Normal file
View File

@@ -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

View File

@@ -211,7 +211,7 @@ foreign lib {
[[ 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.
@@ -416,8 +416,11 @@ foreign lib {
}
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))
}
}
}

View File

@@ -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_OSRELDATE :: 26 // i32: OS release date
KERN_OSVERSION :: 65 // Build number, e.g. 21F79
KERN_PROCARGS :: 38
KERN_PROCARGS2 :: 49
CTL_VM :: 2
CTL_VFS :: 3
CTL_NET :: 4
@@ -82,4 +84,4 @@ CTL_HW :: 6
HW_AVAILCPU :: 25 /* int: number of available CPUs */
CTL_MACHDEP :: 7
CTL_USER :: 8
CTL_USER :: 8

View File

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