From efe68c2e24e0a38e591f146822ed93904e4193d7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 27 Jul 2024 04:20:03 +0200 Subject: [PATCH 01/31] posix: add package --- core/c/libc/complex.odin | 4 +- core/c/libc/errno.odin | 2 +- core/c/libc/setjmp.odin | 19 +- core/c/libc/stdio.odin | 43 +- core/c/libc/stdlib.odin | 21 +- core/c/libc/string.odin | 2 +- core/c/libc/time.odin | 32 +- core/mem/virtual/virtual_darwin.odin | 151 +- core/os/dir_unix.odin | 1 + core/os/os_darwin.odin | 72 +- core/os/os_freebsd.odin | 10 +- core/os/os_linux.odin | 6 +- core/os/os_netbsd.odin | 8 +- core/os/os_openbsd.odin | 6 +- core/path/filepath/path_unix.odin | 7 +- core/prof/spall/spall_unix.odin | 33 +- core/sys/posix/README.md | 57 + core/sys/posix/arpa_inet.odin | 58 + core/sys/posix/dirent.odin | 205 +++ core/sys/posix/dlfcn.odin | 115 ++ core/sys/posix/errno.odin | 373 ++++ core/sys/posix/fcntl.odin | 415 +++++ core/sys/posix/fnmatch.odin | 58 + core/sys/posix/glob.odin | 179 ++ core/sys/posix/grp.odin | 130 ++ core/sys/posix/iconv.odin | 50 + core/sys/posix/langinfo.odin | 285 +++ core/sys/posix/libgen.odin | 74 + core/sys/posix/limits.odin | 459 +++++ core/sys/posix/locale.odin | 93 + core/sys/posix/monetary.odin | 42 + core/sys/posix/net_if.odin | 60 + core/sys/posix/netdb.odin | 443 +++++ core/sys/posix/netinet_in.odin | 199 +++ core/sys/posix/netinet_tcp.odin | 11 + core/sys/posix/poll.odin | 78 + core/sys/posix/posix.odin | 26 + core/sys/posix/pthread.odin | 518 ++++++ core/sys/posix/pwd.odin | 168 ++ core/sys/posix/sched.odin | 105 ++ core/sys/posix/setjmp.odin | 58 + core/sys/posix/signal.odin | 1131 ++++++++++++ core/sys/posix/stdio.odin | 332 ++++ core/sys/posix/stdlib.odin | 450 +++++ core/sys/posix/string.odin | 47 + core/sys/posix/sys_ipc.odin | 89 + core/sys/posix/sys_mman.odin | 229 +++ core/sys/posix/sys_msg.odin | 161 ++ core/sys/posix/sys_resource.odin | 152 ++ core/sys/posix/sys_select.odin | 120 ++ core/sys/posix/sys_sem.odin | 132 ++ core/sys/posix/sys_shm.odin | 146 ++ core/sys/posix/sys_socket.odin | 491 ++++++ core/sys/posix/sys_stat.odin | 540 ++++++ core/sys/posix/sys_statvfs.odin | 135 ++ core/sys/posix/sys_time.odin | 82 + core/sys/posix/sys_times.odin | 38 + core/sys/posix/sys_uio.odin | 42 + core/sys/posix/sys_un.odin | 17 + core/sys/posix/sys_utsname.odin | 55 + core/sys/posix/sys_wait.odin | 380 ++++ core/sys/posix/termios.odin | 475 +++++ core/sys/posix/time.odin | 234 +++ core/sys/posix/ulimit.odin | 43 + core/sys/posix/unistd.odin | 1914 +++++++++++++++++++++ core/sys/posix/utime.odin | 36 + core/sys/posix/wordexp.odin | 107 ++ core/sys/unix/pthread_netbsd.odin | 2 +- core/sys/unix/pthread_unix.odin | 5 + core/sys/unix/time_unix.odin | 83 - core/time/time_linux.odin | 38 + core/time/time_unix.odin | 42 +- examples/all/all_posix.odin | 6 + src/big_int.cpp | 4 + src/check_builtin.cpp | 17 + src/check_decl.cpp | 20 +- src/checker_builtin_procs.hpp | 4 + tests/core/normal.odin | 1 + tests/core/sys/posix/posix.odin | 211 +++ tests/core/sys/posix/structs.odin | 127 ++ tests/core/sys/posix/structs/.gitignore | 2 + tests/core/sys/posix/structs/structs.c | 103 ++ tests/core/sys/posix/structs/structs.odin | 74 + 83 files changed, 12665 insertions(+), 328 deletions(-) create mode 100644 core/sys/posix/README.md create mode 100644 core/sys/posix/arpa_inet.odin create mode 100644 core/sys/posix/dirent.odin create mode 100644 core/sys/posix/dlfcn.odin create mode 100644 core/sys/posix/errno.odin create mode 100644 core/sys/posix/fcntl.odin create mode 100644 core/sys/posix/fnmatch.odin create mode 100644 core/sys/posix/glob.odin create mode 100644 core/sys/posix/grp.odin create mode 100644 core/sys/posix/iconv.odin create mode 100644 core/sys/posix/langinfo.odin create mode 100644 core/sys/posix/libgen.odin create mode 100644 core/sys/posix/limits.odin create mode 100644 core/sys/posix/locale.odin create mode 100644 core/sys/posix/monetary.odin create mode 100644 core/sys/posix/net_if.odin create mode 100644 core/sys/posix/netdb.odin create mode 100644 core/sys/posix/netinet_in.odin create mode 100644 core/sys/posix/netinet_tcp.odin create mode 100644 core/sys/posix/poll.odin create mode 100644 core/sys/posix/posix.odin create mode 100644 core/sys/posix/pthread.odin create mode 100644 core/sys/posix/pwd.odin create mode 100644 core/sys/posix/sched.odin create mode 100644 core/sys/posix/setjmp.odin create mode 100644 core/sys/posix/signal.odin create mode 100644 core/sys/posix/stdio.odin create mode 100644 core/sys/posix/stdlib.odin create mode 100644 core/sys/posix/string.odin create mode 100644 core/sys/posix/sys_ipc.odin create mode 100644 core/sys/posix/sys_mman.odin create mode 100644 core/sys/posix/sys_msg.odin create mode 100644 core/sys/posix/sys_resource.odin create mode 100644 core/sys/posix/sys_select.odin create mode 100644 core/sys/posix/sys_sem.odin create mode 100644 core/sys/posix/sys_shm.odin create mode 100644 core/sys/posix/sys_socket.odin create mode 100644 core/sys/posix/sys_stat.odin create mode 100644 core/sys/posix/sys_statvfs.odin create mode 100644 core/sys/posix/sys_time.odin create mode 100644 core/sys/posix/sys_times.odin create mode 100644 core/sys/posix/sys_uio.odin create mode 100644 core/sys/posix/sys_un.odin create mode 100644 core/sys/posix/sys_utsname.odin create mode 100644 core/sys/posix/sys_wait.odin create mode 100644 core/sys/posix/termios.odin create mode 100644 core/sys/posix/time.odin create mode 100644 core/sys/posix/ulimit.odin create mode 100644 core/sys/posix/unistd.odin create mode 100644 core/sys/posix/utime.odin create mode 100644 core/sys/posix/wordexp.odin delete mode 100644 core/sys/unix/time_unix.odin create mode 100644 core/time/time_linux.odin create mode 100644 examples/all/all_posix.odin create mode 100644 tests/core/sys/posix/posix.odin create mode 100644 tests/core/sys/posix/structs.odin create mode 100644 tests/core/sys/posix/structs/.gitignore create mode 100644 tests/core/sys/posix/structs/structs.c create mode 100644 tests/core/sys/posix/structs/structs.odin diff --git a/core/c/libc/complex.odin b/core/c/libc/complex.odin index 81d2b75be..98fd7b1bb 100644 --- a/core/c/libc/complex.odin +++ b/core/c/libc/complex.odin @@ -47,8 +47,8 @@ foreign libc { clogf :: proc(z: complex_float) -> complex_float --- // 7.3.8 Power and absolute-value functions - cabs :: proc(z: complex_double) -> complex_double --- - cabsf :: proc(z: complex_float) -> complex_float --- + cabs :: proc(z: complex_double) -> double --- + cabsf :: proc(z: complex_float) -> float --- cpow :: proc(x, y: complex_double) -> complex_double --- cpowf :: proc(x, y: complex_float) -> complex_float --- csqrt :: proc(z: complex_double) -> complex_double --- diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index d28a24f56..843b2f1b6 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -102,6 +102,6 @@ when ODIN_OS == .Haiku { // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like // it actually is. -errno :: #force_inline proc() -> ^int { +errno :: #force_inline proc "contextless" () -> ^int { return _get_errno() } diff --git a/core/c/libc/setjmp.odin b/core/c/libc/setjmp.odin index 68f5ac010..101b614b3 100644 --- a/core/c/libc/setjmp.odin +++ b/core/c/libc/setjmp.odin @@ -32,24 +32,21 @@ when ODIN_OS == .Windows { // the RDX register will contain zero and correctly set the flag to disable // stack unwinding. @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- + setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- } } else { @(default_calling_convention="c") foreign libc { // 7.13.1 Save calling environment - // - // NOTE(dweiler): C11 requires setjmp be a macro, which means it won't - // necessarily export a symbol named setjmp but rather _setjmp in the case - // of musl, glibc, BSD libc, and msvcrt. - @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf) -> int --- + @(link_name=LSETJMP) + setjmp :: proc(env: ^jmp_buf) -> int --- } } @(default_calling_convention="c") foreign libc { // 7.13.2 Restore calling environment + @(link_name=LLONGJMP) longjmp :: proc(env: ^jmp_buf, val: int) -> ! --- } @@ -64,3 +61,11 @@ foreign libc { // The choice of 4096 bytes for storage of this type is more than enough on all // relevant platforms. jmp_buf :: struct #align(16) { _: [4096]char, } + +when ODIN_OS == .NetBSD { + @(private) LSETJMP :: "__setjmp14" + @(private) LLONGJMP :: "__longjmp14" +} else { + @(private) LSETJMP :: "setjmp" + @(private) LLONGJMP :: "longjmp" +} diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 3e1d0f5a2..4be00ff0b 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -17,6 +17,12 @@ when ODIN_OS == .Windows { FILE :: struct {} +Whence :: enum int { + SET = SEEK_SET, + CUR = SEEK_CUR, + END = SEEK_END, +} + // MSVCRT compatible. when ODIN_OS == .Windows { _IOFBF :: 0x0000 @@ -101,6 +107,8 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + foreign libc { __sF: [3]FILE } @@ -128,6 +136,8 @@ when ODIN_OS == .FreeBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + foreign libc { @(link_name="__stderrp") stderr: ^FILE @(link_name="__stdinp") stdin: ^FILE @@ -195,10 +205,21 @@ when ODIN_OS == .Haiku { } } +when ODIN_OS == .NetBSD { + @(private) LRENAME :: "__posix_rename" + @(private) LFGETPOS :: "__fgetpos50" + @(private) LFSETPOS :: "__fsetpos50" +} else { + @(private) LRENAME :: "rename" + @(private) LFGETPOS :: "fgetpos" + @(private) LFSETPOS :: "fsetpos" +} + @(default_calling_convention="c") foreign libc { // 7.21.4 Operations on files remove :: proc(filename: cstring) -> int --- + @(link_name=LRENAME) rename :: proc(old, new: cstring) -> int --- tmpfile :: proc() -> ^FILE --- tmpnam :: proc(s: [^]char) -> [^]char --- @@ -240,8 +261,10 @@ foreign libc { fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t --- // 7.21.9 File positioning functions + @(link_name=LFGETPOS) fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- - fseek :: proc(stream: ^FILE, offset: long, whence: int) -> int --- + fseek :: proc(stream: ^FILE, offset: long, whence: Whence) -> int --- + @(link_name=LFSETPOS) fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- ftell :: proc(stream: ^FILE) -> long --- rewind :: proc(stream: ^FILE) --- @@ -288,11 +311,11 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, unknown_or_eof(file) } - if fseek(file, long(offset), SEEK_SET) != 0 { + if fseek(file, long(offset), .SET) != 0 { return 0, unknown_or_eof(file) } - defer fseek(file, long(curr), SEEK_SET) + defer fseek(file, long(curr), .SET) n = i64(fread(raw_data(p), size_of(byte), len(p), file)) if n == 0 { err = unknown_or_eof(file) } @@ -307,17 +330,21 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, unknown_or_eof(file) } - if fseek(file, long(offset), SEEK_SET) != 0 { + if fseek(file, long(offset), .SET) != 0 { return 0, unknown_or_eof(file) } - defer fseek(file, long(curr), SEEK_SET) + defer fseek(file, long(curr), .SET) n = i64(fwrite(raw_data(p), size_of(byte), len(p), file)) if n == 0 { err = unknown_or_eof(file) } case .Seek: - if fseek(file, long(offset), int(whence)) != 0 { + #assert(int(Whence.SET) == int(io.Seek_From.Start)) + #assert(int(Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(Whence.END) == int(io.Seek_From.End)) + + if fseek(file, long(offset), Whence(whence)) != 0 { return 0, unknown_or_eof(file) } @@ -326,9 +353,9 @@ to_stream :: proc(file: ^FILE) -> io.Stream { if curr == -1 { return 0, unknown_or_eof(file) } - defer fseek(file, curr, SEEK_SET) + defer fseek(file, curr, .SET) - if fseek(file, 0, SEEK_END) != 0 { + if fseek(file, 0, .END) != 0 { return 0, unknown_or_eof(file) } diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index d797b8746..08c6fa6f0 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -40,10 +40,9 @@ when ODIN_OS == .Linux { } -when ODIN_OS == .Darwin { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { RAND_MAX :: 0x7fffffff - // GLIBC and MUSL only @(private="file") @(default_calling_convention="c") foreign libc { @@ -55,6 +54,20 @@ when ODIN_OS == .Darwin { } } +when ODIN_OS == .NetBSD { + RAND_MAX :: 0x7fffffff + + @(private="file") + @(default_calling_convention="c") + foreign libc { + __mb_cur_max: size_t + } + + MB_CUR_MAX :: #force_inline proc() -> size_t { + return __mb_cur_max + } +} + // C does not declare what these values should be, as an implementation is free // to use any two distinct values it wants to indicate success or failure. // However, nobody actually does and everyone appears to have agreed upon these @@ -99,7 +112,7 @@ foreign libc { at_quick_exit :: proc(func: proc "c" ()) -> int --- exit :: proc(status: int) -> ! --- _Exit :: proc(status: int) -> ! --- - getenv :: proc(name: cstring) -> [^]char --- + getenv :: proc(name: cstring) -> cstring --- quick_exit :: proc(status: int) -> ! --- system :: proc(cmd: cstring) -> int --- @@ -150,4 +163,4 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) { } else { free(ptr) } -} \ No newline at end of file +} diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index e6a959f7b..cde9c7e6b 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -40,7 +40,7 @@ foreign libc { strtok :: proc(s1: [^]char, s2: cstring) -> [^]char --- // 7.24.6 Miscellaneous functions - strerror :: proc(errnum: int) -> [^]char --- + strerror :: proc(errnum: int) -> cstring --- strlen :: proc(s: cstring) -> size_t --- } memset :: proc "c" (s: rawptr, c: int, n: size_t) -> rawptr { diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 924cf4aec..21859c602 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -50,30 +50,56 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS = foreign libc { // 7.27.2 Time manipulation functions clock :: proc() -> clock_t --- + @(link_name=LDIFFTIME) difftime :: proc(time1, time2: time_t) -> double --- + @(link_name=LMKTIME) mktime :: proc(timeptr: ^tm) -> time_t --- + @(link_name=LTIME) time :: proc(timer: ^time_t) -> time_t --- timespec_get :: proc(ts: ^timespec, base: int) -> int --- // 7.27.3 Time conversion functions asctime :: proc(timeptr: ^tm) -> [^]char --- + @(link_name=LCTIME) ctime :: proc(timer: ^time_t) -> [^]char --- + @(link_name=LGMTIME) gmtime :: proc(timer: ^time_t) -> ^tm --- + @(link_name=LLOCALTIME) localtime :: proc(timer: ^time_t) -> ^tm --- strftime :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t --- } + when ODIN_OS == .NetBSD { + @(private) LDIFFTIME :: "__difftime50" + @(private) LMKTIME :: "__mktime50" + @(private) LTIME :: "__time50" + @(private) LCTIME :: "__ctime50" + @(private) LGMTIME :: "__gmtime50" + @(private) LLOCALTIME :: "__localtime50" + } else { + @(private) LDIFFTIME :: "difftime" + @(private) LMKTIME :: "mktime" + @(private) LTIME :: "ltime" + @(private) LCTIME :: "ctime" + @(private) LGMTIME :: "gmtime" + @(private) LLOCALTIME :: "localtime" + } + when ODIN_OS == .OpenBSD { CLOCKS_PER_SEC :: 100 } else { CLOCKS_PER_SEC :: 1000000 } - TIME_UTC :: 1 + TIME_UTC :: 1 - time_t :: distinct i64 + time_t :: distinct i64 - clock_t :: long + when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + clock_t :: distinct int32_t + } else { + clock_t :: distinct long + } timespec :: struct { tv_sec: time_t, diff --git a/core/mem/virtual/virtual_darwin.odin b/core/mem/virtual/virtual_darwin.odin index d2e3c8b51..9098866d0 100644 --- a/core/mem/virtual/virtual_darwin.odin +++ b/core/mem/virtual/virtual_darwin.odin @@ -2,141 +2,45 @@ //+private package mem_virtual -foreign import libc "system:System.framework" -import "core:c" +import "core:sys/posix" -PROT_NONE :: 0x0 /* [MC2] no permissions */ -PROT_READ :: 0x1 /* [MC2] pages can be read */ -PROT_WRITE :: 0x2 /* [MC2] pages can be written */ -PROT_EXEC :: 0x4 /* [MC2] pages can be executed */ - -// Sharing options -MAP_SHARED :: 0x1 /* [MF|SHM] share changes */ -MAP_PRIVATE :: 0x2 /* [MF|SHM] changes are private */ - -// Other flags -MAP_FIXED :: 0x0010 /* [MF|SHM] interpret addr exactly */ -MAP_RENAME :: 0x0020 /* Sun: rename private pages to file */ -MAP_NORESERVE :: 0x0040 /* Sun: don't reserve needed swap area */ -MAP_RESERVED0080 :: 0x0080 /* previously unimplemented MAP_INHERIT */ -MAP_NOEXTEND :: 0x0100 /* for MAP_FILE, don't change file size */ -MAP_HASSEMAPHORE :: 0x0200 /* region may contain semaphores */ -MAP_NOCACHE :: 0x0400 /* don't cache pages for this mapping */ -MAP_JIT :: 0x0800 /* Allocate a region that will be used for JIT purposes */ - -// Mapping type -MAP_FILE :: 0x0000 /* map from file (default) */ -MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ - - -/* - * The MAP_RESILIENT_* flags can be used when the caller wants to map some - * possibly unreliable memory and be able to access it safely, possibly - * getting the wrong contents rather than raising any exception. - * For safety reasons, such mappings have to be read-only (PROT_READ access - * only). - * - * MAP_RESILIENT_CODESIGN: - * accessing this mapping will not generate code-signing violations, - * even if the contents are tainted. - * MAP_RESILIENT_MEDIA: - * accessing this mapping will not generate an exception if the contents - * are not available (unreachable removable or remote media, access beyond - * end-of-file, ...). Missing contents will be replaced with zeroes. - */ -MAP_RESILIENT_CODESIGN :: 0x2000 /* no code-signing failures */ -MAP_RESILIENT_MEDIA :: 0x4000 /* no backing-store failures */ - -MAP_32BIT :: 0x8000 /* Return virtual addresses <4G only */ - -// Flags used to support translated processes. -MAP_TRANSLATED_ALLOW_EXECUTE :: 0x20000 /* allow execute in translated processes */ -MAP_UNIX03 :: 0x40000 /* UNIX03 compliance */ - -// Process memory locking -MCL_CURRENT :: 0x0001 /* [ML] Lock only current memory */ -MCL_FUTURE :: 0x0002 /* [ML] Lock all future memory as well */ - -MADV_NORMAL :: 0 /* [MC1] no further special treatment */ -MADV_RANDOM :: 1 /* [MC1] expect random page refs */ -MADV_SEQUENTIAL :: 2 /* [MC1] expect sequential page refs */ -MADV_WILLNEED :: 3 /* [MC1] will need these pages */ -MADV_DONTNEED :: 4 /* [MC1] dont need these pages */ -MADV_FREE :: 5 /* pages unneeded, discard contents */ -MADV_ZERO_WIRED_PAGES :: 6 /* zero the wired pages that have not been unwired before the entry is deleted */ -MADV_FREE_REUSABLE :: 7 /* pages can be reused (by anyone) */ -MADV_FREE_REUSE :: 8 /* caller wants to reuse those pages */ -MADV_CAN_REUSE :: 9 -MADV_PAGEOUT :: 10 /* page out now (internal only) */ - -// msync() flags -MS_ASYNC :: 0x0001 /* [MF|SIO] return immediately */ -MS_INVALIDATE :: 0x0002 /* [MF|SIO] invalidate all cached data */ -MS_SYNC :: 0x0010 /* [MF|SIO] msync synchronously */ -MS_KILLPAGES :: 0x0004 /* invalidate pages, leave mapped */ -MS_DEACTIVATE :: 0x0008 /* deactivate pages, leave mapped */ - -// Return bits from mincore -MINCORE_INCORE :: 0x1 /* Page is incore */ -MINCORE_REFERENCED :: 0x2 /* Page has been referenced by us */ -MINCORE_MODIFIED :: 0x4 /* Page has been modified by us */ -MINCORE_REFERENCED_OTHER :: 0x8 /* Page has been referenced */ -MINCORE_MODIFIED_OTHER :: 0x10 /* Page has been modified */ -MINCORE_PAGED_OUT :: 0x20 /* Page has been paged out */ -MINCORE_COPIED :: 0x40 /* Page has been copied */ -MINCORE_ANONYMOUS :: 0x80 /* Page belongs to an anonymous object */ - -// Allocation failure result -MAP_FAILED : rawptr = rawptr(~uintptr(0)) - -foreign libc { - @(link_name="mlockall") _mlockall :: proc(flags: c.int) -> c.int --- - @(link_name="munlockall") _munlockall :: proc() -> c.int --- - @(link_name="mlock") _mlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="mmap") _mmap :: proc(addr: rawptr, len: c.size_t, prot: c.int, flags: c.int, fd: c.int, offset: int) -> rawptr --- - @(link_name="mprotect") _mprotect :: proc(addr: rawptr, len: c.size_t, prot: c.int) -> c.int --- - @(link_name="msync") _msync :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munlock") _munlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munmap") _munmap :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="shm_open") _shm_open :: proc(name: cstring, oflag: c.int, #c_vararg args: ..any) -> c.int --- - @(link_name="shm_unlink") _shm_unlink :: proc(name: cstring) -> c.int --- - @(link_name="posix_madvise") _posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="madvise") _madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="mincore") _mincore :: proc(addr: rawptr, len: c.size_t, vec: cstring) -> c.int --- - @(link_name="minherit") _minherit :: proc(addr: rawptr, len: c.size_t, inherit: c.int) -> c.int --- -} +MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ +MADV_FREE :: 5 /* pages unneeded, discard contents */ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - result := _mmap(nil, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) - if result == MAP_FAILED { + flags := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS) + result := posix.mmap(nil, size, {}, flags) + if result == posix.MAP_FAILED { return nil, .Out_Of_Memory } + return ([^]byte)(uintptr(result))[:size], nil } _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { - result := _mprotect(data, size, PROT_READ|PROT_WRITE) - if result != 0 { + if posix.mprotect(data, size, { .READ, .WRITE }) != .OK { return .Out_Of_Memory } + return nil } + _decommit :: proc "contextless" (data: rawptr, size: uint) { - _mprotect(data, size, PROT_NONE) - _madvise(data, size, MADV_FREE) + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) } + _release :: proc "contextless" (data: rawptr, size: uint) { - _munmap(data, size) + posix.munmap(data, size) } + _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { - pflags: c.int - pflags = PROT_NONE - if .Read in flags { pflags |= PROT_READ } - if .Write in flags { pflags |= PROT_WRITE } - if .Execute in flags { pflags |= PROT_EXEC } - err := _mprotect(data, size, pflags) - return err == 0 + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Protect_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Protect_Flag.Write)) + #assert(i32(posix.Prot_Flag_Bits.EXEC) == i32(Protect_Flag.Execute)) + + return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK } @@ -149,16 +53,11 @@ _platform_memory_init :: proc() { _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { - prot, mflags: c.int - if .Read in flags { - prot |= PROT_READ - } - if .Write in flags { - prot |= PROT_WRITE - } - mflags |= MAP_SHARED - addr := _mmap(nil, c.size_t(size), prot, mflags, i32(fd), 0) - if addr == nil { + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write)) + + addr := posix.mmap(nil, uint(size), transmute(posix.Prot_Flags)flags, { .SHARED }, posix.FD(fd)) + if addr == posix.MAP_FAILED || addr == nil { return nil, .Map_Failure } return ([^]byte)(addr)[:size], nil diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index b472e89b7..b0e8e0732 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -1,6 +1,7 @@ //+build darwin, linux, netbsd, freebsd, openbsd package os +import "base:runtime" import "core:strings" @(require_results) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 0644ca645..940c0294e 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -584,7 +584,7 @@ F_GETPATH :: 50 // return the full path of the fd foreign libc { @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @@ -628,23 +628,23 @@ foreign libc { @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(buf: cstring, mode: u16) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - @(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int --- - @(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int --- - @(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int --- - @(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="getsockopt") _unix_getsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- - @(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- - @(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int --- + @(link_name="socket") _unix_socket :: proc(domain: c.int, type: c.int, protocol: c.int) -> c.int --- + @(link_name="listen") _unix_listen :: proc(socket: c.int, backlog: c.int) -> c.int --- + @(link_name="accept") _unix_accept :: proc(socket: c.int, addr: rawptr, addr_len: rawptr) -> c.int --- + @(link_name="connect") _unix_connect :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="bind") _unix_bind :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="setsockopt") _unix_setsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: socklen_t) -> c.int --- + @(link_name="getsockopt") _unix_getsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: ^socklen_t) -> c.int --- + @(link_name="recvfrom") _unix_recvfrom :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- + @(link_name="recv") _unix_recv :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="sendto") _unix_sendto :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- + @(link_name="send") _unix_send :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="shutdown") _unix_shutdown :: proc(socket: c.int, how: c.int) -> c.int --- @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- @@ -661,9 +661,9 @@ when ODIN_ARCH != .arm64 { } foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: int) -> rawptr --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> int --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } @@ -1040,10 +1040,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := cast(cstring)path_ptr - path = strings.clone(string(path_cstr)) + path = strings.clone(string(path_ptr)) return path, nil } @@ -1154,7 +1153,7 @@ current_thread_id :: proc "contextless" () -> int { dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, flags) + handle := _unix_dlopen(cstr, c.int(flags)) return handle } @(require_results) @@ -1208,26 +1207,24 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -@(require_results) socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { - result := _unix_socket(domain, type, protocol) + result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) if result < 0 { return 0, get_last_error() } return Socket(result), nil } -@(require_results) connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := _unix_connect(int(sd), addr, len) + result := _unix_connect(c.int(sd), addr, len) if result < 0 { return get_last_error() } return nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := _unix_bind(int(sd), addr, len) +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Error) { + result := _unix_bind(c.int(sd), addr, len) if result < 0 { return get_last_error() } @@ -1235,15 +1232,15 @@ bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { } accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { - result := _unix_accept(int(sd), rawptr(addr), len) + result := _unix_accept(c.int(sd), rawptr(addr), len) if result < 0 { return 0, get_last_error() } return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> Error { - result := _unix_listen(int(sd), backlog) +listen :: proc(sd: Socket, backlog: int) -> (Error) { + result := _unix_listen(c.int(sd), c.int(backlog)) if result < 0 { return get_last_error() } @@ -1251,7 +1248,7 @@ listen :: proc(sd: Socket, backlog: int) -> Error { } setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := _unix_setsockopt(int(sd), level, optname, optval, optlen) + result := _unix_setsockopt(c.int(sd), c.int(level), c.int(optname), optval, optlen) if result < 0 { return get_last_error() } @@ -1259,7 +1256,8 @@ setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: } getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := _unix_getsockopt(int(sd), level, optname, optval, optlen) + optlen := optlen + result := _unix_getsockopt(c.int(sd), c.int(level), c.int(optname), optval, &optlen) if result < 0 { return get_last_error() } @@ -1267,7 +1265,7 @@ getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: } recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { - result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size) + result := _unix_recvfrom(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addr_size) if result < 0 { return 0, get_last_error() } @@ -1275,7 +1273,7 @@ recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_siz } recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_recv(int(sd), raw_data(data), len(data), flags) + result := _unix_recv(c.int(sd), raw_data(data), len(data), c.int(flags)) if result < 0 { return 0, get_last_error() } @@ -1283,7 +1281,7 @@ recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { } sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { - result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) + result := _unix_sendto(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addrlen) if result < 0 { return 0, get_last_error() } @@ -1291,15 +1289,15 @@ 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(int(sd), raw_data(data), len(data), 0) + result := _unix_send(c.int(sd), raw_data(data), len(data), 0) if result < 0 { return 0, get_last_error() } return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> Error { - result := _unix_shutdown(int(sd), how) +shutdown :: proc(sd: Socket, how: int) -> (Error) { + result := _unix_shutdown(c.int(sd), c.int(how)) if result < 0 { return get_last_error() } diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index e37f9767b..ba7e7ccf3 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -371,7 +371,7 @@ F_KINFO :: 22 foreign libc { @(link_name="__error") __Error_location :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.uint16_t) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @@ -402,7 +402,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -430,7 +430,7 @@ get_last_error :: proc "contextless" () -> Error { open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), c.int(mode)) + handle := _unix_open(cstr, c.int(flags), u16(mode)) if handle == -1 { return INVALID_HANDLE, get_last_error() } @@ -786,10 +786,10 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 78da32a57..0fcd1a21a 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -491,7 +491,7 @@ foreign libc { @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- } @@ -917,9 +917,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index ffaf4a5f8..fac1bc311 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -330,7 +330,7 @@ dev_t :: u64 ino_t :: u64 nlink_t :: u32 off_t :: i64 -mode_t :: u16 +mode_t :: u32 pid_t :: u32 uid_t :: u32 gid_t :: u32 @@ -454,7 +454,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -832,9 +832,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index a41637ea3..caae19e6e 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -379,7 +379,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -746,9 +746,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) { if path_ptr == nil { return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path = strings.clone(string(cstring(path_ptr))) + path = strings.clone(string(path_ptr)) return path, nil } diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index b44a6a344..361bba49a 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -32,10 +32,9 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { if path_ptr == nil { return "", __error()^ == 0 } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := cstring(path_ptr) - path_str := strings.clone(string(path_cstr), allocator) + path_str := strings.clone(string(path_ptr), allocator) return path_str, true } @@ -52,7 +51,7 @@ join :: proc(elems: []string, allocator := context.allocator) -> string { @(private) foreign libc { - realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="free") _unix_free :: proc(ptr: rawptr) --- } diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index 174b3a11b..fc05b8525 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -5,22 +5,7 @@ package spall // Only for types. import "core:os" -when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" -} else { - foreign import libc "system:c" -} - -timespec :: struct { - tv_sec: i64, // seconds - tv_nsec: i64, // nanoseconds -} - -foreign libc { - __error :: proc() -> ^i32 --- - @(link_name="write") _unix_write :: proc(handle: os.Handle, buffer: rawptr, count: uint) -> int --- - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- -} +import "core:sys/posix" MAX_RW :: 0x7fffffff @@ -32,7 +17,7 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.E for n < len(data) { chunk := data[:min(len(data), MAX_RW)] - written := _unix_write(fd, raw_data(chunk), len(chunk)) + written := posix.write(posix.FD(fd), raw_data(chunk), len(chunk)) if written < 0 { return n, os.get_last_error() } @@ -42,11 +27,17 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.E return n, nil } -CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. +// NOTE(tetra): "RAW" means: Not adjusted by NTP. +when ODIN_OS == .Darwin { + CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + CLOCK :: posix.Clock.MONOTONIC +} @(no_instrumentation) _tick_now :: proc "contextless" () -> (ns: i64) { - t: timespec - _unix_clock_gettime(CLOCK_MONOTONIC_RAW, &t) - return t.tv_sec*1e9 + t.tv_nsec + t: posix.timespec + posix.clock_gettime(CLOCK, &t) + return i64(t.tv_sec)*1e9 + i64(t.tv_nsec) } diff --git a/core/sys/posix/README.md b/core/sys/posix/README.md new file mode 100644 index 000000000..4afe50d6a --- /dev/null +++ b/core/sys/posix/README.md @@ -0,0 +1,57 @@ +# POSIX + +defines bindings for most posix APIs. + +If a header is added, all of it must be implemented. + +Each platform must define the exact same symbols, different values are allowed, even structs with different non-standard fields. + +APIs part of extensions may be left out completely if one target doesn't implement it. + +APIs with a direct replacement in `core` might not be implemented. + +Macros are emulated with force inlined functions. + +Struct fields defined by the posix standard (and thus portable) are documented with `[PSX]`. + + +ADD A TEST FOR SIGINFO, one thread signalling and retrieving the signal out of siginfo or something. +ADD A TEST FOR wait.h +ADD A TEST FOR pthread. +ADDD A test for stat.h. +ADD A TEST FOR setjmp.h. +HAIKU. + +Unimplemented POSIX headers: + +- aio.h +- complex.h | See `core:c/libc` and our own complex types +- cpio.h +- ctype.h | See `core:c/libc` for most of it +- ndbm.h | Never seen or heard of it +- fenv.h +- float.h +- fmtmsg.h +- ftw.h +- semaphore.h | See `core:sync` +- inttypes.h | See `core:c` +- iso646.h | Impossible +- math.h | See `core:c/libc` +- mqueue.h | Targets don't seem to have implemented it +- regex.h | See `core:regex` +- search.h | Not useful in Odin +- spawn.h | Use `fork`, `execve`, etc. +- stdarg.h | See `core:c/libc` +- stdint.h | See `core:c` +- stropts.h +- syslog.h +- pthread.h | Only the actual threads API is bound, see `core:sync` for synchronization primitives +- string.h | Most of this is not useful in Odin, only a select few symbols are bound +- tar.h +- tgmath.h +- trace.h +- wchar.h +- wctype.h + +TODO: +- time.h | Docs diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin new file mode 100644 index 000000000..7e950c4be --- /dev/null +++ b/core/sys/posix/arpa_inet.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// arpa/inet.h - definitions for internet operations + +foreign lib { + // Use Odin's native big endian types `u32be` and `u16be` instead. + // htonl :: proc(c.uint32_t) -> c.uint32_t --- + // htons :: proc(c.uint16_t) -> c.uint16_t --- + // ntohl :: proc(c.uint32_t) -> c.uint32_t --- + // ntohs :: proc(c.uint16_t) -> c.uint16_t --- + + // Use of this function is problematic because -1 is a valid address (255.255.255.255). + // Avoid its use in favor of inet_aton(), inet_pton(3), or getaddrinfo(3) which provide a cleaner way to indicate error return. + // inet_addr :: proc(cstring) -> in_addr_t --- + + // Convert the Internet host address specified by in to a string in the Internet standard dot notation. + // + // NOTE: returns a static string overwritten by further calls. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntoa.html ]] + inet_ntoa :: proc(in_addr) -> cstring --- + + // Convert a numeric address into a text string suitable for presentation. + // + // Returns `nil` and sets `errno` on failure. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_ntop :: proc( + af: AF, // INET or INET6 + src: rawptr, // either ^in_addr or ^in_addr6 + dst: [^]byte, // use `INET_ADDRSTRLEN` or `INET6_ADDRSTRLEN` for minimum lengths + size: socklen_t, + ) -> cstring --- + + // Convert an address in its standard text presentation form into its numeric binary form. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_pton :: proc( + af: AF, // INET or INET6 + src: cstring, + dst: rawptr, // either ^in_addr or ^in_addr6 + size: socklen_t, // size_of(dst^) + ) -> pton_result --- +} + +pton_result :: enum c.int { + AFNOSUPPORT = -1, + INVALID = 0, + SUCCESS = 1, +} diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin new file mode 100644 index 000000000..bbb5416c5 --- /dev/null +++ b/core/sys/posix/dirent.odin @@ -0,0 +1,205 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// dirent.h - format of directory entries + +foreign lib { + /* + can be used as the comparison function for the scandir() function to sort the directory entries, d1 and d2, into alphabetical order. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LALPHASORT) + alphasort :: proc([^]^dirent, [^]^dirent) -> c.int --- + + /* + Scan the directory dir, calling the function referenced by sel on each directory entry. + + Example: + list: [^]^posix.dirent + ret := posix.scandir(#directory, &list, nil, posix.alphasort) + if ret < 0 { + panic(string(posix.strerror(posix.errno()))) + } + defer posix.free(list) + + entries := list[:ret] + for entry in entries { + log.info(entry) + posix.free(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LSCANDIR) + scandir :: proc( + dir: cstring, + sel: ^[^]^dirent, + filter: proc "c" (^dirent) -> b32 = nil, + compar: proc "c" ([^]^dirent, [^]^dirent) -> c.int = alphasort, + ) -> c.int --- + + /* + Close the directory stream referred to by the argument dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/closedir.html ]] + */ + closedir :: proc(dirp: DIR) -> result --- + + /* + Return a file descriptor referring to the same directory as the dirp argument. + + // TODO: this is a macro on NetBSD? + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc(dirp: DIR) -> FD --- + + /* + Equivalent to the opendir() function except that the directory is specified by a file descriptor + rather than by a name. + The file offset associated with the file descriptor at the time of the call determines + which entries are returned. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name="fdopendir" + INODE_SUFFIX) + fdopendir :: proc(dirp: FD) -> DIR --- + + /* + Open a directory stream corresponding to the directory named by the dirname argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name=LOPENDIR) + opendir :: proc(path: cstring) -> DIR --- + + /* + Returns a pointer to a structure representing the directory entry at the current position + in the directory stream specified by the argument dirp, and position the directory stream at + the next entry. + + Returns nil when the end is reached or an error occurred (which sets errno). + + Example: + posix.set_errno(.NONE) + entry := posix.readdir(dirp) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + panic(string(posix.strerror(errno))) + } else { + fmt.println("end of directory stream") + } + } else { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]] + */ + @(link_name=LREADDIR) + readdir :: proc(dirp: DIR) -> ^dirent --- + + /* + Reset the position of the directory stream to which dirp refers to the beginning of the directory. + It shall also cause the directory stream to refer to the current state of the corresponding directory, + as a call to opendir() would have done. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rewinddir.html ]] + */ + @(link_name="rewinddir" + INODE_SUFFIX) + rewinddir :: proc(dirp: DIR) --- + + /* + The seekdir() function shall set the position of the next readdir() operation on the directory + stream specified by dirp to the position specified by loc. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seekdir.html ]] + */ + @(link_name="seekdir" + INODE_SUFFIX) + seekdir :: proc(dirp: DIR, loc: dir_loc) --- + + /* + The telldir() function shall obtain the current location associated with the directory stream + specified by dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/telldir.html ]] + */ + @(link_name="telldir" + INODE_SUFFIX) + telldir :: proc(dirp: DIR) -> dir_loc --- + + // deprecated. + // readdir_r :: proc(DIR, ^dirent, ^^dirent) -> c.int --- +} + +DIR :: distinct rawptr + +dir_loc :: c.long + +// NOTE: `d_type` is not a POSIX standard field, but all targets we support add it. +D_Type :: enum c.uint8_t { + UNKNOWN = 0, + FIFO = 1, + CHR = 2, + DIR = 4, + BLK = 6, + REG = 8, + LNK = 10, + SOCK = 12, + WHT = 14, +} + +when ODIN_OS == .NetBSD { + @(private) LALPHASORT :: "__alphasort30" + @(private) LSCANDIR :: "__scandir30" + @(private) LOPENDIR :: "__opendir30" + @(private) LREADDIR :: "__readdir30" +} else { + @(private) LALPHASORT :: "alphasort" + INODE_SUFFIX + @(private) LSCANDIR :: "scandir" + INODE_SUFFIX + @(private) LOPENDIR :: "opendir" + INODE_SUFFIX + @(private) LREADDIR :: "readdir" + INODE_SUFFIX +} + +when ODIN_OS == .Darwin { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_seekoff: c.uint64_t, /* seek offset */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [1024]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_off: off_t, /* directory offset of the next entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_type: D_Type, /* file type */ + d_namelen: c.uint8_t, /* length of string in d_name */ + d_pad0: c.uint32_t, + d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .NetBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [512]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin new file mode 100644 index 000000000..0ee666e24 --- /dev/null +++ b/core/sys/posix/dlfcn.odin @@ -0,0 +1,115 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// dlfcn.h - dynamic linking + +foreign lib { + /* + inform the system that the object referenced by a handle returned from a previous dlopen() + invocation is no longer needed by the application. + + Returns: 0 on success, non-zero on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html ]] + */ + dlclose :: proc(handle: Symbol_Table) -> c.int --- + + /* + return a null-terminated character string (with no trailing ) that describes + the last error that occurred during dynamic linking processing. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlerror.html ]] + */ + dlerror :: proc() -> cstring --- + + /* + Make the symbols (function identifiers and data object identifiers) in the executable object + file specified by file available to the calling program. + + Returns: a reference to the symbol table on success, nil on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html ]] + */ + dlopen :: proc(file: cstring, mode: RTLD_Flags) -> Symbol_Table --- + + /* + Obtain the address of a symbol (a function identifier or a data object identifier) + defined in the symbol table identified by the handle argument. + + Returns: the address of the matched symbol on success, nil on failure (use dlerror() for more information) + + Example: + handle := posix.dlopen("/usr/home/me/libfoo.so", posix.RTLD_LOCAL + { .RTLD_LAZY }) + defer posix.dlclose(handle) + + if handle == nil { + panic(string(posix.dlerror())) + } + + foo: proc(a, b: int) -> int + foo = auto_cast posix.dlsym(handle, "foo") + + if foo == nil { + panic(string(posix.dlerror())) + } + + fmt.printfln("foo(%v, %v) == %v", 1, 2, foo(1, 2)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html ]] + */ + dlsym :: proc(handle: Symbol_Table, name: cstring) -> rawptr --- +} + +RTLD_Flag_Bits :: enum c.int { + LAZY = log2(RTLD_LAZY), + NOW = log2(RTLD_NOW), + GLOBAL = log2(RTLD_GLOBAL), + + // NOTE: use with `posix.RTLD_LOCAL + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + + _MAX = 31, +} +RTLD_Flags :: bit_set[RTLD_Flag_Bits; c.int] + +Symbol_Table :: distinct rawptr + +when ODIN_OS == .Darwin { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x4 + RTLD_GLOBAL :: 0x8 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + RTLD_LAZY :: 1 + RTLD_NOW :: 2 + _RTLD_LOCAL :: 0 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{} + +} else when ODIN_OS == .NetBSD { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x200 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/errno.odin b/core/sys/posix/errno.odin new file mode 100644 index 000000000..4ef10aadf --- /dev/null +++ b/core/sys/posix/errno.odin @@ -0,0 +1,373 @@ +package posix + +import "core:c" +import "core:c/libc" + +// errno.h - system error numbers + +EDOM :: libc.EDOM +EILSEQ :: libc.EILSEQ +ERANGE :: libc.ERANGE + +@(no_instrumentation) +get_errno :: #force_inline proc "contextless" () -> Errno { + return (^Errno)(libc.errno())^ +} + +set_errno :: #force_inline proc "contextless" (err: Errno) { + libc.errno()^ = i32(err) +} + +errno :: proc { + get_errno, + set_errno, +} + +Errno :: enum c.int { + NONE = 0, + EDOM = EDOM, + EILSEQ = EILSEQ, + ERANGE = ERANGE, + E2BIG = E2BIG, + EACCES = EACCES, + EADDRINUSE = EADDRINUSE, + EADDRNOTAVAIL = EADDRNOTAVAIL, + EAFNOSUPPORT = EAFNOSUPPORT, + EAGAIN = EAGAIN, + EALREADY = EALREADY, + EBADF = EBADF, + EBADMSG = EBADMSG, + EBUSY = EBUSY, + ECANCELED = ECANCELED, + ECHILD = ECHILD, + ECONNABORTED = ECONNABORTED, + ECONNREFUSED = ECONNREFUSED, + ECONNRESET = ECONNRESET, + EDEADLK = EDEADLK, + EDESTADDRREQ = EDESTADDRREQ, + EDQUOT = EDQUOT, + EEXIST = EEXIST, + EFAULT = EFAULT, + EFBIG = EFBIG, + EHOSTUNREACH = EHOSTUNREACH, + EIDRM = EIDRM, + EINPROGRESS = EINPROGRESS, + EINTR = EINTR, + EINVAL = EINVAL, + EIO = EIO, + EISCONN = EISCONN, + EISDIR = EISDIR, + ELOOP = ELOOP, + EMFILE = EMFILE, + EMLINK = EMLINK, + EMSGSIZE = EMSGSIZE, + EMULTIHOP = EMULTIHOP, + ENAMETOOLONG = ENAMETOOLONG, + ENETDOWN = ENETDOWN, + ENETRESET = ENETRESET, + ENETUNREACH = ENETUNREACH, + ENFILE = ENFILE, + ENOBUFS = ENOBUFS, + ENODATA = ENODATA, + ENODEV = ENODEV, + ENOENT = ENOENT, + ENOEXEC = ENOEXEC, + ENOLCK = ENOLCK, + ENOLINK = ENOLINK, + ENOMEM = ENOMEM, + ENOMSG = ENOMSG, + ENOPROTOOPT = ENOPROTOOPT, + ENOSPC = ENOSPC, + ENOSR = ENOSR, + ENOSTR = ENOSTR, + ENOSYS = ENOSYS, + ENOTCONN = ENOTCONN, + ENOTDIR = ENOTDIR, + ENOTEMPTY = ENOTEMPTY, + ENOTRECOVERABLE = ENOTRECOVERABLE, + ENOTSOCK = ENOTSOCK, + ENOTSUP = ENOTSUP, + ENOTTY = ENOTTY, + ENXIO = ENXIO, + EOPNOTSUPP = EOPNOTSUPP, + EOVERFLOW = EOVERFLOW, + EOWNERDEAD = EOWNERDEAD, + EPERM = EPERM, + EPIPE = EPIPE, + EPROTO = EPROTO, + EPROTONOSUPPORT = EPROTONOSUPPORT, + EPROTOTYPE = EPROTOTYPE, + EROFS = EROFS, + ESPIPE = ESPIPE, + ESRCH = ESRCH, + ESTALE = ESTALE, + ETIME = ETIME, + ETIMEDOUT = ETIMEDOUT, + ETXTBSY = ETXTBSY, + EWOULDBLOCK = EWOULDBLOCK, + EXDEV = EXDEV, +} + +when ODIN_OS == .Darwin { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + ECANCELED :: 89 + EIDRM :: 90 + ENOMSG :: 91 + EBADMSG :: 94 + EMULTIHOP :: 95 + ENODATA :: 96 + ENOLINK :: 97 + ENOSR :: 98 + ENOSTR :: 99 + EPROTO :: 100 + ETIME :: 101 + ENOTRECOVERABLE :: 104 + EOWNERDEAD :: 105 +} else when ODIN_OS == .FreeBSD { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 85 + EBADMSG :: 89 + EMULTIHOP :: 90 + ENOLINK :: 91 + EPROTO :: 92 + ENOTRECOVERABLE :: 95 + EOWNERDEAD :: 96 + + // NOTE: not defined for freebsd + ENODATA :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: 35 + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: 45 + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + + when ODIN_OS == .NetBSD { + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 87 + EBADMSG :: 88 + ENODATA :: 89 + EMULTIHOP :: 94 + ENOLINK :: 95 + EPROTO :: 96 + ENOTRECOVERABLE :: 98 + EOWNERDEAD :: 97 + ENOSR :: 90 + ENOSTR :: 91 + ETIME :: 92 + } else { + EOVERFLOW :: 87 + EIDRM :: 89 + ENOMSG :: 90 + ECANCELED :: 88 + EBADMSG :: 92 + EPROTO :: 95 + ENOTRECOVERABLE :: 93 + EOWNERDEAD :: 94 + // NOTE: not defined for openbsd + ENODATA :: -1 + EMULTIHOP :: -1 + ENOLINK :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 + } + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin new file mode 100644 index 000000000..436104613 --- /dev/null +++ b/core/sys/posix/fcntl.odin @@ -0,0 +1,415 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fcntl.h - file control options + +foreign lib { + /* + Implemented as `return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);` + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/creat.html ]] + */ + creat :: proc(path: cstring, mode: mode_t) -> FD --- + + /* + Perform the operations on open files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html ]] + */ + fcntl :: proc(fd: FD, cmd: FCNTL_Cmd, arg: rawptr = nil) -> c.int --- + + /* + Establish the connection between a file and a file descriptor. + It shall create an open file description that refers to a file and a file descriptor that + refers to that open file description. The file descriptor is used by other I/O functions to + refer to that file. + The path argument points to a pathname naming the file + + Returns: -1 on failure (setting errno), a file descriptor on success. + + Example: + // The following example opens the file /tmp/file, either by creating it (if it does not already exist), + // or by truncating its length to 0 (if it does exist). In the former case, if the call creates a new file, + // the access permission bits in the file mode of the file are set to permit reading and writing by the owner, + // and to permit reading only by group members and others. + fd := posix.open("/tmp/file", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + + // The following example uses the open() function to try to create the LOCKFILE file and open it for writing. + // Since the open() function specifies the O_EXCL flag, the call fails if the file already exists. + // In that case, the program assumes that someone else is updating the password file and exits. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .EXCL }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open /etc/ptmp") + } + + // The following example opens a file for writing, creating the file if it does not already exist. + // If the file does exist, the system truncates the file to zero bytes. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open output file") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + open :: proc(path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD --- + + /* + Equivalent to the open() function except in the case where path specifies a relative path. + In this case the file to be opened is determined relative to the directory associated with the + file descriptor fd instead of the current working directory. + + Returns: -1 on failure (setting errno), a file descriptor on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + openat :: proc(fd: FD, path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD --- +} + +FCNTL_Cmd :: enum c.int { + DUPFD = F_DUPFD, + DUPFD_CLOEXEC = F_DUPFD_CLOEXEC, + GETFD = F_GETFD, + SETFD = F_SETFD, + GETFL = F_GETFL, + SETFL = F_SETFL, + GETLK = F_GETLK, + SETLK = F_SETLK, + SETLKW = F_SETLKW, + GETOWN = F_GETOWN, + SETOWN = F_SETOWN, +} + +Lock_Type :: enum c.short { + RDLCK = F_RDLCK, + UNLCK = F_UNLCK, + WRLCK = F_WRLCK, +} + +// Assertions made to unify this bit set. +#assert(O_RDONLY == 0) + +O_Flag_Bits :: enum c.int { + // Sets FD_CLOEXEC on the file descriptor. + CLOEXEC = log2(O_CLOEXEC), + // If not exists, combined with DIRECTORY will cause creation of a directory, otherwise a regular file. + CREAT = log2(O_CREAT), + // Fails if the opened descriptor would not be a directory. + DIRECTORY = log2(O_DIRECTORY), + // If combined with CREAT, causes a failure if the file already exists. + EXCL = log2(O_EXCL), + // If terminal device, do not make it the controlling terminal for the process. + NOCTTY = log2(O_NOCTTY), + // Don't follow symbolic links, fail with errno ELOOP. + NOFOLLOW = log2(O_NOFOLOW), + // If exists and regular, truncate the length to 0. + TRUNC = log2(O_TRUNC), + + // NOTE: use with `posix.O_TTY_INIT + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // TTY_INIT = O_TTY_INIT, + + // Set file offset to end of file prior to each write. + APPEND = log2(O_APPEND), + // Write I/O shall complete as defined by synchronized I/O data integrity completion. + DSYNC = log2(O_DSYNC), + // Causes nonblocking behaviour in various situations. + NONBLOCK = log2(O_NONBLOCK), + // Write I/O shall complete as defined by synchronized I/O file integrity completion. + SYNC = log2(O_SYNC), + // NOTE: use with `posix.O_RSYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // RSYNC = O_RSYNC, + + // Execute only. + EXEC = log2(O_EXEC), + // Reading and writing. + RDWR = log2(O_RDWR), + // Writing only. + WRONLY = log2(O_WRONLY), + // Reading only. + // RDONLY = 0, // Default + +} +O_Flags :: bit_set[O_Flag_Bits; c.int] + +// A mask of all the access mode bits. +O_ACCMODE :: O_Flags{ .EXEC, .RDWR, .WRONLY } + +AT_Flag_Bits :: enum c.int { + EACCESS = log2(AT_EACCESS), + SYMLINK_NOFOLLOW = log2(AT_SYMLINK_NOFOLLOW), + SYMLINK_FOLLOW = log2(AT_SYMLINK_FOLLOW), + REMOVEDIR = log2(AT_REMOVEDIR), +} +AT_Flags :: bit_set[AT_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 67 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x01000000 + O_CREAT :: 0x00000200 + O_DIRECTORY :: 0x00100000 + O_EXCL :: 0x00000800 + O_NOCTTY :: 0x00020000 + O_NOFOLOW :: 0x00000100 + O_TRUNC :: 0x00000400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} + + O_APPEND :: 0x00000008 + O_DSYNC :: 0x00400000 + O_NONBLOCK :: 0x00000004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x40000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC | O_DIRECTORY + O_SEARCH :: O_Flags{ .EXEC, .DIRECTORY } + + AT_FDCWD: FD: -2 + + AT_EACCESS :: 0x0010 + AT_SYMLINK_NOFOLLOW :: 0x0020 + AT_SYMLINK_FOLLOW :: 0x0040 + AT_REMOVEDIR :: 0x0080 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else when ODIN_OS == .FreeBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 17 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00100000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x00020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0x00080000 + O_TTY_INIT :: O_Flags{O_Flag_Bits(log2(_O_TTY_INIT))} + + O_APPEND :: 0x0008 + O_DSYNC :: 0x01000000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x00040000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC|O_DIRECTORY + O_SEARCH :: O_Flags{ .EXEC, .DIRECTORY } + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x0100 + AT_SYMLINK_NOFOLLOW :: 0x0200 + AT_SYMLINK_FOLLOW :: 0x0400 + AT_REMOVEDIR :: 0x0800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + l_sysid: c.int, + } + +} else when ODIN_OS == .NetBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 12 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00400000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x0020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0x0002 + O_RSYNC :: O_Flags{O_Flag_Bits(log2(_O_RSYNC))} + + + O_EXEC :: 0x04000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0x00800000 + O_SEARCH :: O_Flags{O_Flag_Bits(log2(_O_SEARCH))} + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x100 + AT_SYMLINK_NOFOLLOW :: 0x200 + AT_SYMLINK_FOLLOW :: 0x400 + AT_REMOVEDIR :: 0x800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } +} else when ODIN_OS == .OpenBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 10 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x10000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x20000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: O_SYNC + O_RSYNC :: O_Flags{ .SYNC } + + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0 + O_SEARCH :: O_Flags{} // NOTE: not defined in the headers + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x01 + AT_SYMLINK_NOFOLLOW :: 0x02 + AT_SYMLINK_FOLLOW :: 0x04 + AT_REMOVEDIR :: 0x08 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin new file mode 100644 index 000000000..9e54972e7 --- /dev/null +++ b/core/sys/posix/fnmatch.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fnmatch.h - filename-matching types + +foreign lib { + /* + Match patterns as described in XCU [[ Patterns Matching a Single Character; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 ]] + // and [[ Patterns Matching Multiple Characters; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_02 ]]. + It checks the string specified by the string argument to see if it matches the pattern specified by the pattern argument. + + Returns: 0 when matched. if there is no match, fnmatch() shall return FNM_NOMATCH. Non-zero on other errors. + + Example: + assert(posix.fnmatch("*.odin", "foo.odin", {}) == 0) + assert(posix.fnmatch("*.txt", "foo.odin", {}) == posix.FNM_NOMATCH) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html ]] + */ + fnmatch :: proc(pattern: cstring, string: cstring, flags: FNM_Flags) -> c.int --- +} + +FNM_Flag_Bits :: enum c.int { + // A character ( '/' ) in string shall be explicitly matched by a in pattern; + // it shall not be matched by either the or special characters, + // nor by a bracket expression. + PATHNAME = log2(FNM_PATHNAME), + + // A leading ( '.' ) in string shall match a in pattern; + // as described by rule 2 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]] + // where the location of "leading" is indicated by the value of PATHNAME: + // 1. If PATHNAME is set, a is "leading" if it is the first character in string or if it immediately follows a . + // 2. If PATHNAME is not set, a is "leading" only if it is the first character of string. + PERIOD = log2(FNM_PERIOD), + + // A character shall be treated as an ordinary character. + NOESCAPE = log2(FNM_NOESCAPE), +} +FNM_Flags :: bit_set[FNM_Flag_Bits; c.int] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + FNM_NOMATCH :: 1 + + FNM_PATHNAME :: 0x02 + FNM_PERIOD :: 0x04 + FNM_NOESCAPE :: 0x01 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin new file mode 100644 index 000000000..4f41d83db --- /dev/null +++ b/core/sys/posix/glob.odin @@ -0,0 +1,179 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// glob.h - pathname pattern-matching types + +foreign lib { + /* + The glob() function is a pathname generator that shall implement the rules defined in + [[ XCU Pattern Matching Notation; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13 ]], + with optional support for rule 3 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOB) + glob :: proc( + pattern: cstring, + flags: Glob_Flags, + errfunc: proc "c" (epath: cstring, eerrno: Errno) -> b32 = nil, // Return `true` to abort the glob(). + pglob: ^glob_t, + ) -> Glob_Result --- + + /* + Free the glob results. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOBFREE) + globfree :: proc(^glob_t) --- +} + +Glob_Flag_Bits :: enum c.int { + // Append pathnames generated to the ones from a previous call to glob(). + APPEND = log2(GLOB_APPEND), + // Make use of pglob->gl_offs. If this flag is set, pglob->gl_offs is used to specify how many null pointers to add to the beginning of pglob->gl_pathv. + // In other words, pglob->gl_pathv shall point to pglob->gl_offs null pointers, followed by pglob->gl_pathc pathname pointers, followed by a null pointer. + DOOFFS = log2(GLOB_DOOFFS), + // Cause glob() to return when it encounters a directory that it cannot open or read. Ordinarily, + // glob() continues to find matches. + ERR = log2(GLOB_ERR), + // Each pathname that is a directory that matches pattern shall have a appended. + MARK = log2(GLOB_MARK), + // Supports rule 3 in [[ XCU Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + // If pattern does not match any pathname, then glob() shall return a list consisting of only pattern, + // and the number of matched pathnames is 1. + NOCHECK = log2(GLOB_NOCHECK), + // Disable backslash escaping. + NOESCAPE = log2(GLOB_NOESCAPE), + // Ordinarily, glob() sorts the matching pathnames according to the current setting of the + // LC_COLLATE category; see XBD LC_COLLATE. When this flag is used, + // the order of pathnames returned is unspecified. + NOSORT = log2(GLOB_NOSORT), +} +Glob_Flags :: bit_set[Glob_Flag_Bits; c.int] + +Glob_Result :: enum c.int { + SUCCESS = 0, + ABORTED = GLOB_ABORTED, + NOMATCH = GLOB_NOMATCH, + NOSPACE = GLOB_NOSPACE, +} + +when ODIN_OS == .NetBSD { + @(private) LGLOB :: "__glob30" + @(private) LGLOBFREE :: "__globfree30" +} else { + @(private) LGLOB :: "glob" + INODE_SUFFIX + @(private) LGLOBFREE :: "globfree" +} + +when ODIN_OS == .Darwin { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.int, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + using _: struct #raw_union { + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + gl_errblk: proc "c" (cstring, c.int) -> c.int, + }, + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 when ODIN_OS == .FreeBSD else 0x0100 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .OpenBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + gl_statv: [^]stat_t, + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x1000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin new file mode 100644 index 000000000..c8a39de6a --- /dev/null +++ b/core/sys/posix/grp.odin @@ -0,0 +1,130 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// grp.h - group structure + +foreign lib { + /* + Closes the group database. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + endgrent :: proc() --- + + /* + Rewinds the group database so getgrent() returns the first entry again. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + setgrent :: proc() --- + + /* + Returns a pointer to an entry of the group database. + + Opens the group database if it isn't. + + Returns: nil on failure (setting errno) or EOF (not setting errno), the entry otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + getgrent :: proc() -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid :: proc(gid: gid_t) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid_r :: proc(gid: gid_t, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam :: proc(name: cstring) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + Example: + length := posix.sysconf(._GETGR_R_SIZE_MAX) + if length == -1 { + length = 1024 + } + + result: posix.group + resultp: ^posix.group + + e: posix.Errno + + buffer: [dynamic]byte + defer delete(buffer) + + for { + mem_err := resize(&buffer, length) + assert(mem_err == nil) + + e = posix.getgrnam_r("nobody", &result, raw_data(buffer), len(buffer), &resultp) + if e != .ERANGE { + break + } + + length *= 2 + assert(length > 0) + } + + if e != .NONE { + panic(string(posix.strerror(e))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam_r :: proc(name: cstring, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + gid_t :: distinct c.uint32_t + + group :: struct { + gr_name: cstring, /* [PSX] group name */ + gr_passwd: cstring, /* group password */ + gr_gid: gid_t, /* [PSX] group id */ + gr_mem: [^]cstring, /* [PSX] group members */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/iconv.odin b/core/sys/posix/iconv.odin new file mode 100644 index 000000000..59248890f --- /dev/null +++ b/core/sys/posix/iconv.odin @@ -0,0 +1,50 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + // NOTE: iconv is in a different library + foreign import lib "system:iconv" +} else { + foreign import lib "system:c" +} + +// iconv.h - codeset conversion facility + +iconv_t :: distinct rawptr + +foreign lib { + /* + Convert the sequence of characters from one codeset, in the array specified by inbuf, + into a sequence of corresponding characters in another codeset, in the array specified by outbuf. + + Returns: -1 (setting errno) on failure, the number of non-identical conversions performed on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv.html ]] + */ + iconv :: proc( + cd: iconv_t, + inbuf: ^[^]byte, + inbytesleft: ^c.size_t, + outbuf: ^[^]byte, + outbyteslen: ^c.size_t, + ) -> c.size_t --- + + /* + Deallocates the conversion descriptor cd and all other associated resources allocated by iconv_open(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_close.html ]] + */ + iconv_close :: proc(cd: iconv_t) -> result --- + + /* + Returns a conversion descriptor that describes a conversion from the codeset specified by the + string pointed to by the fromcode argument to the codeset specified by the string pointed to by + the tocode argument. + + Returns: -1 (setting errno) on failure, a conversion descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_open.html ]] + */ + iconv_open :: proc(tocode: cstring, fromcode: cstring) -> iconv_t --- +} diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin new file mode 100644 index 000000000..24ecc917a --- /dev/null +++ b/core/sys/posix/langinfo.odin @@ -0,0 +1,285 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// langinfo.h - language information constants + +foreign lib { + /* + Return a pointer to a string containing information relevant to the particular language or + cultural area defined in the current locale. + + Returns: a string that should not be freed or modified, and that can be invalidated at any time later + + Example: + for item in posix.nl_item { + fmt.printfln("%v: %q", item, posix.nl_langinfo(item)) + } + + Possible Output: + CODESET: "US-ASCII" + D_T_FMT: "%a %b %e %H:%M:%S %Y" + D_FMT: "%m/%d/%y" + T_FMT: "%H:%M:%S" + T_FMT_AMPM: "%I:%M:%S %p" + AM_STR: "AM" + PM_STR: "PM" + DAY_1: "Sunday" + DAY_2: "Monday" + DAY_3: "Tuesday" + DAY_4: "Wednesday" + DAY_5: "Thursday" + DAY_6: "Friday" + DAY_7: "Saturday" + ABDAY_1: "Sun" + ABDAY_2: "Mon" + ABDAY_3: "Tue" + ABDAY_4: "Wed" + ABDAY_5: "Thu" + ABDAY_6: "Fri" + ABDAY_7: "Sat" + MON_1: "January" + MON_2: "February" + MON_3: "March" + MON_4: "April" + MON_5: "May" + MON_6: "June" + MON_7: "July" + MON_8: "August" + MON_9: "September" + MON_10: "October" + MON_11: "November" + MON_12: "December" + ABMON_1: "Jan" + ABMON_2: "Feb" + ABMON_3: "Mar" + ABMON_4: "Apr" + ABMON_5: "May" + ABMON_6: "Jun" + ABMON_7: "Jul" + ABMON_8: "Aug" + ABMON_9: "Sep" + ABMON_10: "Oct" + ABMON_11: "Nov" + ABMON_12: "Dec" + ERA: "" + ERA_D_FMT: "" + ERA_D_T_FMT: "" + ERA_T_FMT: "" + ALT_DIGITS: "" + RADIXCHAR: "." + THOUSEP: "" + YESEXPR: "^[yY]" + NOEXPR: "^[nN]" + CRNCYSTR: "" + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nl_langinfo.html ]] + */ + nl_langinfo :: proc(nl_item) -> cstring --- +} + +nl_item :: enum nl_item_t { + CODESET = CODESET, + D_T_FMT = D_T_FMT, + D_FMT = D_FMT, + T_FMT = T_FMT, + T_FMT_AMPM = T_FMT_AMPM, + AM_STR = AM_STR, + PM_STR = PM_STR, + DAY_1 = DAY_1, + DAY_2 = DAY_2, + DAY_3 = DAY_3, + DAY_4 = DAY_4, + DAY_5 = DAY_5, + DAY_6 = DAY_6, + DAY_7 = DAY_7, + ABDAY_1 = ABDAY_1, + ABDAY_2 = ABDAY_2, + ABDAY_3 = ABDAY_3, + ABDAY_4 = ABDAY_4, + ABDAY_5 = ABDAY_5, + ABDAY_6 = ABDAY_6, + ABDAY_7 = ABDAY_7, + MON_1 = MON_1, + MON_2 = MON_2, + MON_3 = MON_3, + MON_4 = MON_4, + MON_5 = MON_5, + MON_6 = MON_6, + MON_7 = MON_7, + MON_8 = MON_8, + MON_9 = MON_9, + MON_10 = MON_10, + MON_11 = MON_11, + MON_12 = MON_12, + ABMON_1 = ABMON_1, + ABMON_2 = ABMON_2, + ABMON_3 = ABMON_3, + ABMON_4 = ABMON_4, + ABMON_5 = ABMON_5, + ABMON_6 = ABMON_6, + ABMON_7 = ABMON_7, + ABMON_8 = ABMON_8, + ABMON_9 = ABMON_9, + ABMON_10 = ABMON_10, + ABMON_11 = ABMON_11, + ABMON_12 = ABMON_12, + ERA = ERA, + ERA_D_FMT = ERA_D_FMT, + ERA_D_T_FMT = ERA_D_T_FMT, + ERA_T_FMT = ERA_T_FMT, + ALT_DIGITS = ALT_DIGITS, + RADIXCHAR = RADIXCHAR, + THOUSEP = THOUSEP, + YESEXPR = YESEXPR, + NOEXPR = NOEXPR, + CRNCYSTR = CRNCYSTR, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 0 + D_T_FMT :: 1 + D_FMT :: 2 + T_FMT :: 3 + T_FMT_AMPM :: 4 + AM_STR :: 5 + PM_STR :: 6 + + DAY_1 :: 7 + DAY_2 :: 8 + DAY_3 :: 9 + DAY_4 :: 10 + DAY_5 :: 11 + DAY_6 :: 12 + DAY_7 :: 13 + + ABDAY_1 :: 14 + ABDAY_2 :: 15 + ABDAY_3 :: 16 + ABDAY_4 :: 17 + ABDAY_5 :: 18 + ABDAY_6 :: 19 + ABDAY_7 :: 20 + + MON_1 :: 21 + MON_2 :: 22 + MON_3 :: 23 + MON_4 :: 24 + MON_5 :: 25 + MON_6 :: 26 + MON_7 :: 27 + MON_8 :: 28 + MON_9 :: 29 + MON_10 :: 30 + MON_11 :: 31 + MON_12 :: 32 + + ABMON_1 :: 33 + ABMON_2 :: 34 + ABMON_3 :: 35 + ABMON_4 :: 36 + ABMON_5 :: 37 + ABMON_6 :: 38 + ABMON_7 :: 39 + ABMON_8 :: 40 + ABMON_9 :: 41 + ABMON_10 :: 42 + ABMON_11 :: 43 + ABMON_12 :: 44 + + ERA :: 45 + ERA_D_FMT :: 46 + ERA_D_T_FMT :: 47 + ERA_T_FMT :: 48 + ALT_DIGITS :: 49 + + RADIXCHAR :: 50 + THOUSEP :: 51 + + YESEXPR :: 52 + NOEXPR :: 53 + + CRNCYSTR :: 56 + +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 51 + D_T_FMT :: 0 + D_FMT :: 1 + T_FMT :: 2 + T_FMT_AMPM :: 3 + AM_STR :: 4 + PM_STR :: 5 + + DAY_1 :: 6 + DAY_2 :: 7 + DAY_3 :: 8 + DAY_4 :: 9 + DAY_5 :: 10 + DAY_6 :: 11 + DAY_7 :: 12 + + ABDAY_1 :: 13 + ABDAY_2 :: 14 + ABDAY_3 :: 15 + ABDAY_4 :: 16 + ABDAY_5 :: 17 + ABDAY_6 :: 18 + ABDAY_7 :: 19 + + MON_1 :: 20 + MON_2 :: 21 + MON_3 :: 22 + MON_4 :: 23 + MON_5 :: 24 + MON_6 :: 25 + MON_7 :: 26 + MON_8 :: 27 + MON_9 :: 28 + MON_10 :: 29 + MON_11 :: 30 + MON_12 :: 31 + + ABMON_1 :: 32 + ABMON_2 :: 33 + ABMON_3 :: 34 + ABMON_4 :: 35 + ABMON_5 :: 36 + ABMON_6 :: 37 + ABMON_7 :: 38 + ABMON_8 :: 39 + ABMON_9 :: 40 + ABMON_10 :: 41 + ABMON_11 :: 42 + ABMON_12 :: 43 + + ERA :: 52 + ERA_D_FMT :: 53 + ERA_D_T_FMT :: 54 + ERA_T_FMT :: 55 + ALT_DIGITS :: 56 + + RADIXCHAR :: 44 + THOUSEP :: 45 + + YESEXPR :: 47 + NOEXPR :: 49 + + CRNCYSTR :: 50 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/libgen.odin b/core/sys/posix/libgen.odin new file mode 100644 index 000000000..99506797e --- /dev/null +++ b/core/sys/posix/libgen.odin @@ -0,0 +1,74 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// libgen.h - definitions for pattern matching functions + +foreign lib { + /* + Takes the pathname pointed to by path and return a pointer to the final component of the + pathname, deleting any trailing '/' characters. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + Example: + tests := []string{ + "usr", "usr/", "", "/", "//", "///", "/usr/", "/usr/lib", + "//usr//lib//", "/home//dwc//test", + } + + tbl: table.Table + table.init(&tbl) + table.header(&tbl, "input", "dirname", "basename") + + for test in tests { + din := strings.clone_to_cstring(test); defer delete(din) + dir := strings.clone_from_cstring(posix.dirname(din)) + + bin := strings.clone_to_cstring(test); defer delete(bin) + base := strings.clone_from_cstring(posix.basename(bin)) + table.row(&tbl, test, dir, base) + } + + table.write_plain_table(os.stream_from_handle(os.stdout), &tbl) + + Output: + +----------------+----------+--------+ + |input |dirname |basename| + +----------------+----------+--------+ + |usr |. |usr | + |usr/ |. |usr | + | |. |. | + |/ |/ |/ | + |// |/ |/ | + |/// |/ |/ | + |/usr/ |/ |usr | + |/usr/lib |/usr |lib | + |//usr//lib// |//usr |lib | + |/home//dwc//test|/home//dwc|test | + +----------------+----------+--------+ + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]] + */ + basename :: proc(path: cstring) -> cstring --- + + /* + Takes a string that contains a pathname, and returns a string that is a pathname of the parent + directory of that file. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + See example for basename(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirname.html ]] + */ + dirname :: proc(path: cstring) -> cstring --- +} diff --git a/core/sys/posix/limits.odin b/core/sys/posix/limits.odin new file mode 100644 index 000000000..7bb561215 --- /dev/null +++ b/core/sys/posix/limits.odin @@ -0,0 +1,459 @@ +package posix + +// limits.h - implementation-defined constants + +// NOTE: numerical limits are left out because Odin provides `min(T)` and `max(T)`. + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation. +// A conforming implementation shall provide values no larger than these values. +// A conforming application must not require a smaller value for correct operation. + +_POSIX_CLOCKRES_MIN :: 20000000 + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation conforming to +// this volume of POSIX.1-2017. +// Related symbolic constants are defined elsewhere in this volume of POSIX.1-2017 which reflect +// the actual implementation and which need not be as restrictive. For each of these limits, +// a conforming implementation shall provide a value at least this large or shall have no limit. +// A strictly conforming application must not require a larger value for correct operation. + +_POSIX_AIO_LISTIO_MAX :: 2 +_POSIX_AIO_MAX :: 1 +_POSIX_ARG_MAX :: 4096 +_POSIX_CHILD_MAX :: 25 +_POSIX_DELAYTIMER_MAX :: 32 +_POSIX_HOST_NAME_MAX :: 255 +_POSIX_LINK_MAX :: 8 +_POSIX_MAX_CANON :: 255 +_POSIX_MAX_INPUT :: 255 +_POSIX_MQ_OPEN_MAX :: 8 +_POSIX_MQ_PRIO_MAX :: 32 +_POSIX_NAME_MAX :: 14 +_POSIX_NGROUPS_MAX :: 8 +_POSIX_OPEN_MAX :: 20 +_POSIX_PATH_MAX :: 256 +_POSIX_PIPE_BUF :: 512 +_POSIX_RE_DUP_MAX :: 255 +_POSIX_RTSIG_MAX :: 8 +_POSIX_SEM_NSEMS_MAX :: 256 +_POSIX_SEM_VALUE_MAX :: 32767 +_POSIX_SS_REPL_MAX :: 4 +_POSIX_STREAM_MAX :: 8 +_POSIX_SYMLINK_MAX :: 255 +_POSIX_SYMLOOP_MAX :: 8 +_POSIX_THREAD_DESTRUCTION_ITERATIONS :: 4 +_POSIX_THREAD_KEYS_MAX :: 128 +_POSIX_THREADS_THREADS_MAX :: 64 +_POSIX_TIMER_MAX :: 32 +_POSIX_TRAXE_EVENT_NAME_MAX :: 30 +_POSIX_TRACE_NAME_MAX :: 8 +_POSIX_TRACE_SYS_MAX :: 8 +_POSIX_TRACE_USER_EVENT_MAX :: 32 +_POSIX_TTY_NAME_MAX :: 9 +_POSIX_TZNAME_MAX :: 6 +_POSIX2_BC_BASE_MAX :: 99 +_POSIX2_BC_DIM_MAX :: 2048 +_POSIX2_BC_SCALE_MAX :: 99 +_POSIX2_CHARCLASS_NAME_MAX :: 14 +_POSIX2_COLL_WEIGHTS_MAX :: 2 +_POSIX2_EXPR_NEST_MAX :: 32 +_POSIX2_LINE_MAX :: 2048 +_POSIX2_RE_DUP_MAX :: 255 +_XOPEN_IOV_MAX :: 16 +_XOPEN_NAME_MAX :: 255 +_XOPEN_PATH_MAX :: 1024 + +/* +NOTE: for full portability, usage should look something like: + + page_size: uint + when #defined(posix.PAGESIZE) { + page_size = posix.PAGESIZE + } else { + page_size = posix.sysconf(._PAGESIZE) + } +*/ + +when ODIN_OS == .Darwin { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 1024 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 266 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + // MQ_PRIO_MAX :: sysconf(._MQ_PRIO_MAX) + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 512 + PTHREAD_STACK_MIN :: 16384 when ODIN_ARCH == .arm64 else 8192 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 1024 + MAX_INPUT :: 1024 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 2 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .FreeBSD { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 2 * 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 40 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + MQ_PRIO_MAX :: 64 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: MINSIGSTKSZ + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + // LINK_MAX :: pathconf(foo.txt", ._LINK_MAX) + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 10 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 1023 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 4096 + NL_LANGMAX :: 31 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 0 + +} else when ODIN_OS == .NetBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 160 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 17 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + // PTHREAD_STACK_MIN :: sysconf(._THREAD_STACK_MIN) + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 511 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .OpenBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 512 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 80 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 32 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: 1 << 12 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + SEM_VALUE_MAX :: max(u32) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + SYMLOOP_MAX :: 32 + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + SYMLINK_MAX :: PATH_MAX + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 255 + NZERO :: 20 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/locale.odin b/core/sys/posix/locale.odin new file mode 100644 index 000000000..1f2a336b5 --- /dev/null +++ b/core/sys/posix/locale.odin @@ -0,0 +1,93 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// locale.h - category macros + +foreign lib { + /* + Sets the components of an object with the type lconv with the values appropriate for the + formatting of numeric quantities (monetary and otherwise) according to the rules of the current + locale. + + Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale() + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]] + */ + localeconv :: proc() -> ^lconv --- + + /* + Selects the appropriate piece of the global locale, as specified by the category and locale arguments, + and can be used to change or query the entire global locale or portions thereof. + + Returns: the current locale if `locale` is `nil`, the set locale otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]] + */ + @(link_name=LSETLOCALE) + setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring --- +} + +Locale_Category :: enum c.int { + ALL = LC_ALL, + COLLATE = LC_COLLATE, + CTYPE = LC_CTYPE, + MESSAGES = LC_MESSAGES, + MONETARY = LC_MONETARY, + NUMERIC = LC_NUMERIC, + TIME = LC_TIME, +} + +when ODIN_OS == .NetBSD { + @(private) LSETLOCALE :: "__setlocale50" +} else { + @(private) LSETLOCALE :: "setlocale" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: All of these fields are standard ([PSX]). + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + int_p_cs_precedes: c.char, + int_n_cs_precedes: c.char, + int_p_sep_by_space: c.char, + int_n_sep_by_space: c.char, + int_p_sign_posn: c.char, + int_n_sign_posn: c.char, + } + + LC_ALL :: 0 + LC_COLLATE :: 1 + LC_CTYPE :: 2 + LC_MESSAGES :: 6 + LC_MONETARY :: 3 + LC_NUMERIC :: 4 + LC_TIME :: 5 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/monetary.odin b/core/sys/posix/monetary.odin new file mode 100644 index 000000000..b4f0c31ee --- /dev/null +++ b/core/sys/posix/monetary.odin @@ -0,0 +1,42 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// monetary.h - monetary types + +foreign lib { + + /* + Places characters into the array pointed to by s as controlled by the string format. + No more than maxsize bytes are placed into the array. + + Returns: -1 (setting errno) on failure, the number of bytes added to s otherwise + + Example: + posix.setlocale(.ALL, "en_US.UTF-8") + value := 123456.789 + buffer: [100]byte + size := posix.strfmon(raw_data(buffer[:]), len(buffer), "%n", value) + if int(size) == -1 { + fmt.panicf("strfmon failure: %s", posix.strerror(posix.errno())) + } + fmt.println(string(buffer[:size])) + + Output: + $123,456.79 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strfmon.html ]] + */ + strfmon :: proc( + s: [^]byte, + maxsize: c.size_t, + format: cstring, + #c_vararg args: ..any, + ) -> c.size_t --- +} diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin new file mode 100644 index 000000000..aaeb5088a --- /dev/null +++ b/core/sys/posix/net_if.odin @@ -0,0 +1,60 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// net/if.h - sockets local interfaces + +foreign lib { + /* + Retrieve an array of name indexes. Where the last one has an index of 0 and name of nil. + + Returns: nil (setting errno) on failure, an array that should be freed with if_freenameindex otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nameindex.html ]] + */ + if_nameindex :: proc() -> [^]if_nameindex_t --- + + /* + Returns the interface index matching the name or zero. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nametoindex.html ]] + */ + if_nametoindex :: proc(name: cstring) -> c.uint --- + + /* + Returns the name corresponding to the index. + + ifname should be at least IF_NAMESIZE bytes in size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_indextoname.html ]] + */ + if_indextoname :: proc(ifindex: c.uint, ifname: [^]byte) -> cstring --- + + /* + Frees memory allocated by if_nameindex. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_freenameindex.html ]] + */ + if_freenameindex :: proc(ptr: ^if_nameindex_t) --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: `_t` suffix added due to name conflict. + + if_nameindex_t :: struct { + if_index: c.uint, /* [PSX] 1, 2, ... */ + if_name: cstring, /* [PSX] null terminated name: "le0", ... */ + } + + IF_NAMESIZE :: 16 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin new file mode 100644 index 000000000..7570f9a22 --- /dev/null +++ b/core/sys/posix/netdb.odin @@ -0,0 +1,443 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netdb.h - definitions for network database operations + +foreign lib { + /* + Translate node/serv name and return a set of socket addresses and associated information to be + used in creating a socket with which to address the specified service. + + Example: + // The following (incomplete) program demonstrates the use of getaddrinfo() to obtain the + // socket address structure(s) for the service named in the program's command-line argument. + // The program then loops through each of the address structures attempting to create and bind + // a socket to the address, until it performs a successful bind(). + + args := runtime.args__ + if len(args) != 2 { + fmt.eprintfln("Usage: %s port", args[0]) + posix.exit(1) + } + + hints: posix.addrinfo + hints.ai_socktype = .DGRAM + hints.ai_flags = { .PASSIVE } + + result: ^posix.addrinfo + s := posix.getaddrinfo(nil, args[1], &hints, &result) + if s != .NONE { + fmt.eprintfln("getaddrinfo: %s", posix.gai_strerror(s)) + posix.exit(1) + } + defer posix.freeaddrinfo(result) + + // Try each address until a successful bind(). + rp: ^posix.addrinfo + for rp = result; rp != nil; rp = rp.ai_next { + sfd := posix.socket(rp.ai_family, rp.ai_socktype, rp.ai_protocol) + if sfd == -1 { + continue + } + + if posix.bind(sfd, rp.ai_addr, rp.ai_addrlen) == 0 { + // Success. + break + } + + posix.close(sfd) + } + + if rp == nil { + fmt.eprintln("Could not bind") + posix.exit(1) + } + + // Use the socket... + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + getaddrinfo :: proc( + nodename: cstring, + servname: cstring, + hints: ^addrinfo, + res: ^^addrinfo, + ) -> Info_Errno --- + + /* + Frees the given address info linked list. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + freeaddrinfo :: proc(ai: ^addrinfo) --- + + /* + Translate a socket address to a node name and service location. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getnameinfo.html ]] + */ + getnameinfo :: proc( + sa: ^sockaddr, salen: socklen_t, + node: [^]byte, nodelen: socklen_t, + service: [^]byte, servicelen: socklen_t, + flags: Nameinfo_Flags, + ) -> Info_Errno --- + + /* + Get a textual description for the address info errors. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gai_strerror.html ]] + */ + gai_strerror :: proc(ecode: Info_Errno) -> cstring --- + + /* + Opens a connection to the database and set the next entry to the first entry in the database. + + This reads /etc/hosts on most systems. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + sethostent :: proc(stayopen: b32) --- + + /* + Reads the next entry in the database, opening and closing a connection as necessary. + + This reads /etc/hosts on most systems. + + Example: + posix.sethostent(true) + defer posix.endhostent() + for ent := posix.gethostent(); ent != nil; ent = posix.gethostent() { + fmt.println(ent) + fmt.println(ent.h_addr_list[0][:ent.h_length]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + gethostent :: proc() -> ^hostent --- + + /* + Closes the connection to the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + endhostent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + setnetent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setnetent(true) + defer posix.endnetent() + for ent := posix.getnetent(); ent != nil; ent = posix.getnetent() { + fmt.println(ent) + fmt.println(transmute([4]byte)ent.n_net) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetent :: proc() -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyaddr :: proc(net: c.uint32_t, type: AF) -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyname :: proc(name: cstring) -> ^netent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + endnetent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + setprotoent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setprotoent(true) + defer posix.endprotoent() + for ent := posix.getprotoent(); ent != nil; ent = posix.getprotoent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotoent :: proc() -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobyname :: proc(name: cstring) -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobynumber :: proc(proto: c.int) -> ^protoent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + endprotoent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + setservent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setservent(true) + defer posix.endservent() + for ent := posix.getservent(); ent != nil; ent = posix.getservent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservent :: proc() -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyname :: proc(name: cstring, proto: cstring) -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyport :: proc(port: c.int, proto: cstring) -> ^servent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + endservent :: proc() --- +} + +Addrinfo_Flag_Bits :: enum c.int { + // Socket address is intended for bind(). + PASSIVE = log2(AI_PASSIVE), + // Request for canonical name. + CANONNAME = log2(AI_CANONNAME), + // Return numeric host address as name. + NUMERICHOST = log2(AI_NUMERICHOST), + // Inhibit service name resolution. + NUMERICSERV = log2(AI_NUMERICSERV), + // If no IPv6 addresses are found, query for IPv4 addresses and return them to the + // caller as IPv4-mapped IPv6 addresses. + V4MAPPED = log2(AI_V4MAPPED), + // Query for both IPv4 and IPv6 addresses. + ALL = log2(AI_ALL), + // Query for IPv4 addresses only when an IPv4 address is configured; query for IPv6 addresses + // only when an IPv6 address is configured. + ADDRCONFIG = log2(AI_ADDRCONFIG), +} +Addrinfo_Flags :: bit_set[Addrinfo_Flag_Bits; c.int] + +Nameinfo_Flag_Bits :: enum c.int { + // Only the nodename portion of the FQDN is returned for local hosts. + NOFQDN = log2(NI_NOFQDN), + // The numeric form of the node's address is returned instead of its name. + NUMERICHOST = log2(NI_NUMERICHOST), + // Return an error if the node's name cannot be located in the database. + NAMEREQD = log2(NI_NAMEREQD), + // The numeric form of the service address is returned instead of its name. + NUMERICSERV = log2(NI_NUMERICSERV), + // For IPv6 addresses, the numeric form of the scope identifier is returned instead of its name. + NUMERICSCOPE = log2(NI_NUMERICSCOPE), + // Indicates that the service is a datagram service (SOCK_DGRAM). + DGRAM = log2(NI_DGRAM), +} +Nameinfo_Flags :: bit_set[Nameinfo_Flag_Bits; c.int] + +Info_Errno :: enum c.int { + NONE = 0, + // The name could not be resolved at this time. Future attempts may succeed. + AGAIN = EAI_AGAIN, + // The flags had an invalid value. + BADFLAGS = EAI_BADFLAGS, + // A non-recoverable error ocurred. + FAIL = EAI_FAIL, + // The address family was not recognized or the address length was invald for the specified family. + FAMILY = EAI_FAMILY, + // There was a memory allocation failure. + MEMORY = EAI_MEMORY, + // The name does not resolve for the supplied parameters. + NONAME = EAI_NONAME, + // The service passed was not recognized for the specified socket. + SERVICE = EAI_SERVICE, + // The intended socket type was not recognized. + SOCKTYPE = EAI_SOCKTYPE, + // A system error occurred. The error code can be found in errno. + SYSTEM = EAI_SYSTEM, + // An argument buffer overflowed. + OVERFLOW = EAI_OVERFLOW, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + hostent :: struct { + h_name: cstring, /* [PSX] official name of host */ + h_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + h_addrtype: AF, /* [PSX] host address type */ + h_length: c.int, /* [PSX] length of address */ + h_addr_list: [^][^]byte `fmt:"v,0"`, /* [PSX] list of addresses from name server */ + } + + netent :: struct { + n_name: cstring, /* [PSX] official name of net */ + n_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + n_addrtype: AF, /* [PSX] net address type */ + n_net: c.uint32_t, /* [PSX] network # */ + } + + protoent :: struct { + p_name: cstring, /* [PSX] official protocol name */ + p_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + p_proto: c.int, /* [PSX] protocol # */ + } + + servent :: struct { + s_name: cstring, /* [PSX] official service name */ + s_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + s_port: c.int, /* [PSX] port # */ + s_proto: cstring, /* [PSX] protocol # */ + } + + // The highest reserved port number. + IPPORT_RESERVED :: 1024 + + addrinfo :: struct { + ai_flags: Addrinfo_Flags, /* [PSX] input flags */ + ai_family: AF, /* [PSX] address family of socket */ + ai_socktype: Sock, /* [PSX] socket type */ + ai_protocol: Protocol, /* [PSX] protocol of socket */ + ai_addrlen: socklen_t, /* [PSX] length of socket address */ + ai_canonname: cstring, /* [PSX] canonical name of service location */ + ai_addr: ^sockaddr, /* [PSX] binary address */ + ai_next: ^addrinfo, /* [PSX] pointer to next in list */ + } + + when ODIN_OS == .Darwin { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00001000 + AI_V4MAPPED :: 0x00000800 + AI_ALL :: 0x00000100 + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000100 + NI_DGRAM :: 0x00000010 + + } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00000008 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented on netbsd + AI_ALL :: 0x00000100 // NOTE: not implemented on netbsd + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000010 + NI_DGRAM :: 0x00000020 + + } else when ODIN_OS == .OpenBSD { + + AI_PASSIVE :: 1 + AI_CANONNAME :: 2 + AI_NUMERICHOST :: 4 + AI_NUMERICSERV :: 16 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented + AI_ALL :: 0x00000100 // NOTE: not implemented + AI_ADDRCONFIG :: 64 + + NI_NOFQDN :: 4 + NI_NUMERICHOST :: 1 + NI_NAMEREQD :: 8 + NI_NUMERICSERV :: 2 + NI_NUMERICSCOPE :: 32 + NI_DGRAM :: 16 + } + + when ODIN_OS == .OpenBSD { + EAI_AGAIN :: -3 + EAI_BADFLAGS :: -1 + EAI_FAIL :: -4 + EAI_FAMILY :: -6 + EAI_MEMORY :: -10 + EAI_NONAME :: -2 + EAI_SERVICE :: -8 + EAI_SOCKTYPE :: -7 + EAI_SYSTEM :: -11 + EAI_OVERFLOW :: -14 + } else { + EAI_AGAIN :: 2 + EAI_BADFLAGS :: 3 + EAI_FAIL :: 4 + EAI_FAMILY :: 5 + EAI_MEMORY :: 6 + EAI_NONAME :: 8 + EAI_SERVICE :: 9 + EAI_SOCKTYPE :: 10 + EAI_SYSTEM :: 11 + EAI_OVERFLOW :: 14 + } + +}else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin new file mode 100644 index 000000000..3926c5288 --- /dev/null +++ b/core/sys/posix/netinet_in.odin @@ -0,0 +1,199 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netinet/in.h - Internet address family + +foreign lib { + in6addr_any: in6_addr + in6addr_loopback: in6_addr +} + +in_port_t :: u16be +in_addr_t :: u32be + +INET_ADDRSTRLEN :: 16 +INET6_ADDRSTRLEN :: 46 + +Protocol :: enum c.int { + IP = IPPROTO_IP, + ICMP = IPPROTO_ICMP, + IPV6 = IPPROTO_IPV6, + RAW = IPPROTO_RAW, + TCP = IPPROTO_TCP, + UDP = IPPROTO_UDP, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + in_addr :: struct { + s_addr: in_addr_t, /* [PSX] big endian address */ + } + + in6_addr :: struct { + using _: struct #raw_union { + s6_addr: [16]c.uint8_t, /* [PSX] big endian address */ + __u6_addr16: [8]c.uint16_t, + __u6_addr32: [4]c.uint32_t, + }, + } + + sockaddr_in :: struct { + sin_len: c.uint8_t, + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_len: c.uint8_t, + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: c.uint32_t, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + + IPPROTO_IP :: 0 + IPPROTO_ICMP :: 1 + IPPROTO_IPV6 :: 41 + IPPROTO_RAW :: 255 + IPPROTO_TCP :: 6 + IPPROTO_UDP :: 17 + + INADDR_ANY :: 0x00000000 + INADDR_BROADCAST :: 0xFFFFFFFF + + IPV6_JOIN_GROUP :: 12 + IPV6_LEAVE_GROUP :: 13 + IPV6_MULTICAST_HOPS :: 10 + IPV6_MULTICAST_IF :: 9 + IPV6_MULTICAST_LOOP :: 11 + IPV6_UNICAST_HOPS :: 4 + IPV6_V6ONLY :: 27 + + IN6_IS_ADDR_UNSPECIFIED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr == 0 + } + + IN6_IS_ADDR_LOOPBACK :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^u32be)(&a.s6_addr[12])^ == 1 \ + ) + } + + IN6_IS_ADDR_MULTICAST :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xff + } + + IN6_IS_ADDR_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0x80 + } + + IN6_IS_ADDR_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0xc0 + } + + IN6_IS_ADDR_V4MAPPED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^u32be)(&a.s6_addr[8])^ == 0x0000ffff \ + ) + } + + IN6_IS_ADDR_V4COMPAT :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^c.uint32_t)(&a.s6_addr[12])^ != 0 && + (^u32be)(&a.s6_addr[12])^ != 1 \ + ) + } + + @(private) + __IPV6_ADDR_SCOPE_NODELOCAL :: 0x01 + @(private) + __IPV6_ADDR_SCOPE_LINKLOCAL :: 0x02 + @(private) + __IPV6_ADDR_SCOPE_SITELOCAL :: 0x05 + @(private) + __IPV6_ADDR_SCOPE_ORGLOCAL :: 0x08 + @(private) + __IPV6_ADDR_SCOPE_GLOBAL :: 0x0e + + @(private) + IPV6_ADDR_MC_FLAGS :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0xf0 + } + + @(private) + IPV6_ADDR_MC_FLAGS_TRANSIENT :: 0x10 + @(private) + IPV6_ADDR_MC_FLAGS_PREFIX :: 0x20 + @(private) + IPV6_ADDR_MC_FLAGS_UNICAST_BASED :: IPV6_ADDR_MC_FLAGS_TRANSIENT | IPV6_ADDR_MC_FLAGS_PREFIX + + @(private) + __IPV6_ADDR_MC_SCOPE :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0x0f + } + + IN6_IS_ADDR_MC_NODELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_NODELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (IPV6_ADDR_MC_FLAGS(a) != IPV6_ADDR_MC_FLAGS_UNICAST_BASED) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_LINKLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_SITELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_ORGLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_ORGLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_GLOBAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_GLOBAL) \ + ) + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/netinet_tcp.odin b/core/sys/posix/netinet_tcp.odin new file mode 100644 index 000000000..ecd084b38 --- /dev/null +++ b/core/sys/posix/netinet_tcp.odin @@ -0,0 +1,11 @@ +package posix + +// netinet/tcp.h - definitions for the Internet Transmission Control Protocol (TCP) + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + TCP_NODELAY :: 0x01 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin new file mode 100644 index 000000000..3e825e009 --- /dev/null +++ b/core/sys/posix/poll.odin @@ -0,0 +1,78 @@ +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// poll.h - definitions for the poll() function + +foreign lib { + /* + For each pointer in fds, poll() shall examine the given descriptor for the events. + poll will identify on which descriptors writes or reads can be done. + + Returns: -1 (setting errno) on failure, 0 on timeout, the amount of fds that have been changed on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html ]] + */ + poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: c.int) -> c.int --- +} + +nfds_t :: c.uint + +Poll_Event_Bits :: enum c.short { + // Data other than high-priority data may be read without blocking. + IN = log2(POLLIN), + // Normal data may be read without blocking. + RDNORM = log2(POLLRDNORM), + // Priority data may be read without blocking. + RDBAND = log2(POLLRDBAND), + // High priority data may be read without blocking. + PRI = log2(POLLPRI), + + // Normal data may be written without blocking. + OUT = log2(POLLOUT), + // Equivalent to POLLOUT. + WRNORM = log2(POLLWRNORM), + // Priority data may be written. + WRBAND = log2(POLLWRBAND), + + // An error has occurred (revents only). + ERR = log2(POLLERR), + // Device hsa been disconnected (revents only). + HUP = log2(POLLHUP), + // Invalid fd member (revents only). + NVAL = log2(POLLNVAL), +} +Poll_Event :: bit_set[Poll_Event_Bits; c.short] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + pollfd :: struct { + fd: FD, /* [PSX] the following descriptor being polled */ + events: Poll_Event, /* [PSX] the input event flags */ + revents: Poll_Event, /* [PSX] the output event flags */ + } + + POLLIN :: 0x0001 + POLLRDNORM :: 0x0040 + POLLRDBAND :: 0x0080 + POLLPRI :: 0x0002 + POLLOUT :: 0x0004 + POLLWRNORM :: POLLOUT + POLLWRBAND :: 0x0100 + + POLLERR :: 0x0008 + POLLHUP :: 0x0010 + POLLNVAL :: 0x0020 + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin new file mode 100644 index 000000000..eb0109a3e --- /dev/null +++ b/core/sys/posix/posix.odin @@ -0,0 +1,26 @@ +package posix + +import "base:intrinsics" + +import "core:c" + +result :: enum c.int { + // Use `errno` and `strerror` for more information. + FAIL = -1, + // Operation succeeded. + OK = 0, +} + +FD :: distinct c.int + +@(private) +log2 :: intrinsics.constant_log2 + +when ODIN_OS == .Darwin && ODIN_ARCH == .amd64 { + @(private) + INODE_SUFFIX :: "$INODE64" +} else { + @(private) + INODE_SUFFIX :: "" +} + diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin new file mode 100644 index 000000000..e264f6f6c --- /dev/null +++ b/core/sys/posix/pthread.odin @@ -0,0 +1,518 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + foreign import lib "system:pthread" +} else { + foreign import lib "system:c" +} + +// pthread.h - threads + +// NOTE: mutexes, rwlock, condition variables, once and barriers are left out in favour of `core:sync`. + +foreign lib { + /* + Initializes a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_init :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + Destroys a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_destroy :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_getdetachstate :: proc(attr: ^pthread_attr_t, detachstate: ^Detach_State) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_setdetachstate :: proc(attr: ^pthread_attr_t, detachstate: Detach_State) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_getguardsize :: proc(attr: ^pthread_attr_t, guardsize: ^c.size_t) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_setguardsize :: proc(attr: ^pthread_attr_t, guardsize: c.size_t) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_getinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: ^Inherit_Sched) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_setinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: Inherit_Sched) -> Errno --- + + /* + Gets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_getschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_setschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Gets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_getschedpolicy :: proc(attr: ^pthread_attr_t, policy: ^Sched_Policy) -> Errno --- + + /* + Sets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_setschedpolicy :: proc(attr: ^pthread_attr_t, policy: Sched_Policy) -> Errno --- + + /* + Gets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_getscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Sets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Get the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_getstack :: proc(attr: ^pthread_attr_t, stackaddr: ^[^]byte, stacksize: ^c.size_t) -> Errno --- + + /* + Specify the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_setstack :: proc(attr: ^pthread_attr_t, stackaddr: [^]byte, stacksize: c.size_t) -> Errno --- + + /* + Gets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_getstacksize :: proc(attr: ^pthread_attr_t, stacksize: ^c.size_t) -> Errno --- + + /* + Sets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_setstacksize :: proc(attr: ^pthread_attr_t, stacksize: c.size_t) -> Errno --- + + /* + Register fork handlers to be called before and after fork(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html ]] + */ + pthread_atfork :: proc(prepare: proc "c" (), parent: proc "c" (), child: proc "c" ()) -> Errno --- + + + /* + Cancel the execution of a thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cancel.html ]] + */ + pthread_cancel :: proc(thread: pthread_t) -> Errno --- + + /* + Creates a new thread with the given attributes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html ]] + */ + pthread_create :: proc( + thread: ^pthread_t, + attr: ^pthread_attr_t, + start_routine: proc "c" (arg: rawptr) -> rawptr, + arg: rawptr, + ) -> Errno --- + + + /* + Indicate that storage for the thread can be reclaimed when the thread terminates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_detach.html ]] + */ + pthread_detach :: proc(thread: pthread_t) -> Errno --- + + /* + Compare thread IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_equal.html ]] + */ + pthread_equal :: proc(t1: pthread_t, t2: pthread_t) -> b32 --- + + /* + Terminates the calling thread and make the given value available to any successfull join calls. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_exit.html ]] + */ + pthread_exit :: proc(value_ptr: rawptr) -> ! --- + + /* + Gets the current concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_getconcurrency :: proc() -> c.int --- + + /* + Sets the current desired concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_setconcurrency :: proc(new_level: c.int) -> Errno --- + + /* + Access a thread CPU-time clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getcpuclockid.html ]] + */ + pthread_getcpuclockid :: proc(thread_id: pthread_t, clock_id: ^clockid_t) -> Errno --- + + /* + Gets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_getschedparam :: proc(thread: pthread_t, policy: ^Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_setschedparam :: proc(thread: pthread_t, policy: Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Creates a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html ]] + */ + pthread_key_create :: proc(key: ^pthread_key_t, destructor: proc "c" (value: rawptr) = nil) -> Errno --- + + /* + Deletes a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_delete.html ]] + */ + pthread_key_delete :: proc(key: pthread_key_t) -> Errno --- + + /* + Returns the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_getspecific :: proc(key: pthread_key_t) -> rawptr --- + + /* + Sets the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_setspecific :: proc(key: pthread_key_t, value: rawptr) -> Errno --- + + /* + Suspends execution of the calling thread until the target thread terminates. + + Example: + ar: [10_000]i32 + + sb1 := ar[:5_000] + sb2 := ar[5_000:] + + th1, th2: posix.pthread_t + + posix.pthread_create(&th1, nil, incer, &sb1) + posix.pthread_create(&th2, nil, incer, &sb2) + + posix.pthread_join(th1) + posix.pthread_join(th2) + + incer :: proc "c" (arg: rawptr) -> rawptr { + sb := (^[]i32)(arg) + for &val in sb { + val += 1 + } + + return nil + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html ]] + */ + pthread_join :: proc(thread: pthread_t, value_ptr: ^rawptr = nil) -> Errno --- + + /* + Get the calling thread ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html ]] + */ + pthread_self :: proc() -> pthread_t --- + + /* + Atomically set the calling thread's cancelability and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcancelstate :: proc(state: Cancel_State, oldstate: ^Cancel_State) -> Errno --- + + /* + Atomically set the calling thread's cancel type and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcanceltype :: proc(type: Cancel_Type, oldtype: ^Cancel_Type) -> Errno --- + + + /* + Creates a cancellation point in the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_testcancel.html ]] + */ + pthread_testcancel :: proc() --- + + /* + Sets the scheduling priority for the thread given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setschedprio.html ]] + */ + pthread_setschedprio :: proc(thread: pthread_t, prio: c.int) -> Errno --- +} + +Detach_State :: enum c.int { + // Causes all threads to be in the joinable state. + CREATE_JOINABLE = PTHREAD_CREATE_JOINABLE, + // Causes all threads to be in the detached state. + CREATE_DETACHED = PTHREAD_CREATE_DETACHED, +} + +Inherit_Sched :: enum c.int { + // Threads inherit from the creating thread. + INHERIT_SCHED = PTHREAD_INHERIT_SCHED, + // Threads scheduling shall be set to the corresponding values from the attributes object. + EXPLICIT_SCHED = PTHREAD_EXPLICIT_SCHED, +} + +Thread_Scope :: enum c.int { + // System scheduling contention scope. + SYSTEM = PTHREAD_SCOPE_SYSTEM, + // Process scheduling contention scope. + PROCESS = PTHREAD_SCOPE_PROCESS, +} + +Cancel_State :: enum c.int { + ENABLE = PTHREAD_CANCEL_ENABLE, + DISABLE = PTHREAD_CANCEL_DISABLE, +} + +Cancel_Type :: enum c.int { + DEFERRED = PTHREAD_CANCEL_DEFERRED, + ASYNCHRONOUS = PTHREAD_CANCEL_ASYNCHRONOUS, +} + +when ODIN_OS == .Darwin { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x00 + PTHREAD_CANCEL_DEFERRED :: 0x02 + + PTHREAD_CANCEL_DISABLE :: 0x00 + PTHREAD_CANCEL_ENABLE :: 0x01 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 2 + PTHREAD_CREATE_JOINABLE :: 1 + + PTHREAD_EXPLICIT_SCHED :: 2 + PTHREAD_INHERIT_SCHED :: 1 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 2 + + PTHREAD_SCOPE_PROCESS :: 2 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct u64 + + pthread_attr_t :: struct { + __sig: c.long, + __opaque: [56]c.char, + } + + pthread_key_t :: distinct c.ulong + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + _: [4]c.char, + } + +} else when ODIN_OS == .FreeBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x02 + PTHREAD_CANCEL_DEFERRED :: 0x00 + + PTHREAD_CANCEL_DISABLE :: 0x01 + PTHREAD_CANCEL_ENABLE :: 0x00 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 0 + PTHREAD_PROCESS_PRIVATE :: 1 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 2 + + pthread_t :: distinct u64 + + pthread_attr_t :: distinct rawptr + + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .NetBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 1 + PTHREAD_INHERIT_SCHED :: 0 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 0 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct rawptr + + pthread_attr_t :: struct { + pta_magic: c.uint, + pta_flags: c.int, + pta_private: rawptr, + } + + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .OpenBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 2 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 0x1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 0x4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 0 + PTHREAD_PROCESS_PRIVATE :: 1 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 0x2 + + pthread_t :: distinct rawptr + pthread_attr_t :: distinct rawptr + pthread_key_t :: distinct c.int + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/pwd.odin b/core/sys/posix/pwd.odin new file mode 100644 index 000000000..546d58309 --- /dev/null +++ b/core/sys/posix/pwd.odin @@ -0,0 +1,168 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// pwd.h - password structure + +foreign lib { + /* + Rewinds the user database so that the next getpwent() returns the first entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + setpwent :: proc() --- + + /* + Returns the current entry in the user database. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + Example: + posix.setpwent() + defer posix.endpwent() + for e := posix.getpwent(); e != nil; e = posix.getpwent() { + fmt.println(e) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + @(link_name=LGETPWENT) + getpwent :: proc() -> ^passwd --- + + /* + Closes the user database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + endpwent :: proc() --- + + /* + Searches the database for an entry with a matching name. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAM) + getpwnam :: proc(name: cstring) -> ^passwd --- + + /* + Searches the database for an entry with a matching name. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + Example: + length := posix.sysconf(._GETPW_R_SIZE_MAX) + length = length == -1 ? 1024 : length + + buffer: [dynamic]byte + defer delete(buffer) + + result: posix.passwd + resultp: ^posix.passwd + errno: posix.Errno + for { + if err := resize(&buffer, length); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + errno = posix.getpwnam_r("root", &result, raw_data(buffer), len(buffer), &resultp) + if errno != .ERANGE { + break + } + } + + if errno != .NONE { + panic(string(posix.strerror(errno))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAMR) + getpwnam_r :: proc(name: cstring, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- + + /* + Searches the database for an entry with a matching uid. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid.html ]] + */ + @(link_name=LGETPWUID) + getpwuid :: proc(uid: uid_t) -> ^passwd --- + + /* + Searches the database for an entry with a matching uid. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + See the example for getpwnam_r. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html ]] + */ + @(link_name=LGETPWUIDR) + getpwuid_r :: proc(uid: uid_t, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- +} + +when ODIN_OS == .NetBSD { + @(private) LGETPWENT :: "__getpwent50" + @(private) LGETPWNAM :: "__getpwnam50" + @(private) LGETPWNAMR :: "__getpwnam_r50" + @(private) LGETPWUID :: "__getpwuid50" + @(private) LGETPWUIDR :: "__getpwuid_r50" +} else { + @(private) LGETPWENT :: "getpwent" + @(private) LGETPWNAM :: "getpwnam" + @(private) LGETPWNAMR :: "getpwnam_r" + @(private) LGETPWUID :: "getpwuid" + @(private) LGETPWUIDR :: "getpwuid_r" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + } + +} else when ODIN_OS == .FreeBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + pw_fields: c.int, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin new file mode 100644 index 000000000..6623ba6e6 --- /dev/null +++ b/core/sys/posix/sched.odin @@ -0,0 +1,105 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sched.h - execution scheduling + +foreign lib { + /* + Returns the minimum for the given scheduling policy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_max :: proc(policy: Sched_Policy) -> c.int --- + + /* + Returns the maximum for the given scheduling policy. + + Returns: -1 (setting errno) on failure, the maximum on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_min :: proc(policy: Sched_Policy) -> c.int --- + + /* + Forces the running thread to relinquish the processor until it again becomes the head of its thread list. + */ + sched_yield :: proc() -> result --- + + /* NOTE: unimplemented on darwin (I think?). + /* + Get the scheduling params of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getparam.html ]] + */ + sched_getparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + /* + Sets the scheduling parameters of the given process, pid of 0 will set that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setparam.html ]] + */ + sched_setparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + + /* + Returns the scheduling policy of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getscheduler.html ]] + */ + sched_getscheduler :: proc(pid: pid_t) -> Sched_Policy --- + + /* + Sets the scheduling policy and parameters of the process, pid 0 will be the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setscheduler.html ]] + */ + sched_setscheduler :: proc(pid: pid_t, policy: Sched_Policy, param: ^sched_param) -> result --- + + /* + Updates the timespec structure to contain the current execution time limit for the process. + pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_rr_get_interval.html ]] + */ + sched_rr_get_interval :: proc(pid: pid_t, interval: ^timespec) -> result --- + */ +} + +Sched_Policy :: enum c.int { + // Error condition of sched_getscheduler. + ERROR = -1, + // First in-first out (FIFO) scheduling policy. + FIFO = SCHED_FIFO, + // Round robin scheduling policy. + RR = SCHED_RR, + // Another scheduling policy. + OTHER = SCHED_OTHER, +} + +when ODIN_OS == .Darwin { + + SCHED_FIFO :: 4 + SCHED_RR :: 2 + // SCHED_SPORADIC :: 3 NOTE: not a thing on freebsd, netbsd and probably others, leaving it out + SCHED_OTHER :: 1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + SCHED_FIFO :: 1 + SCHED_RR :: 3 + SCHED_OTHER :: 2 + +} else when ODIN_OS == .NetBSD { + + SCHED_OTHER :: 0 + SCHED_FIFO :: 1 + SCHED_RR :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/setjmp.odin b/core/sys/posix/setjmp.odin new file mode 100644 index 000000000..cb1dad184 --- /dev/null +++ b/core/sys/posix/setjmp.odin @@ -0,0 +1,58 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// setjmp.h - stack environment declarations + +foreign lib { + /* + Equivalent to longjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _longjmp :: proc(env: ^jmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _setjmp :: proc(env: ^jmp_buf) -> c.int --- + + /* + Equivalent to longjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGLONGJMP) + siglongjmp :: proc(env: ^sigjmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGSETJMP) + sigsetjmp :: proc(env: ^sigjmp_buf, savemask: b32) -> c.int --- +} + +jmp_buf :: libc.jmp_buf +sigjmp_buf :: distinct jmp_buf + +longjmp :: libc.longjmp +setjmp :: libc.setjmp + +when ODIN_OS == .NetBSD { + @(private) LSIGSETJMP :: "__sigsetjmp14" + @(private) LSIGLONGJMP :: "__siglongjmp14" +} else { + @(private) LSIGSETJMP :: "sigsetjmp" + @(private) LSIGLONGJMP :: "siglongjmp" +} diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin new file mode 100644 index 000000000..3bfd662f0 --- /dev/null +++ b/core/sys/posix/signal.odin @@ -0,0 +1,1131 @@ +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// signal.h - signals + +foreign lib { + // LIBC: + + /* + Set a signal handler. + + func can either be: + - `auto_cast posix.SIG_DFL` setting the default handler for that specific signal + - `auto_cast posix.SIG_IGN` causing the specific signal to be ignored + - a custom signal handler + + Returns: SIG_ERR (setting errno), the last value of func on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html ]] + */ + signal :: proc(sig: Signal, func: proc "c" (Signal)) -> proc "c" (Signal) --- + + /* + Raises a signal, calling its handler and then returning. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html ]] + */ + raise :: proc(sig: Signal) -> result --- + + // POSIX: + + /* + Raise a signal to the process/group specified by pid. + + If sig is 0, this function can be used to check if the pid is just checked for validity. + + If pid is -1, the signal is sent to all processes that the current process has permission to send. + + If pid is negative (not -1), the signal is sent to all processes in the group identifier by the + absolute value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html ]] + */ + kill :: proc(pid: pid_t, sig: Signal) -> result --- + + /* + Shorthand for `kill(-pgrp, sig)` which will kill all processes in the given process group. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html ]] + */ + killpg :: proc(pgrp: pid_t, sig: Signal) -> result --- + + /* + Writes a language-dependent message to stderror. + + Example: + posix.psignal(.SIGSEGV, "that didn't go well") + + Possible Output: + that didn't go well: Segmentation fault + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/psignal.html ]] + */ + psignal :: proc(signum: Signal, message: cstring) --- + + /* + Send a signal to a thread. + + As with kill, if sig is 0, only validation (of the pthread_t given) is done and no signal is sent. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html ]] + */ + pthread_kill :: proc(thread: pthread_t, sig: Signal) -> Errno --- + + /* + Examine and change blocked signals. + + Equivalent to sigprocmask(), without the restriction that the call be made in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + pthread_sigmask :: proc(how: Sig, set: ^sigset_t, oset: ^sigset_t) -> Errno --- + + /* + Examine and change blocked signals in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + @(link_name=LSIGPROCMASK) + sigprocmask :: proc(how: Sig, set: ^sigset_t, oldset: ^sigset_t) -> result --- + + /* + Examine and change a signal action. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html ]] + */ + @(link_name=LSIGACTION) + sigaction :: proc(sig: Signal, act: ^sigaction_t, oact: ^sigaction_t) -> result --- + + @(link_name=LSIGADDSET) + sigaddset :: proc(set: ^sigset_t, signo: Signal) -> result --- + @(link_name=LSIGDELSET) + sigdelset :: proc(^sigset_t, Signal) -> c.int --- + @(link_name=LSIGEMPTYSET) + sigemptyset :: proc(^sigset_t) -> c.int --- + @(link_name=LSIGFILLSET) + sigfillset :: proc(^sigset_t) -> c.int --- + + /* + Set and get the signal alternate stack context. + + Example: + sigstk := posix.stack_t { + ss_sp = make([^]byte, posix.SIGSTKSZ) or_else panic("allocation failure"), + ss_size = posix.SIGSTKSZ, + ss_flags = {}, + } + if posix.sigaltstack(&sigstk, nil) != .OK { + fmt.panicf("sigaltstack failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaltstack.html ]] + */ + @(link_name=LSIGALTSTACK) + sigaltstack :: proc(ss: ^stack_t, oss: ^stack_t) -> result --- + + /* + Adds sig to the signal mask of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sighold :: proc(sig: Signal) -> result --- + + /* + Sets the disposition of sig to SIG_IGN. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigignore :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process and suspend the calling process until + a signal is received. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigpause :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process. + + Returns: always -1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigrelse :: proc(sig: Signal) -> result --- + + /* + Changes the restart behavior when a function is interrupted by the specified signal. + + If flag is true, SA_RESTART is removed, added otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siginterrupt.html ]] + */ + siginterrupt :: proc(sig: Signal, flag: b32) -> result --- + + /* + Test for a signal in a signal set. + + Returns: 1 if it is a member, 0 if not, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigismember.html ]] + */ + @(link_name=LSIGISMEMBER) + sigismember :: proc(set: ^sigset_t, signo: Signal) -> c.int --- + + /* + Stores the set of signals that are blocked from delivery to the calling thread and that are pending + on the process or the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigpending.html ]] + */ + @(link_name=LSIGPENDING) + sigpending :: proc(set: ^sigset_t) -> result --- + + /* + Wait for one of the given signals. + + Returns: always -1 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigsuspend.html ]] + */ + @(link_name=LSIGSUSPEND) + sigsuspend :: proc(sigmask: ^sigset_t) -> result --- + + /* + Wait for queued signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigwait.html ]] + */ + sigwait :: proc(set: ^sigset_t, sig: ^Signal) -> Errno --- + + /* NOTE: unimplemented on darwin. + + void psiginfo(const siginfo_t *, const char *); + int sigqueue(pid_t, int, union sigval); + void (*sigset(int, void (*)(int)))(int); + int sigsuspend(const sigset_t *); + int sigtimedwait(const sigset_t *restrict, siginfo_t *restrict, + const struct timespec *restrict); + int sigwaitinfo(const sigset_t *restrict, siginfo_t *restrict); + */ +} + +sigval :: struct #raw_union { + sigval_int: c.int, /* [PSX] integer signal value */ + sigval_ptr: rawptr, /* [PSX] pointer signal value */ +} + +Signal :: enum c.int { + NONE, + + // LIBC: + + // Process abort signal. + SIGABRT = SIGABRT, + // Erronous arithemtic operation. + SIGFPE = SIGFPE, + // Illegal instruction. + SIGILL = SIGILL, + // Terminal interrupt signal. + SIGINT = SIGINT, + // Invalid memory reference. + SIGSEGV = SIGSEGV, + // Termination signal. + SIGTERM = SIGTERM, + + // POSIX: + + // Process abort signal. + SIGALRM = SIGALRM, + // Access to an undefined portion of a memory object. + SIGBUS = SIGBUS, + // Child process terminated, stopped, or continued. + SIGCHLD = SIGCHLD, + // Continue execution, if stopped. + SIGCONT = SIGCONT, + // Hangup. + SIGHUP = SIGHUP, + // Kill (cannot be caught or ignored). + SIGKILL = SIGKILL, + // Write on a pipe with no one to read it. + SIGPIPE = SIGPIPE, + // Terminal quit signal. + SIGQUIT = SIGQUIT, + // Stop executing (cannot be caught or ignored). + SIGSTOP = SIGSTOP, + // Terminal stop process. + SIGTSTP = SIGTSTP, + // Background process attempting read. + SIGTTIN = SIGTTIN, + // Background process attempting write. + SIGTTOU = SIGTTOU, + // User-defined signal 1. + SIGUSR1 = SIGUSR1, + // User-defined signal 2. + SIGUSR2 = SIGUSR2, + // Pollable event. + SIGPOLL = SIGPOLL, + // Profiling timer expired. + SIGPROF = SIGPROF, + // Bad system call. + SIGSYS = SIGSYS, + // Trace/breakpoint trap. + SIGTRAP = SIGTRAP, + // High bandwidth data is available at a socket. + SIGURG = SIGURG, + // Virtual timer expired. + SIGVTALRM = SIGVTALRM, + // CPU time limit exceeded. + SIGXCPU = SIGXCPU, + // File size limit exceeded. + SIGXFSZ = SIGXFSZ, +} + +ILL_Code :: enum c.int { + // Illegal opcode. + ILLOPC = ILL_ILLOPC, + // Illegal operand. + ILLOPN = ILL_ILLOPN, + // Illegal addressing mode. + ILLADR = ILL_ILLADR, + // Illegal trap. + ILLTRP = ILL_ILLTRP, + // Priviledged opcode. + PRVOPC = ILL_PRVOPC, + // Priviledged register. + PRVREG = ILL_PRVREG, + // Coprocessor error. + COPROC = ILL_COPROC, + // Internal stack error. + BADSTK = ILL_BADSTK, +} + +FPE_Code :: enum c.int { + // Integer divide by zero. + INTDIV = FPE_INTDIV, + // Integer overflow. + INTOVF = FPE_INTOVF, + // Floating-point divide by zero. + FLTDIV = FPE_FLTDIV, + // Floating-point overflow. + FLTOVF = FPE_FLTOVF, + // Floating-point underflow. + FLTUND = FPE_FLTUND, + // Floating-point inexact result. + FLTRES = FPE_FLTRES, + // Invalid floating-point operation. + FLTINV = FPE_FLTINV, + // Subscript out of range. + FLTSUB = FPE_FLTSUB, +} + +SEGV_Code :: enum c.int { + // Address not mapped to object. + MAPERR = SEGV_MAPERR, + // Invalid permissions for mapped object. + ACCERR = SEGV_ACCERR, +} + +BUS_Code :: enum c.int { + // Invalid address alignment. + ADRALN = BUS_ADRALN, + // Nonexistent physical address. + ADRERR = BUS_ADRERR, + // Object-specific hardware error. + OBJERR = BUS_OBJERR, +} + +TRAP_Code :: enum c.int { + // Process breakpoint. + BRKPT = TRAP_BRKPT, + // Process trace trap. + TRACE = TRAP_TRACE, +} + +CLD_Code :: enum c.int { + // Child has exited.. + EXITED = CLD_EXITED, + // Child has terminated abnormally and did not create a core file. + KILLED = CLD_KILLED, + // Child has terminated abnormally and created a core file. + DUMPED = CLD_DUMPED, + // Traced child trapped. + TRAPPED = CLD_TRAPPED, + // Child has stopped. + STOPPED = CLD_STOPPED, + // Stopped child has continued. + CONTINUED = CLD_CONTINUED, +} + +POLL_Code :: enum c.int { + // Data input is available. + IN = POLL_IN, + // Output buffers available. + OUT = POLL_OUT, + // Input message available. + MSG = POLL_MSG, + // I/O error. + ERR = POLL_ERR, + // High priority input available. + PRI = POLL_PRI, + // Device disconnected. + HUP = POLL_HUP, +} + +Any_Code :: enum c.int { + // Signal sent by kill(). + USER = SI_USER, + // Signal sent by sigqueue(). + QUEUE = SI_QUEUE, + // Signal generated by expiration of a timer set by timer_settime(). + TIMER = SI_TIMER, + // Signal generated by completion of an asynchronous I/O request. + ASYNCIO = SI_ASYNCIO, + // Signal generated by arrival of a message on an empty message queue. + MESGQ = SI_MESGQ, +} + +SA_Flags_Bits :: enum c.int { + // Do not generate SIGCHLD when children stop or stopped children continue. + NOCLDSTOP = log2(SA_NOCLDSTOP), + // Cause signal delivery to occur on an alternate stack. + ONSTACK = log2(SA_ONSTACK), + // Cause signal disposition to be set to SIG_DFL on entry to signal handlers. + RESETHAND = log2(SA_RESETHAND), + // Cause certain functions to become restartable. + RESTART = log2(SA_RESTART), + // Cause extra information to be passed to signal handlers at the time of receipt of a signal. + SIGINFO = log2(SA_SIGINFO), + // Cause implemention not to create zombie processes or status information on child termination. + NOCLDWAIT = log2(SA_NOCLDWAIT), + // Cause signal not to be automatically blocked on entry to signal handler. + SA_NODEFER = log2(SA_NODEFER), +} +SA_Flags :: bit_set[SA_Flags_Bits; c.int] + +SS_Flag_Bits :: enum c.int { + // Process is executing on an alternate signal stack. + ONSTACK = log2(SS_ONSTACK), + // Alternate signal stack is disabled. + DISABLE = log2(SS_DISABLE), +} +SS_Flags :: bit_set[SS_Flag_Bits; c.int] + +Sig :: enum c.int { + // Resulting set is the union of the current set and the signal set and the complement of + // the signal set pointed to by the argument. + BLOCK = SIG_BLOCK, + // Resulting set is the intersection of the current set and the complement of the signal set + // pointed to by the argument. + UNBLOCK = SIG_UNBLOCK, + // Resulting set is the signal set pointed to by the argument. + SETMASK = SIG_SETMASK, +} + +// Request for default signal handling. +SIG_DFL :: libc.SIG_DFL +// Return value from signal() in case of error. +SIG_ERR :: libc.SIG_ERR +// Request that signal be ignored. +SIG_IGN :: libc.SIG_IGN + +SIGABRT :: libc.SIGABRT +SIGFPE :: libc.SIGFPE +SIGILL :: libc.SIGILL +SIGINT :: libc.SIGINT +SIGSEGV :: libc.SIGSEGV +SIGTERM :: libc.SIGTERM + +when ODIN_OS == .NetBSD { + @(private) LSIGPROCMASK :: "__sigprocmask14" + @(private) LSIGACTION :: "__sigaction_siginfo" + @(private) LSIGADDSET :: "__sigaddset14" + @(private) LSIGDELSET :: "__sigdelset14" + @(private) LSIGEMPTYSET :: "__sigemptyset14" + @(private) LSIGFILLSET :: "__sigfillset14" + @(private) LSIGALTSTACK :: "__sigaltstack14" + @(private) LSIGISMEMBER :: "__sigismember14" + @(private) LSIGPENDING :: "__sigpending14" + @(private) LSIGSUSPEND :: "__sigsuspend14" +} else { + @(private) LSIGPROCMASK :: "sigprocmask" + @(private) LSIGACTION :: "sigaction" + @(private) LSIGADDSET :: "sigaddset" + @(private) LSIGDELSET :: "sigdelset" + @(private) LSIGEMPTYSET :: "sigemptyset" + @(private) LSIGFILLSET :: "sigfillset" + @(private) LSIGALTSTACK :: "sigaltstack" + @(private) LSIGISMEMBER :: "sigismember" + @(private) LSIGPENDING :: "sigpending" + @(private) LSIGSUSPEND :: "sigsuspend" +} + +when ODIN_OS == .Darwin { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(5)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: + // SIGRTMAX :: + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 32768 + SIGSTKSZ :: 131072 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value of signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + si_band: c.long, /* [PSX] band event for SIGPOLL */ + __pad: [7]c.ulong, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 4 + ILL_ILLADR :: 5 + ILL_ILLTRP :: 2 + ILL_PRVOPC :: 3 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 7 + FPE_INTOVF :: 8 + FPE_FLTDIV :: 1 + FPE_FLTOVF :: 2 + FPE_FLTUND :: 3 + FPE_FLTRES :: 4 + FPE_FLTINV :: 5 + FPE_FLTSUB :: 6 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .FreeBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: 65 + // SIGRTMAX :: 126 + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_flags: SA_Flags, /* [PSX] special flags */ + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm32 { + MINSIGSTKSZ :: 1024 * 4 + } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + MINSIGSTKSZ :: 512 * 4 + } + + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value of signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + using _reason: struct #raw_union { + _fault: struct { + _trapno: c.int, /* machine specific trap code */ + }, + _timer: struct { + _timerid: c.int, + _overrun: c.int, + }, + _mesgq: struct { + _mqd: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + _capsicum: struct { + _syscall: c.int, /* syscall number for signals delivered as a result of system calls denied by capsicum */ + }, + __spare__: struct { + __spare1__: c.long, + __spare2__: [7]c.int, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 2 + FPE_INTOVF :: 1 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .NetBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + // MOTE: unimplemented on darwin. + // + // SIGRTMIN :: 33 + // SIGRTMAX :: 63 + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 8192 + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + @(private) + lwpid_t :: c.int32_t + + siginfo_t :: struct #raw_union { + si_pad: [128]byte, + using _info: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + // #ifdef _LP64 + /* In _LP64 the union starts on an 8-byte boundary. */ + _pad: c.int, + // #endif + using _reason: struct #raw_union { + using _rt: struct { + _pid: pid_t, + _uid: uid_t, + si_value: sigval, /* [PSX] signal value */ + }, + using _child: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value of signal */ + _utime: clock_t, + _stime: clock_t, + }, + using _fault: struct { + si_addr: rawptr, /* [PSX] address of faulting instruction */ + _trap: c.int, + _trap2: c.int, + _trap3: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + _fd: FD, + }, + _syscall: struct { + _sysnum: c.int, + _retval: [2]c.int, + _error: c.int, + _args: [8]c.uint64_t, + }, + _ptrace_state: struct { + _pe_report_event: c.int, + _option: struct #raw_union { + _pe_other_pid: pid_t, + _pe_lwp: lwpid_t, + }, + }, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -1 + SI_TIMER :: -2 + SI_ASYNCIO :: -3 + SI_MESGQ :: -4 + +} else when ODIN_OS == .OpenBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 3 << 12 + SIGSTKSZ :: MINSIGSTKSZ + (1 << 12) * 4 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + SI_MAXSZ :: 128 + SI_PAD :: (SI_MAXSZ / size_of(c.int)) - 3 + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + using _data: struct #raw_union { + _pad: [SI_PAD]c.int, + using _proc: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + using _pdata: struct #raw_union { + using _kill: struct { + si_value: sigval, + }, + using _cld: struct { + _utime: clock_t, + _stime: clock_t, + si_status: c.int, + }, + }, + }, + using _fault: struct { + si_addr: rawptr, + _trapno: c.int, + }, + using _file: struct { + _fd: FD, + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -2 + SI_TIMER :: -3 + SI_ASYNCIO :: -4 // NOTE: not implemented + SI_MESGQ :: -5 // NOTE: not implemented + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/stdio.odin b/core/sys/posix/stdio.odin new file mode 100644 index 000000000..de716f8d7 --- /dev/null +++ b/core/sys/posix/stdio.odin @@ -0,0 +1,332 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdio.h - standard buffered input/output + +foreign lib { + /* + Generates a string that, when used as a pathname, + refers to the current controlling terminal for the current process. + + If s is nil, the returned string might be static and overwritten by subsequent calls or other factors. + If s is not nil, s is assumed len(s) >= L_ctermid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctermid.html ]] + */ + ctermid :: proc(s: [^]byte) -> cstring --- + + /* + Equivalent to fprintf but output is written to the file descriptor. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + dprintf :: proc(fildse: FD, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Equivalent to fprintf but output is written to s, it is the user's responsibility to + ensure there is enough space. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + sprintf :: proc(s: [^]byte, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Associate a stream with a file descriptor. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopen.html ]] + */ + fdopen :: proc(fildes: FD, mode: cstring) -> ^FILE --- + + /* + Map a stream pointer to a file descriptor. + + Returns: the file descriptor or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fileno.html ]] + */ + fileno :: proc(stream: ^FILE) -> FD --- + + /* + Locks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + flockfile :: proc(file: ^FILE) --- + + /* + Tries to lock a file. + + Returns: 0 if it could be locked, non-zero if it couldn't + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + ftrylockfile :: proc(file: ^FILE) -> c.int --- + + /* + Unlocks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + funlockfile :: proc(file: ^FILE) --- + + /* + Open a memory buffer stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html ]] + */ + fmemopen :: proc(buf: [^]byte, size: c.size_t, mode: cstring) -> ^FILE --- + + /* + Reposition a file-position indicator in a stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseeko.html ]] + */ + fseeko :: proc(stream: ^FILE, offset: off_t, whence: Whence) -> result --- + + /* + Return the file offset in a stream. + + Returns: the current file offset, -1 (setting errno) on error + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftello.html ]] + */ + ftello :: proc(^FILE) -> off_t --- + + /* + Open a dynamic memory buffer stream. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open_memstream.html ]] + */ + open_memstream :: proc(bufp: ^[^]byte, sizep: ^c.size_t) -> ^FILE --- + + /* + Equivalent to getc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + getc_unlocked :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to getchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + getchar_unlocked :: proc() -> c.int --- + + /* + Equivalent to putc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + putc_unlocked :: proc(ch: c.int, stream: ^FILE) -> c.int --- + + /* + Equivalent to putchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + putchar_unlocked :: proc(ch: c.int) -> c.int --- + + /* + Read a delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getdelim :: proc(lineptr: ^cstring, n: ^c.size_t, delimiter: c.int, stream: ^FILE) -> c.ssize_t --- + + /* + Read a line delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + Example: + fp := posix.fopen(#file, "r") + if fp == nil { + posix.exit(1) + } + + line: cstring + length: uint + for { + read := posix.getline(&line, &length, fp) + if read == -1 do break + posix.printf("Retrieved line of length %zu :\n", read) + posix.printf("%s", line) + } + if posix.ferror(fp) != 0 { + /* handle error */ + } + posix.free(rawptr(line)) + posix.fclose(fp) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getline :: proc(lineptr: ^cstring, n: ^c.size_t, stream: ^FILE) -> c.ssize_t --- + + /* + Get a string from the stdin stream. + + It is up to the user to make sure s is big enough. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html ]] + */ + gets :: proc(s: [^]byte) -> cstring --- + + /* + Create a name for a temporary file. + + Returns: an allocated cstring that needs to be freed, nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html ]] + */ + tempnam :: proc(dir: cstring, pfx: cstring) -> cstring --- + + /* + Executes the command specified, creating a pipe and returning a pointer to a stream that can + read or write from/to the pipe. + + Returns: nil (setting errno) on failure or a pointer to the stream + + Example: + fp := posix.popen("ls *", "r") + if fp == nil { + /* Handle error */ + } + + path: [1024]byte + for posix.fgets(raw_data(path[:]), len(path), fp) != nil { + posix.printf("%s", &path) + } + + status := posix.pclose(fp) + if status == -1 { + /* Error reported by pclose() */ + } else { + /* Use functions described under wait() to inspect `status` in order + to determine success/failure of the command executed by popen() */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html ]] + */ + popen :: proc(command: cstring, mode: cstring) -> ^FILE --- + + /* + Closes a pipe stream to or from a process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pclose.html ]] + */ + pclose :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to rename but relative directories are resolved from their respective fds. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html ]] + */ + renameat :: proc(oldfd: FD, old: cstring, newfd: FD, new: cstring) -> result --- +} + +clearerr :: libc.clearerr +fclose :: libc.fclose +feof :: libc.feof +ferror :: libc.ferror +fflush :: libc.fflush +fgetc :: libc.fgetc +fgetpos :: libc.fgetpos +fgets :: libc.fgets +fopen :: libc.fopen +fprintf :: libc.fprintf +fputc :: libc.fputc +fread :: libc.fread +freopen :: libc.freopen +fscanf :: libc.fscanf +fseek :: libc.fseek +fsetpos :: libc.fsetpos +ftell :: libc.ftell +fwrite :: libc.fwrite +getc :: libc.getc +getchar :: libc.getchar +perror :: libc.perror +printf :: libc.printf +putc :: libc.puts +putchar :: libc.putchar +puts :: libc.puts +remove :: libc.remove +rename :: libc.rename +rewind :: libc.rewind +scanf :: libc.scanf +setbuf :: libc.setbuf +setvbuf :: libc.setvbuf +snprintf :: libc.snprintf +sscanf :: libc.sscanf +tmpfile :: libc.tmpfile +tmpnam :: libc.tmpnam +vfprintf :: libc.vfprintf +vfscanf :: libc.vfscanf +vprintf :: libc.vprintf +vscanf :: libc.vscanf +vsnprintf :: libc.vsnprintf +vsprintf :: libc.vsprintf +vsscanf :: libc.vsscanf +ungetc :: libc.ungetc + +to_stream :: libc.to_stream + +Whence :: libc.Whence +FILE :: libc.FILE +fpos_t :: libc.fpos_t + +BUFSIZ :: libc.BUFSIZ + +_IOFBF :: libc._IOFBF +_IOLBF :: libc._IOLBF +_IONBF :: libc._IONBF + +SEEK_CUR :: libc.SEEK_CUR +SEEK_END :: libc.SEEK_END +SEEK_SET :: libc.SEEK_SET + +FILENAME_MAX :: libc.FILENAME_MAX +FOPEN_MAX :: libc.FOPEN_MAX +TMP_MAX :: libc.TMP_MAX + +EOF :: libc.EOF + +stderr := libc.stderr +stdin := libc.stdin +stdout := libc.stdout + +when ODIN_OS == .Darwin { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/var/tmp/" + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/tmp/" + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/stdlib.odin b/core/sys/posix/stdlib.odin new file mode 100644 index 000000000..a1e2eab50 --- /dev/null +++ b/core/sys/posix/stdlib.odin @@ -0,0 +1,450 @@ +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdlib.h - standard library definitions + +atof :: libc.atof +atoi :: libc.atoi +atol :: libc.atol +atoll :: libc.atoll +strtod :: libc.strtod +strtof :: libc.strtof +strtol :: libc.strtol +strtoll :: libc.strtoll +strtoul :: libc.strtoul +strtoull :: libc.strtoull + +rand :: libc.rand +srand :: libc.srand + +calloc :: libc.calloc +malloc :: libc.malloc +realloc :: libc.realloc + +abort :: libc.abort +atexit :: libc.atexit +at_quick_exit :: libc.at_quick_exit +exit :: libc.exit +_Exit :: libc._Exit +getenv :: libc.getenv +quick_exit :: libc.quick_exit +system :: libc.system + +bsearch :: libc.bsearch +qsort :: libc.qsort + +abs :: libc.abs +labs :: libc.labs +llabs :: libc.llabs +div :: libc.div +ldiv :: libc.ldiv +lldiv :: libc.lldiv + +mblen :: libc.mblen +mbtowc :: libc.mbtowc +wctomb :: libc.wctomb + +mbstowcs :: libc.mbstowcs +wcstombs :: libc.wcstombs + +free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { + libc.free(rawptr(ptr)) +} + +foreign lib { + /* + Takes a pointer to a radix-64 representation, in which the first digit is the least significant, + and return the corresponding long value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + a64l :: proc(s: cstring) -> c.long --- + + /* + The l64a() function shall take a long argument and return a pointer to the corresponding + radix-64 representation. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + l64a :: proc(value: c.long) -> cstring --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + drand48 :: proc() -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + erand48 :: proc(xsubi: ^[3]c.ushort) -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + mrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + jrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + nrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + srand48 :: proc(seedval: c.long) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lcong48 :: proc(param: ^[7]c.ushort) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + seed48 :: proc(seed16v: ^[3]c.ushort) -> ^[3]c.ushort --- + + /* + Parses suboption arguments in a flag argument. + + Returns: the index of the matched token string, or -1 if no token strings were matched + + Example: + args := runtime.args__ + + Opt :: enum { + RO, + RW, + NAME, + NIL, + } + token := [Opt]cstring{ + .RO = "ro", + .RW = "rw", + .NAME = "name", + .NIL = nil, + } + + Options :: struct { + readonly, readwrite: bool, + name: cstring, + + } + opts: Options + + errfnd: bool + for { + opt := posix.getopt(i32(len(args)), raw_data(args), "o:") + if opt == -1 { + break + } + + switch opt { + case 'o': + subopt := posix.optarg + value: cstring + for subopt != "" && !errfnd { + o := posix.getsubopt(&subopt, &token[.RO], &value) + switch Opt(o) { + case .RO: opts.readonly = true + case .RW: opts.readwrite = true + case .NAME: + if value == nil { + fmt.eprintfln("missing value for suboption %s", token[.NAME]) + errfnd = true + continue + } + + opts.name = value + case .NIL: + fallthrough + case: + fmt.eprintfln("no match found for token: %s", value) + errfnd = true + } + } + if opts.readwrite && opts.readonly { + fmt.eprintfln("Only one of %s and %s can be specified", token[.RO], token[.RW]) + errfnd = true + } + case: + errfnd = true + } + } + + if errfnd || len(args) == 1 { + fmt.eprintfln("\nUsage: %s -o ", args[0]) + fmt.eprintfln("suboptions are 'ro', 'rw', and 'name='") + posix.exit(1) + } + + fmt.println(opts) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsubopt.html ]] + */ + getsubopt :: proc(optionp: ^cstring, keylistp: [^]cstring, valuep: ^cstring) -> c.int --- + + /* + Changes the mode and ownership of the slave pseudo-terminal device associated with its master pseudo-terminal counterpart. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html ]] + */ + grantpt :: proc(fildes: FD) -> result --- + + /* + Allows a state array, pointed to by the state argument, to be initialized for future use. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LINITSTATE) + initstate :: proc(seed: c.uint, state: [^]byte, size: c.size_t) -> [^]byte --- + + /* + Sets the state array of the random number generator. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + setstate :: proc(state: [^]byte) -> [^]byte --- + + /* + Use a non-linear additive feedback random-number generator employing a default state array + size of 31 long integers to return successive pseudo-random numbers in the range from 0 to 231-1. + The period of this random-number generator is approximately 16 x (231-1). + The size of the state array determines the period of the random-number generator. + Increasing the state array size shall increase the period. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + random :: proc() -> c.long --- + + /* + Initializes the current state array using the value of seed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LSRANDOM) + srandom :: proc(seed: c.uint) --- + + /* + Creates a directory with a unique name derived from template. + The application shall ensure that the string provided in template is a pathname ending + with at least six trailing 'X' characters. + + Returns: nil (setting errno) on failure, template on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkdtemp :: proc(template: [^]byte) -> cstring --- + + /* + Creates a regular file with a unique name derived from template and return a file descriptor + for the file open for reading and writing. + The application shall ensure that the string provided in template is a pathname ending with + at least six trailing 'X' characters. + + Returns: -1 (setting errno) on failure, an open file descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkstemp :: proc(template: cstring) -> FD --- + + /* + Allocates size bytes aligned on a boundary specified by alignment, and shall return a pointer + to the allocated memory in memptr. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html ]] + */ + posix_memalign :: proc(memptr: ^[^]byte, alignment: c.size_t, size: c.size_t) -> Errno --- + + /* + Establishes a connection between a master device for a pseudo-terminal and a file descriptor. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html ]] + */ + posix_openpt :: proc(oflag: O_Flags) -> FD --- + + /* + Returns the name of the slave pseudo-terminal device associated with a master pseudo-terminal device. + + Returns: nil (setting errno) on failure, the name on success, which may be invalidated on subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ptsname.html ]] + */ + ptsname :: proc(fildes: FD) -> cstring --- + + /* + Unlocks the slave pseudo-terminal device associated with the master to which fildes refers. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html ]] + */ + unlockpt :: proc(fildes: FD) -> result --- + + /* + Uses the string argument to set environment variable values. + + Returns: 0 on success, non-zero (setting errno) on failure + + Example: + if posix.putenv("HOME=/usr/home") != 0 { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html ]] + */ + @(link_name=LPUTENV) + putenv :: proc(string: cstring) -> c.int --- + + /* + Updates or add a variable in the environment of the calling process. + + Example: + if posix.setenv("HOME", "/usr/home") != .OK { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html ]] + */ + setenv :: proc(envname: cstring, envval: cstring, overwrite: b32) -> result --- + + /* + Removes an environment variable from the environment of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html ]] + */ + @(link_name=LUNSETENV) + unsetenv :: proc(name: cstring) -> result --- + + /* + Computes a sequence of pseudo-random integers in the range [0, {RAND_MAX}]. + (The value of the {RAND_MAX} macro shall be at least 32767.) + + If rand_r() is called with the same initial value for the object pointed to by seed and that object is not modified between successive returns and calls to rand_r(), the same sequence shall be generated. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rand_r.html ]] + */ + rand_r :: proc(seed: ^c.uint) -> c.int --- + + /* + Derive, from the pathname file_name, an absolute pathname that resolves to the same directory entry, + whose resolution does not involve '.', '..', or symbolic links. + + If resolved_name is not `nil` it should be larger than `PATH_MAX` and the result will use it as a backing buffer. + If resolved_name is `nil` the returned string is allocated by `malloc`. + + Returns: `nil` (setting errno) on failure, the "real path" otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html ]] + */ + realpath :: proc(file_name: cstring, resolved_name: [^]byte = nil) -> cstring --- + + /* + Provides access to an implementation-defined encoding algorithm. + The argument of setkey() is an array of length 64 bytes containing only the bytes with numerical + value of 0 and 1. + + If this string is divided into groups of 8, the low-order bit in each group is ignored; this gives a 56-bit key which is used by the algorithm. + This is the key that shall be used with the algorithm to encode a string block passed to encrypt(). + + The setkey() function shall not change the setting of errno if successful. + An application wishing to check for error situations should set errno to 0 before calling setkey(). + If errno is non-zero on return, an error has occurred. + + Example: + key: [64]byte + // set key bytes... + + posix.set_errno(.NONE) + posix.setkey(raw_data(key)) + if errno := posix.errno(); errno != .NONE { + fmt.panicf("setkey failure: %s", posix.strerror(errno)) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setkey.html ]] + */ + setkey :: proc(key: [^]byte) --- +} + +EXIT_FAILURE :: libc.EXIT_FAILURE +EXIT_SUCCESS :: libc.EXIT_SUCCESS + +RAND_MAX :: libc.RAND_MAX +MB_CUR_MAX :: libc.MB_CUR_MAX + +div_t :: libc.div_t +ldiv_t :: libc.ldiv_t +lldiv_t :: libc.lldiv_t + +when ODIN_OS == .NetBSD { + @(private) LPUTENV :: "__putenv50" + @(private) LINITSTATE :: "__initstate60" + @(private) LSRANDOM :: "__srandom60" + @(private) LUNSETENV :: "__unsetenv13" +} else { + @(private) LPUTENV :: "putenv" + @(private) LINITSTATE :: "initstate" + @(private) LSRANDOM :: "srandom" + @(private) LUNSETENV :: "unsetenv" +} diff --git a/core/sys/posix/string.odin b/core/sys/posix/string.odin new file mode 100644 index 000000000..d22f49a96 --- /dev/null +++ b/core/sys/posix/string.odin @@ -0,0 +1,47 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// string.h - string operations + +// NOTE: most of the symbols in this header are not useful in Odin and have been left out. + +foreign lib { + /* + Map the error number to a locale-dependent error message string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html ]] + */ + @(link_name="strerror") + _strerror :: proc(errnum: Errno) -> cstring --- + + /* + Map the error number to a locale-dependent error message string and put it in the buffer. + + Returns: ERANGE if the buffer is not big enough + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror_r.html ]] + */ + strerror_r :: proc(errnum: Errno, strerrbuf: [^]byte, buflen: c.size_t) -> Errno --- + + /* + Map the signal number to an implementation-defined string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strsignal.html ]] + */ + strsignal :: proc(sig: Signal) -> cstring --- +} + +strerror :: #force_inline proc "contextless" (errnum: Maybe(Errno) = nil) -> cstring { + return _strerror(errnum.? or_else errno()) +} diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin new file mode 100644 index 000000000..f8778ee15 --- /dev/null +++ b/core/sys/posix/sys_ipc.odin @@ -0,0 +1,89 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/ipc.h = XSI interprocess communication access structure + +foreign lib { + /* + Generate an IPC key. + + Returns: -1 (setting errno) on failure, the key otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftok.html ]] + */ + ftok :: proc(path: cstring, id: c.int) -> key_t --- +} + +IPC_Cmd :: enum c.int { + RMID = IPC_RMID, + SET = IPC_SET, + STAT = IPC_STAT, +} + +IPC_Flag_Bits :: enum c.int { + CREAT = log2(IPC_CREAT), + EXCL = log2(IPC_EXCL), + NOWAIT = log2(IPC_NOWAIT), + + MSG_NOERROR = log2(MSG_NOERROR), +} +IPC_Flags :: bit_set[IPC_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + key_t :: distinct c.int32_t + + ipc_perm :: struct { + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + key_t :: distinct c.long + + ipc_perm :: struct { + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin new file mode 100644 index 000000000..217d321ac --- /dev/null +++ b/core/sys/posix/sys_mman.odin @@ -0,0 +1,229 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// mman.h - memory management declarations + +foreign lib { + /* + Establish a mapping between an address space of a process and a memory object. + + Returns: MAP_FAILED (setting errno) on failure, the address in memory otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html ]] + */ + mmap :: proc( + addr: rawptr, + len: c.size_t, + prot: Prot_Flags, + flags: Map_Flags, + fd: FD = -1, + off: off_t = 0, + ) -> rawptr --- + + /* + Unmaps pages of memory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/munmap.html ]] + */ + munmap :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + mlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Unlocks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + munlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + mlockall :: proc(flags: Lock_Flags) -> result --- + + /* + Unlocks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + munlockall :: proc() -> result --- + + /* + Set protection of a memory mapping. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html ]] + */ + mprotect :: proc(addr: rawptr, len: c.size_t, prot: Prot_Flags) -> result --- + + /* + Write all modified data to permanent storage locations. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msync.html ]] + */ + @(link_name=LMSYNC) + msync :: proc(addr: rawptr, len: c.size_t, flags: Sync_Flags) -> result --- + + /* + Advise the implementation of expected behavior of the application. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_madvise.html ]] + */ + posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: MAdvice) -> Errno --- + + /* + Open a shared memory object. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] + */ + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + + /* + Removes a shared memory object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html ]] + */ + shm_unlink :: proc(name: cstring) -> result --- +} + +#assert(_PROT_NONE == 0) +PROT_NONE :: Prot_Flags{} + +Prot_Flag_Bits :: enum c.int { + // Data can be executed. + EXEC = log2(PROT_EXEC), + // Data can be read. + READ = log2(PROT_READ), + // Data can be written. + WRITE = log2(PROT_WRITE), +} +Prot_Flags :: bit_set[Prot_Flag_Bits; c.int] + +Map_Flag_Bits :: enum c.int { + // Interpret addr exactly. + FIXED = log2(MAP_FIXED), + // Changes are private. + PRIVATE = log2(MAP_PRIVATE), + // Changes are shared. + SHARED = log2(MAP_SHARED), +} +Map_Flags :: bit_set[Map_Flag_Bits; c.int] + +Lock_Flag_Bits :: enum c.int { + // Lock all pages currently mapped into the address space of the process. + CURRENT = log2(MCL_CURRENT), + // Lock all pages that become mapped into the address space of the process in the future, + // when those mappings are established. + FUTURE = log2(MCL_FUTURE), +} +Lock_Flags :: bit_set[Lock_Flag_Bits; c.int] + +Sync_Flags_Bits :: enum c.int { + // Perform asynchronous writes. + ASYNC = log2(MS_ASYNC), + // Invalidate cached data. + INVALIDATE = log2(MS_INVALIDATE), + + // Perform synchronous writes. + // NOTE: use with `posix.MS_SYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + // SYNC = MS_SYNC, + + _MAX = 31, +} +Sync_Flags :: bit_set[Sync_Flags_Bits; c.int] + +MAdvice :: enum c.int { + DONTNEED = POSIX_MADV_DONTNEED, + NORMAL = POSIX_MADV_NORMAL, + RANDOM = POSIX_MADV_RANDOM, + SEQUENTIAL = POSIX_MADV_SEQUENTIAL, + WILLNEED = POSIX_MADV_WILLNEED, +} + +when ODIN_OS == .NetBSD { + @(private) LMSYNC :: "__msync13" +} else { + @(private) LMSYNC :: "msync" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + + when ODIN_OS == .Darwin { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0010 + } else when ODIN_OS == .NetBSD { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0004 + } else when ODIN_OS == .OpenBSD { + MS_INVALIDATE :: 0x0004 + _MS_SYNC :: 0x0002 + } + MS_ASYNC :: 0x0001 + MS_SYNC :: Sync_Flags{Sync_Flags_Bits(log2(_MS_SYNC))} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} else when ODIN_OS == .FreeBSD { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + + MS_ASYNC :: 0x0001 + MS_INVALIDATE :: 0x0002 + MS_SYNC :: Sync_Flags{} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin new file mode 100644 index 000000000..a8b86e501 --- /dev/null +++ b/core/sys/posix/sys_msg.odin @@ -0,0 +1,161 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/msg.h = XSI message queue structures + +foreign lib { + /* + Provides various operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgctl.html ]] + */ + @(link_name=LMSGCTL) + msgctl :: proc(msqid: FD, cmd: IPC_Cmd, buf: ^msqid_ds) -> result --- + + /* + Returns the message queue identifier associated with the argument key. + + Returns: -1 (setting errno) on failure, the identifier otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgget.html ]] + */ + msgget :: proc(key: key_t, msgflg: IPC_Flags) -> FD --- + + /* + Read a message from the queue. + + Returns: -1 (setting errno) on failure, the bytes received otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgrcv.html ]] + */ + msgrcv :: proc( + msgid: FD, + msgp: rawptr, + msgsz: c.size_t, + msgtyp: c.long, + msgflg: IPC_Flags, + ) -> c.ssize_t --- + + /* + Send a message on the queue. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgsnd.html ]] + */ + msgsnd :: proc(msgid: FD, msgp: rawptr, msgsz: c.size_t, msgflg: IPC_Flags) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LMSGCTL :: "__msgctl50" +} else { + @(private) LMSGCTL :: "msgctl" +} + +when ODIN_OS == .Darwin { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + // NOTE: this is #pragma pack(4) + + msqid_ds :: struct #align(4) { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_first: c.int32_t, + msg_last: c.int32_t, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.int32_t, + using _: struct #align(4) { + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.int32_t, + using _: struct #align(4) { + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.int32_t, + msg_pad4: [4]c.int32_t, + }, + }, + } + +} else when ODIN_OS == .FreeBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + } + +} else when ODIN_OS == .NetBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.size_t + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + + _msg_first: rawptr, + _msg_last: rawptr, + _msg_cbytes: msglen_t, + } + +} else when ODIN_OS == .OpenBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.long, + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.long, + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.long, + msg_pad4: [4]c.long, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin new file mode 100644 index 000000000..6716d60c3 --- /dev/null +++ b/core/sys/posix/sys_resource.odin @@ -0,0 +1,152 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/resource.h - definitions XSI resource operations + +foreign lib { + /* + Gets the nice value of the process, process group or user given. + + Note that a nice value can be -1, so checking for an error would mean clearing errno, doing the + call and then checking that this returns -1 and it has an errno. + + Returns: -1 (setting errno) on failure, the value otherwise + + Example: + pid := posix.getpid() + posix.set_errno(.NONE) + prio := posix.getpriority(.PROCESS, pid) + if err := posix.errno(); prio == -1 && err != .NONE { + // Handle error... + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + getpriority :: proc(which: Which_Prio, who: id_t) -> c.int --- + + /* + Sets the nice value of the process, process group or user given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + setpriority :: proc(which: Which_Prio, who: id_t, value: c.int) -> result --- + + /* + Get a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + getrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Set a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + setrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Get resource usage. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrusage.html ]] + */ + @(link_name=LGETRUSAGE) + getrusage :: proc(who: Which_Usage, rusage: ^rusage) -> result --- +} + +Which_Prio :: enum c.int { + PROCESS = PRIO_PROCESS, + PGRP = PRIO_PGRP, + USER = PRIO_USER, +} + +Which_Usage :: enum c.int { + SELF = RUSAGE_SELF, + CHILDREN = RUSAGE_CHILDREN, +} + +Resource :: enum c.int { + // Maximum byte size of a core file that may be created by a process. + CORE = RLIMIT_CORE, + // Maximum amount of CPU time, in seconds, used by a process. + CPU = RLIMIT_CPU, + // Maximum size of data segment of the process, in bytes. + DATA = RLIMIT_DATA, + // Maximum size of a file, in bytes, that may be created by a process. + FSIZE = RLIMIT_FSIZE, + // A number one greater than the maximum value that the system may assign to a newly-created descriptor. + NOFILE = RLIMIT_NOFILE, + // The maximum size of the initial thread's stack, in bytes. + STACK = RLIMIT_STACK, + // Maximum size of total available memory of the process, in bytes. + AS = RLIMIT_AS, +} + +when ODIN_OS == .NetBSD { + @(private) LGETRUSAGE :: "__getrusage50" +} else { + @(private) LGETRUSAGE :: "getrusage" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + PRIO_PROCESS :: 0 + PRIO_PGRP :: 1 + PRIO_USER :: 2 + + rlim_t :: distinct c.uint64_t + + RLIM_INFINITY :: (rlim_t(1) << 63) - 1 + RLIM_SAVED_MAX :: RLIM_INFINITY + RLIM_SAVED_CUR :: RLIM_INFINITY + + RUSAGE_SELF :: 0 + RUSAGE_CHILDREN :: -1 + + rlimit :: struct { + rlim_cur: rlim_t, /* [PSX] the current (soft) limit */ + rlim_max: rlim_t, /* [PSX] the hard limit */ + } + + rusage :: struct { + ru_utime: timeval, /* [PSX] user time used */ + ru_stime: timeval, /* [PSX] system time used */ + + // Informational aliases for source compatibility with programs + // that need more information than that provided by standards, + // and which do not mind being OS-dependent. + + ru_maxrss: c.long, /* max resident set size (PL) */ + ru_ixrss: c.long, /* integral shared memory size (NU) */ + ru_idrss: c.long, /* integral unshared data (NU) */ + ru_isrss: c.long, /* integral unshared stack (NU) */ + ru_minflt: c.long, /* page reclaims (NU) */ + ru_majflt: c.long, /* page faults (NU) */ + ru_nswap: c.long, /* swaps (NU) */ + ru_inblock: c.long, /* block input operations (atomic) */ + ru_outblock: c.long, /* block output operations (atomic) */ + ru_msgsnd: c.long, /* messages sent (atomic) */ + ru_msgrcv: c.long, /* messages received (atomic) */ + ru_nsignals: c.long, /* signals received (atomic) */ + ru_nvcsw: c.long, /* voluntary context switches (atomic) */ + ru_nivcsw: c.long, /* involuntary " */ + } + + RLIMIT_CORE :: 4 + RLIMIT_CPU :: 0 + RLIMIT_DATA :: 2 + RLIMIT_FSIZE :: 1 + RLIMIT_NOFILE :: 8 + RLIMIT_STACK :: 3 + RLIMIT_AS :: 5 when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD else 10 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin new file mode 100644 index 000000000..3392e02bc --- /dev/null +++ b/core/sys/posix/sys_select.odin @@ -0,0 +1,120 @@ +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/select.h - select types + +foreign lib { + /* + Examines the file descriptor sets to see whether some of their descriptors are ready for writing, + or have an exceptional condition pending, respectively. + + Returns: -1 (setting errno) on failure, total amount of bits set in the bit masks otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LPSELECT) + pselect :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timespec, + sigmask: ^sigset_t, + ) -> c.int --- + + /* + Equivalent to pselect() except a more specific timeout resolution (nanoseconds), + does not have a signal mask, and may modify the timeout. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LSELECT) + select :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timeval, + ) -> c.int --- +} + +when ODIN_OS == .NetBSD { + LPSELECT :: "__pselect50" + LSELECT :: "__select50" +} else { + LPSELECT :: "pselect" + LSELECT :: "select" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + suseconds_t :: distinct (c.int32_t when ODIN_OS == .Darwin || ODIN_OS == .NetBSD else c.long) + + timeval :: struct { + tv_sec: time_t, /* [PSX] seconds */ + tv_usec: suseconds_t, /* [PSX] microseconds */ + } + + // Maximum number of file descriptors in the fd_set structure. + FD_SETSIZE :: #config(POSIX_FD_SETSIZE, 256 when ODIN_OS == .NetBSD else 1024) + + @(private) + __NFDBITS :: size_of(c.int32_t) * 8 + + // NOTE: this seems correct for FreeBSD but they do use a set backed by the long type themselves (thus the align change). + @(private) + ALIGN :: align_of(c.long) when ODIN_OS == .FreeBSD else align_of(c.int32_t) + + fd_set :: struct #align(ALIGN) { + fds_bits: [(FD_SETSIZE / __NFDBITS) when (FD_SETSIZE % __NFDBITS) == 0 else (FD_SETSIZE / __NFDBITS) + 1]c.int32_t, + } + + @(private) + __check_fd_set :: #force_inline proc "contextless" (_a: FD, _b: rawptr) -> bool { + if _a < 0 { + set_errno(.EINVAL) + } + + if _a >= FD_SETSIZE { + set_errno(.EINVAL) + } + + return true + } + + FD_CLR :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] &= ~cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ISSET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) -> bool { + if __check_fd_set(_fd, _p) { + return bool(_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] & cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS))) + } + + return false + } + + FD_SET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] |= cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ZERO :: #force_inline proc "contextless" (_p: ^fd_set) { + intrinsics.mem_zero(_p, size_of(fd_set)) + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin new file mode 100644 index 000000000..3fcde325b --- /dev/null +++ b/core/sys/posix/sys_sem.odin @@ -0,0 +1,132 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/sem.h - XSI semaphore facility + +foreign lib { + /* + Provides various semaphore control operation as specified by cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semctl.html ]] + */ + @(link_name=LSEMCTL) + semctl :: proc(semid: FD, semnum: c.int, cmd: Sem_Cmd, arg: ^semun = nil) -> c.int --- + + /* + Returns the semaphore identifier associated with key. + + Returns: -1 (setting errno) on failure, a semaphore file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semget.html ]] + */ + semget :: proc(key: key_t, nsems: c.int, semflg: IPC_Flags) -> FD --- + + /* + Perform atomically a user-defined array of semaphore operations in array order on the set of + semaphores associated with the semaphore identifier specified by the argument semid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semop.html ]] + */ + semop :: proc(semid: FD, sops: [^]sembuf, nsops: c.size_t) -> result --- +} + +Sem_Cmd :: enum c.int { + // Returns the value of semncnt. + GETNCNT = GETNCNT, + // Returns the value of sempid. + GETPID = GETPID, + // Return the value of semval. + GETVAL = GETVAL, + // Returns the value of semval for each semaphore in the semaphore set. + GETALL = GETALL, + // Returns the value of semzcnt. + GETZCNT = GETZCNT, + // Sets the value of semval to arg.val. + SETVAL = SETVAL, + // Sets the value of semval for each semaphore in the set. + SETALL = SETALL, +} + +semun :: struct #raw_union { + val: c.int, + buf: ^semid_ds, + array: [^]c.ushort, +} + +when ODIN_OS == .NetBSD { + @(private) LSEMCTL :: "__semctl50" +} else { + @(private) LSEMCTL :: "semctl" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + SEM_UNDO :: 0o10000 + + GETNCNT :: 3 + GETPID :: 4 + GETVAL :: 5 + GETALL :: 6 + GETZCNT :: 7 + SETVAL :: 8 + SETALL :: 9 + + when ODIN_OS == .Darwin { + // NOTE: this is #pragma pack(4) + + semid_ds :: struct #align(4) { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: c.int32_t, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.int32_t, + using _: struct #align(4) { + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.int32_t, + sem_pad3: [4]c.int32_t, + }, + } + } else when ODIN_OS == .FreeBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + } + } else when ODIN_OS == .NetBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + _sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + } + } else when ODIN_OS == .OpenBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.long, + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.long, + sem_pad3: [4]c.long, + } + } + + sembuf :: struct { + sem_num: c.ushort, /* [PSX] semaphore number */ + sem_op: c.short, /* [PSX] semaphore operation */ + sem_flg: c.short, /* [PSX] operation flags */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin new file mode 100644 index 000000000..3bc883ce4 --- /dev/null +++ b/core/sys/posix/sys_shm.odin @@ -0,0 +1,146 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/shm.h = XSI shared memory facility + +foreign lib { + /* + Attaches the shared memory segment associated with the identifier + into the address space of the calling process. + + Returns: nil (setting errno) on failure, the address otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmat.html ]] + */ + shmat :: proc(shmid: FD, shmaddr: rawptr, shmflag: SHM_Flags) -> rawptr --- + + /* + Provides various shared memory operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmctl.html ]] + */ + @(link_name=LSHMCTL) + shmctl :: proc(shmid: FD, cmd: IPC_Cmd, buf: ^shmid_ds) -> result --- + + /* + Detaches the shared memory segment located at the address specified. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmdt.html ]] + */ + shmdt :: proc(shmaddr: rawptr) -> result --- + + /* + Returns the shared memory identifier associated with key. + + Returns: -1 (setting errno) on failure, the shared memory ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmget.html ]] + */ + shmget :: proc(key: key_t, size: c.size_t, shmflag: SHM_Flags) -> FD --- +} + +SHM_Flag_Bits :: enum c.int { + RDONLY = log2(SHM_RDONLY), + RND = log2(SHM_RND), +} +SHM_Flags :: bit_set[SHM_Flag_Bits; c.int] + +when ODIN_OS == .NetBSD { + @(private) LSHMCTL :: "__shmctl50" +} else { + @(private) LSHMCTL :: "shmctl" +} + +when ODIN_OS == .Darwin { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 16 * 1024 when ODIN_ARCH == .arm64 else 4096 + + shmatt_t :: distinct c.ushort + + // NOTE: this is #pragma pack(4) + + shmid_ds :: struct #align(4) { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + using _: struct #align(4) { + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_internal: rawptr, + }, + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: PAGESIZE + + shmatt_t :: distinct c.uint + + when ODIN_OS == .FreeBSD { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + } + } else { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + _shm_internal: rawptr, + } + } + +} else when ODIN_OS == .OpenBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 1 << 12 + + shmatt_t :: distinct c.short + + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.int, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + __shm_atimensec: c.long, + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + __shm_dtimensec: c.long, + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + __shm_ctimensec: c.long, + _shm_internal: rawptr, + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin new file mode 100644 index 000000000..e82101367 --- /dev/null +++ b/core/sys/posix/sys_socket.odin @@ -0,0 +1,491 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/socket.h - main sockets header + +#assert(Protocol.IP == Protocol(0), "socket() assumes this") + +foreign libc { + /* + Creates a socket. + + Returns: -1 (setting errno) on failure, file descriptor of socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html ]] + */ + @(link_name=LSOCKET) + socket :: proc(domain: AF, type: Sock, protocol: Protocol = .IP) -> FD --- + + /* + Extracts the first connection on the queue of pending connections. + + Blocks (if not O_NONBLOCK) if there is no pending connection. + + Returns: -1 (setting errno) on failure, file descriptor of accepted socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html ]] + */ + accept :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> FD --- + + /* + Assigns a local socket address to the socket. + + Example: + sfd := posix.socket(.UNIX, .STREAM) + if sfd == -1 { + /* Handle error */ + } + + addr: posix.sockaddr_un + addr.sun_family = .UNIX + copy(addr.sun_path[:], "/somepath\x00") + + if posix.bind(sfd, (^posix.sockaddr)(&addr), size_of(addr)) != .OK { + /* Handle error */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html ]] + */ + bind :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Attempt to make a connection. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html ]] + */ + connect :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Get the peer address of the specified socket. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html ]] + */ + getpeername :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Get the socket name. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html ]] + */ + getsockname :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Retrieves the value for the option specified by option_name. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html ]] + */ + getsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: ^socklen_t, + ) -> result --- + + /* + Sets the specified option. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html ]] + */ + setsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: socklen_t, + ) -> result --- + + /* + Mark the socket as a socket accepting connections. + + backlog provides a hint to limit the number of connections on the listen queue. + Implementation may silently reduce the backlog, additionally `SOMAXCONN` specifies the maximum + an implementation has to support. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html ]] + */ + listen :: proc(socket: FD, backlog: c.int) -> result --- + + /* + Receives a message from a socket. + + Blocks (besides with O_NONBLOCK) if there is nothing to receive. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html ]] + */ + recv :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Equivalent to recv() but retrieves the source address too. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html ]] + */ + recvfrom :: proc( + socket: FD, + buffer: rawptr, + length: c.size_t, + flags: Msg_Flags, + address: ^sockaddr, + address_len: ^socklen_t, + ) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html ]] + */ + recvmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html ]] + */ + send :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html ]] + */ + sendmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + If the socket is connectionless, the dest_addr is used to send to. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html ]] + */ + sendto :: proc( + socket: FD, + message: rawptr, + length: c.size_t, + flags: Msg_Flags, + dest_addr: ^sockaddr, + dest_len: socklen_t, + ) -> c.ssize_t --- + + /* + Shuts down a socket end or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html ]] + */ + shutdown :: proc(socket: FD, how: Shut) -> result --- + + /* + Determine wheter a socket is at the out-of-band mark. + + Returns: -1 (setting errno) on failure, 0 if not at the mark, 1 if it is + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sockatmark.html ]] + */ + sockatmark :: proc(socket: FD) -> c.int --- + + /* + Create a pair of connected sockets. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html ]] + */ + socketpair :: proc(domain: AF, type: Sock, protocol: Protocol, socket_vector: ^[2]FD) -> result --- +} + +AF_UNSPEC :: 0 + +AF :: enum c.int { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +sa_family_t :: enum _sa_family_t { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +Sock :: enum c.int { + // Datagram socket. + DGRAM = SOCK_DGRAM, + // Raw Protocol Interface. + RAW = SOCK_RAW, + // Sequenced-packet socket. + SEQPACKET = SOCK_SEQPACKET, + // Byte-stream socket. + STREAM = SOCK_STREAM, +} + +Shut :: enum c.int { + // Disables further receive operations. + RD = SHUT_RD, + // Disables further send and receive operations. + RDWR = SHUT_RDWR, + // Disables further send operations. + WR = SHUT_WR, +} + +Msg_Flag_Bits :: enum c.int { + // Control data truncated. + CTRUNC = log2(MSG_CTRUNC), + // Send without using routing table. + DONTROUTE = log2(MSG_DONTROUTE), + // Terminates a record (if supported by protocol). + EOR = log2(MSG_EOR), + // Out-of-band data. + OOB = log2(MSG_OOB), + // No SIGPIPE is generated when an attempt to send is made on a stream-oriented socket that is + // no longer connected. + NOSIGNAL = log2(MSG_NOSIGNAL), + // Leave received data in queue. + PEEK = log2(MSG_PEEK), + // Normal data truncated. + TRUNC = log2(MSG_TRUNC), + // Attempt to fill the read buffer. + WAITALL = log2(MSG_WAITALL), +} +Msg_Flags :: bit_set[Msg_Flag_Bits; c.int] + +Sock_Option :: enum c.int { + // Transmission of broadcast message is supported. + BROADCAST = SO_BROADCAST, + // Debugging information is being recorded. + DEBUG = SO_DEBUG, + // Bypass normal routing. + DONTROUTE = SO_DONTROUTE, + // Socket error status. + ERROR = SO_ERROR, + // Connections are kept alive with periodic messages. + KEEPALIVE = SO_KEEPALIVE, + // Socket lingers on close. + LINGER = SO_LINGER, + // Out-of-band data is transmitted in line. + OOBINLINE = SO_OOBINLINE, + // Receive buffer size. + RCVBUF = SO_RCVBUF, + // Receive low water mark. + RCVLOWAT = SO_RCVLOWAT, + // Receive timeout. + RCVTIMEO = SO_RCVTIMEO, + // Reuse of local addresses is supported. + REUSEADDR = SO_REUSEADDR, + // Send buffer size. + SNDBUF = SO_SNDBUF, + // Send low water mark. + SNDLOWAT = SO_SNDLOWAT, + // Send timeout. + SNDTIMEO = SO_SNDTIMEO, + // Socket type. + TYPE = SO_TYPE, +} + +when ODIN_OS == .NetBSD { + @(private) LSOCKET :: "__socket30" +} else { + @(private) LSOCKET :: "socket" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + socklen_t :: distinct c.uint + + _sa_family_t :: distinct c.uint8_t + + sockaddr :: struct { + sa_len: c.uint8_t, /* total length */ + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } + + + when ODIN_OS == .OpenBSD { + @(private) + _SS_PAD1SIZE :: 6 + @(private) + _SS_PAD2SIZE :: 240 + } else { + @(private) + _SS_MAXSIZE :: 128 + @(private) + _SS_ALIGNSIZE :: size_of(c.int64_t) + @(private) + _SS_PAD1SIZE :: _SS_ALIGNSIZE - size_of(c.uint8_t) - size_of(sa_family_t) + @(private) + _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uint8_t) - size_of(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE + } + + sockaddr_storage :: struct { + ss_len: c.uint8_t, /* address length */ + ss_family: sa_family_t, /* [PSX] address family */ + __ss_pad1: [_SS_PAD1SIZE]c.char, + __ss_align: c.int64_t, /* force structure storage alignment */ + __ss_pad2: [_SS_PAD2SIZE]c.char, + } + + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.int, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } + + cmsghdr :: struct { + cmsg_len: socklen_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } + + SCM_RIGHTS :: 0x01 + + @(private) + __ALIGN32 :: #force_inline proc "contextless" (p: uintptr) -> uintptr { + __ALIGNBYTES32 :: size_of(c.uint32_t) - 1 + return (p + __ALIGNBYTES32) &~ __ALIGNBYTES32 + } + + // Returns a pointer to the data array. + CMSG_DATA :: #force_inline proc "contextless" (cmsg: ^cmsghdr) -> [^]c.uchar { + return ([^]c.uchar)(uintptr(cmsg) + __ALIGN32(size_of(cmsghdr))) + } + + // Returns a pointer to the next cmsghdr or nil. + CMSG_NXTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr, cmsg: ^cmsghdr) -> ^cmsghdr { + if cmsg == nil { + return CMSG_FIRSTHDR(mhdr) + } + + ptr := uintptr(cmsg) + __ALIGN32(uintptr(cmsg.cmsg_len)) + if ptr + __ALIGN32(size_of(cmsghdr)) > uintptr(mhdr.msg_control) + uintptr(mhdr.msg_controllen) { + return nil + } + + return (^cmsghdr)(ptr) + } + + // Returns a pointer to the first cmsghdr or nil. + CMSG_FIRSTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr) -> ^cmsghdr { + if mhdr.msg_controllen >= size_of(cmsghdr) { + return (^cmsghdr)(mhdr.msg_control) + } + + return nil + } + + linger :: struct { + l_onoff: c.int, /* [PSX] indicates whether linger option is enabled */ + l_linger: c.int, /* [PSX] linger time in seconds */ + } + + SOCK_DGRAM :: 2 + SOCK_RAW :: 3 + SOCK_SEQPACKET :: 5 + SOCK_STREAM :: 1 + + // Options to be accessed at socket level, not protocol level. + SOL_SOCKET :: 0xffff + + SO_ACCEPTCONN :: 0x0002 + SO_BROADCAST :: 0x0020 + SO_DEBUG :: 0x0001 + SO_DONTROUTE :: 0x0010 + SO_ERROR :: 0x1007 + SO_KEEPALIVE :: 0x0008 + SO_OOBINLINE :: 0x0100 + SO_RCVBUF :: 0x1002 + SO_RCVLOWAT :: 0x1004 + SO_REUSEADDR :: 0x0004 + SO_SNDBUF :: 0x1001 + SO_SNDLOWAT :: 0x1003 + SO_TYPE :: 0x1008 + + when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + SO_LINGER :: 0x1080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .NetBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x100c + SO_SNDTIMEO :: 0x100b + } else when ODIN_OS == .OpenBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } + + // The maximum backlog queue length for listen(). + SOMAXCONN :: 128 + + MSG_CTRUNC :: 0x20 + MSG_DONTROUTE :: 0x4 + MSG_EOR :: 0x8 + MSG_OOB :: 0x1 + MSG_PEEK :: 0x2 + MSG_TRUNC :: 0x10 + MSG_WAITALL :: 0x40 + + when ODIN_OS == .Darwin { + MSG_NOSIGNAL :: 0x80000 + } else when ODIN_OS == .FreeBSD { + MSG_NOSIGNAL :: 0x00020000 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + MSG_NOSIGNAL :: 0x0400 + } + + AF_INET :: 2 + AF_UNIX :: 1 + + when ODIN_OS == .Darwin { + AF_INET6 :: 30 + } else when ODIN_OS == .FreeBSD { + AF_INET6 :: 28 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + AF_INET6 :: 24 + } + + SHUT_RD :: 0 + SHUT_RDWR :: 2 + SHUT_WR :: 1 + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin new file mode 100644 index 000000000..016f1b1e6 --- /dev/null +++ b/core/sys/posix/sys_stat.odin @@ -0,0 +1,540 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/stat.h - data returned by the stat() function + +foreign lib { + + /* + Equivalent to either stat or lstat (based on the SYMLINK_NOFOLLOW bit in flags) + but resolves relative paths based on the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name="fstatat" + INODE_SUFFIX) + fstatat :: proc(fd: FD, path: cstring, buf: ^stat_t, flag: AT_Flags) -> result --- + + /* + Obtain information about a "file" at the given path. + + Follows symbolic links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LSTAT) + stat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Obtain information about an open file. + + Follows symbol links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html ]] + */ + @(link_name=LFSTAT) + fstat :: proc(fildes: FD, buf: ^stat_t) -> result --- + + /* + Obtain information about a "file" at the given path. + + Does not follow symlinks (will stat the symlink itself). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LLSTAT) + lstat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Change the mode of a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + chmod :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to chmod but takes an open file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html ]] + */ + fchmod :: proc(fd: FD, mode: mode_t) -> result --- + + /* + Equivalent to chmod but follows (or doesn't) symlinks based on the flag and resolves + relative paths from the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + fchmodat :: proc(fd: FD, path: cstring, mode: mode_t, flag: AT_Flags) -> result --- + + /* + Make a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdir :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkdir but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdirat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make a FIFO special file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifo :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkfifo but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifoat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make directory, special file, or regular file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + @(link_name=LMKNOD) + mknod :: proc(path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Equivalent to mknod but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + mknodat :: proc(fd: FD, path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Sets the file access and modification time of the given file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + futimens :: proc(fd: FD, times: ^[2]timespec) -> result --- + + /* + Equivalent to futimens. + Relative directories are based on fd. + Symlinks may or may not be followed based on the flags. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + utimensat :: proc(fd: FD, path: cstring, times: ^[2]timespec, flag: AT_Flags) -> result --- + + /* + Set and get the file mode creation flags. + + Makes the file mode permissions bits in cmask the new default for the process. + + Returns: the previous value + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/umask.html ]] + */ + umask :: proc(cmask: mode_t) -> mode_t --- +} + +// Read, write, execute user. +S_IRWXU :: mode_t{ .IRUSR, .IWUSR, .IXUSR } +// Read, write, execute group. +S_IRWXG :: mode_t{ .IRGRP, .IWGRP, .IXGRP } +// Read, write, execute other. +S_IRWXO :: mode_t{ .IROTH, .IWOTH, .IXOTH } + +Mode_Bits :: enum c.int { + // File type: + + IFBLK = log2(S_IFBLK), /* Block special */ + IFCHR = log2(S_IFCHR), /* Character special */ + IFIFO = log2(S_IFIFO), /* FIFO special */ + IFREG = log2(S_IFREG), /* Regular */ + IFDIR = log2(S_IFDIR), /* Directory */ + IFLNK = log2(S_IFLNK), /* Symbolic link */ + IFSOCK = log2(S_IFSOCK), /* Socket */ + + // Permissions: + + IRUSR = log2(_S_IRUSR), /* R for owner */ + IWUSR = log2(_S_IWUSR), /* W for owner */ + IXUSR = log2(_S_IXUSR), /* X for owner */ + + IRGRP = log2(_S_IRGRP), /* R for group */ + IWGRP = log2(_S_IWGRP), /* W for group */ + IXGRP = log2(_S_IXGRP), /* X for group */ + + IROTH = log2(_S_IROTH), /* R for other */ + IWOTH = log2(_S_IWOTH), /* W for other */ + IXOTH = log2(_S_IXOTH), /* X for other */ + + ISUID = log2(_S_ISUID), /* Set user ID on execution */ + ISGID = log2(_S_ISGID), /* Set group ID on execution */ + ISVXT = log2(_S_ISVTX), /* On directories, restricted deletion flag */ +} +mode_t :: bit_set[Mode_Bits; _mode_t] +#assert(size_of(mode_t) == size_of(_mode_t)) + +// NOTE: making these `.IFREG in m` would probably be fine too, +// but implementations make this an exclusive check so lets stick to it. + +_S_IFMT :: mode_t{ .IFBLK, .IFCHR, .IFIFO, .IFREG, .IFDIR, .IFLNK, .IFSOCK } + +// Test for a block special file. +S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFBLK } +} + +// Test for a character special file. +S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFCHR } +} + +// Test for a pipe or FIFO special file. +S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFIFO } +} + +// Test for a regular file. +S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFREG } +} + +// Test for a directory. +S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFDIR } +} + +// Test for a symbolic link. +S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFLNK } +} + +// Test for a socket. +S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & _S_IFMT) == { .IFSOCK } +} + +// Test for a message queue. +S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { + return _S_TYPEISMQ(m) +} + +// Test for a semaphore. +S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return _S_TYPEISSEM(m) +} + +// Test for a shared memory object. +S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return _S_TYPEISSHM(m) +} + +// Test macro for a typed memory object. +S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return _S_TYPEISTMO(m) +} + +_S_IRWXU :: 0o000700 +_S_IRUSR :: 0o000400 +_S_IWUSR :: 0o000200 +_S_IXUSR :: 0o000100 + +_S_IRWXG :: 0o000070 +_S_IRGRP :: 0o000040 +_S_IWGRP :: 0o000020 +_S_IXGRP :: 0o000010 + +_S_IRWXO :: 0o000007 +_S_IROTH :: 0o000004 +_S_IWOTH :: 0o000002 +_S_IXOTH :: 0o000001 + +_S_ISUID :: 0o004000 +_S_ISGID :: 0o002000 +_S_ISVTX :: 0o001000 + +when ODIN_OS == .NetBSD { + @(private) LSTAT :: "__stat50" + @(private) LFSTAT :: "__fstat50" + @(private) LLSTAT :: "__lstat50" + @(private) LMKNOD :: "__mknod50" +} else { + @(private) LSTAT :: "stat" + INODE_SUFFIX + @(private) LFSTAT :: "fstat" + INODE_SUFFIX + @(private) LLSTAT :: "lstat" + INODE_SUFFIX + @(private) LMKNOD :: "mknod" +} + +when ODIN_OS == .Darwin { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint16_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_mode: mode_t, /* [XSI] mode of file */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_ino: ino_t, /* [XSI] file serial number */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint32_t, /* file generation number */ + st_lspare: c.int32_t, /* RESERVED */ + st_qspare: [2]c.int64_t, /* RESERVED */ + } + + S_IFBLK :: 0o060000 + S_IFCHR :: 0o020000 + S_IFIFO :: 0o010000 + S_IFREG :: 0o100000 + S_IFDIR :: 0o040000 + S_IFLNK :: 0o120000 + S_IFSOCK :: 0o140000 + + __S_IFMT :: 0o170000 + + _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .FreeBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint64_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + when ODIN_ARCH == .i386 { + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_mode: mode_t, /* [XSI] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [XSI] device ID */ + st_atim_ext: c.int32_t, + st_atim: timespec, /* [XSI] time of last access */ + st_mtim_ext: c.int32_t, + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim_ext: c.int32_t, + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } else { + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_mode: mode_t, /* [XSI] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } + + S_IFBLK :: 0o060000 + S_IFCHR :: 0o020000 + S_IFIFO :: 0o010000 + S_IFREG :: 0o100000 + S_IFDIR :: 0o040000 + S_IFLNK :: 0o120000 + S_IFSOCK :: 0o140000 + + __S_IFMT :: 0o170000 + + _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .NetBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_mode: mode_t, /* [XSI] mode of file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [2]c.uint32_t, + } + + S_IFBLK :: 0o060000 + S_IFCHR :: 0o020000 + S_IFIFO :: 0o010000 + S_IFREG :: 0o100000 + S_IFDIR :: 0o040000 + S_IFLNK :: 0o120000 + S_IFSOCK :: 0o140000 + + __S_IFMT :: 0o170000 + + _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + UTIME_NOW :: (1 << 30) - 1 + UTIME_OMIT :: (1 << 30) - 2 + +} else when ODIN_OS == .OpenBSD { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_mode: mode_t, /* [XSI] mode of file */ + st_dev: dev_t, /* [XSI] ID of device containing file */ + st_ino: ino_t, /* [XSI] file serial number */ + st_nlink: nlink_t, /* [XSI] number of hard links */ + st_uid: uid_t, /* [XSI] user ID of the file */ + st_gid: gid_t, /* [XSI] group ID of the file */ + st_rdev: dev_t, /* [XSI] device ID */ + st_atim: timespec, /* [XSI] time of last access */ + st_mtim: timespec, /* [XSI] time of last data modification */ + st_ctim: timespec, /* [XSI] time of last status change */ + st_size: off_t, /* [XSI] file size, in bytes */ + st_blocks: blkcnt_t, /* [XSI] blocks allocated for file */ + st_blksize: blksize_t, /* [XSI] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.int32_t, + st_birthtimespec: timespec, + } + + S_IFBLK :: 0o060000 + S_IFCHR :: 0o020000 + S_IFIFO :: 0o010000 + S_IFREG :: 0o100000 + S_IFDIR :: 0o040000 + S_IFLNK :: 0o120000 + S_IFSOCK :: 0o140000 + + __S_IFMT :: 0o170000 + + _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return false + } + + UTIME_NOW :: -2 + UTIME_OMIT :: -1 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_statvfs.odin b/core/sys/posix/sys_statvfs.odin new file mode 100644 index 000000000..eb6c16806 --- /dev/null +++ b/core/sys/posix/sys_statvfs.odin @@ -0,0 +1,135 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/statvfs.h - VFS File System information structure + +foreign lib { + + /* + Obtains information about the file system containing the fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LFSTATVFS) + fstatvfs :: proc(fildes: FD, buf: ^statvfs_t) -> result --- + + /* + Obtains information about the file system containing the file named by path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LSTATVFS) + statvfs :: proc(path: cstring, buf: ^statvfs_t) -> result --- +} + +VFS_Flag_Bits :: enum c.ulong { + // Read-only file system. + RDONLY = log2(ST_RDONLY), + // Does not support the semantics of the ST_ISUID and ST_ISGID file mode bits. + NOSUID = log2(ST_NOSUID), +} +VFS_Flags :: bit_set[VFS_Flag_Bits; c.ulong] + +when ODIN_OS == .NetBSD { + @(private) LFSTATVFS :: "__fstatvfs90" + @(private) LSTATVFS :: "__statvfs90" +} else { + @(private) LFSTATVFS :: "fstatvfs" + @(private) LSTATVFS :: "statvfs" +} + +when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + + fsblkcnt_t :: distinct c.uint + + statvfs_t :: struct { + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .FreeBSD { + + fsblkcnt_t :: distinct c.uint64_t + + statvfs_t :: struct { + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .NetBSD { + + fsblkcnt_t :: distinct c.uint64_t + + @(private) + _VFS_NAMELEN :: 1024 + + @(private) + fsid_t :: struct { + __fsid_val: [2]c.int, + } + + statvfs_t :: struct { + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_iosize: c.ulong, + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bresvd: fsblkcnt_t, + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fresvd: fsblkcnt_t, + f_syncreads: c.uint64_t, + f_syncwrites: c.uint64_t, + f_asyncreads: c.uint64_t, + f_asyncwrites: c.uint64_t, + f_fsidx: fsid_t, + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + f_owner: uid_t, + f_spare: [4]c.uint64_t, + f_fstypename: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntonname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromlabel: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000008 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin new file mode 100644 index 000000000..093fdd688 --- /dev/null +++ b/core/sys/posix/sys_time.odin @@ -0,0 +1,82 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/time.h - time types + +foreign lib { + /* + Store the current value of timer into value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LGETITIMER) + getitimer :: proc(which: ITimer, value: ^itimerval) -> result --- + + /* + Set the timer to the value given, and store the previous value in ovalue if it is not nil. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LSETITIMER) + setitimer :: proc(which: ITimer, value: ^itimerval, ovalue: ^itimerval) -> result --- + + /* + Obtains the current time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html ]] + */ + @(link_name=LGETTIMEOFDAY) + gettimeofday :: proc(tp: ^timeval, tzp: rawptr = nil) -> result --- + + /* + Sets the access and modification times of the file at the given path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html ]] + */ + @(link_name=LUTIMES) + utimes :: proc(path: cstring, times: ^[2]timeval) -> result --- +} + +ITimer :: enum c.int { + // Decrements in real time. + REAL = ITIMER_REAL, + // Decrements in process virtual time, only when the process is executing. + VIRTUAL = ITIMER_VIRTUAL, + // Decrements both in process virtual time and when the system is running on + // behalf of the process. + PROF = ITIMER_PROF, +} + +when ODIN_OS == .NetBSD { + @(private) LGETITIMER :: "__getitimer50" + @(private) LSETITIMER :: "__setitimer50" + @(private) LGETTIMEOFDAY :: "__gettimeofday50" + @(private) LUTIMES :: "__utimes50" +} else { + @(private) LGETITIMER :: "getitimer" + @(private) LSETITIMER :: "setitimer" + @(private) LGETTIMEOFDAY :: "gettimeofday" + @(private) LUTIMES :: "utimes" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + itimerval :: struct { + it_interval: timeval, /* [PSX] timer interval */ + it_value: timeval, /* [PSX] current value */ + } + + ITIMER_REAL :: 0 + ITIMER_VIRTUAL :: 1 + ITIMER_PROF :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin new file mode 100644 index 000000000..685ced515 --- /dev/null +++ b/core/sys/posix/sys_times.odin @@ -0,0 +1,38 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/times.h - file access and modification times structure + +foreign lib { + /* + Get time accounting information. + + Returns: -1 (setting errno) on failure, the elapsed real time, since an arbitrary point in the past + */ + @(link_name=LTIMES) + times :: proc(buffer: ^tms) -> clock_t --- +} + +when ODIN_OS == .NetBSD { + @(private) LTIMES :: "__times13" +} else { + @(private) LTIMES :: "times" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + tms :: struct { + tms_utime: clock_t, /* [PSX] user CPU time */ + tms_stime: clock_t, /* [PSX] system CPU time */ + tms_cutime: clock_t, /* [PSX] terminated children user CPU time */ + tms_cstime: clock_t, /* [PSX] terminated children system CPU time */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin new file mode 100644 index 000000000..01664e576 --- /dev/null +++ b/core/sys/posix/sys_uio.odin @@ -0,0 +1,42 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/uio.h - definitions for vector I/O operations + +foreign libc { + /* + Equivalent to read() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + readv :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- + + /* + Equivalent to write() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + writev :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + iovec :: struct { + iov_base: rawptr, /* [PSX] base address of I/O memory region */ + iov_len: c.size_t, /* [PSX] size of the region iov_base points to */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_un.odin b/core/sys/posix/sys_un.odin new file mode 100644 index 000000000..146882051 --- /dev/null +++ b/core/sys/posix/sys_un.odin @@ -0,0 +1,17 @@ +package posix + +import "core:c" + +// sys/un.h = definitions for UNIX domain sockets + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + sockaddr_un :: struct { + sun_len: c.uchar, /* sockaddr len including nil */ + sun_family: sa_family_t, /* [PSX] address family */ + sun_path: [104]c.char, /* [PSX] socket pathname */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin new file mode 100644 index 000000000..803f40ffd --- /dev/null +++ b/core/sys/posix/sys_utsname.odin @@ -0,0 +1,55 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/utsname.h = system name structure + +foreign lib { + /* + Stores information identifying the current system in the given structure. + + Returns: non-negative on success, -1 (setting errno) on failure + + NOTE: have a look at `core:sys/info` for similar/better system information. + + Example: + uname: posix.utsname + posix.uname(&uname) + fmt.printfln("%#v", uname) + + Possible Output: + utsname{ + sysname = Darwin, + nodename = Laytans-MacBook-Pro.local, + release = 23.5.0, + version = Darwin Kernel Version 23.5.0: Wed May 1 20:16:51 PDT 2024; root:xnu-11331.111.3~1/RELEASE_ARM64_T8103, + machine = arm64, + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/uname.html ]] + */ + uname :: proc(uname: ^utsname) -> c.int --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + @(private) + _SYS_NAMELEN :: 256 + + utsname :: struct { + sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ + nodename: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of this network node */ + release: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] release level */ + version: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] version level */ + machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin new file mode 100644 index 000000000..6d0336e80 --- /dev/null +++ b/core/sys/posix/sys_wait.odin @@ -0,0 +1,380 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/wait.h - declarations for waiting + +foreign lib { + /* + Obtains status information pertaining to one of the caller's child processes. + + Returns: -1 (setting errno) on failure or signal on calling process, the pid of the process that caused the return otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + wait :: proc(stat_loc: ^c.int) -> pid_t --- + + /* + Obtains status information pertaining to the given pid specifier. + + If pid is -1, status is requested for any child process. + If pid is greater than 0, it specifies the process ID of a single child process. + If pid is 0, it specifies any child process whose process group ID is equal to that of the call. + If pid is < -1, status is requested for any child whose process group ID is the absolute value of pid. + + Returns: -1 (setting errno) on failure or signal on calling process, 0 if NOHANG and status is not available, the pid of the process that caused the return otherwise + + Example: + // The following example demonstrates the use of waitpid(), fork(), and the macros used to + // interpret the status value returned by waitpid() (and wait()). The code segment creates a + // child process which does some unspecified work. Meanwhile the parent loops performing calls + // to waitpid() to monitor the status of the child. The loop terminates when child termination + // is detected. + + child_pid := posix.fork(); switch child_pid { + case -1: // `fork` failed. + panic("fork failed") + + case 0: // This is the child. + + // Do some work... + + case: + for { + status: i32 + wpid := posix.waitpid(child_pid, &status, { .UNTRACED, .CONTINUED }) + if wpid == -1 { + panic("waitpid failure") + } + + switch { + case posix.WIFEXITED(status): + fmt.printfln("child exited, status=%v", posix.WEXITSTATUS(status)) + case posix.WIFSIGNALED(status): + fmt.printfln("child killed (signal %v)", posix.WTERMSIG(status)) + case posix.WIFSTOPPED(status): + fmt.printfln("child stopped (signal %v", posix.WSTOPSIG(status)) + case posix.WIFCONTINUED(status): + fmt.println("child continued") + case: + // Should never happen. + fmt.println("unexpected status (%x)", status) + } + + if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { + break + } + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + waitpid :: proc(pid: pid_t, stat_loc: ^c.int, options: Wait_Flags) -> pid_t --- + + /* + Obtains status information pertaining to the given idtype_t and id specifier. + + Returns: 0 if WNOHANG and no status available, 0 if child changed state, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html ]] + */ + waitid :: proc(idtype: idtype_t, id: id_t, infop: ^siginfo_t, options: Wait_Flags) -> c.int --- +} + +// If terminated normally. +WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFEXITED(x) +} + +// If WIFEXITED is true, returns the exit status. +WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return _WEXITSTATUS(x) +} + +// If terminated due to an uncaught signal. +WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSIGNALED(x) +} + +// If WIFSIGNALED is true, returns the signal. +WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WTERMSIG(x) +} + +// If status was returned for a child process that is currently stopped. +WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSTOPPED(x) +} + +// If WIFSTOPPED, the signal that caused the child process to stop. +WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WSTOPSIG(x) +} + +// If status was returned for a child process that has continued from a job control stop. +WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFCONTINUED(x) +} + +idtype_t :: enum c.int { + // Wait for any children and `id` is ignored. + P_ALL, + // Wait for any child wiith a process group ID equal to `id`. + P_PID, + // Wait for any child with a process group ID equal to `id`. + P_PGID, +} + +Wait_Flag_Bits :: enum c.int { + // Report the status of any continued child process specified by pid whose status has not been + // reported since it continued from a job control stop. + CONTINUED = log2(WCONTINUED), + // Don't suspend execution of the calling thread if status is not immediately available for one + // of the child processes specified by pid. + NOHANG = log2(WNOHANG), + // The status of any child process specified by pid that are stopped, and whose status has not + // yet been reported since they stopped, shall also be reported to the requesting process. + UNTRACED = log2(WUNTRACED), + + // Following are only available on `waitid`, not `waitpid`. + + // Wait for processes that have exited. + EXITED = log2(WEXITED), + // Keep the process whose status is returned in a waitable state, so it may be waited on again. + NOWAIT = log2(WNOWAIT), + // Children that have stopped upon receipt of a signal, and whose status either hasn't been reported + // or has been reported but that report was called with NOWAIT. + STOPPED = log2(WSTOPPED), +} +Wait_Flags :: bit_set[Wait_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + id_t :: distinct c.uint + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000004 + WNOWAIT :: 0x00000020 + WSTOPPED :: 0x00000008 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) != .SIGCONT + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) == .SIGCONT + } + +} else when ODIN_OS == .FreeBSD { + + id_t :: distinct c.int64_t + + WCONTINUED :: 4 + WNOHANG :: 1 + WUNTRACED :: 2 + + WEXITED :: 16 + WNOWAIT :: 8 + WSTOPPED :: 2 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 && x != c.int(Signal.SIGCONT) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == c.int(Signal.SIGCONT) + } +} else when ODIN_OS == .NetBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x >> 8) & 0x000000ff + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return !WIFSTOPPED(x) && !WIFCONTINUED(x) && !WIFEXITED(x) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && !WIFCONTINUED(x) + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal((x >> 8) & 0xff) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == 0xffff + } + +} else when ODIN_OS == .OpenBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + @(private) + _WCONTINUED :: 0o177777 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x >> 8) & 0x000000ff + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & 0xff) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal((x >> 8) & 0xff) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & _WCONTINUED) == _WCONTINUED + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/termios.odin b/core/sys/posix/termios.odin new file mode 100644 index 000000000..372e67e06 --- /dev/null +++ b/core/sys/posix/termios.odin @@ -0,0 +1,475 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// termios.h - define values for termios + +foreign lib { + /* + Get the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html ]] + */ + cfgetispeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html ]] + */ + cfsetispeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Get the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html ]] + */ + cfgetospeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html ]] + */ + cfsetospeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Wait for transmission of output. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html ]] + */ + tcdrain :: proc(fildes: FD) -> result --- + + /* + Suspend or restart the transmission or reception of data. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html ]] + */ + tcflow :: proc(fildes: FD, action: TC_Action) -> result --- + + /* + Flush non-transmitted output data, non-read input data, or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html ]] + */ + tcflush :: proc(fildes: FD, queue_selector: TC_Queue) -> result --- + + /* + Get the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html ]] + */ + tcgetattr :: proc(fildes: FD, termios_p: ^termios) -> result --- + + /* + Get the process group ID for the session leader for the controlling terminal. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html ]] + */ + tcgetsid :: proc(fildes: FD) -> pid_t --- + + /* + Send a break for a specific duration. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html ]] + */ + tcsendbreak :: proc(fildes: FD, duration: c.int) -> result --- + + /* + Set the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html ]] + */ + tcsetattr :: proc(fildes: FD, optional_actions: c.int, termios_p: ^termios) -> result --- +} + +Control_Char :: enum c.int { + VEOF = VEOF, + VEOL = VEOL, + VERASE = VERASE, + VINTR = VINTR, + VKILL = VKILL, + VMIN = VMIN, + VQUIT = VQUIT, + VSTART = VSTART, + VSTOP = VSTOP, + VSUSP = VSUSP, + VTIME = VTIME, + + NCCS = NCCS-1, +} +#assert(len(#sparse [Control_Char]cc_t) == NCCS) + +CInput_Flag_Bits :: enum tcflag_t { + IGNBRK = log2(IGNBRK), /* ignore BREAK condition */ + BRKINT = log2(BRKINT), /* map BREAK to SIGINTR */ + IGNPAR = log2(IGNPAR), /* ignore (discard) parity errors */ + PARMRK = log2(PARMRK), /* mark parity and framing errors */ + INPCK = log2(INPCK), /* enable checking of parity errors */ + ISTRIP = log2(ISTRIP), /* strip 8th bit off chars */ + INLCR = log2(INLCR), /* map NL into CR */ + IGNCR = log2(IGNCR), /* ignore CR */ + ICRNL = log2(ICRNL), /* map CR to NL (ala CRMOD) */ + IXON = log2(IXON), /* enable output flow control */ + IXOFF = log2(IXOFF), /* enable input flow control */ + IXANY = log2(IXANY), /* any char will restart after stop */ +} +CInput_Flags :: bit_set[CInput_Flag_Bits; tcflag_t] + +CLocal_Flag_Bits :: enum tcflag_t { + ECHO = log2(ECHO), /* visual erase for line kill */ + ECHOE = log2(ECHOE), /* visually erase chars */ + ECHOK = log2(ECHOK), /* echo NL after line kill */ + ECHONL = log2(ECHONL), /* echo NL even if ECHO is off */ + ICANON = log2(ICANON), /* canonicalize input lines */ + IEXTEN = log2(IEXTEN), /* enable DISCARD and LNEXT */ + ISIG = log2(ISIG), /* enable signals INTR, QUIT, [D]SUSP */ + NOFLSH = log2(NOFLSH), /* don't flush after interrupt */ + TOSTOP = log2(TOSTOP), /* stop background jobs from output */ +} +CLocal_Flags :: bit_set[CLocal_Flag_Bits; tcflag_t] + +CControl_Flag_Bits :: enum tcflag_t { + // CS5 = log2(CS5), /* 5 bits (pseudo) (default) */ + CS6 = log2(CS6), /* 6 bits */ + CS7 = log2(CS7), /* 7 bits */ + CS8 = log2(CS8), /* 8 bits */ + CSTOPB = log2(CSTOPB), /* send 2 stop bits */ + CREAD = log2(CREAD), /* enable receiver */ + PARENB = log2(PARENB), /* parity enable */ + PARODD = log2(PARODD), /* odd parity, else even */ + HUPCL = log2(HUPCL), /* hang up on last close */ + CLOCAL = log2(CLOCAL), /* ignore modem status lines */ +} +CControl_Flags :: bit_set[CControl_Flag_Bits; tcflag_t] + +// character size mask +CSIZE :: CControl_Flags{ .CS6, .CS7, .CS8 } + +COutput_Flag_Bits :: enum tcflag_t { + OPOST = log2(OPOST), /* enable following output processing */ + ONLCR = log2(ONLCR), /* map NL to CR-NL (ala CRMOD) */ + OCRNL = log2(OCRNL), /* map CR to NL on output */ + ONOCR = log2(ONOCR), /* no CR output at column 0 */ + ONLRET = log2(ONLRET), /* NL performs CR function */ + OFDEL = log2(OFDEL), /* fill is DEL, else NUL */ + OFILL = log2(OFILL), /* use fill characters for delay */ + // NL0 = log2(NL0), /* \n delay 0 (default) */ + NL1 = log2(NL1), /* \n delay 1 */ + // CR0 = log2(CR0), /* \r delay 0 (default) */ + CR1 = log2(CR1), /* \r delay 1 */ + CR2 = log2(CR2), /* \r delay 2 */ + CR3 = log2(CR3), /* \r delay 3 */ + // TAB0 = log2(TAB0),/* horizontal tab delay 0 (default) */ + TAB1 = log2(TAB1), /* horizontal tab delay 1 */ + TAB3 = log2(TAB3), /* horizontal tab delay 3 */ + // BS0 = log2(BS0), /* \b delay 0 (default) */ + BS1 = log2(BS1), /* \b delay 1 */ + // VT0 = log2(VT0), /* vertical tab delay 0 (default) */ + VT1 = log2(VT1), /* vertical tab delay 1 */ + // FF0 = log2(FF0), /* form feed delay 0 (default) */ + FF1 = log2(FF1), /* form feed delay 1 */ +} +COutput_Flags :: bit_set[COutput_Flag_Bits; tcflag_t] + +// \n delay mask +NLDLY :: COutput_Flags{ .NL1, COutput_Flag_Bits(9) } +// \r delay mask +CRDLY :: COutput_Flags{ .CR1, .CR2, .CR3 } +// horizontal tab delay mask +TABDLY :: COutput_Flags{ .TAB1, .TAB3, COutput_Flag_Bits(2) } +// \b delay mask +BSDLY :: COutput_Flags{ .BS1 } +// vertical tab delay mask +VTDLY :: COutput_Flags{ .VT1 } +// form feed delay mask +FFDLY :: COutput_Flags{ .FF1 } + +speed_t :: enum _speed_t { + B0 = B0, + B50 = B50, + B75 = B75, + B110 = B110, + B134 = B134, + B150 = B150, + B200 = B200, + B300 = B300, + B600 = B600, + B1200 = B1200, + B1800 = B1800, + B2400 = B2400, + B4800 = B4800, + B9600 = B9600, + B19200 = B19200, + B38400 = B38400, +} + +TC_Action :: enum c.int { + TCIOFF = TCIOFF, + TCION = TCION, + TCOOFF = TCOOFF, + TCOON = TCOON, +} + +TC_Queue :: enum c.int { + TCIFLUSH = TCIFLUSH, + TCOFLUSH = TCOFLUSH, + TCIOFLUSH = TCIOFLUSH, +} + +when ODIN_OS == .Darwin { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.ulong + tcflag_t :: distinct c.ulong + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + OFDEL :: 0x00020000 + OFILL :: 0x00000080 + _NLDLY :: 0x00000300 + NL0 :: 0x00000000 + NL1 :: 0x00000100 + _CRDLY :: 0x00003000 + CR0 :: 0x00000000 + CR1 :: 0x00001000 + CR2 :: 0x00002000 + CR3 :: 0x00003000 + _TABDLY :: 0x00000c04 + TAB0 :: 0x00000000 + TAB1 :: 0x00000400 + TAB3 :: 0x00000800 + _BSDLY :: 0x00008000 + BS0 :: 0x00000000 + BS1 :: 0x00008000 + _VTDLY :: 0x00010000 + VT0 :: 0x00000000 + VT1 :: 0x00010000 + _FFDLY :: 0x00004000 + FF0 :: 0x00000000 + FF1 :: 0x00004000 + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.uint + tcflag_t :: distinct c.uint + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + when ODIN_OS == .OpenBSD { + ONOCR :: 0x00000040 + ONLRET :: 0x00000080 + } else { + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + } + OFDEL :: 0x00020000 // NOTE: not in headers + OFILL :: 0x00000080 // NOTE: not in headers + _NLDLY :: 0x00000300 // NOTE: not in headers + NL0 :: 0x00000000 // NOTE: not in headers + NL1 :: 0x00000100 // NOTE: not in headers + _CRDLY :: 0x00003000 // NOTE: not in headers + CR0 :: 0x00000000 // NOTE: not in headers + CR1 :: 0x00001000 // NOTE: not in headers + CR2 :: 0x00002000 // NOTE: not in headers + CR3 :: 0x00003000 // NOTE: not in headers + _TABDLY :: 0x00000004 // NOTE: not in headers (netbsd) + TAB0 :: 0x00000000 // NOTE: not in headers (netbsd) + TAB1 :: 0x00000004 // NOTE: not in headers + TAB3 :: 0x00000004 // NOTE: not in headers (netbsd) + _BSDLY :: 0x00008000 // NOTE: not in headers + BS0 :: 0x00000000 // NOTE: not in headers + BS1 :: 0x00008000 // NOTE: not in headers + _VTDLY :: 0x00010000 // NOTE: not in headers + VT0 :: 0x00000000 // NOTE: not in headers + VT1 :: 0x00010000 // NOTE: not in headers + _FFDLY :: 0x00004000 // NOTE: not in headers + FF0 :: 0x00000000 // NOTE: not in headers + FF1 :: 0x00004000 // NOTE: not in headers + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin new file mode 100644 index 000000000..9b91c9558 --- /dev/null +++ b/core/sys/posix/time.odin @@ -0,0 +1,234 @@ +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// time.h - time types + +foreign lib { + /* + Convert the broken down time in the structure to a string form: Sun Sep 16 01:03:52 1973\n\0 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime_r.html ]] + */ + asctime_r :: proc(tm: ^tm, buf: [^]c.char) -> cstring --- + + /* + Convert a time value to a date and time string in the same format as asctime(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime_r.html ]] + */ + @(link_name=LCTIMER) + ctime_r :: proc(clock: ^time_t, buf: [^]c.char) -> cstring --- + + /* + Converts the time in seconds since epoch to a broken-down tm struct. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime_r.html ]] + */ + @(link_name=LGMTIMER) + gmtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Convert the time in seconds since epoch to a broken-down tm struct in local time. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime_r.html ]] + */ + @(link_name=LLOCALTIMER) + localtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Returns the resolution of any clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETRES) + clock_getres :: proc(clock_id: Clock, res: ^timespec) -> result --- + + /* + Returns the current value tp for the specified clock, clock_id. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETTIME) + clock_gettime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Sets the specified clock's time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKSETTIME) + clock_settime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Converts a string representation of a date or time into a broken-down time. + + Returns: nil (setting getdate_err) on failure, the broken-down time otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdate.html ]] + */ + getdate :: proc(string: cstring) -> ^tm --- + + /* + Causes the current thread to be suspended from execution until either the time interval + specified by rqtp has elapsed or a signal is delivered. + + Returns: -1 on failure (setting errno), if it was due to a signal, rmtp will be filled with the + remaining time, 0 if all time has been slept + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html ]] + */ + @(link_name=LNANOSLEEP) + nanosleep :: proc(rqtp: ^timespec, rmtp: ^timespec) -> result --- + + /* + Converts the character string to values which are stored in tm, using the specified format. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strptime.html ]] + */ + strptime :: proc(buf: [^]c.char, format: cstring, tm: ^tm) -> cstring --- + + /* + Uses the value of the environment variable TZ (or default) to set time conversion info. + + `daylight` is set to whether daylight saving time conversion should be done. + `timezone` is set to the difference, in seconds, between UTC and local standard time. + `tzname` is set by `tzname[0] = "std"` and `tzname[1] = "dst"` + + Example: + posix.tzset() + fmt.println(posix.tzname) + fmt.println(posix.daylight) + fmt.println(posix.timezone) + + Possible Output: + ["CET", "CEST"] + true + -3600 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html ]] + */ + tzset :: proc() --- + + // Whether daylight saving conversion should be done. + daylight: b32 + // The time in seconds between UTC and local standard time. + @(link_name=LTIMEZONE) + timezone: c.long + tzname: [2]cstring +} + +time_t :: libc.time_t +clock_t :: libc.clock_t + +tm :: libc.tm +timespec :: libc.timespec + +CLOCKS_PER_SEC :: libc.CLOCKS_PER_SEC + +asctime :: libc.asctime +clock :: libc.clock +ctime :: libc.ctime +difftime :: libc.difftime +gmtime :: libc.gmtime +localtime :: libc.localtime +mktime :: libc.mktime +strftime :: libc.strftime +time :: libc.time + +Clock :: enum clockid_t { + // system-wide monotonic clock, defined as clock measuring real time, + // can be set with clock_settime() and cannot have negative clock jumps. + MONOTONIC = CLOCK_MONOTONIC, + // CPU-time clock associated with the process making a clock() function call. + PROCESS_CPUTIME_ID = CLOCK_PROCESS_CPUTIME_ID, + // system-wide clock measuring real time. + REALTIME = CLOCK_REALTIME, + // CPU-time clock associated with the thread making a clock() function call. + THREAD_CPUTIME_ID = CLOCK_THREAD_CPUTIME_ID, +} + +when ODIN_OS == .NetBSD { + @(private) LCTIMER :: "__ctime_r50" + @(private) LGMTIMER :: "__gmtime_r50" + @(private) LLOCALTIMER :: "__localtime_r50" + @(private) LCLOCKGETRES :: "__clock_getres50" + @(private) LCLOCKGETTIME :: "__clock_gettime50" + @(private) LCLOCKSETTIME :: "__clock_settime50" + @(private) LNANOSLEEP :: "__nanosleep50" + @(private) LTIMEZONE :: "__timezone13" +} else { + @(private) LCTIMER :: "ctime_r" + @(private) LGMTIMER :: "gmtime_r" + @(private) LLOCALTIMER :: "localtime_r" + @(private) LCLOCKGETRES :: "clock_getres" + @(private) LCLOCKGETTIME :: "clock_gettime" + @(private) LCLOCKSETTIME :: "clock_settime" + @(private) LNANOSLEEP :: "nanosleep" + @(private) LTIMEZONE :: "timezone" +} + +when ODIN_OS == .Darwin { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 6 + CLOCK_PROCESS_CPUTIME_ID :: 12 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 16 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .FreeBSD { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 4 + CLOCK_PROCESS_CPUTIME_ID :: 15 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 14 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .NetBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 0x40000000 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 0x20000000 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .OpenBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 4 + + getdate_err: Errno = .ENOSYS // NOTE: looks like it's not a thing on OpenBSD. + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin new file mode 100644 index 000000000..067b83271 --- /dev/null +++ b/core/sys/posix/ulimit.odin @@ -0,0 +1,43 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// ulimit.h - ulimit commands + +foreign lib { + /* + Control process limits. + + Note that -1 is a valid return value, applications should clear errno, do this call and then + check both -1 and the errno to determine status. + + Returns: -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ulimit.html ]] + */ + ulimit :: proc(i: c.int, #c_vararg arg: ..c.long) -> c.long --- +} + +Ulimit_Cmd :: enum c.int { + // Returns the file size limit of the process in units of 512-byte blocks inherited by children. + GETFSIZE = UL_GETFSIZE, + // Set the file size limit for output operations, taken as a long, multiplied by 512. + SETFSIZE = UL_SETFSIZE, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + UL_GETFSIZE :: 1 + UL_SETFSIZE :: 2 + + // NOTE: I don't think OpenBSD implements this API. + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin new file mode 100644 index 000000000..8e08860b4 --- /dev/null +++ b/core/sys/posix/unistd.odin @@ -0,0 +1,1914 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// unistd.h - standard symbolic constants and types + +foreign lib { + /* + Checks the file named by the pathname pointed to by the path argument for + accessibility according to the bit pattern contained in amode. + + Example: + if (posix.access("/tmp/myfile", posix.F_OK) != .OK) { + fmt.printfln("/tmp/myfile access check failed: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + access :: proc(path: cstring, amode: Mode_Flags = F_OK) -> result --- + + /* + Equivalent to `access` but relative paths are resolved based on `fd`. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + faccessat :: proc(fd: FD, path: cstring, amode: Mode_Flags, flag: AT_Flags) -> result --- + + /* + The alarm() function shall cause the system to generate a SIGALRM signal for the process after the number of realtime seconds specified by seconds have elapsed. Processor scheduling delays may prevent the process from handling the signal as soon as it is generated. + + If seconds is 0, a pending alarm request, if any, is canceled. + + Returns: the time left on the previous alarm() or 0 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/alarm.html ]] + */ + alarm :: proc(seconds: c.uint) -> c.uint --- + + /* + Causes the directory named by path to become the current working directory. + + Example: + if (posix.chdir("/tmp") == .OK) { + fmt.println("changed current directory to /tmp") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html ]] + */ + chdir :: proc(path: cstring) -> result --- + + /* + Equivalent to chdir but instead of a path the fildes is resolved to a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html ]] + */ + fchdir :: proc(fildes: FD) -> result --- + + /* + Changes the user and group ownership of a file. + + If owner or group is specified as (uid_t)-1 or (gid_t)-1, respectively, the corresponding ID of the file shall not be changed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + @(link_name=LCHOWN) + chown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Equivalent to chown expect that it takes a file descriptor. + + Example: + fildes := posix.open("/home/cnd/mod1", {.RDWR}) + pwd := posix.getpwnam("jones") + grp := posix.getgrnam("cnd") + posix.fchown(fildes, pwd.pw_uid, grp.gr_gid) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html ]] + */ + @(link_name=LFCHOWN) + fchown :: proc(fildes: FD, owner: uid_t, mode: gid_t) -> result --- + + /* + Equivalent to fchown except that relative paths are based on the given fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + fchownat :: proc(fildes: FD, path: cstring, owner: uid_t, group: gid_t, flag: AT_Flags) -> result --- + + /* + If path points to a symbolic link, the owner and group of the link itself is changed. + Equivalent to chown on normal files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lchown.html ]] + */ + @(link_name=LLCHOWN) + lchown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Deallocates the file descriptor indicated by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html ]] + */ + close :: proc(fildes: FD) -> result --- + + /* + Return configuration-defined string values. + Its use and purpose are similar to sysconf(), but it is used where string values rather than numeric values are returned. + + Returns: 0 (setting errno) if `name` is invalid, need `buf` `len` if buf is `nil`, amount of bytes added to buf otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html ]] + */ + confstr :: proc(name: CS, buf: [^]c.char, len: c.size_t) -> c.size_t --- + + /* + Determines the current value of a configurable limit or option that is associated with a file or directory. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + pathconf :: proc(path: cstring, name: PC) -> c.long --- + + /* + Equivalent to pathconf but takes a file descriptor instead of a path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + fpathconf :: proc(fildes: FD, name: PC) -> c.long --- + + /* + Determines the current value of configurable system limit or options. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html ]] + */ + sysconf :: proc(name: SC) -> c.long --- + + /* + A string encoding function. The algorithm is implementation-defined. + + The use of crypt() for anything other than password hashing is not recommended. + + Returns: a static string overwritten by subsequent calls, `nil` (setting errno) on failure + */ + crypt :: proc(key: cstring, salt: cstring) -> cstring --- + + /* + An implementation-defined encoding algorithm. + The key generated by setkey() is used to encrypt the string block with encrypt(). + + block must be 64 bytes. + + decode controls if the block is encoded or decoded. + + May set errno to ENOSYS if the functionality is not supported. + + Example: + block: [64]byte + copy(block[:], "Hello, World!") + + posix.set_errno(.NONE) + posix.encrypt(raw_data(block[:]), decode=false) + assert(posix.errno() == .NONE, "encrypt not supported") + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/encrypt.html ]] + */ + encrypt :: proc(block: [^]c.char, decode: b32) --- + + /* + Returns a new file descriptor referring to the one given, sharing locks, clearing CLOEXEC. + + Returns: -1 (setting errno) on failure, the new file descriptor on success + + Example: + // Redirecting stdout to a file: + file := posix.open("/tmp/out", { .RDWR }) + posix.close(1) + posix.dup(file) + posix.close(file) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup :: proc(fildes: FD) -> FD --- + + /* + Causes the file descriptor fildes2 to refer to the same open file description as + the file descriptor fildes and to share any locks, and shall return fildes2. + + Returns: -1 (setting errno) on failure, fildes2 on success + + Example: + // Redirecting stderr to stdout: + posix.dup2(1, 2) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup2 :: proc(fildes, fildes2: FD) -> FD --- + + /* + Exits but, shall not call functions registered with atexit() nor any registered signal handlers. + Open streams shall not be flushed. + Whether open streams are closed (without flushing) is implementation-defined. Finally, the calling process shall be terminated with the consequences described below. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] + */ + _exit :: proc(status: c.int) --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execl("/bin/ls", "ls", "-l", nil) + fmt.panicf("could not execute: %v %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execl :: proc(path: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + After the arguments an array of environment strings (also nil terminated) is expected. + + Example: + env := []cstring{ + "HOME=/usr/home", + "LOGNAME=home", + nil, + } + ret := posix.execle("/bin/ls", "ls", cstring("-l"), cstring(nil), raw_data(env)) + fmt.panicf("could not execute: %v", posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execle :: proc(path: cstring, arg0: cstring, #c_vararg args: ..any) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execlp("ls", "-l", cstring(nil)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execlp :: proc(file: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + + Example: + args := []cstring{ "ls", "-l", nil } + ret := posix.execv("/bin/ls", raw_data(args)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execv :: proc(path: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + ret := posix.execvp("ls", raw_data(cmd)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execvp :: proc(file: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + Takes environment variables as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.execve("/bin/ls", raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execve :: proc(path: cstring, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Equivalent to execve but takes a file descriptor instead of a path. + + Example: + ls := posix.open("/bin/ls", { .EXEC }) + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.fexecve(ls, raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + fexecve :: proc(fd: FD, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + Example: + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + environ: [^]cstring + + /* + Forcec all currently queued I/O operations associated with the file indicated by file descriptor + fildes to the synchronized I/O completion state. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html ]] + */ + fdatasync :: proc(fd: FD) -> result --- + + /* + The fork() function shall create a new process. + The new process (child process) shall be an exact copy of the calling process (parent process). + With some exceptions outlined below. + + Result: -1 (setting errno) on failure, otherwise 0 to the child process and the child process id to the parent process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html ]] + */ + fork :: proc() -> pid_t --- + + /* + Requests that all data for the open file descriptor named by fildes is to be transferred + to the storage device associated with the file described by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html ]] + */ + fsync :: proc(fildes: FD) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html ]] + */ + truncate :: proc(path: cstring, length: off_t) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html ]] + */ + ftruncate :: proc(fildes: FD, length: off_t) -> result --- + + /* + Places an absolute pathname of the current working directory into buf. + + Returns: buf as a cstring on success, nil (setting errno) on failure + + Example: + size: int + path_max := posix.pathconf(".", ._PATH_MAX) + if path_max == -1 { + size = 1024 + } else if path_max > 10240 { + size = 10240 + } else { + size = int(path_max) + } + + buf: [dynamic]byte + cwd: cstring + for ; cwd == nil; size *= 2 { + if err := resize(&buf, size); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if errno := posix.errno(); cwd == nil && errno != .ERANGE { + fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + } + } + + fmt.println(path_max, cwd) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html ]] + */ + getcwd :: proc(buf: [^]c.char, size: c.size_t) -> cstring --- + + /* + Returns the effective group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getegid.html ]] + */ + getegid :: proc() -> gid_t --- + + /* + Returns the effective user ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/geteuid.html ]] + */ + geteuid :: proc() -> uid_t --- + + /* + Returns the real group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgid.html ]] + */ + getgid :: proc() -> gid_t --- + + /* + Fills the grouplist array with the current supplementary group IDs of the calling process. + + Returns: -1 (setting errno) on failure, desired grouplist length if gidsetsize is 0, amount of IDs added otherwise + + Example: + length := posix.getgroups(0, nil) + if length == -1 { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + groups := make([]posix.gid_t, length) or_else panic("allocation failure") + if posix.getgroups(length, raw_data(groups)) != length { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + fmt.println(groups) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgroups.html ]] + */ + getgroups :: proc(gidsetsize: c.int, grouplist: [^]gid_t) -> c.int --- + + /* + Retrieves a 32-bit identifier for the current host. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostid.html ]] + */ + gethostid :: proc() -> c.long --- + + /* + Returns the standard host name for the current machine. + + Host names are limited to HOST_NAME_MAX bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html ]] + */ + gethostname :: proc(name: [^]c.char, namelen: c.size_t) -> result --- + + /* + Returns a string containing the user name associated by the login activity. + + Returns: nil (setting errno) on failure, the login name otherwise in a potentially static buffer overwritten by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin :: proc() -> cstring --- + + /* + Equivalent to getlogin but puts the name in the name buffer given. + + The name is limited to LOGIN_NAME_MAX bytes. + + Example: + max := posix.sysconf(posix._SC_LOGIN_NAME_MAX)+1 + buf := make([]byte, max) + posix.getlogin_r(raw_data(buf), uint(len(max))) + fmt.printfln("login: %v", cstring(buf)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin_r :: proc(name: [^]c.char, namelen: c.size_t) -> Errno --- + + /* + A command-line parser, see linked docs. + + Example: + // The following code fragment shows how you might process the arguments for a utility that + // can take the mutually-exclusive options a and b and the options f and o, both of which + // require arguments. + + bflg, aflg, errflg: bool + ifile: string + ofile: string + + for { + c := posix.getopt(i32(len(runtime.args__)), raw_data(runtime.args__), ":abf:o:") + (c != -1) or_break + + switch c { + case 'a': + if bflg { + errflg = true + } else { + aflg = true + } + case 'b': + if aflg { + errflg = true + } else { + bflg = true + } + case 'f': + ifile = string(posix.optarg) + case 'o': + ofile = string(posix.optarg) + case ':': /* -f or -o without operand */ + fmt.eprintfln("Option -%c requires an operand", posix.optopt) + errflg = true + case '?': + fmt.eprintfln("Unrecognized option: '-%c'", posix.optopt) + errflg = true + } + } + + if errflg { + fmt.eprintfln("usage: . . . ") + posix.exit(2) + } + + // Loop through remaining arguments: + for ; posix.optind < i32(len(runtime.args__)); posix.optind += 1 { + fmt.println(runtime.args__[posix.optind]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html ]] + */ + getopt :: proc(argc: c.int, argv: [^]cstring, optstring: cstring) -> c.int --- + + optarg: cstring + opterr: c.int + optind: c.int + optopt: c.int + + /* + Returns the process group ID of the process whose process ID is equal to pid. + If pid is 0, it returns the process group ID of the calling process. + + Returns: -1 on failure, the ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html ]] + */ + getpgid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the process group ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgrp.html ]] + */ + getpgrp :: proc() -> pid_t --- + + /* + Returns the ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html ]] + */ + getpid :: proc() -> pid_t --- + + /* + Returns the parent process ID. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getppid.html ]] + */ + getppid :: proc() -> pid_t --- + + + /* + Get the process group ID of the session leader. + If pid is 0, it is the current process. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsid.html ]] + */ + getsid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the real user ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html ]] + */ + getuid :: proc() -> uid_t --- + + /* + Tests whether fildes is associated with a terminal device. + + Returns: false (setting errno) if fildes is invalid or not a terminal, true otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html ]] + */ + isatty :: proc(fildes: FD) -> b32 --- + + /* + Creates a new link for the existing file path1 to path2. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + link :: proc(path1: cstring, path2: cstring) -> result --- + + /* + If path1 is relative it is relative to directory fd1. + If path2 is relative it is relative to directory fd2. + If flag is { .SYMLINK_FOLLOW } path1 is resolved to its link if it is a link. + Equivalent to link otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + linkat :: proc(fd1: FD, path1: cstring, fd2: FD, path2: cstring, flag: AT_Flags) -> result --- + + /* + Creates a symbolic link called path2 that contains a link to path1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlink :: proc(path1: cstring, path2: cstring) -> result --- + + /* + Equivalent to symlink but relative paths are resolved to dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlinkat :: proc(path1: cstring, fd: FD, path2: cstring) -> result --- + + /* + Locks sections of a file with advisory-mode locks. + + Example: + fildes := posix.open("/home/cnd/mod1", { .RDWR }) + if posix.lockf(fildes, .TLOCK, 10000) != .OK { + errno := posix.errno(); #partial switch errno { + case .EACCES, .EAGAIN: + // File is already locked. + case: + // Other error. + fmt.panicf("lockf failure: %v", posix.strerror(errno)) + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lockf.html ]] + */ + lockf :: proc(fildes: FD, function: Lock_Function, size: off_t) -> result --- + + /* + Sets the file offset of the given file descriptor. + + If whence is .SET, the offset is set + If whence is .CUR, the offset is the current offset + given offset + If whence is .END, the offset is set to the size of the file + given offset + + Returns: the resulting offset or -1 (setting errno) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html ]] + */ + lseek :: proc(fildes: FD, offset: off_t, whence: Whence) -> off_t --- + + /* + Changes the nice value of a process. + + Higher values result in less favorable scheduling. + + Because -1 is a valid nice value, checking failure would be done by first setting errno to .NONE + and then calling nice. + + Returns: the new nice value, or -1 (setting) errno on failure + + Example: + posix.set_errno(.NONE) + niceness := posix.nice(-20) + if errno := posix.errno(); niceness == -1 && errno != .NONE { + fmt.panicf("nice failure: %v", posix.strerror(errno)) + } + fmt.printfln("Niceness is now: %v", niceness) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nice.html ]] + */ + nice :: proc(incr: c.int) -> c.int --- + + /* + Suspend the thread until a signal is received. + + Returns: -1 (setting errno to EINTR) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html ]] + */ + pause :: proc() -> c.int --- + + /* + Create an interprocess channel. + + Example: + fildes: [2]posix.FD + if posix.pipe(&fildes) != .OK { + // Handle error ... + } + + switch posix.fork() { + case -1: + // Handle error ... + + case 0: /* Child - reads from pipe */ + BSIZE :: 100 + buf: [BSIZE]byte + nbytes: int + + posix.close(fildes[1]) /* Write end is unused */ + nbytes = posix.read(fildes[0], raw_data(buf[:]), BSIZE) /* Get data from pipe */ + /* At this point, a further read would see end-of-file ... */ + posix.close(fildes[0]) /* Finished with pipe */ + + fmt.println(string(buf[:nbytes])) + + posix.exit(0) + + case: /* Parent - write to pipe */ + msg := raw_data(transmute([]byte)string("Hello world\n")) + posix.close(fildes[0]) /* Read end is unused */ + posix.write(fildes[1], msg, 12); /* Write data on pipe */ + posix.close(fildes[1]) + posix.exit(0) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html ]] + */ + pipe :: proc(fildes: ^[2]FD) -> result --- + + /* + Read from a file. + + Returns: the amount of bytes read or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + read :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t) -> c.ssize_t --- + + /* + Equivalent to read on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + pread :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Write on a file. + + Returns: the amount of bytes written or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + write :: proc(fd: FD, buf: [^]byte, buflen: c.size_t) -> c.ssize_t --- + + /* + Equivalent to write on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + pwrite :: proc(fd: FD, buf: [^]byte, buflen: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Read the contents of a symbolic link. + + Returns: the amount of bytes read or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlink :: proc(path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Equivalent to readlink but relative paths are resolved based on the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlinkat :: proc(fd: FD, path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Remove an (empty) directory. + + ]] More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html ]] + */ + rmdir :: proc(path: cstring) -> result --- + + /* + Set the effective group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setegid.html ]] + */ + setegid :: proc(gid: gid_t) -> result --- + + /* + Sets the effective user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seteuid.html ]] + */ + seteuid :: proc(uid: uid_t) -> result --- + + /* + Sets the group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setgid.html ]] + */ + setgid :: proc(gid: gid_t) -> result --- + + /* + Set process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html ]] + */ + setpgid :: proc(pid: pid_t, pgid: pid_t) -> result --- + + /* + Set the process group ID to that of the process. + + Returns: the process group id, no failures are defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgrp.html ]] + */ + setpgrp :: proc() -> pid_t --- + + /* + Set the real and effective group IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setregid.html ]] + */ + setregid :: proc(rgid: gid_t, egid: gid_t) -> result --- + + /* + Set real and effective user IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setreuid.html ]] + */ + setreuid :: proc(ruid: uid_t, euid: uid_t) -> result --- + + /* + Create session and set process group ID. + + Returns: the new process group ID or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html ]] + */ + setsid :: proc() -> pid_t --- + + /* + Set user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setuid.html ]] + */ + setuid :: proc(uid: uid_t) -> result --- + + /* + Suspend execution for an interval of time. + + Returns: the time left to sleep (may be > 0 in case of signals) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html ]] + */ + sleep :: proc(seconds: c.uint) -> c.uint --- + + /* + Copy nbyte bytes, from src, to dest, exchanging adjecent bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/swab.html ]] + */ + swab :: proc(src: [^]byte, dest: [^]byte, nbytes: c.ssize_t) --- + + /* + Schedule file system updates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html ]] + */ + sync :: proc() --- + + /* + Get the foreground process group ID. + + Returns: -1 (setting errno) on failure, the id otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetpgrp.html ]] + */ + tcgetpgrp :: proc(fildes: FD) -> pid_t --- + + /* + Set the foreground process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html ]] + */ + tcsetpgrp :: proc(fildes: FD, pgid_id: pid_t) -> result --- + + /* + Find the path name of a terminal. + + Returns: nil (setting errno) on failure, the name, which may be invalidated by subsequent calls on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname :: proc(fildes: FD) -> cstring --- + + /* + Equivalent to ttyname but name is placed into the buf. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname_r :: proc(fildes: FD, name: [^]byte, namesize: c.size_t) -> Errno --- + + /* + Remove a directory entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + unlink :: proc(path: cstring) -> result --- + + /* + Equivalent to unlink or rmdir (if flag is .REMOVEDIR) but relative paths are relative to the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + unlinkat :: proc(fd: FD, path: cstring, flag: AT_Flags) -> result --- +} + +STDERR_FILENO :: 2 +STDIN_FILENO :: 0 +STDOUT_FILENO :: 1 + +Mode_Flag_Bits :: enum c.int { + X_OK = log2(X_OK), + W_OK = log2(W_OK), + R_OK = log2(R_OK), +} +Mode_Flags :: bit_set[Mode_Flag_Bits; c.int] + +#assert(_F_OK == 0) +F_OK :: Mode_Flags{} + +CS :: enum c.int { + _PATH = _CS_PATH, + _POSIX_V6_ILP32_OFF32_CFLAGS = _CS_POSIX_V6_ILP32_OFF32_CFLAGS, + _POSIX_V6_ILP32_OFF32_LDFLAGS = _CS_POSIX_V6_ILP32_OFF32_LDFLAGS, + _POSIX_V6_ILP32_OFF32_LIBS = _CS_POSIX_V6_ILP32_OFF32_LIBS, + _POSIX_V6_ILP32_OFFBIG_CFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS, + _POSIX_V6_ILP32_OFFBIG_LDFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS, + _POSIX_V6_ILP32_OFFBIG_LIBS = _CS_POSIX_V6_ILP32_OFFBIG_LIBS, + _POSIX_V6_LP64_OFF64_CFLAGS = _CS_POSIX_V6_LP64_OFF64_CFLAGS, + _POSIX_V6_LP64_OFF64_LDFLAGS = _CS_POSIX_V6_LP64_OFF64_LDFLAGS, + _POSIX_V6_LP64_OFF64_LIBS = _CS_POSIX_V6_LP64_OFF64_LIBS, + _POSIX_V6_LPBIG_OFFBIG_CFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LDFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LIBS = _CS_POSIX_V6_LPBIG_OFFBIG_LIBS, + _POSIX_V6_WIDTH_RESTRICTED_ENVS = _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS, +} + +PC :: enum c.int { + _2_SYMLINK = _PC_2_SYMLINK, + _ALLOC_SIZE_MIN = _PC_ALLOC_SIZE_MIN, + _ASYNC_IO = _PC_ASYNC_IO, + _CHOWN_RESTRICTED = _PC_CHOWN_RESTRICTED, + _FILESIZEBITS = _PC_FILESIZEBITS, + _LINK_MAX = _PC_LINK_MAX, + _MAX_CANON = _PC_MAX_CANON, + _MAX_INPUT = _PC_MAX_INPUT, + _NAME_MAX = _PC_NAME_MAX, + _NO_TRUNC = _PC_NO_TRUNC, + _PATH_MAX = _PC_PATH_MAX, + _PIPE_BUF = _PC_PIPE_BUF, + _PRIO_IO = _PC_PRIO_IO, + _REC_INCR_XFER_SIZE = _PC_REC_INCR_XFER_SIZE, + _REC_MAX_XFER_SIZE = _PC_REC_MAX_XFER_SIZE, + _REC_MIN_XFER_SIZE = _PC_REC_MIN_XFER_SIZE, + _REC_XFER_ALIGN = _PC_REC_XFER_ALIGN, + _SYMLINK_MAX = _PC_SYMLINK_MAX, + _SYNC_IO = _PC_SYNC_IO, + _VDISABLE = _PC_VDISABLE, +} + +SC :: enum c.int { + _2_C_BIND = _SC_2_C_BIND, + _2_C_DEV = _SC_2_C_DEV, + _2_CHAR_TERM = _SC_2_CHAR_TERM, + _2_FORT_DEV = _SC_2_FORT_DEV, + _2_FORT_RUN = _SC_2_FORT_RUN, + _2_LOCALEDEF = _SC_2_LOCALEDEF, + _2_PBS = _SC_2_PBS, + _2_PBS_ACCOUNTING = _SC_2_PBS_ACCOUNTING, + _2_PBS_CHECKPOINT = _SC_2_PBS_CHECKPOINT, + _2_PBS_LOCATE = _SC_2_PBS_LOCATE, + _2_PBS_MESSAGE = _SC_2_PBS_MESSAGE, + _2_PBS_TRACK = _SC_2_PBS_TRACK, + _2_SW_DEV = _SC_2_SW_DEV, + _2_UPE = _SC_2_UPE, + _2_VERSION = _SC_2_VERSION, + _ADVISORY_INFO = _SC_ADVISORY_INFO, + _AIO_LISTIO_MAX = _SC_AIO_LISTIO_MAX, + _AIO_MAX = _SC_AIO_MAX, + _AIO_PRIO_DELTA_MAX = _SC_AIO_PRIO_DELTA_MAX, + _ARG_MAX = _SC_ARG_MAX, + _ASYNCHRONOUS_IO = _SC_ASYNCHRONOUS_IO, + _ATEXIT_MAX = _SC_ATEXIT_MAX, + _BARRIERS = _SC_BARRIERS, + _BC_BASE_MAX = _SC_BC_BASE_MAX, + _BC_DIM_MAX = _SC_BC_DIM_MAX, + _BC_SCALE_MAX = _SC_BC_SCALE_MAX, + _BC_STRING_MAX = _SC_BC_STRING_MAX, + _CHILD_MAX = _SC_CHILD_MAX, + _CLK_TCK = _SC_CLK_TCK, + _CLOCK_SELECTION = _SC_CLOCK_SELECTION, + _COLL_WEIGHTS_MAX = _SC_COLL_WEIGHTS_MAX, + _CPUTIME = _SC_CPUTIME, + _DELAYTIMER_MAX = _SC_DELAYTIMER_MAX, + _EXPR_NEST_MAX = _SC_EXPR_NEST_MAX, + _FSYNC = _SC_FSYNC, + _GETGR_R_SIZE_MAX = _SC_GETGR_R_SIZE_MAX, + _GETPW_R_SIZE_MAX = _SC_GETPW_R_SIZE_MAX, + _HOST_NAME_MAX = _SC_HOST_NAME_MAX, + _IOV_MAX = _SC_IOV_MAX, + _IPV6 = _SC_IPV6, + _JOB_CONTROL = _SC_JOB_CONTROL, + _LINE_MAX = _SC_LINE_MAX, + _LOGIN_NAME_MAX = _SC_LOGIN_NAME_MAX, + _MAPPED_FILES = _SC_MAPPED_FILES, + _MEMLOCK = _SC_MEMLOCK, + _MEMLOCK_RANGE = _SC_MEMLOCK_RANGE, + _MEMORY_PROTECTION = _SC_MEMORY_PROTECTION, + _MESSAGE_PASSING = _SC_MESSAGE_PASSING, + _MONOTONIC_CLOCK = _SC_MONOTONIC_CLOCK, + _MQ_OPEN_MAX = _SC_MQ_OPEN_MAX, + _MQ_PRIO_MAX = _SC_MQ_PRIO_MAX, + _NGROUPS_MAX = _SC_NGROUPS_MAX, + _OPEN_MAX = _SC_OPEN_MAX, + _PAGE_SIZE = _SC_PAGE_SIZE, + _PAGESIZE = _SC_PAGESIZE, + _PRIORITIZED_IO = _SC_PRIORITIZED_IO, + _PRIORITY_SCHEDULING = _SC_PRIORITY_SCHEDULING, + _RAW_SOCKETS = _SC_RAW_SOCKETS, + _RE_DUP_MAX = _SC_RE_DUP_MAX, + _READER_WRITER_LOCKS = _SC_READER_WRITER_LOCKS, + _REALTIME_SIGNALS = _SC_REALTIME_SIGNALS, + _REGEXP = _SC_REGEXP, + _RTSIG_MAX = _SC_RTSIG_MAX, + _SAVED_IDS = _SC_SAVED_IDS, + _SEM_NSEMS_MAX = _SC_SEM_NSEMS_MAX, + _SEM_VALUE_MAX = _SC_SEM_VALUE_MAX, + _SEMAPHORES = _SC_SEMAPHORES, + _SHARED_MEMORY_OBJECTS = _SC_SHARED_MEMORY_OBJECTS, + _SHELL = _SC_SHELL, + _SIGQUEUE_MAX = _SC_SIGQUEUE_MAX, + _SPAWN = _SC_SPAWN, + _SPIN_LOCKS = _SC_SPIN_LOCKS, + _SPORADIC_SERVER = _SC_SPORADIC_SERVER, + _SS_REPL_MAX = _SC_SS_REPL_MAX, + _STREAM_MAX = _SC_STREAM_MAX, + _SYMLOOP_MAX = _SC_SYMLOOP_MAX, + _SYNCHRONIZED_IO = _SC_SYNCHRONIZED_IO, + _THREAD_ATTR_STACKADDR = _SC_THREAD_ATTR_STACKADDR, + _THREAD_ATTR_STACKSIZE = _SC_THREAD_ATTR_STACKSIZE, + _THREAD_CPUTIME = _SC_THREAD_CPUTIME, + _THREAD_DESTRUCTOR_ITERATIONS = _SC_THREAD_DESTRUCTOR_ITERATIONS, + _THREAD_KEYS_MAX = _SC_THREAD_KEYS_MAX, + _THREAD_PRIO_INHERIT = _SC_THREAD_PRIO_INHERIT, + _THREAD_PRIO_PROTECT = _SC_THREAD_PRIO_PROTECT, + _THREAD_PRIORITY_SCHEDULING = _SC_THREAD_PRIORITY_SCHEDULING, + _THREAD_PROCESS_SHARED = _SC_THREAD_PROCESS_SHARED, + _THREAD_SAFE_FUNCTIONS = _SC_THREAD_SAFE_FUNCTIONS, + _THREAD_SPORADIC_SERVER = _SC_THREAD_SPORADIC_SERVER, + _THREAD_STACK_MIN = _SC_THREAD_STACK_MIN, + _THREAD_THREADS_MAX = _SC_THREAD_THREADS_MAX, + _THREADS = _SC_THREADS, + _TIMEOUTS = _SC_TIMEOUTS, + _TIMER_MAX = _SC_TIMER_MAX, + _TIMERS = _SC_TIMERS, + _TRACE = _SC_TRACE, + _TRACE_EVENT_FILTER = _SC_TRACE_EVENT_FILTER, + _TRACE_EVENT_NAME_MAX = _SC_TRACE_EVENT_NAME_MAX, + _TRACE_INHERIT = _SC_TRACE_INHERIT, + _TRACE_LOG = _SC_TRACE_LOG, + _TRACE_NAME_MAX = _SC_TRACE_NAME_MAX, + _TRACE_SYS_MAX = _SC_TRACE_SYS_MAX, + _TRACE_USER_EVENT_MAX = _SC_TRACE_USER_EVENT_MAX, + _TTY_NAME_MAX = _SC_TTY_NAME_MAX, + _TYPED_MEMORY_OBJECTS = _SC_TYPED_MEMORY_OBJECTS, + _TZNAME_MAX = _SC_TZNAME_MAX, + _V6_ILP32_OFF32 = _SC_V6_ILP32_OFF32, + _V6_ILP32_OFFBIG = _SC_V6_ILP32_OFFBIG, + _V6_LP64_OFF64 = _SC_V6_LP64_OFF64, + _V6_LPBIG_OFFBIG = _SC_V6_LPBIG_OFFBIG, + _VERSION = _SC_VERSION, + _XOPEN_CRYPT = _SC_XOPEN_CRYPT, + _XOPEN_ENH_I18N = _SC_XOPEN_ENH_I18N, + _XOPEN_REALTIME = _SC_XOPEN_REALTIME, + _XOPEN_REALTIME_THREADS = _SC_XOPEN_REALTIME_THREADS, + _XOPEN_SHM = _SC_XOPEN_SHM, + _XOPEN_STREAMS = _SC_XOPEN_STREAMS, + _XOPEN_UNIX = _SC_XOPEN_UNIX, + _XOPEN_VERSION = _SC_XOPEN_VERSION, +} + +Lock_Function :: enum c.int { + // Lock a section for exclusive use. + LOCK = F_LOCK, + // Test a section for locks by other processes. + TEST = F_TEST, + // Test and lock a section for exclusive use. + TLOCK = F_TLOCK, + // Unlock locked sections. + ULOCK = F_ULOCK, +} + +when ODIN_OS == .NetBSD { + @(private) LCHOWN :: "__posix_chown" + @(private) LFCHOWN :: "__posix_fchown" + @(private) LLCHOWN :: "__posix_lchown" +} else { + @(private) LCHOWN :: "chown" + @(private) LFCHOWN :: "fchown" + @(private) LLCHOWN :: "lchown" +} + +when ODIN_OS == .Darwin { + + _F_OK :: 0 + X_OK :: (1<<0) + W_OK :: (1<<1) + R_OK :: (1<<2) + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 15 + _PC_ALLOC_SIZE_MIN :: 16 + _PC_ASYNC_IO :: 17 + _PC_FILESIZEBITS :: 18 + _PC_PRIO_IO :: 19 + _PC_REC_INCR_XFER_SIZE :: 20 + _PC_REC_MAX_XFER_SIZE :: 21 + _PC_REC_MIN_XFER_SIZE :: 22 + _PC_REC_XFER_ALIGN :: 23 + _PC_SYMLINK_MAX :: 24 + _PC_SYNC_IO :: 25 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_PAGE_SIZE :: 29 + _SC_PAGESIZE :: _SC_PAGE_SIZE + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_MAPPED_FILES :: 47 + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + + _POSIX_VDISABLE :: '\377' + +} else when ODIN_OS == .FreeBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers (freebsd) + _PC_ALLOC_SIZE_MIN :: 10 + _PC_ASYNC_IO :: 53 + _PC_FILESIZEBITS :: 12 + _PC_PRIO_IO :: 54 + _PC_REC_INCR_XFER_SIZE :: 14 + _PC_REC_MAX_XFER_SIZE :: 15 + _PC_REC_MIN_XFER_SIZE :: 16 + _PC_REC_XFER_ALIGN :: 17 + _PC_SYMLINK_MAX :: 18 + _PC_SYNC_IO :: 55 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_MAPPED_FILES :: 29 + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_PAGE_SIZE :: 47 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 // NOTE: not in headers + _SC_TRACE_EVENT_NAME_MAX :: 127 // NOTE: not in headers + _SC_TRACE_NAME_MAX :: 128 // NOTE: not in headers + _SC_TRACE_SYS_MAX :: 129 // NOTE: not in headers + _SC_TRACE_USER_EVENT_MAX :: 130 // NOTE: not in headers + + _POSIX_VDISABLE :: 0xff + +} else when ODIN_OS == .NetBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers + _PC_ALLOC_SIZE_MIN :: 10 // NOTE: not in headers + _PC_ASYNC_IO :: 53 // NOTE: not in headers + _PC_FILESIZEBITS :: 11 + _PC_PRIO_IO :: 54 // NOTE: not in headers + _PC_REC_INCR_XFER_SIZE :: 14 // NOTE: not in headers + _PC_REC_MAX_XFER_SIZE :: 15 // NOTE: not in headers + _PC_REC_MIN_XFER_SIZE :: 16 // NOTE: not in headers + _PC_REC_XFER_ALIGN :: 17 // NOTE: not in headers + _PC_SYMLINK_MAX :: 12 + _PC_SYNC_IO :: 10 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGE_SIZE :: 28 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SYNCHRONIZED_IO :: 31 + _SC_IOV_MAX :: 32 + _SC_MAPPED_FILES :: 33 + _SC_MEMLOCK :: 34 + _SC_MEMLOCK_RANGE :: 35 + _SC_MEMORY_PROTECTION :: 36 + _SC_LOGIN_NAME_MAX :: 37 + _SC_MONOTONIC_CLOCK :: 38 + _SC_CLK_TCK :: 39 + + _SC_ATEXIT_MAX :: 40 + _SC_THREADS :: 41 + _SC_SEMAPHORES :: 42 + _SC_BARRIERS :: 43 + _SC_TIMERS :: 44 + _SC_SPIN_LOCKS :: 45 + _SC_READER_WRITER_LOCKS :: 46 + _SC_GETGR_R_SIZE_MAX :: 47 + _SC_GETPW_R_SIZE_MAX :: 48 + _SC_CLOCK_SELECTION :: 49 + + _SC_ASYNCHRONOUS_IO :: 50 + _SC_AIO_LISTIO_MAX :: 51 + _SC_AIO_MAX :: 52 + _SC_MESSAGE_PASSING :: 53 + _SC_MQ_OPEN_MAX :: 54 + _SC_MQ_PRIO_MAX :: 55 + _SC_PRIORITY_SCHEDULING :: 56 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 57 + _SC_THREAD_KEYS_MAX :: 58 + _SC_THREAD_STACK_MIN :: 59 + + _SC_THREAD_THREADS_MAX :: 60 + _SC_THREAD_ATTR_STACKADDR :: 61 + _SC_THREAD_ATTR_STACKSIZE :: 62 + _SC_THREAD_PRIORITY_SCHEDULING :: 63 + _SC_THREAD_PRIO_INHERIT :: 64 + _SC_THREAD_PRIO_PROTECT :: 65 + _SC_THREAD_PROCESS_SHARED :: 66 + _SC_THREAD_SAFE_FUNCTIONS :: 67 + _SC_TTY_NAME_MAX :: 68 + _SC_HOST_NAME_MAX :: 69 + + _SC_PASS_MAX :: 70 + _SC_REGEXP :: 71 + _SC_SHELL :: 72 + _SC_SYMLOOP_MAX :: 73 + _SC_V6_ILP32_OFF32 :: 74 + _SC_V6_ILP32_OFFBIG :: 75 + _SC_V6_LP64_OFF64 :: 76 + _SC_V6_LPBIG_OFFBIG :: 77 + + _SC_2_PBS :: 80 + _SC_2_PBS_ACCOUNTING :: 81 + _SC_2_PBS_CHECKPOINT :: 82 + _SC_2_PBS_LOCATE :: 83 + _SC_2_PBS_MESSAGE :: 84 + _SC_2_PBS_TRACK :: 85 + _SC_SPAWN :: 86 + _SC_SHARED_MEMORY_OBJECTS :: 87 + _SC_TIMER_MAX :: 88 + _SC_SEM_NSEMS_MAX :: 89 + + _SC_CPUTIME :: 90 + _SC_THREAD_CPUTIME :: 91 + _SC_DELAYTIMER_MAX :: 92 + _SC_SIGQUEUE_MAX :: 93 + _SC_REALTIME_SIGNALS :: 94 + _SC_RTSIG_MAX :: 95 + + _POSIX_VDISABLE :: '\377' + + // NOTE: following are not defined in netbsd headers. + + _SC_SPORADIC_SERVER :: 81 + _SC_SEM_VALUE_MAX :: 50 + + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + _SC_TRACE_LOG :: 100 + _SC_TYPED_MEMORY_OBJECTS :: 102 + + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_TIMEOUTS :: 95 + + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_PRIORITIZED_IO :: 34 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ADVISORY_INFO :: 65 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + +} else when ODIN_OS == .OpenBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 10 + _PC_ALLOC_SIZE_MIN :: 11 + _PC_ASYNC_IO :: 12 + _PC_FILESIZEBITS :: 13 + _PC_PRIO_IO :: 14 + _PC_REC_INCR_XFER_SIZE :: 15 + _PC_REC_MAX_XFER_SIZE :: 16 + _PC_REC_MIN_XFER_SIZE :: 17 + _PC_REC_XFER_ALIGN :: 18 + _PC_SYMLINK_MAX :: 19 + _PC_SYNC_IO :: 20 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGESIZE :: 28 + _SC_PAGE_SIZE :: _SC_PAGESIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SEM_NSEMS_MAX :: 31 + _SC_SEM_VALUE_MAX :: 32 + _SC_HOST_NAME_MAX :: 33 + _SC_MONOTONIC_CLOCK :: 34 + _SC_2_PBS :: 35 + _SC_2_PBS_ACCOUNTING :: 36 + _SC_2_PBS_CHECKPOINT :: 37 + _SC_2_PBS_LOCATE :: 38 + _SC_2_PBS_MESSAGE :: 39 + + _SC_2_PBS_TRACK :: 40 + _SC_ADVISORY_INFO :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ASYNCHRONOUS_IO :: 45 + _SC_ATEXIT_MAX :: 46 + _SC_BARRIERS :: 47 + _SC_CLOCK_SELECTION :: 48 + _SC_CPUTIME :: 49 + + _SC_DELAYTIMER_MAX :: 50 + _SC_IOV_MAX :: 51 + _SC_IPV6 :: 52 + _SC_MAPPED_FILES :: 53 + _SC_MEMLOCK :: 54 + _SC_MEMLOCK_RANGE :: 55 + _SC_MEMORY_PROTECTION :: 56 + _SC_MESSAGE_PASSING :: 57 + _SC_MQ_OPEN_MAX :: 58 + _SC_MQ_PRIO_MAX :: 59 + + _SC_PRIORITIZED_IO :: 60 + _SC_PRIORITY_SCHEDULING :: 61 + _SC_RAW_SOCKETS :: 62 + _SC_READER_WRITER_LOCKS :: 63 + _SC_REALTIME_SIGNALS :: 64 + _SC_REGEXP :: 65 + _SC_RTSIG_MAX :: 66 + _SC_SEMAPHORES :: 67 + _SC_SHARED_MEMORY_OBJECTS :: 68 + _SC_SHELL :: 69 + + _SC_SIGQUEUE_MAX :: 70 + _SC_SPAWN :: 71 + _SC_SPIN_LOCKS :: 72 + _SC_SPORADIC_SERVER :: 73 + _SC_SS_REPL_MAX :: 74 + _SC_SYNCHRONIZED_IO :: 75 + _SC_SYMLOOP_MAX :: 76 + _SC_THREAD_ATTR_STACKADDR :: 77 + _SC_THREAD_ATTR_STACKSIZE :: 78 + _SC_THREAD_CPUTIME :: 79 + + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 80 + _SC_THREAD_KEYS_MAX :: 81 + _SC_THREAD_PRIO_INHERIT :: 82 + _SC_THREAD_PRIO_PROTECT :: 83 + _SC_THREAD_PRIORITY_SCHEDULING :: 84 + _SC_THREAD_PROCESS_SHARED :: 85 + _SC_THREAD_ROBUST_PRIO_INHERIT :: 86 + _SC_THREAD_ROBUST_PRIO_PROTECT :: 87 + _SC_THREAD_SPORADIC_SERVER :: 88 + _SC_THREAD_STACK_MIN :: 89 + + _SC_THREAD_THREADS_MAX :: 90 + _SC_THREADS :: 91 + _SC_TIMEOUTS :: 92 + _SC_TIMER_MAX :: 93 + _SC_TIMERS :: 94 + _SC_TRACE :: 95 + _SC_TRACE_EVENT_FILTER :: 96 + _SC_TRACE_EVENT_NAME_MAX :: 97 + _SC_TRACE_INHERIT :: 98 + _SC_TRACE_LOG :: 99 + + _SC_GETGR_R_SIZE_MAX :: 100 + _SC_GETPW_R_SIZE_MAX :: 101 + _SC_LOGIN_NAME_MAX :: 102 + _SC_THREAD_SAFE_FUNCTIONS :: 103 + _SC_TRACE_NAME_MAX :: 104 + _SC_TRACE_SYS_MAX :: 105 + _SC_TRACE_USER_EVENT_MAX :: 106 + _SC_TTY_NAME_MAX :: 107 + _SC_TYPED_MEMORY_OBJECTS :: 108 + _SC_V6_ILP32_OFF32 :: 109 + + _SC_V6_ILP32_OFFBIG :: 110 + _SC_V6_LP64_OFF64 :: 111 + _SC_V6_LPBIG_OFFBIG :: 112 + _SC_V7_ILP32_OFF32 :: 113 + _SC_V7_ILP32_OFFBIG :: 114 + _SC_V7_LP64_OFF64 :: 115 + _SC_V7_LPBIG_OFFBIG :: 116 + _SC_XOPEN_CRYPT :: 117 + _SC_XOPEN_ENH_I18N :: 118 + _SC_XOPEN_LEGACY :: 119 + + _SC_XOPEN_REALTIME :: 120 + _SC_XOPEN_REALTIME_THREADS :: 121 + _SC_XOPEN_STREAMS :: 122 + _SC_XOPEN_UNIX :: 123 + _SC_XOPEN_UUCP :: 124 + _SC_XOPEN_VERSION :: 125 + + _SC_PHYS_PAGES :: 500 + _SC_AVPHYS_PAGES :: 501 + _SC_NPROCESSORS_CONF :: 502 + _SC_NPROCESSORS_ONLN :: 503 + + _POSIX_VDISABLE :: '\377' + +} else { + #panic("posix is unimplemented for the current target") +} + diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin new file mode 100644 index 000000000..591a6db06 --- /dev/null +++ b/core/sys/posix/utime.odin @@ -0,0 +1,36 @@ +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// utime.h - access and modification time structure + +foreign lib { + /* + Set file access and modification times. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utime.html ]] + */ + @(link_name=LUTIME) + utime :: proc(path: cstring, times: ^utimbuf) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LUTIME :: "__utime50" +} else { + @(private) LUTIME :: "utime" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + utimbuf :: struct { + actime: time_t, /* [PSX] access time (seconds since epoch) */ + modtime: time_t, /* [PSX] modification time (seconds since epoch) */ + } + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin new file mode 100644 index 000000000..d730db0f7 --- /dev/null +++ b/core/sys/posix/wordexp.odin @@ -0,0 +1,107 @@ +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// wordexp.h - word-expansion type + +foreign lib { + /* + Perform word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordexp :: proc(words: cstring, pwordexp: ^wordexp_t, flags: WRDE_Flags) -> WRDE_Errno --- + + /* + Free the space allocated during word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordfree :: proc(pwordexp: ^wordexp_t) --- +} + +WRDE_Flag_Bits :: enum c.int { + // Appends words to those previously generated. + APPEND = log2(WRDE_APPEND), + // Number of null pointers to prepend to we_wordv. + DOOFFS = log2(WRDE_DOOFFS), + // Fail if command substitution is requested. + NOCMD = log2(WRDE_NOCMD), + // The pwordexp argument was passed to a previous successful call to wordexp(), + // and has not been passed to wordfree(). + REUSE = log2(WRDE_REUSE), + // Do not redirect stderr to /dev/null. + SHOWERR = log2(WRDE_SHOWERR), + // Report error on attempt to expand an undefined shell variable. + UNDEF = log2(WRDE_UNDEF), +} +WRDE_Flags :: bit_set[WRDE_Flag_Bits; c.int] + +WRDE_Errno :: enum c.int { + OK = 0, + // One of the unquoted characters- , '|', '&', ';', '<', '>', '(', ')', '{', '}' - + // appears in words in an inappropriate context. + BADCHAR = WRDE_BADCHAR, + // Reference to undefined shell variable when WRDE_UNDEF is set in flags. + BADVAL = WRDE_BADVAL, + // Command substitution requested when WRDE_NOCMD was set in flags. + CMDSUB = WRDE_CMDSUB, + // Attempt to allocate memory failed. + NOSPACE = WRDE_NOSPACE, + // Shell syntax error, such as unbalanced parentheses or an unterminated string. + SYNTAX = WRDE_SYNTAX, +} + +when ODIN_OS == .Darwin { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + we_strings: [^]byte, /* storage for wordv strings */ + we_nbytes: c.size_t, /* size of we_strings */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin index afbbc321c..9107f1139 100644 --- a/core/sys/unix/pthread_netbsd.odin +++ b/core/sys/unix/pthread_netbsd.odin @@ -2,7 +2,7 @@ package unix import "core:c" -pthread_t :: distinct u64 +pthread_t :: distinct rawptr SEM_T_SIZE :: 8 diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index c876a214a..68a0859b4 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -5,6 +5,11 @@ foreign import "system:pthread" import "core:c" +timespec :: struct { + tv_sec: i64, + tv_nsec: i64, +} + // // On success, these functions return 0. // diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin deleted file mode 100644 index 442202d36..000000000 --- a/core/sys/unix/time_unix.odin +++ /dev/null @@ -1,83 +0,0 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku -package unix - -when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" -} else { - foreign import libc "system:c" -} - -import "core:c" - -when ODIN_OS == .NetBSD { - @(default_calling_convention="c") - foreign libc { - @(link_name="__clock_gettime50") clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- - @(link_name="__nanosleep50") nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- - @(link_name="sleep") sleep :: proc(seconds: c.uint) -> c.int --- - } -} else { - @(default_calling_convention="c") - foreign libc { - clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- - sleep :: proc(seconds: c.uint) -> c.int --- - nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- - } -} - -timespec :: struct { - tv_sec: i64, // seconds - tv_nsec: i64, // nanoseconds -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - -boot_time_in_nanoseconds :: proc "c" () -> i64 { - ts_now, ts_boottime: timespec - clock_gettime(CLOCK_REALTIME, &ts_now) - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return i64(ns) -} - -seconds_since_boot :: proc "c" () -> f64 { - ts_boottime: timespec - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - -inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { - s, ns := nanoseconds / 1e9, nanoseconds % 1e9 - requested := timespec{tv_sec=s, tv_nsec=ns} - res = nanosleep(&requested, &remaining) - return -} - diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin new file mode 100644 index 000000000..649f601dc --- /dev/null +++ b/core/time/time_linux.odin @@ -0,0 +1,38 @@ +package time + +import "core:sys/linux" + +_IS_SUPPORTED :: true + +_now :: proc "contextless" () -> Time { + time_spec_now, _ := linux.clock_gettime(.REALTIME) + ns := time_spec_now.time_sec * 1e9 + time_spec_now.time_nsec + return Time{_nsec=i64(ns)} +} + +_sleep :: proc "contextless" (d: Duration) { + ds := duration_seconds(d) + seconds := uint(ds) + nanoseconds := uint((ds - f64(seconds)) * 1e9) + + ts := linux.Time_Spec{ + time_sec = seconds, + time_nsec = nanoseconds, + } + + for { + if linux.nanosleep(&ts, &ts) != .EINTR { + break + } + } +} + +_tick_now :: proc "contextless" () -> Tick { + t, _ := linux.clock_gettime(.MONOTONIC_RAW) + return Tick{_nsec = i64(t.time_sec*1e9 + t.time_nsec)} +} + +_yield :: proc "contextless" () { + linux.sched_yield() +} + diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index f3e0d6640..61543b99c 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,34 +1,50 @@ //+private -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +//+build darwin, freebsd, openbsd, netbsd, haiku package time -import "core:sys/unix" +import "core:sys/posix" -_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - time_spec_now: unix.timespec - unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec + time_spec_now: posix.timespec + posix.clock_gettime(.REALTIME, &time_spec_now) + ns := i64(time_spec_now.tv_sec) * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } _sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) - seconds := u32(ds) + seconds := posix.time_t(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { unix.sleep(seconds) } - if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } + ts := posix.timespec{ + tv_sec = seconds, + tv_nsec = nanoseconds, + } + + for { + res := posix.nanosleep(&ts, &ts) + if res == .OK || posix.errno() != .EINTR { + break + } + } +} + +when ODIN_OS == .Darwin { + TICK_CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + TICK_CLOCK :: posix.Clock.MONOTONIC } _tick_now :: proc "contextless" () -> Tick { - t: unix.timespec - unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) - return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} + t: posix.timespec + posix.clock_gettime(TICK_CLOCK, &t) + return Tick{_nsec = i64(t.tv_sec)*1e9 + t.tv_nsec} } _yield :: proc "contextless" () { - unix.sched_yield() + posix.sched_yield() } diff --git a/examples/all/all_posix.odin b/examples/all/all_posix.odin new file mode 100644 index 000000000..819dd6dd3 --- /dev/null +++ b/examples/all/all_posix.odin @@ -0,0 +1,6 @@ +//+build darwin, openbsd, freebsd, netbsd +package all + +import posix "core:sys/posix" + +_ :: posix diff --git a/src/big_int.cpp b/src/big_int.cpp index e350687b4..83235483c 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -621,3 +621,7 @@ gb_internal String big_int_to_string(gbAllocator allocator, BigInt const *x, u64 } return make_string(cast(u8 *)buf.data, buf.count); } + +gb_internal int big_int_log2(BigInt const *x) { + return mp_count_bits(x) - 1; +} diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index bde102a8d..e5282f63e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3979,6 +3979,23 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_constant_log2: { + Operand o = {}; + check_expr(c, &o, ce->args[0]); + + if (!is_type_integer(o.type) && (o.mode != Addressing_Constant)) { + error(ce->args[0], "Expected a constant integer for '%.*s'", LIT(builtin_name)); + return false; + } + + int log2 = big_int_log2(&o.value.value_integer); + + operand->mode = Addressing_Constant; + operand->value = exact_value_i64(cast(i64)log2); + operand->type = t_untyped_integer; + break; + } + case BuiltinProc_soa_struct: { Operand x = {}; Operand y = {}; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 1425aafa8..0d4b47037 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -756,13 +756,29 @@ gb_internal bool are_signatures_similar_enough(Type *a_, Type *b_) { for (isize i = 0; i < a->param_count; i++) { Type *x = core_type(a->params->Tuple.variables[i]->type); Type *y = core_type(b->params->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + if (!signature_parameter_similar_enough(x, y)) { return false; } } for (isize i = 0; i < a->result_count; i++) { - Type *x = base_type(a->results->Tuple.variables[i]->type); - Type *y = base_type(b->results->Tuple.variables[i]->type); + Type *x = core_type(a->results->Tuple.variables[i]->type); + Type *y = core_type(b->results->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + if (!signature_parameter_similar_enough(x, y)) { return false; } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 6245dadaf..ef07938c7 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -46,6 +46,8 @@ enum BuiltinProcId { BuiltinProc_has_target_feature, + BuiltinProc_constant_log2, + BuiltinProc_transpose, BuiltinProc_outer_product, BuiltinProc_hadamard_product, @@ -380,6 +382,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("has_target_feature"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("constant_log2"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("transpose"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("outer_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/tests/core/normal.odin b/tests/core/normal.odin index e35d86598..45c66ed8d 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -38,6 +38,7 @@ download_assets :: proc() { @(require) import "slice" @(require) import "strconv" @(require) import "strings" +@(require) import "sys/posix" @(require) import "sys/windows" @(require) import "text/i18n" @(require) import "text/match" diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin new file mode 100644 index 000000000..1942bfbda --- /dev/null +++ b/tests/core/sys/posix/posix.odin @@ -0,0 +1,211 @@ +//+build darwin, freebsd, openbsd, netbsd +package tests_core_posix + +import "core:sys/posix" +import "core:testing" +import "core:log" +import "core:strings" +import "core:path/filepath" + +@(test) +test_arpa_inet :: proc(t: ^testing.T) { + + check :: proc(t: ^testing.T, $af: posix.AF, src: cstring, expect: posix.pton_result, loc := #caller_location) { + when af == .INET { + addr: posix.in_addr + dst: [posix.INET_ADDRSTRLEN]byte + } else { + addr: posix.in6_addr + dst: [posix.INET6_ADDRSTRLEN]byte + } + + res := posix.inet_pton(af, src, &addr, size_of(addr)) + testing.expect_value(t, res, expect, loc) + + if expect == .SUCCESS { + back := posix.inet_ntop(af, &addr, raw_data(dst[:]), len(dst)) + testing.expect_value(t, back, src, loc) + + when af == .INET { + back = posix.inet_ntoa(addr) + testing.expect_value(t, back, src, loc) + } + } + } + + check(t, .INET, "127.0.0.1", .SUCCESS) + check(t, .INET, "blah", .INVALID) + check(t, .INET6, "::1", .SUCCESS) + check(t, .INET6, "L", .INVALID) + check(t, .UNIX, "127.0.0.1", .AFNOSUPPORT) +} + +@(test) +test_dirent :: proc(t: ^testing.T) { + test := #load_directory(#directory) + test_map: map[string]struct{} + defer delete(test_map) + + test_map[".."] = {} + test_map["."] = {} + + for file in test { + test_map[filepath.base(file.name)] = {} + } + + { + list: [^]^posix.dirent + ret := posix.scandir(#directory, &list) + testing.expectf(t, ret >= 0, "%v >= 0: %v", ret, posix.strerror(posix.errno())) + defer posix.free(list) + + entries := list[:ret] + for entry in entries { + defer posix.free(entry) + + if entry.d_type != .REG { + continue + } + + name := string(cstring(raw_data(entry.d_name[:]))) + testing.expectf(t, name in test_map, "%v in %v", name, test_map) + } + } + + { + dir := posix.opendir(#directory) + defer posix.closedir(dir) + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + testing.expect_value(t, posix.errno(), posix.Errno.NONE) + break + } + + if entry.d_type != .REG { + continue + } + + name := string(cstring(raw_data(entry.d_name[:]))) + testing.expectf(t, name in test_map, "%v in %v", name, test_map) + } + } +} + +@(test) +test_errno :: proc(t: ^testing.T) { + posix.errno(posix.Errno.ENOMEM) + testing.expect_value(t, posix.errno(), posix.Errno.ENOMEM) + + res := posix.open("", {}) + testing.expect_value(t, res, -1) + testing.expect_value(t, posix.errno(), posix.Errno.ENOENT) +} + +@(test) +test_fcntl :: proc(t: ^testing.T) { + res := posix.open(#file, { .WRONLY, .CREAT, .EXCL }) + testing.expect_value(t, res, -1) + testing.expect_value(t, posix.errno(), posix.Errno.EEXIST) +} + +@(test) +test_fnmatch :: proc(t: ^testing.T) { + testing.expect_value(t, posix.fnmatch("*.odin", #file, {}), 0) + testing.expect_value(t, posix.fnmatch("*.txt", #file, {}), posix.FNM_NOMATCH) + testing.expect_value(t, posix.fnmatch("**/*.odin", #file, {}), 0) +} + +@(test) +test_glob :: proc(t: ^testing.T) { + glob: posix.glob_t + res := posix.glob(#directory + ":)))))))", {}, nil, &glob) + testing.expect_value(t, res, posix.Glob_Result.NOMATCH) + posix.globfree(&glob) +} + +@(test) +test_langinfo :: proc(t: ^testing.T) { + locale := posix.setlocale(.TIME, nil) + testing.expectf(t, locale == "POSIX" || locale == "C", "invalid locale for test: %v", locale) + + day1 := posix.nl_langinfo(.DAY_1) + testing.expect_value(t, day1, "Sunday") +} + +@(test) +test_libgen :: proc(t: ^testing.T) { + tests := [][3]cstring{ + { "usr", ".", "usr" }, + { "usr/", ".", "usr" }, + { "", ".", "." }, + { "/", "/", "/" }, + { "//", "/", "/" }, + { "///", "/", "/" }, + { "/usr/", "/", "usr" }, + { "/usr/lib", "/usr", "lib" }, + { "//usr//lib//", "//usr", "lib" }, + { "/home//dwc//test", "/home//dwc", "test" }, + } + + for test in tests { + // NOTE: dir/basename can change their input so they can't be literals. + + dinput := strings.clone_to_cstring(string(test[0])) + defer delete(dinput) + + dir := posix.dirname(dinput) + testing.expectf(t, dir == test[1], "dirname(%q) == %q, expected %q", test[0], dir, test[1]) + + binput := strings.clone_to_cstring(string(test[0])) + defer delete(binput) + + base := posix.basename(binput) + testing.expectf(t, base == test[2], "basename(%q) == %q, expected %q", test[0], base, test[2]) + } +} + +@(test) +test_locale :: proc(t: ^testing.T) { + lconv := posix.localeconv() + testing.expect(t, lconv != nil) + + locale := posix.setlocale(.ALL, nil) + testing.expectf(t, locale == "POSIX" || locale == "C", "%q is not POSIX or C", locale) +} + +@(test) +test_monetary :: proc(t: ^testing.T) { + when ODIN_OS == .Darwin && .Address in ODIN_SANITIZER_FLAGS { + log.warn("skipping on darwin with -sanitize:address, this fails inside macOS (also from C/clang)") + return + } + + value := 123456.789 + buf: [128]byte + size := posix.strfmon(raw_data(buf[:]), len(buf), "%n", value) + testing.expectf(t, int(size) != -1, "strfmon failure: %v", posix.strerror(posix.errno())) + log.debug(string(buf[:size])) +} + +@(test) +test_stat :: proc(t: ^testing.T) { + testing.expect_value(t, posix.S_IRWXU, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXU)) + testing.expect_value(t, posix.S_IRWXG, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXG)) + testing.expect_value(t, posix.S_IRWXO, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXO)) + testing.expect_value(t, posix._S_IFMT, transmute(posix.mode_t)posix._mode_t(posix.__S_IFMT)) +} + +@(test) +test_termios :: proc(t: ^testing.T) { + testing.expect_value(t, transmute(posix.CControl_Flags)posix.tcflag_t(posix._CSIZE), posix.CSIZE) + + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._NLDLY), posix.NLDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._CRDLY), posix.CRDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._TABDLY), posix.TABDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._BSDLY), posix.BSDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._VTDLY), posix.VTDLY) + testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._FFDLY), posix.FFDLY) +} diff --git a/tests/core/sys/posix/structs.odin b/tests/core/sys/posix/structs.odin new file mode 100644 index 000000000..bdb1c24e3 --- /dev/null +++ b/tests/core/sys/posix/structs.odin @@ -0,0 +1,127 @@ +//+build darwin, freebsd, openbsd, netbsd +package tests_core_posix + +import "core:log" +import "core:testing" +import "core:sys/posix" + +// This test tests some of the process APIs of posix while also double checking size and alignment +// of the structs we bound. + +@(test) +execute_struct_checks :: proc(t: ^testing.T) { + log.debug("compiling C project") + { + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + case 0: + c_compiler := posix.getenv("CC") + if c_compiler == nil { + c_compiler = "clang" + } + + posix.execlp(c_compiler, + c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs", nil) + posix.exit(69) + case: + if !wait_for(t, pid) { return } + log.debug("C code has been compiled!") + } + } + + log.debug("compiling Odin project") + { + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + case 0: + posix.execlp(ODIN_ROOT + "/odin", + ODIN_ROOT + "/odin", "build", #directory + "/structs/structs.odin", "-out:" + #directory + "/structs/odin_structs", "-file", nil) + posix.exit(69) + case: + if !wait_for(t, pid) { return } + log.debug("Odin code has been compiled!") + } + } + + c_buf: [dynamic]byte + defer delete(c_buf) + c_out := get_output(t, &c_buf, #directory + "/structs/c_structs", nil) + + odin_buf: [dynamic]byte + defer delete(odin_buf) + odin_out := get_output(t, &odin_buf, #directory + "/structs/odin_structs", nil) + + testing.expectf(t, c_out == odin_out, "The C output and Odin output differ!\nC output:\n%s\n\n\n\nOdin Output:\n%s", c_out, odin_out) + + /* ----------- HELPERS ----------- */ + + wait_for :: proc(t: ^testing.T, pid: posix.pid_t) -> (ok: bool) { + log.debugf("waiting on pid %v", pid) + + waiting: for { + status: i32 + wpid := posix.waitpid(pid, &status, {}) + if !testing.expectf(t, wpid != -1, "waitpid() failure: %v", posix.strerror()) { + return false + } + + switch { + case posix.WIFEXITED(status): + ok = testing.expect_value(t, posix.WEXITSTATUS(status), 0) + break waiting + case posix.WIFSIGNALED(status): + log.errorf("child process raised: %v", posix.strsignal(posix.WTERMSIG(status))) + ok = false + break waiting + case: + log.errorf("unexpected status (this should never happen): %v", status) + ok = false + break waiting + } + } + + return + } + + get_output :: proc(t: ^testing.T, output: ^[dynamic]byte, cmd: ..cstring) -> (out_str: string) { + log.debugf("capturing output of: %v", cmd) + + pipe: [2]posix.FD + if !testing.expect_value(t, posix.pipe(&pipe), posix.result.OK) { + return + } + + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failed: %s", posix.strerror()) + return + case 0: + posix.close(pipe[0]) + posix.dup2(pipe[1], 1) + posix.execv(cmd[0], raw_data(cmd[:])) + panic(string(posix.strerror())) + case: + posix.close(pipe[1]) + log.debugf("waiting on pid %v", pid) + + reader: for { + buf: [256]byte + switch read := posix.read(pipe[0], &buf[0], 256); { + case read < 0: + log.errorf("read output failed: %v", posix.strerror()) + return + case read == 0: + break reader + case: + append(output, ..buf[:read]) + } + } + + wait_for(t, pid) + + return string(output[:]) + } + } +} diff --git a/tests/core/sys/posix/structs/.gitignore b/tests/core/sys/posix/structs/.gitignore new file mode 100644 index 000000000..646eae0a2 --- /dev/null +++ b/tests/core/sys/posix/structs/.gitignore @@ -0,0 +1,2 @@ +c_structs +odin_structs diff --git a/tests/core/sys/posix/structs/structs.c b/tests/core/sys/posix/structs/structs.c new file mode 100644 index 000000000..7d8038fbb --- /dev/null +++ b/tests/core/sys/posix/structs/structs.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + printf("dirent %zu %zu\n", sizeof(struct dirent), _Alignof(struct dirent)); + printf("flock %zu %zu\n", sizeof(struct flock), _Alignof(struct flock)); + printf("glob_t %zu %zu\n", sizeof(glob_t), _Alignof(glob_t)); + printf("group %zu %zu\n", sizeof(struct group), _Alignof(struct group)); + printf("lconv %zu %zu\n", sizeof(struct lconv), _Alignof(struct lconv)); + + printf("pthread_t %zu %zu\n", sizeof(pthread_t), _Alignof(pthread_t)); + printf("pthread_attr_t %zu %zu\n", sizeof(pthread_attr_t), _Alignof(pthread_attr_t)); + printf("pthread_key_t %zu %zu\n", sizeof(pthread_key_t), _Alignof(pthread_key_t)); + + printf("sched_param %zu %zu\n", sizeof(struct sched_param), _Alignof(struct sched_param)); + + printf("termios %zu %zu\n", sizeof(struct termios), _Alignof(struct termios)); + + printf("in_addr %zu %zu\n", sizeof(struct in_addr), _Alignof(struct in_addr)); + printf("in6_addr %zu %zu\n", sizeof(struct in6_addr), _Alignof(struct in6_addr)); + printf("sockaddr_in %zu %zu\n", sizeof(struct sockaddr_in), _Alignof(struct sockaddr_in)); + printf("sockaddr_in6 %zu %zu\n", sizeof(struct sockaddr_in6), _Alignof(struct sockaddr_in6)); + printf("ipv6_mreq %zu %zu\n", sizeof(struct ipv6_mreq), _Alignof(struct ipv6_mreq)); + + printf("sockaddr_storage %zu %zu\n", sizeof(struct sockaddr_storage), _Alignof(struct sockaddr_storage)); + printf("msghdr %zu %zu\n", sizeof(struct msghdr), _Alignof(struct msghdr)); + printf("cmsghdr %zu %zu\n", sizeof(struct cmsghdr), _Alignof(struct cmsghdr)); + printf("linger %zu %zu\n", sizeof(struct linger), _Alignof(struct linger)); + + printf("hostent %zu %zu\n", sizeof(struct hostent), _Alignof(struct hostent)); + printf("netent %zu %zu\n", sizeof(struct netent), _Alignof(struct netent)); + printf("protoent %zu %zu\n", sizeof(struct protoent), _Alignof(struct protoent)); + printf("servent %zu %zu\n", sizeof(struct servent), _Alignof(struct servent)); + printf("addrinfo %zu %zu\n", sizeof(struct addrinfo), _Alignof(struct addrinfo)); + + printf("pollfd %zu %zu\n", sizeof(struct pollfd), _Alignof(struct pollfd)); + + printf("passwd %zu %zu\n", sizeof(struct passwd), _Alignof(struct passwd)); + + printf("shmid_ds %zu %zu\n", sizeof(struct shmid_ds), _Alignof(struct shmid_ds)); + printf("ipc_perm %zu %zu\n", sizeof(struct ipc_perm), _Alignof(struct ipc_perm)); + printf("msqid_ds %zu %zu\n", sizeof(struct msqid_ds), _Alignof(struct msqid_ds)); + + printf("rlimit %zu %zu\n", sizeof(struct rlimit), _Alignof(struct rlimit)); + printf("rusage %zu %zu\n", sizeof(struct rusage), _Alignof(struct rusage)); + + printf("sockaddr_un %zu %zu\n", sizeof(struct sockaddr_un), _Alignof(struct sockaddr_un)); + + printf("utsname %zu %zu\n", sizeof(struct utsname), _Alignof(struct utsname)); + + printf("tms %zu %zu\n", sizeof(struct tms), _Alignof(struct tms)); + + printf("sigaction %zu %zu\n", sizeof(struct sigaction), _Alignof(struct sigaction)); + printf("stack_t %zu %zu\n", sizeof(stack_t), _Alignof(stack_t)); + printf("siginfo_t %zu %zu\n", sizeof(siginfo_t), _Alignof(siginfo_t)); + + printf("fd_set %zu %zu\n", sizeof(fd_set), _Alignof(fd_set)); + + printf("iovec %zu %zu\n", sizeof(struct iovec), _Alignof(struct iovec)); + + printf("semid_ds %zu %zu\n", sizeof(struct semid_ds), _Alignof(struct semid_ds)); + printf("sembuf %zu %zu\n", sizeof(struct sembuf), _Alignof(struct sembuf)); + + printf("itimerval %zu %zu\n", sizeof(struct itimerval), _Alignof(struct itimerval)); + + printf("utimbuf %zu %zu\n", sizeof(struct utimbuf), _Alignof(struct utimbuf)); + + printf("wordexp_t %zu %zu\n", sizeof(wordexp_t), _Alignof(wordexp_t)); + + printf("time_t %zu %zu\n", sizeof(time_t), _Alignof(time_t)); + printf("timespec %zu %zu\n", sizeof(struct timespec), _Alignof(struct timespec)); + printf("clock_t %zu %zu\n", sizeof(clock_t), _Alignof(clock_t)); + + return 0; +} diff --git a/tests/core/sys/posix/structs/structs.odin b/tests/core/sys/posix/structs/structs.odin new file mode 100644 index 000000000..2663d1e30 --- /dev/null +++ b/tests/core/sys/posix/structs/structs.odin @@ -0,0 +1,74 @@ +package main + +import "core:fmt" +import "core:sys/posix" + +main :: proc() { + fmt.println("dirent", size_of(posix.dirent), align_of(posix.dirent)) + fmt.println("flock", size_of(posix.flock), align_of(posix.flock)) + fmt.println("glob_t", size_of(posix.glob_t), align_of(posix.glob_t)) + fmt.println("group", size_of(posix.group), align_of(posix.group)) + fmt.println("lconv", size_of(posix.lconv), align_of(posix.lconv)) + + fmt.println("pthread_t", size_of(posix.pthread_t), align_of(posix.pthread_t)) + fmt.println("pthread_attr_t", size_of(posix.pthread_attr_t), align_of(posix.pthread_attr_t)) + fmt.println("pthread_key_t", size_of(posix.pthread_key_t), align_of(posix.pthread_key_t)) + + fmt.println("sched_param", size_of(posix.sched_param), align_of(posix.sched_param)) + + fmt.println("termios", size_of(posix.termios), align_of(posix.termios)) + + fmt.println("in_addr", size_of(posix.in_addr), align_of(posix.in_addr)) + fmt.println("in6_addr", size_of(posix.in6_addr), align_of(posix.in6_addr)) + fmt.println("sockaddr_in", size_of(posix.sockaddr_in), align_of(posix.sockaddr_in)) + fmt.println("sockaddr_in6", size_of(posix.sockaddr_in6), align_of(posix.sockaddr_in6)) + fmt.println("ipv6_mreq", size_of(posix.ipv6_mreq), align_of(posix.ipv6_mreq)) + + fmt.println("sockaddr_storage", size_of(posix.sockaddr_storage), align_of(posix.sockaddr_storage)) + fmt.println("msghdr", size_of(posix.msghdr), align_of(posix.msghdr)) + fmt.println("cmsghdr", size_of(posix.cmsghdr), align_of(posix.cmsghdr)) + fmt.println("linger", size_of(posix.linger), align_of(posix.linger)) + + fmt.println("hostent", size_of(posix.hostent), align_of(posix.hostent)) + fmt.println("netent", size_of(posix.netent), align_of(posix.netent)) + fmt.println("protoent", size_of(posix.protoent), align_of(posix.protoent)) + fmt.println("servent", size_of(posix.servent), align_of(posix.servent)) + fmt.println("addrinfo", size_of(posix.addrinfo), align_of(posix.addrinfo)) + + fmt.println("pollfd", size_of(posix.pollfd), align_of(posix.pollfd)) + fmt.println("passwd", size_of(posix.passwd), align_of(posix.passwd)) + + fmt.println("shmid_ds", size_of(posix.shmid_ds), align_of(posix.shmid_ds)) + fmt.println("ipc_perm", size_of(posix.ipc_perm), align_of(posix.ipc_perm)) + fmt.println("msqid_ds", size_of(posix.msqid_ds), align_of(posix.msqid_ds)) + + fmt.println("rlimit", size_of(posix.rlimit), align_of(posix.rlimit)) + fmt.println("rusage", size_of(posix.rusage), align_of(posix.rusage)) + + fmt.println("sockaddr_un", size_of(posix.sockaddr_un), align_of(posix.sockaddr_un)) + + fmt.println("utsname", size_of(posix.utsname), align_of(posix.utsname)) + + fmt.println("tms", size_of(posix.tms), align_of(posix.tms)) + + fmt.println("sigaction", size_of(posix.sigaction_t), align_of(posix.sigaction_t)) + fmt.println("stack_t", size_of(posix.stack_t), align_of(posix.stack_t)) + fmt.println("siginfo_t", size_of(posix.siginfo_t), align_of(posix.siginfo_t)) + + fmt.println("fd_set", size_of(posix.fd_set), align_of(posix.fd_set)) + + fmt.println("iovec", size_of(posix.iovec), align_of(posix.iovec)) + + fmt.println("semid_ds", size_of(posix.semid_ds), align_of(posix.semid_ds)) + fmt.println("sembuf", size_of(posix.sembuf), align_of(posix.sembuf)) + + fmt.println("itimerval", size_of(posix.itimerval), align_of(posix.itimerval)) + + fmt.println("utimbuf", size_of(posix.utimbuf), align_of(posix.utimbuf)) + + fmt.println("wordexp_t", size_of(posix.wordexp_t), align_of(posix.wordexp_t)) + + fmt.println("time_t", size_of(posix.time_t), align_of(posix.time_t)) + fmt.println("timespec", size_of(posix.timespec), align_of(posix.timespec)) + fmt.println("clock_t", size_of(posix.clock_t), align_of(posix.clock_t)) +} From 9f80191f60b52236e86f884880a9e21dc2e8397f Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:26:53 +0200 Subject: [PATCH 02/31] os2: add default make_directory permissions --- core/os/os2/path.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 3bf422ccb..254950d68 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -13,13 +13,13 @@ is_path_separator :: proc(c: byte) -> bool { mkdir :: make_directory -make_directory :: proc(name: string, perm: int) -> Error { +make_directory :: proc(name: string, perm: int = 0o755) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all -make_directory_all :: proc(path: string, perm: int) -> Error { +make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { return _mkdir_all(path, perm) } From 07b9f7d280771e7080f7d1a6257ee7988b5d2218 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:27:17 +0200 Subject: [PATCH 03/31] os2: fix read_entire_file bad logic --- core/os/os2/file_util.odin | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index b982afb3e..97e6090a3 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -119,23 +119,20 @@ read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) - @(require_results) read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (data: []byte, err: Error) { size: int - has_size := true + has_size := false if size64, serr := file_size(f); serr == nil { - if i64(int(size64)) != size64 { + if i64(int(size64)) == size64 { + has_size = true size = int(size64) } - } else if serr == .No_Size { - has_size = false - } else { + } else if serr != .No_Size { return } - size += 1 // for EOF - // TODO(bill): Is this correct logic? if has_size { total: int data = make([]byte, size, allocator) or_return - for { + for total < len(data) { n: int n, err = read(f, data[total:]) total += n @@ -144,15 +141,16 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d err = nil } data = data[:total] - return + break } } + return } else { buffer: [1024]u8 out_buffer := make([dynamic]u8, 0, 0, allocator) total := 0 for { - n: int = --- + n: int n, err = read(f, buffer[:]) total += n append_elems(&out_buffer, ..buffer[:total]) From ff0ca0bd5388450b357549e33a81489cd2b75158 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:27:55 +0200 Subject: [PATCH 04/31] os2: make config directory on darwin also ~/.config, it is the proper place --- core/os/os2/user.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index ca099f7ae..af0bc5da4 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -42,7 +42,7 @@ user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Erro case .Darwin: dir = get_env("HOME", allocator) if dir != "" { - dir = concatenate({dir, "/Library/Application Support"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) From a4d459f6517d23e6dc2129112e99a14ef158a45e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:31:30 +0200 Subject: [PATCH 05/31] os2: initial implementation for Darwin&BSDs, process API is only thing incomplete --- core/os/os2/allocators.odin | 1 - core/os/os2/dir_posix.odin | 71 ++++ core/os/os2/env_posix.odin | 78 +++++ core/os/os2/errors_posix.odin | 30 ++ core/os/os2/file_posix.odin | 446 ++++++++++++++++++++++++++ core/os/os2/heap_posix.odin | 97 ++++++ core/os/os2/internal_util.odin | 2 +- core/os/os2/path_posix.odin | 127 ++++++++ core/os/os2/pipe_posix.odin | 37 +++ core/os/os2/process.odin | 22 +- core/os/os2/process_posix.odin | 69 ++++ core/os/os2/process_posix_darwin.odin | 251 +++++++++++++++ core/os/os2/process_posix_other.odin | 15 + core/os/os2/stat.odin | 4 +- core/os/os2/stat_linux.odin | 2 + core/os/os2/stat_posix.odin | 113 +++++++ core/os/os2/stat_windows.odin | 8 + core/os/os2/temp_file.odin | 2 +- core/os/os2/temp_file_posix.odin | 20 ++ core/sys/darwin/proc.odin | 142 ++++++++ core/sys/posix/unistd.odin | 9 +- core/sys/unix/sysctl_darwin.odin | 4 +- core/sys/unix/sysctl_freebsd.odin | 2 + 23 files changed, 1535 insertions(+), 17 deletions(-) create mode 100644 core/os/os2/dir_posix.odin create mode 100644 core/os/os2/env_posix.odin create mode 100644 core/os/os2/errors_posix.odin create mode 100644 core/os/os2/file_posix.odin create mode 100644 core/os/os2/heap_posix.odin create mode 100644 core/os/os2/path_posix.odin create mode 100644 core/os/os2/pipe_posix.odin create mode 100644 core/os/os2/process_posix.odin create mode 100644 core/os/os2/process_posix_darwin.odin create mode 100644 core/os/os2/process_posix_other.odin create mode 100644 core/os/os2/stat_posix.odin create mode 100644 core/os/os2/temp_file_posix.odin create mode 100644 core/sys/darwin/proc.odin diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index ef73809b1..0ab3adfb2 100644 --- a/core/os/os2/allocators.odin +++ b/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 return tmp, loc } - diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin new file mode 100644 index 000000000..69cf7930f --- /dev/null +++ b/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 +} diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin new file mode 100644 index 000000000..d7dc6d55a --- /dev/null +++ b/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 +} + + diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin new file mode 100644 index 000000000..7143557a6 --- /dev/null +++ b/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) + } +} diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin new file mode 100644 index 000000000..b77824843 --- /dev/null +++ b/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, ×) != .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), ×) != .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 + } +} diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin new file mode 100644 index 000000000..eb543d383 --- /dev/null +++ b/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 +} diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index f7a38f3f1..ed65ab7e0 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -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()) } diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin new file mode 100644 index 000000000..1e59afdc1 --- /dev/null +++ b/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 +} diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin new file mode 100644 index 000000000..62d6fc9fb --- /dev/null +++ b/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 +} + diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 3f3e64668..f7a542276 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -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) } /* diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin new file mode 100644 index 000000000..8a27efce4 --- /dev/null +++ b/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 +} diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin new file mode 100644 index 000000000..f5816d693 --- /dev/null +++ b/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 +} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin new file mode 100644 index 000000000..ce84b1012 --- /dev/null +++ b/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 +} diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index b3ca47be3..b53ebb3ab 100644 --- a/core/os/os2/stat.odin +++ b/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) { - 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) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 6ccac1be0..2355a09f0 100644 --- a/core/os/os2/stat_linux.odin +++ b/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 _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 diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin new file mode 100644 index 000000000..3ffdefc6a --- /dev/null +++ b/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 +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5e66507be..d0b7c45a7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -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 diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 467775e89..5ca4e1453 100644 --- a/core/os/os2/temp_file.odin +++ b/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`. // 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) diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin new file mode 100644 index 000000000..67ec4d3e8 --- /dev/null +++ b/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) +} diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin new file mode 100644 index 000000000..a49383670 --- /dev/null +++ b/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 diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index 8e08860b4..6ed9e5d11 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -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)) + } } } diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 14d3c113a..92222bdfe 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/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_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 \ No newline at end of file +CTL_USER :: 8 diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 35c5db02c..8ca40ef1b 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -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 From ffff3c3c88b0cab0c4c02de61fb3ebd8f8ec3c74 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:36:40 +0200 Subject: [PATCH 06/31] posix: fix foreign import of dl for free/netbsd --- core/sys/posix/dlfcn.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin index 0ee666e24..0ddc41337 100644 --- a/core/sys/posix/dlfcn.odin +++ b/core/sys/posix/dlfcn.odin @@ -4,6 +4,8 @@ import "core:c" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + foreign import lib "system:dl" } else { foreign import lib "system:c" } From 379cd6fe66311e6bd619fa6ce6a801a0944e2e97 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 21:59:59 +0200 Subject: [PATCH 07/31] os2: cleanup --- core/os/os2/dir_windows.odin | 1 + core/os/os2/env_windows.odin | 3 +-- core/os/os2/stat_posix.odin | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 84f320095..b62c2528a 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -57,6 +57,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info return } + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() for !it.impl.no_more_files { diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 870b5a731..44485f4b2 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,6 +8,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } + assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) @@ -20,8 +21,6 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - TEMP_ALLOCATOR_GUARD() - b := make([]u16, n+1, temp_allocator()) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 3ffdefc6a..359920b3b 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -3,6 +3,7 @@ package os2 import "base:runtime" + import "core:path/filepath" import "core:sys/posix" import "core:time" @@ -63,7 +64,6 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: E 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 @@ -75,6 +75,11 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err cname := temp_cstring(name) or_return rcname := posix.realpath(cname) + if rcname == nil { + err = .Invalid_Path + return + } + defer posix.free(rcname) stat: posix.stat_t if posix.stat(rcname, &stat) != .OK { @@ -97,6 +102,11 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er cname := temp_cstring(name) or_return rcname := posix.realpath(cname) + if rcname == nil { + err = .Invalid_Path + return + } + defer posix.free(rcname) stat: posix.stat_t if posix.lstat(rcname, &stat) != .OK { From 7c9ca60ff03680921466aea20ef77de86802ea93 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 22:17:10 +0200 Subject: [PATCH 08/31] mem/virtual: support the BSDs --- core/mem/virtual/virtual_other.odin | 3 +++ .../{virtual_darwin.odin => virtual_posix.odin} | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) rename core/mem/virtual/{virtual_darwin.odin => virtual_posix.odin} (82%) diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index 96d9683c4..4fcb61b04 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,5 +1,8 @@ //+private //+build !darwin +//+build !freebsd +//+build !openbsd +//+build !netbsd //+build !linux //+build !windows package mem_virtual diff --git a/core/mem/virtual/virtual_darwin.odin b/core/mem/virtual/virtual_posix.odin similarity index 82% rename from core/mem/virtual/virtual_darwin.odin rename to core/mem/virtual/virtual_posix.odin index 9098866d0..fbe89abfa 100644 --- a/core/mem/virtual/virtual_darwin.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -1,12 +1,19 @@ -//+build darwin +//+build darwin, netbsd, freebsd, openbsd //+private package mem_virtual import "core:sys/posix" -MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ +// Define non-posix needed flags: +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ -MADV_FREE :: 5 /* pages unneeded, discard contents */ + MADV_FREE :: 5 /* pages unneeded, discard contents */ +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { + MAP_ANONYMOUS :: 0x1000 + + MADV_FREE :: 6 +} _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { flags := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS) @@ -43,15 +50,13 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK } - _platform_memory_init :: proc() { - DEFAULT_PAGE_SIZE = 4096 + DEFAULT_PAGE_SIZE = posix.PAGE_SIZE // is power of two assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } - _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read)) #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write)) From 9e2c5acb9dc629b922f08ea3f3f826cb259c36e8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 1 Aug 2024 23:22:29 +0200 Subject: [PATCH 09/31] os2: fix bit checks --- core/os/os2/process_posix_darwin.odin | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index f5816d693..9d30d13fd 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -54,7 +54,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator // 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 } { + if selection & {.PPid, .Priority, .Username } != {} { ppid, mnice, uid, ok := get_pidinfo(pid, selection) if !ok { if err == nil { @@ -111,7 +111,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } - args: if selection >= { .Command_Line, .Command_Args, .Environment } { + args: if selection & { .Command_Line, .Command_Args, .Environment } != {} { mib := []i32{ unix.CTL_KERN, unix.KERN_PROCARGS2, @@ -129,6 +129,11 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if err == nil { err = _get_platform_error() + + // Looks like EINVAL is returned here if you don't have permission. + if err == Platform_Error(posix.Errno.EINVAL) { + err = .Permission_Denied + } } break args } From 2a5ceff6672f05ae8781e8de15f593bbee1d4b3e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 00:09:36 +0200 Subject: [PATCH 10/31] posix: move doc --- core/sys/posix/README.md | 57 --------------------------------------- core/sys/posix/posix.odin | 44 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 57 deletions(-) delete mode 100644 core/sys/posix/README.md diff --git a/core/sys/posix/README.md b/core/sys/posix/README.md deleted file mode 100644 index 4afe50d6a..000000000 --- a/core/sys/posix/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# POSIX - -defines bindings for most posix APIs. - -If a header is added, all of it must be implemented. - -Each platform must define the exact same symbols, different values are allowed, even structs with different non-standard fields. - -APIs part of extensions may be left out completely if one target doesn't implement it. - -APIs with a direct replacement in `core` might not be implemented. - -Macros are emulated with force inlined functions. - -Struct fields defined by the posix standard (and thus portable) are documented with `[PSX]`. - - -ADD A TEST FOR SIGINFO, one thread signalling and retrieving the signal out of siginfo or something. -ADD A TEST FOR wait.h -ADD A TEST FOR pthread. -ADDD A test for stat.h. -ADD A TEST FOR setjmp.h. -HAIKU. - -Unimplemented POSIX headers: - -- aio.h -- complex.h | See `core:c/libc` and our own complex types -- cpio.h -- ctype.h | See `core:c/libc` for most of it -- ndbm.h | Never seen or heard of it -- fenv.h -- float.h -- fmtmsg.h -- ftw.h -- semaphore.h | See `core:sync` -- inttypes.h | See `core:c` -- iso646.h | Impossible -- math.h | See `core:c/libc` -- mqueue.h | Targets don't seem to have implemented it -- regex.h | See `core:regex` -- search.h | Not useful in Odin -- spawn.h | Use `fork`, `execve`, etc. -- stdarg.h | See `core:c/libc` -- stdint.h | See `core:c` -- stropts.h -- syslog.h -- pthread.h | Only the actual threads API is bound, see `core:sync` for synchronization primitives -- string.h | Most of this is not useful in Odin, only a select few symbols are bound -- tar.h -- tgmath.h -- trace.h -- wchar.h -- wctype.h - -TODO: -- time.h | Docs diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin index eb0109a3e..5cb4122a6 100644 --- a/core/sys/posix/posix.odin +++ b/core/sys/posix/posix.odin @@ -1,3 +1,47 @@ +/* +Bindings for most POSIX APIs. + +APIs that have been left out are due to not being useful, +being fully replaced (and better) by other Odin packages, +or when one of the targets hasn't implemented the API or option. + +The struct fields that are cross-platform are documented with `[PSX]`. +Accessing these fields on one target should be the same on others. +Other fields are implementation specific. + +Most macros have been reimplemented in Odin with inlined functions. + +Unimplemented headers: +- aio.h +- complex.h | See `core:c/libc` and our own complex types +- cpio.h +- ctype.h | See `core:c/libc` for most of it +- ndbm.h +- fenv.h +- float.h +- fmtmsg.h +- ftw.h +- semaphore.h | See `core:sync` +- inttypes.h | See `core:c` +- iso646.h | Impossible +- math.h | See `core:c/libc` +- mqueue.h | Targets don't seem to have implemented it +- regex.h | See `core:regex` +- search.h | Not useful in Odin +- spawn.h | Use `fork`, `execve`, etc. +- stdarg.h | See `core:c/libc` +- stdint.h | See `core:c` +- stropts.h +- syslog.h +- pthread.h | Only the actual threads API is bound, see `core:sync` for synchronization primitives +- string.h | Most of this is not useful in Odin, only a select few symbols are bound +- tar.h +- tgmath.h +- trace.h +- wchar.h +- wctype.h + +*/ package posix import "base:intrinsics" From 726891588f67e76a8ab09601c16dfe4e61c0eb3e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 01:40:05 +0200 Subject: [PATCH 11/31] posix: more tests --- core/sys/posix/signal.odin | 6 +-- tests/core/sys/posix/posix.odin | 77 +++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin index 3bfd662f0..6b1817d4f 100644 --- a/core/sys/posix/signal.odin +++ b/core/sys/posix/signal.odin @@ -559,7 +559,7 @@ when ODIN_OS == .Darwin { }, si_pid: pid_t, /* [PSX] sending process ID */ si_uid: uid_t, /* [PSX] real user ID of sending process */ - si_status: c.int, /* [PSX] exit value of signal */ + si_status: c.int, /* [PSX] exit value or signal */ si_addr: rawptr, /* [PSX] address of faulting instruction */ si_value: sigval, /* [PSX] signal value */ si_band: c.long, /* [PSX] band event for SIGPOLL */ @@ -709,7 +709,7 @@ when ODIN_OS == .Darwin { }, si_pid: pid_t, /* [PSX] sending process ID */ si_uid: uid_t, /* [PSX] real user ID of sending process */ - si_status: c.int, /* [PSX] exit value of signal */ + si_status: c.int, /* [PSX] exit value or signal */ si_addr: rawptr, /* [PSX] address of faulting instruction */ si_value: sigval, /* [PSX] signal value */ using _reason: struct #raw_union { @@ -889,7 +889,7 @@ when ODIN_OS == .Darwin { using _child: struct { si_pid: pid_t, /* [PSX] sending process ID */ si_uid: uid_t, /* [PSX] real user ID of sending process */ - si_status: c.int, /* [PSX] exit value of signal */ + si_status: c.int, /* [PSX] exit value or signal */ _utime: clock_t, _stime: clock_t, }, diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin index 1942bfbda..03a2f932c 100644 --- a/tests/core/sys/posix/posix.odin +++ b/tests/core/sys/posix/posix.odin @@ -1,11 +1,15 @@ //+build darwin, freebsd, openbsd, netbsd package tests_core_posix +import "base:runtime" + +import "core:log" +import "core:path/filepath" +import "core:strings" +import "core:sync" import "core:sys/posix" import "core:testing" -import "core:log" -import "core:strings" -import "core:path/filepath" +import "core:time" @(test) test_arpa_inet :: proc(t: ^testing.T) { @@ -196,6 +200,14 @@ test_stat :: proc(t: ^testing.T) { testing.expect_value(t, posix.S_IRWXG, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXG)) testing.expect_value(t, posix.S_IRWXO, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXO)) testing.expect_value(t, posix._S_IFMT, transmute(posix.mode_t)posix._mode_t(posix.__S_IFMT)) + + stat: posix.stat_t + testing.expect_value(t, posix.stat(#file, &stat), posix.result.OK) + testing.expect(t, posix.S_ISREG(stat.st_mode)) + testing.expect_value(t, stat.st_mode, posix.mode_t{.IROTH, .IRGRP, .IRUSR, .IWUSR, .IFREG}) + + CONTENT := #load(#file) + testing.expect_value(t, stat.st_size, posix.off_t(len(CONTENT))) } @(test) @@ -209,3 +221,62 @@ test_termios :: proc(t: ^testing.T) { testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._VTDLY), posix.VTDLY) testing.expect_value(t, transmute(posix.COutput_Flags)posix.tcflag_t(posix._FFDLY), posix.FFDLY) } + +@(test) +test_signal :: proc(t: ^testing.T) { + @static tt: ^testing.T + tt = t + + @static ctx: runtime.Context + ctx = context + + act: posix.sigaction_t + act.sa_flags = {.SIGINFO, .RESETHAND} + act.sa_sigaction = handler + testing.expect_value(t, posix.sigaction(.SIGCHLD, &act, nil), posix.result.OK) + + handler :: proc "c" (sig: posix.Signal, info: ^posix.siginfo_t, address: rawptr) { + context = ctx + testing.expect_value(tt, sig, posix.Signal.SIGCHLD) + testing.expect_value(tt, info.si_signo, posix.Signal.SIGCHLD) + testing.expect_value(tt, info.si_status, 69) + testing.expect_value(tt, info.si_code.chld, posix.CLD_Code.EXITED) + } + + switch pid := posix.fork(); pid { + case -1: + log.errorf("fork() failure: %v", posix.strerror()) + case 0: + posix.exit(69) + case: + status: i32 + posix.waitpid(pid, &status, {}) + testing.expect(t, posix.WIFEXITED(status)) + testing.expect(t, posix.WEXITSTATUS(status) == 69) + } +} + +@(test) +test_pthreads :: proc(t: ^testing.T) { + testing.set_fail_timeout(t, time.Second) + + NTHREADS :: 3 + thread_ids: [NTHREADS]posix.pthread_t + + @static counter: int + + for &tid in thread_ids { + posix.pthread_create(&tid, nil, thread_function, nil) + } + + for tid in thread_ids { + posix.pthread_join(tid, nil) + } + + testing.expect_value(t, counter, NTHREADS) + + thread_function :: proc "c" (_: rawptr) -> rawptr { + sync.atomic_add(&counter, 1) + return nil + } +} From 4a61bac1001c682738cbead210fc7d948029539e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 02:15:52 +0200 Subject: [PATCH 12/31] posix: fix test on netbsd --- core/sys/posix/signal.odin | 2 +- core/sys/posix/sys_wait.odin | 4 ++-- tests/core/sys/posix/posix.odin | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin index 6b1817d4f..c35494185 100644 --- a/core/sys/posix/signal.odin +++ b/core/sys/posix/signal.odin @@ -408,7 +408,7 @@ SA_Flags_Bits :: enum c.int { RESTART = log2(SA_RESTART), // Cause extra information to be passed to signal handlers at the time of receipt of a signal. SIGINFO = log2(SA_SIGINFO), - // Cause implemention not to create zombie processes or status information on child termination. + // Cause implementation not to create zombie processes or status information on child termination. NOCLDWAIT = log2(SA_NOCLDWAIT), // Cause signal not to be automatically blocked on entry to signal handler. SA_NODEFER = log2(SA_NODEFER), diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin index 6d0336e80..8421c8bfd 100644 --- a/core/sys/posix/sys_wait.odin +++ b/core/sys/posix/sys_wait.odin @@ -290,7 +290,7 @@ when ODIN_OS == .Darwin { @(private) _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { - return (x >> 8) & 0x000000ff + return c.int((c.uint(x) >> 8) & 0xff) } @(private) @@ -310,7 +310,7 @@ when ODIN_OS == .Darwin { @(private) _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { - return Signal((x >> 8) & 0xff) + return Signal(c.int((c.uint(x) >> 8) & 0xff)) } @(private) diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin index 03a2f932c..bfe4f7875 100644 --- a/tests/core/sys/posix/posix.odin +++ b/tests/core/sys/posix/posix.odin @@ -249,10 +249,21 @@ test_signal :: proc(t: ^testing.T) { case 0: posix.exit(69) case: - status: i32 - posix.waitpid(pid, &status, {}) - testing.expect(t, posix.WIFEXITED(status)) - testing.expect(t, posix.WEXITSTATUS(status) == 69) + for { + status: i32 + res := posix.waitpid(pid, &status, {}) + if res == -1 { + if !testing.expect_value(t, posix.errno(), posix.Errno.EINTR) { + break + } + } + + if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { + testing.expect(t, posix.WIFEXITED(status)) + testing.expect(t, posix.WEXITSTATUS(status) == 69) + break + } + } } } From 72220a855f37c54e1f1530289ebc454f378ebfa9 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 03:37:47 +0200 Subject: [PATCH 13/31] posix: fix write error check --- core/os/os2/file_posix.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index b77824843..a956932cf 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -374,7 +374,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, 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 { + if _n := i64(posix.write(fd, raw_data(p), to_write)); _n <= 0 { err = .Unknown return } else { @@ -395,7 +395,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, 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 { + if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); _n <= 0 { err = .Unknown return } else { From 5115aee23f5518586cb0e8f21474a03f5e503b11 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 03:40:19 +0200 Subject: [PATCH 14/31] posix: actually fix netbsd test --- tests/core/sys/posix/posix.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin index bfe4f7875..bb5c7a65b 100644 --- a/tests/core/sys/posix/posix.odin +++ b/tests/core/sys/posix/posix.odin @@ -256,6 +256,7 @@ test_signal :: proc(t: ^testing.T) { if !testing.expect_value(t, posix.errno(), posix.Errno.EINTR) { break } + continue } if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { From 406e60f5ddebb39c94a4447181dd211a1ec1b791 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 17:14:51 +0200 Subject: [PATCH 15/31] posix: revert change to os that was done for testing --- core/os/dir_unix.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index b0e8e0732..b472e89b7 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -1,7 +1,6 @@ //+build darwin, linux, netbsd, freebsd, openbsd package os -import "base:runtime" import "core:strings" @(require_results) From e05fddc00121f1e244fa141c9dc35d4dcee69b98 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:42:07 +0200 Subject: [PATCH 16/31] posix: fix file type checks in stat --- core/sys/posix/sys_stat.odin | 174 +++++++---------------------------- 1 file changed, 33 insertions(+), 141 deletions(-) diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin index 016f1b1e6..dd66d7d14 100644 --- a/core/sys/posix/sys_stat.odin +++ b/core/sys/posix/sys_stat.odin @@ -154,13 +154,10 @@ S_IRWXO :: mode_t{ .IROTH, .IWOTH, .IXOTH } Mode_Bits :: enum c.int { // File type: - IFBLK = log2(S_IFBLK), /* Block special */ - IFCHR = log2(S_IFCHR), /* Character special */ - IFIFO = log2(S_IFIFO), /* FIFO special */ - IFREG = log2(S_IFREG), /* Regular */ - IFDIR = log2(S_IFDIR), /* Directory */ - IFLNK = log2(S_IFLNK), /* Symbolic link */ - IFSOCK = log2(S_IFSOCK), /* Socket */ + IFCHR = log2(_S_IFCHR), /* Character special */ + IFIFO = log2(_S_IFIFO), /* FIFO special */ + IFREG = log2(_S_IFREG), /* Regular */ + IFDIR = log2(_S_IFDIR), /* Directory */ // Permissions: @@ -183,64 +180,53 @@ Mode_Bits :: enum c.int { mode_t :: bit_set[Mode_Bits; _mode_t] #assert(size_of(mode_t) == size_of(_mode_t)) -// NOTE: making these `.IFREG in m` would probably be fine too, -// but implementations make this an exclusive check so lets stick to it. +S_IFMT :: mode_t{ .IFCHR, .IFREG, .IFDIR, .IFIFO } +S_IFSOCK :: mode_t{ .IFREG, .IFDIR } +S_IFLNK :: mode_t{ .IFREG, .IFCHR } +S_IFBLK :: mode_t{ .IFDIR, .IFCHR } +S_IFIFO :: mode_t{ .IFIFO } +S_IFCHR :: mode_t{ .IFCHR } +S_IFDIR :: mode_t{ .IFDIR } +S_IFREG :: mode_t{ .IFREG } -_S_IFMT :: mode_t{ .IFBLK, .IFCHR, .IFIFO, .IFREG, .IFDIR, .IFLNK, .IFSOCK } +#assert(_S_IFMT == _S_IFCHR|_S_IFREG|_S_IFDIR|_S_IFIFO) +#assert(_S_IFSOCK == _S_IFREG|_S_IFDIR) +#assert(_S_IFLNK == _S_IFREG|_S_IFCHR) +#assert(_S_IFBLK == _S_IFDIR|_S_IFCHR) // Test for a block special file. S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFBLK } + return (m & S_IFMT) == S_IFBLK } // Test for a character special file. S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFCHR } + return (m & S_IFMT) == S_IFCHR } // Test for a pipe or FIFO special file. S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFIFO } + return (m & S_IFMT) == S_IFIFO } // Test for a regular file. S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFREG } + return (m & S_IFMT) == S_IFREG } // Test for a directory. S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFDIR } + return (m & S_IFMT) == S_IFDIR } // Test for a symbolic link. S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFLNK } + return (m & S_IFMT) == S_IFLNK } // Test for a socket. S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { - return (m & _S_IFMT) == { .IFSOCK } -} - -// Test for a message queue. -S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { - return _S_TYPEISMQ(m) -} - -// Test for a semaphore. -S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return _S_TYPEISSEM(m) -} - -// Test for a shared memory object. -S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return _S_TYPEISSHM(m) -} - -// Test macro for a typed memory object. -S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return _S_TYPEISTMO(m) + return (m & S_IFMT) == S_IFSOCK } _S_IRWXU :: 0o000700 @@ -262,6 +248,16 @@ _S_ISUID :: 0o004000 _S_ISGID :: 0o002000 _S_ISVTX :: 0o001000 +_S_IFBLK :: 0o060000 +_S_IFCHR :: 0o020000 +_S_IFIFO :: 0o010000 +_S_IFREG :: 0o100000 +_S_IFDIR :: 0o040000 +_S_IFLNK :: 0o120000 +_S_IFSOCK :: 0o140000 + +_S_IFMT :: 0o170000 + when ODIN_OS == .NetBSD { @(private) LSTAT :: "__stat50" @(private) LFSTAT :: "__fstat50" @@ -304,32 +300,6 @@ when ODIN_OS == .Darwin { st_qspare: [2]c.int64_t, /* RESERVED */ } - S_IFBLK :: 0o060000 - S_IFCHR :: 0o020000 - S_IFIFO :: 0o010000 - S_IFREG :: 0o100000 - S_IFDIR :: 0o040000 - S_IFLNK :: 0o120000 - S_IFSOCK :: 0o140000 - - __S_IFMT :: 0o170000 - - _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - UTIME_NOW :: -1 UTIME_OMIT :: -2 @@ -391,32 +361,6 @@ when ODIN_OS == .Darwin { } } - S_IFBLK :: 0o060000 - S_IFCHR :: 0o020000 - S_IFIFO :: 0o010000 - S_IFREG :: 0o100000 - S_IFDIR :: 0o040000 - S_IFLNK :: 0o120000 - S_IFSOCK :: 0o140000 - - __S_IFMT :: 0o170000 - - _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - UTIME_NOW :: -1 UTIME_OMIT :: -2 @@ -449,32 +393,6 @@ when ODIN_OS == .Darwin { st_spare: [2]c.uint32_t, } - S_IFBLK :: 0o060000 - S_IFCHR :: 0o020000 - S_IFIFO :: 0o010000 - S_IFREG :: 0o100000 - S_IFDIR :: 0o040000 - S_IFLNK :: 0o120000 - S_IFSOCK :: 0o140000 - - __S_IFMT :: 0o170000 - - _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - UTIME_NOW :: (1 << 30) - 1 UTIME_OMIT :: (1 << 30) - 2 @@ -506,32 +424,6 @@ when ODIN_OS == .Darwin { st_birthtimespec: timespec, } - S_IFBLK :: 0o060000 - S_IFCHR :: 0o020000 - S_IFIFO :: 0o010000 - S_IFREG :: 0o100000 - S_IFDIR :: 0o040000 - S_IFLNK :: 0o120000 - S_IFSOCK :: 0o140000 - - __S_IFMT :: 0o170000 - - _S_TYPEISMQ :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSEM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISSHM :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - - _S_TYPEISTMO :: #force_inline proc "contextless" (m: mode_t) -> bool { - return false - } - UTIME_NOW :: -2 UTIME_OMIT :: -1 From ea5783c2ac656bc6bc911704fa1f2e7beb1a160c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:42:53 +0200 Subject: [PATCH 17/31] os2: fixes after rebasing --- core/os/os2/file_posix.odin | 27 +++++++++++++++++---------- core/os/os2/stat_posix.odin | 4 ++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index a956932cf..123e288ef 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -26,9 +26,9 @@ File_Impl :: struct { @(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") + 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) { @@ -63,27 +63,34 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err return } - return _new_file(uintptr(fd), name), nil + return _new_file(uintptr(fd), name) } -_new_file :: proc(handle: uintptr, name: string) -> ^File { - if name == "" || handle == ~uintptr(0) { - return nil +_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } else if handle == ~uintptr(0) { + err = .Invalid_File + return } TEMP_ALLOCATOR_GUARD() cname := temp_cstring(name) crname := posix.realpath(cname, nil) - assert(crname != nil) + if crname == nil { + err = _get_platform_error() + return + } rname := string(crname) - f := __new_file(posix.FD(handle)) + f = __new_file(posix.FD(handle)) impl := (^File_Impl)(f.impl) impl.name = rname impl.cname = crname - return f + return f, nil } __new_file :: proc(handle: posix.FD) -> ^File { diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 359920b3b..caf25a277 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -12,10 +12,10 @@ 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.inode = u128(stat.st_ino) fi.size = i64(stat.st_size) - fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix._S_IFMT)) + fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT)) fi.type = .Undetermined switch { From 00eb702c4a5e3ed9c77889b8131f9bd54e3179fb Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:43:19 +0200 Subject: [PATCH 18/31] os2: implement the iterator based read directory --- core/os/os2/dir_posix.odin | 113 ++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 69cf7930f..4bf2156f2 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -2,70 +2,77 @@ //+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) { +Read_Directory_Iterator_Impl :: struct { + dir: posix.DIR, + idx: int, + + // NOTE: could there be paths bigger than this, maybe, probably, but why does it exist then? + fullpath: [posix.PATH_MAX]byte, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + index = it.impl.idx + it.impl.idx += 1 + + 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. + return + } + + cname := cstring(raw_data(entry.d_name[:])) + + // NOTE: these shouldn't be given back, but how? + // if cname == "." || cname == ".." { + // continue + // } + + 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. + return + } + + fimpl := (^File_Impl)(it.f.impl) + + n := copy(it.impl.fullpath[:], fimpl.name) + n += copy(it.impl.fullpath[n:], "/") + n += copy(it.impl.fullpath[n:], string(cname)) + + fi = internal_stat(stat, string(it.impl.fullpath[:n])) + ok = true + return +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { if f == nil || f.impl == nil { err = .Invalid_File return } + iter.f = f + iter.impl.idx = 0 - n := n - if n == 0 { - return - } + impl := (^File_Impl)(f.impl) - impl := (^File_Impl)(f) - - dir := posix.fdopendir(impl.fd) - if dir == nil { + iter.impl.dir = posix.fdopendir(impl.fd) + if iter.impl.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 } + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil || it.impl.dir == nil { + return + } + + posix.closedir(it.impl.dir) +} From 7474db6a3487462e36b9c0e8395b4d1faefc9a91 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:43:49 +0200 Subject: [PATCH 19/31] os2: fix lstat logic --- core/os/os2/path_posix.odin | 3 +-- core/os/os2/stat_posix.odin | 34 ++++++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 1e59afdc1..066ca1ddf 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -96,8 +96,7 @@ _remove_all :: proc(path: string) -> Error { } _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) buf: [dynamic]byte buf.allocator = temp_allocator() diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index caf25a277..2830a1f22 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -97,24 +97,38 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er return } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) or_return + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) - rcname := posix.realpath(cname) - if rcname == nil { - err = .Invalid_Path - return + // NOTE: can't use realpath 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()) + assert(len(fullpath) > 0) + switch { + case fullpath[0] == '/': + // nothing. + case fullpath == ".": + 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, + "/", + fullpath, + }, temp_allocator()) or_return } - defer posix.free(rcname) stat: posix.stat_t - if posix.lstat(rcname, &stat) != .OK { + if posix.lstat(temp_cstring(fullpath), &stat) != .OK { err = _get_platform_error() return } - fullpath := clone_string(string(rcname), allocator) or_return + fullpath = clone_string(fullpath, allocator) or_return return internal_stat(stat, fullpath), nil } From bd808f9ec62de72d1f707805efe88cafe5e422b7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:52:39 +0200 Subject: [PATCH 20/31] os2: fix wiping results with temp allocator guard --- core/os/os2/dir_windows.odin | 3 +-- core/os/os2/env_posix.odin | 3 +-- core/os/os2/env_windows.odin | 3 +-- core/os/os2/file_linux.odin | 2 +- core/os/os2/file_posix.odin | 3 +-- core/os/os2/file_windows.odin | 4 ++-- core/os/os2/path_windows.odin | 2 +- core/os/os2/process_posix_darwin.odin | 3 +-- core/os/os2/process_windows.odin | 14 +++++++------- core/os/os2/stat_linux.odin | 6 ++---- core/os/os2/stat_posix.odin | 3 +-- core/os/os2/stat_windows.odin | 11 +++-------- core/os/os2/temp_file.odin | 2 +- core/os/os2/temp_file_linux.odin | 2 +- core/os/os2/temp_file_windows.odin | 2 +- 15 files changed, 25 insertions(+), 38 deletions(-) diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index b62c2528a..1b9675064 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -57,7 +57,6 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info return } - assert(!is_temp(allocator)) TEMP_ALLOCATOR_GUARD() for !it.impl.no_more_files { @@ -139,4 +138,4 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { } file_info_delete(it.impl.prev_fi, file_allocator()) win32.FindClose(it.impl.find_handle) -} \ No newline at end of file +} diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index d7dc6d55a..eac7a1fea 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -12,8 +12,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) ckey := strings.clone_to_cstring(key, temp_allocator()) cval := posix.getenv(ckey) diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 44485f4b2..0a2bab694 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,8 +8,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) n := win32.GetEnvironmentVariableW(wkey, nil, 0) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 6b981cca1..a26ccb09b 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -309,7 +309,7 @@ _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() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) name_cstr := temp_cstring(name) or_return return _read_link_cstr(name_cstr, allocator) } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 123e288ef..efecf6e49 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -189,8 +189,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cname := temp_cstring(name) buf: [dynamic]byte diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 74067464b..1f5d268f3 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -609,7 +609,7 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) buf := make([]u16, n+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) @@ -635,7 +635,7 @@ _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_GUARD(ignore=is_temp(allocator)) p := _fix_long_path(name, temp_allocator()) or_return handle := _open_sym_link(p) or_return diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 4aa695ee2..e8c34c750 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -108,7 +108,7 @@ _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_GUARD(ignore=is_temp(allocator)) sz_utf16 := win32.GetCurrentDirectoryW(0, nil) dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 9d30d13fd..0c902996b 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -237,8 +237,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) buffer := make([]i32, ret, temp_allocator()) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 47fd62401..a43ab3495 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -158,7 +158,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -172,7 +172,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return @@ -181,7 +181,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return @@ -250,7 +250,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -265,7 +265,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return @@ -275,7 +275,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return @@ -539,7 +539,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_GUARD(ignore=is_temp(allocator)) token_handle: win32.HANDLE if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { err = _get_platform_error() diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 2355a09f0..7704aaf20 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -48,8 +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() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {}) @@ -61,8 +60,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() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 2830a1f22..eb63febe7 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -70,8 +70,7 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err return } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cname := temp_cstring(name) or_return rcname := posix.realpath(cname) diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index d0b7c45a7..46505bfb7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -45,8 +45,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path name = "." } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) p := win32_utf8_to_utf16(name, temp_allocator()) or_return @@ -66,7 +65,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt if len(name) == 0 { return {}, .Not_Exist } - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) wname := _fix_long_path(name, temp_allocator()) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA @@ -130,8 +129,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin return "", _get_platform_error() } - assert(!is_temp(allocator)) - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) @@ -149,9 +147,6 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { 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 diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 5ca4e1453..d76dbe318 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -47,7 +47,7 @@ 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() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(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 diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index d6f90fbaf..3c6da2e23 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,7 +4,7 @@ package os2 import "base:runtime" _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) tmpdir := get_env("TMPDIR", temp_allocator()) if tmpdir == "" { tmpdir = "/tmp" diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index d888eda52..f430d66ea 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -9,7 +9,7 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er if n == 0 { return "", nil } - TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) From de9abe1f7b6bb31fa1166393368f9ce51aadd01a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 21:55:45 +0200 Subject: [PATCH 21/31] os2: skip . and .. in read dir --- core/os/os2/dir_posix.odin | 56 +++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 4bf2156f2..6e01928e0 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -17,36 +17,36 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info index = it.impl.idx it.impl.idx += 1 - 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. + for { + 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. + return + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + 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. + return + } + + fimpl := (^File_Impl)(it.f.impl) + + n := copy(it.impl.fullpath[:], fimpl.name) + n += copy(it.impl.fullpath[n:], "/") + n += copy(it.impl.fullpath[n:], string(cname)) + + fi = internal_stat(stat, string(it.impl.fullpath[:n])) + ok = true return } - - cname := cstring(raw_data(entry.d_name[:])) - - // NOTE: these shouldn't be given back, but how? - // if cname == "." || cname == ".." { - // continue - // } - - 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. - return - } - - fimpl := (^File_Impl)(it.f.impl) - - n := copy(it.impl.fullpath[:], fimpl.name) - n += copy(it.impl.fullpath[n:], "/") - n += copy(it.impl.fullpath[n:], string(cname)) - - fi = internal_stat(stat, string(it.impl.fullpath[:n])) - ok = true - return } @(require_results) From f00f68ef6fcb961e548ac4b685245e7b9b5bd7de Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 2 Aug 2024 22:09:04 +0200 Subject: [PATCH 22/31] posix/os2: fix test and add back in removed temp guard --- core/os/os2/stat_windows.odin | 2 ++ tests/core/sys/posix/posix.odin | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 46505bfb7..5b59f4269 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -147,6 +147,8 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { return nil, _get_platform_error() } + 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 diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin index bb5c7a65b..380149c42 100644 --- a/tests/core/sys/posix/posix.odin +++ b/tests/core/sys/posix/posix.odin @@ -196,11 +196,6 @@ test_monetary :: proc(t: ^testing.T) { @(test) test_stat :: proc(t: ^testing.T) { - testing.expect_value(t, posix.S_IRWXU, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXU)) - testing.expect_value(t, posix.S_IRWXG, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXG)) - testing.expect_value(t, posix.S_IRWXO, transmute(posix.mode_t)posix._mode_t(posix._S_IRWXO)) - testing.expect_value(t, posix._S_IFMT, transmute(posix.mode_t)posix._mode_t(posix.__S_IFMT)) - stat: posix.stat_t testing.expect_value(t, posix.stat(#file, &stat), posix.result.OK) testing.expect(t, posix.S_ISREG(stat.st_mode)) From 939ba4cf0882291b8b35241ef6fc266ae74e9727 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 3 Aug 2024 15:17:29 +0200 Subject: [PATCH 23/31] os2: fix leak and always close directory --- core/os/os2/internal_util.odin | 2 +- core/os/os2/path_posix.odin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index ed65ab7e0..52e7522d1 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -76,7 +76,7 @@ concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: st for s in strings { n += len(s) } - buf := make([]byte, n) or_return + buf := make([]byte, n, allocator) or_return n = 0 for s in strings { n += copy(buf[n:], s) diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 066ca1ddf..2fd289300 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -61,6 +61,7 @@ _remove_all :: proc(path: string) -> Error { if dir == nil { return _get_platform_error() } + defer posix.closedir(dir) for { posix.set_errno(.NONE) @@ -91,7 +92,6 @@ _remove_all :: proc(path: string) -> Error { if posix.rmdir(cpath) != .OK { return _get_platform_error() } - posix.closedir(dir) return nil } From 175f5b0bb19e276816405996bbe574a46a6d3b55 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 3 Aug 2024 15:36:32 +0200 Subject: [PATCH 24/31] os2: don't rely on PATH_MAX in posix read_directory implementation --- core/os/os2/dir_posix.odin | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 6e01928e0..2f537f64c 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -7,13 +7,13 @@ import "core:sys/posix" Read_Directory_Iterator_Impl :: struct { dir: posix.DIR, idx: int, - - // NOTE: could there be paths bigger than this, maybe, probably, but why does it exist then? - fullpath: [posix.PATH_MAX]byte, + fullpath: [dynamic]byte, } @(require_results) _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 @@ -29,6 +29,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info if cname == "." || cname == ".." { continue } + sname := string(cname) stat: posix.stat_t if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { @@ -37,13 +38,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info return } - fimpl := (^File_Impl)(it.f.impl) + n := len(fimpl.name)+1 + non_zero_resize(&it.impl.fullpath, n+len(sname)) + n += copy(it.impl.fullpath[n:], sname) - n := copy(it.impl.fullpath[:], fimpl.name) - n += copy(it.impl.fullpath[n:], "/") - n += copy(it.impl.fullpath[n:], string(cname)) - - fi = internal_stat(stat, string(it.impl.fullpath[:n])) + fi = internal_stat(stat, string(it.impl.fullpath[:])) ok = true return } @@ -55,10 +54,15 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera err = .Invalid_File return } + + impl := (^File_Impl)(f.impl) + iter.f = f iter.impl.idx = 0 - impl := (^File_Impl)(f.impl) + iter.impl.fullpath.allocator = file_allocator() + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") iter.impl.dir = posix.fdopendir(impl.fd) if iter.impl.dir == nil { @@ -75,4 +79,5 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { } posix.closedir(it.impl.dir) + delete(it.impl.fullpath) } From 67ea7bb65a891fe1bc1384199239d82af0032c7b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 3 Aug 2024 22:30:44 +0200 Subject: [PATCH 25/31] posix: address some freebsd feedback --- core/sys/posix/fcntl.odin | 6 +++--- core/sys/posix/sys_socket.odin | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index 436104613..e98086eb3 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -254,15 +254,15 @@ when ODIN_OS == .Darwin { O_NONBLOCK :: 0x0004 O_SYNC :: 0x0080 _O_RSYNC :: 0 - O_RSYNC :: O_Flags{} + O_RSYNC :: O_Flags{} // NOTE: not defined in headers O_EXEC :: 0x00040000 O_RDONLY :: 0 O_RDWR :: 0x0002 O_WRONLY :: 0x0001 - _O_SEARCH :: O_EXEC|O_DIRECTORY - O_SEARCH :: O_Flags{ .EXEC, .DIRECTORY } + _O_SEARCH :: O_EXEC + O_SEARCH :: O_Flags{ .EXEC } AT_FDCWD: FD: -100 diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin index e82101367..36c3c1467 100644 --- a/core/sys/posix/sys_socket.odin +++ b/core/sys/posix/sys_socket.odin @@ -437,10 +437,14 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS SO_SNDLOWAT :: 0x1003 SO_TYPE :: 0x1008 - when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + when ODIN_OS == .Darwin { SO_LINGER :: 0x1080 SO_RCVTIMEO :: 0x1006 SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .FreeBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 } else when ODIN_OS == .NetBSD { SO_LINGER :: 0x0080 SO_RCVTIMEO :: 0x100c From a73677d21aaa5bafdaf28bdce152217fcac367d0 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 4 Aug 2024 01:02:05 +0200 Subject: [PATCH 26/31] os2: nice != priority --- core/os/os2/process_posix_darwin.odin | 20 ++++++++++---------- core/sys/darwin/proc.odin | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 0c902996b..882170670 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -22,16 +22,16 @@ foreign lib { _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) { + get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: 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)) + info: darwin.proc_taskallinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info)) if ret > 0 { - assert(ret == size_of(pinfo)) - ppid = pinfo.pbi_ppid - nice = pinfo.pbi_nice - uid = pinfo.pbi_uid + assert(ret == size_of(info)) + ppid = info.pbsd.pbi_ppid + prio = info.ptinfo.pti_priority + uid = info.pbsd.pbi_uid ok = true return } @@ -55,7 +55,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator pidinfo: { if selection & {.PPid, .Priority, .Username } != {} { - ppid, mnice, uid, ok := get_pidinfo(pid, selection) + ppid, mprio, uid, ok := get_pidinfo(pid, selection) if !ok { if err == nil { err = _get_platform_error() @@ -68,8 +68,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.PPid} } - if nice, has_nice := mnice.?; has_nice && .Priority in selection { - info.priority = int(nice) + if prio, has_prio := mprio.?; has_prio && .Priority in selection { + info.priority = int(prio) info.fields += {.Priority} } diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin index a49383670..8f7eb1fcb 100644 --- a/core/sys/darwin/proc.odin +++ b/core/sys/darwin/proc.odin @@ -98,6 +98,32 @@ vinfo_stat :: struct { vst_qspare: [2]i64, } +proc_taskinfo :: struct { + pti_virtual_size: u64 `fmt:"M"`, + pti_resident_size: u64 `fmt:"M"`, + pti_total_user: u64, + pti_total_system: u64, + pti_threads_user: u64, + pti_threads_system: u64, + pti_policy: i32, + pti_faults: i32, + pti_pageins: i32, + pti_cow_faults: i32, + pti_messages_sent: i32, + pti_messages_received: i32, + pti_syscalls_mach: i32, + pti_syscalls_unix: i32, + pti_csw: i32, + pti_threadnum: i32, + pti_numrunning: i32, + pti_priority: i32, +} + +proc_taskallinfo :: struct { + pbsd: proc_bsdinfo, + ptinfo: proc_taskinfo, +} + fsid_t :: distinct [2]i32 PBI_Flag_Bits :: enum u32 { From b07d0b38b16799052b4d63f80709d0b913f06a87 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 4 Aug 2024 16:58:18 +0200 Subject: [PATCH 27/31] os2: improve absolute/full path handling for posix --- core/os/os2/file.odin | 6 +++- core/os/os2/file_posix.odin | 30 +++++++++--------- core/os/os2/file_posix_darwin.odin | 18 +++++++++++ core/os/os2/file_posix_freebsd.odin | 47 +++++++++++++++++++++++++++++ core/os/os2/file_posix_netbsd.odin | 18 +++++++++++ core/os/os2/file_posix_other.odin | 21 +++++++++++++ core/os/os2/stat_posix.odin | 17 ++++++----- core/os/os_freebsd.odin | 2 +- core/os/os_netbsd.odin | 2 +- core/sys/posix/fcntl.odin | 2 +- 10 files changed, 137 insertions(+), 26 deletions(-) create mode 100644 core/os/os2/file_posix_darwin.odin create mode 100644 core/os/os2/file_posix_freebsd.odin create mode 100644 core/os/os2/file_posix_netbsd.odin create mode 100644 core/os/os2/file_posix_other.odin diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 454bc50b9..f1c5b1fad 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -115,7 +115,11 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, @(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { - return _new_file(handle, name) or_else panic("Out of memory") + file, err := _new_file(handle, name) + if err != nil { + panic(error_string(err)) + } + return file } @(require_results) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index efecf6e49..06526d2e8 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -26,9 +26,18 @@ File_Impl :: struct { @(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") + + stdin = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stdin.impl).name = "/dev/stdin" + (^File_Impl)(stdin.impl).cname = "/dev/stdin" + + stdout = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stdout.impl).name = "/dev/stdout" + (^File_Impl)(stdout.impl).cname = "/dev/stdout" + + stderr = __new_file(posix.STDIN_FILENO) + (^File_Impl)(stderr.impl).name = "/dev/stderr" + (^File_Impl)(stderr.impl).cname = "/dev/stderr" } _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { @@ -75,19 +84,12 @@ _new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) { return } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + crname := _posix_absolute_path(posix.FD(handle), name, file_allocator()) or_return + rname := string(crname) - crname := posix.realpath(cname, nil) - if crname == nil { - err = _get_platform_error() - return - } - rname := string(crname) - - f = __new_file(posix.FD(handle)) + f = __new_file(posix.FD(handle)) impl := (^File_Impl)(f.impl) - impl.name = rname + impl.name = rname impl.cname = crname return f, nil diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin new file mode 100644 index 000000000..056d775e6 --- /dev/null +++ b/core/os/os2/file_posix_darwin.odin @@ -0,0 +1,18 @@ +//+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 50 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin new file mode 100644 index 000000000..e5007f8fe --- /dev/null +++ b/core/os/os2/file_posix_freebsd.odin @@ -0,0 +1,47 @@ +//+private +package os2 + +import "base:runtime" + +import "core:c" +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [posix.PATH_MAX]c.char, + } + + F_KINFO :: 22 + + kinfo: KInfo_File + kinfo.structsize = size_of(KInfo_File) + + res := posix.fcntl(fd, posix.FCNTL_Cmd(F_KINFO), &kinfo) + if res == -1 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&kinfo.path[0])), allocator) +} diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin new file mode 100644 index 000000000..a9cc77a41 --- /dev/null +++ b/core/os/os2/file_posix_netbsd.odin @@ -0,0 +1,18 @@ +//+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 15 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin new file mode 100644 index 000000000..8a76f4cd7 --- /dev/null +++ b/core/os/os2/file_posix_other.odin @@ -0,0 +1,21 @@ +//+private +//+build openbsd +package os2 + +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(ignore=is_temp(allocator)) + cname := temp_cstring(name) + + buf: [posix.PATH_MAX]byte + path = posix.realpath(cname, raw_data(buf[:])) + if path == nil { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(path), allocator) +} diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index eb63febe7..f670dd394 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -73,21 +73,22 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) cname := temp_cstring(name) or_return - rcname := posix.realpath(cname) - if rcname == nil { - err = .Invalid_Path + fd := posix.open(cname, {}) + if fd == -1 { + err = _get_platform_error() return } - defer posix.free(rcname) + defer posix.close(fd) + + fullpath := _posix_absolute_path(fd, name, allocator) or_return stat: posix.stat_t - if posix.stat(rcname, &stat) != .OK { + if posix.stat(fullpath, &stat) != .OK { err = _get_platform_error() return } - fullpath := clone_string(string(rcname), allocator) or_return - return internal_stat(stat, fullpath), nil + return internal_stat(stat, string(fullpath)), nil } _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { @@ -98,7 +99,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) - // NOTE: can't use realpath here because it tries to resolve symlinks. + // 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". diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index ba7e7ccf3..c2d747a60 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -388,7 +388,7 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index fac1bc311..931b38f12 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -440,7 +440,7 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index e98086eb3..8f1b1a511 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -23,7 +23,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html ]] */ - fcntl :: proc(fd: FD, cmd: FCNTL_Cmd, arg: rawptr = nil) -> c.int --- + fcntl :: proc(fd: FD, cmd: FCNTL_Cmd, #c_vararg args: ..any) -> c.int --- /* Establish the connection between a file and a file descriptor. From e94c4e1e184844b3c8ffc9afa4f888b47e947e3e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 4 Aug 2024 18:55:42 +0200 Subject: [PATCH 28/31] posix: remove the is_temp things that prevented use-after-frees https://github.com/odin-lang/Odin/commit/d0709a7de21efded4625167dbff4a7dd13d561b4 fixes those another way. --- core/os/os2/env_posix.odin | 2 +- core/os/os2/env_windows.odin | 2 +- core/os/os2/file_linux.odin | 2 +- core/os/os2/file_posix.odin | 2 +- core/os/os2/file_posix_other.odin | 2 +- core/os/os2/file_windows.odin | 4 ++-- core/os/os2/path_posix.odin | 2 +- core/os/os2/path_windows.odin | 2 +- core/os/os2/process_posix_darwin.odin | 2 +- core/os/os2/process_windows.odin | 14 +++++++------- core/os/os2/stat_linux.odin | 4 ++-- core/os/os2/stat_posix.odin | 4 ++-- core/os/os2/stat_windows.odin | 6 +++--- core/os/os2/temp_file.odin | 2 +- core/os/os2/temp_file_linux.odin | 2 +- core/os/os2/temp_file_windows.odin | 2 +- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index eac7a1fea..93524fb0c 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -12,7 +12,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() ckey := strings.clone_to_cstring(key, temp_allocator()) cval := posix.getenv(ckey) diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 0a2bab694..ac30eb1d4 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,7 +8,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) n := win32.GetEnvironmentVariableW(wkey, nil, 0) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index a26ccb09b..6b981cca1 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -309,7 +309,7 @@ _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(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return return _read_link_cstr(name_cstr, allocator) } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 06526d2e8..3039f11c9 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -191,7 +191,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cname := temp_cstring(name) buf: [dynamic]byte diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin index 8a76f4cd7..929622578 100644 --- a/core/os/os2/file_posix_other.odin +++ b/core/os/os2/file_posix_other.odin @@ -7,7 +7,7 @@ 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(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cname := temp_cstring(name) buf: [posix.PATH_MAX]byte diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 1f5d268f3..74067464b 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -609,7 +609,7 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() buf := make([]u16, n+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) @@ -635,7 +635,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() p := _fix_long_path(name, temp_allocator()) or_return handle := _open_sym_link(p) or_return diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 2fd289300..39ada264e 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -96,7 +96,7 @@ _remove_all :: proc(path: string) -> Error { } _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() buf: [dynamic]byte buf.allocator = temp_allocator() diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index e8c34c750..4aa695ee2 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -108,7 +108,7 @@ _remove_all :: proc(path: string) -> Error { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { win32.AcquireSRWLockExclusive(&cwd_lock) - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() sz_utf16 := win32.GetCurrentDirectoryW(0, nil) dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 882170670..3a9ed835f 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -237,7 +237,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() buffer := make([]i32, ret, temp_allocator()) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index a43ab3495..47fd62401 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -158,7 +158,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -172,7 +172,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return @@ -181,7 +181,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return @@ -250,7 +250,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -265,7 +265,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } if .Environment in selection { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return @@ -275,7 +275,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return @@ -539,7 +539,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(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() token_handle: win32.HANDLE if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { err = _get_platform_error() diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 7704aaf20..6ccac1be0 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -48,7 +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) { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {}) @@ -60,7 +60,7 @@ _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(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index f670dd394..a817a862b 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -70,7 +70,7 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err return } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() cname := temp_cstring(name) or_return fd := posix.open(cname, {}) @@ -97,7 +97,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er return } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5b59f4269..566417c84 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -45,7 +45,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path name = "." } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() p := win32_utf8_to_utf16(name, temp_allocator()) or_return @@ -65,7 +65,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt if len(name) == 0 { return {}, .Not_Exist } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() wname := _fix_long_path(name, temp_allocator()) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA @@ -129,7 +129,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD(ignore=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) diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index d76dbe318..5ca4e1453 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -47,7 +47,7 @@ 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(ignore=is_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 diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index 3c6da2e23..d6f90fbaf 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,7 +4,7 @@ package os2 import "base:runtime" _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() tmpdir := get_env("TMPDIR", temp_allocator()) if tmpdir == "" { tmpdir = "/tmp" diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index f430d66ea..d888eda52 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -9,7 +9,7 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er if n == 0 { return "", nil } - TEMP_ALLOCATOR_GUARD(ignore=is_temp(allocator)) + TEMP_ALLOCATOR_GUARD() b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) From 61ee2efa35a452797672028bb9717c4dba9e6f7e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 6 Aug 2024 20:20:57 +0200 Subject: [PATCH 29/31] os2: just use runtime._heap_allocator_proc instead of a copy for posix --- core/os/os2/heap_posix.odin | 94 +------------------------------------ 1 file changed, 2 insertions(+), 92 deletions(-) diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin index eb543d383..fcae267fa 100644 --- a/core/os/os2/heap_posix.odin +++ b/core/os/os2/heap_posix.odin @@ -2,96 +2,6 @@ //+build darwin, netbsd, freebsd, openbsd package os2 -import "base:intrinsics" +import "base:runtime" -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 -} +_heap_allocator_proc :: runtime.heap_allocator_proc From f837e35f4baf425ef61e8618e249e5ae145c6cc8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 12 Aug 2024 19:10:38 +0200 Subject: [PATCH 30/31] os2: fix bad free for posix file closure --- core/os/os2/file_posix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 3039f11c9..96b7ffe4e 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -115,7 +115,7 @@ _close :: proc(f: ^File_Impl) -> (err: Error) { } delete(f.cname, file_allocator()) - posix.free(f.cname) + free(f, file_allocator()) return } From e29f0a0f40019d9a235e617759a24032a9688f85 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 12 Aug 2024 19:11:31 +0200 Subject: [PATCH 31/31] os2: dup the file descriptor before fdopendir --- core/os/os2/dir_posix.odin | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 2f537f64c..75f620d90 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -63,8 +63,17 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera iter.impl.fullpath.allocator = file_allocator() append(&iter.impl.fullpath, impl.name) append(&iter.impl.fullpath, "/") + defer if err != nil { delete(iter.impl.fullpath) } - iter.impl.dir = posix.fdopendir(impl.fd) + // `fdopendir` consumes the file descriptor so we need to `dup` it. + dupfd := posix.dup(impl.fd) + if dupfd == -1 { + err = _get_platform_error() + return + } + defer if err != nil { posix.close(dupfd) } + + iter.impl.dir = posix.fdopendir(dupfd) if iter.impl.dir == nil { err = _get_platform_error() return