From f22754fc9036cf3c0bb5707e000c5055607ba374 Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 27 Jun 2024 16:29:47 -0400 Subject: [PATCH 1/5] sys/linux: fix some syscalls and types; add more to Sig_Action and Sig_Info; Pid int->i32 --- core/sys/linux/bits.odin | 113 +++++++++++++++++++++++++------------- core/sys/linux/sys.odin | 47 ++++++++++------ core/sys/linux/types.odin | 96 +++++++++++++++++++++++++------- 3 files changed, 181 insertions(+), 75 deletions(-) diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index cfae06013..bf7ab4fae 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -150,44 +150,66 @@ Errno :: enum i32 { RDONLY flag is not present, because it has the value of 0, i.e. it is the default, unless WRONLY or RDWR is specified. */ -Open_Flags_Bits :: enum { - WRONLY = 0, - RDWR = 1, - CREAT = 6, - EXCL = 7, - NOCTTY = 8, - TRUNC = 9, - APPEND = 10, - NONBLOCK = 11, - DSYNC = 12, - ASYNC = 13, - DIRECT = 14, - LARGEFILE = 15, - DIRECTORY = 16, - NOFOLLOW = 17, - NOATIME = 18, - CLOEXEC = 19, - PATH = 21, -} +when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECT = 14, + LARGEFILE = 15, + DIRECTORY = 16, + NOFOLLOW = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } + // https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 + #assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) + #assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) + #assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) + #assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) + #assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) + #assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) + #assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) + #assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) + #assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) + #assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) + #assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) + #assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) + #assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) + #assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) + #assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) + #assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) + #assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) -// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19 -#assert(1 << uint(Open_Flags_Bits.WRONLY) == 0o0000000_1) -#assert(1 << uint(Open_Flags_Bits.RDWR) == 0o0000000_2) -#assert(1 << uint(Open_Flags_Bits.CREAT) == 0o00000_100) -#assert(1 << uint(Open_Flags_Bits.EXCL) == 0o00000_200) -#assert(1 << uint(Open_Flags_Bits.NOCTTY) == 0o00000_400) -#assert(1 << uint(Open_Flags_Bits.TRUNC) == 0o0000_1000) -#assert(1 << uint(Open_Flags_Bits.APPEND) == 0o0000_2000) -#assert(1 << uint(Open_Flags_Bits.NONBLOCK) == 0o0000_4000) -#assert(1 << uint(Open_Flags_Bits.DSYNC) == 0o000_10000) -#assert(1 << uint(Open_Flags_Bits.ASYNC) == 0o000_20000) -#assert(1 << uint(Open_Flags_Bits.DIRECT) == 0o000_40000) -#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000) -#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000) -#assert(1 << uint(Open_Flags_Bits.NOFOLLOW) == 0o00_400000) -#assert(1 << uint(Open_Flags_Bits.NOATIME) == 0o0_1000000) -#assert(1 << uint(Open_Flags_Bits.CLOEXEC) == 0o0_2000000) -#assert(1 << uint(Open_Flags_Bits.PATH) == 0o_10000000) +} else { + Open_Flags_Bits :: enum { + WRONLY = 0, + RDWR = 1, + CREAT = 6, + EXCL = 7, + NOCTTY = 8, + TRUNC = 9, + APPEND = 10, + NONBLOCK = 11, + DSYNC = 12, + ASYNC = 13, + DIRECTORY = 14, + NOFOLLOW = 15, + DIRECT = 16, + LARGEFILE = 17, + NOATIME = 18, + CLOEXEC = 19, + PATH = 21, + } +} /* Bits for FD_Flags bitset @@ -867,7 +889,7 @@ Wait_Option :: enum { WSTOPPED = 1, WEXITED = 2, WCONTINUED = 3, - WNOWAIT = 24, + WNOWAIT = 24, // // For processes created using clone __WNOTHREAD = 29, __WALL = 30, @@ -946,9 +968,22 @@ Sig_Stack_Flag :: enum i32 { AUTODISARM = 31, } +Sig_Action_Flag :: enum u32 { + NOCLDSTOP = 0, + NOCLDWAIT = 1, + SIGINFO = 2, + UNSUPPORTED = 10, + EXPOSE_TAGBITS = 11, + RESTORER = 26, + ONSTACK = 27, + RESTART = 28, + NODEFER = 30, + RESETHAND = 31, +} + /* Type of socket to create - - For TCP you want to use SOCK_STREAM + - For TCP you want to use SOCK_STREAM - For UDP you want to use SOCK_DGRAM Also see `Protocol` */ diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 171829cde..ed40719bf 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -69,7 +69,7 @@ close :: proc "contextless" (fd: Fd) -> (Errno) { stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat) + ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat, 0) return Errno(-ret) } else { ret := syscall(SYS_stat, cast(rawptr) filename, stat) @@ -200,10 +200,25 @@ brk :: proc "contextless" (addr: uintptr) -> (Errno) { return Errno(-ret) } +/* + Returns from signal handlers on some archs. +*/ +rt_sigreturn :: proc "c" () -> ! { + intrinsics.syscall(uintptr(SYS_rt_sigreturn)) + unreachable() +} + /* Alter an action taken by a process. */ -rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action, old_sigaction: ^Sig_Action) -> Errno { +rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action($T), old_sigaction: ^Sig_Action) -> Errno { + // NOTE(jason): It appears that the restorer is required for i386 and amd64 + when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 { + sigaction.flags += {.RESTORER} + } + if sigaction != nil && sigaction.restorer == nil && .RESTORER in sigaction.flags { + sigaction.restorer = rt_sigreturn + } ret := syscall(SYS_rt_sigaction, sig, sigaction, old_sigaction, size_of(Sig_Set)) return Errno(-ret) } @@ -1123,7 +1138,7 @@ ftruncate :: proc "contextless" (fd: Fd, length: i64) -> (Errno) { ret := syscall(SYS_ftruncate64, fd, compat64_arg_pair(length)) return Errno(-ret) } else { - ret := syscall(SYS_truncate, fd, compat64_arg_pair(length)) + ret := syscall(SYS_ftruncate, fd, compat64_arg_pair(length)) return Errno(-ret) } } @@ -1231,7 +1246,7 @@ creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) { */ link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) + ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath, 0) return Errno(-ret) } else { ret := syscall(SYS_link, cast(rawptr) target, cast(rawptr) linkpath) @@ -1261,7 +1276,7 @@ unlink :: proc "contextless" (name: cstring) -> (Errno) { */ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_symlinkat, AT_FDCWD, cast(rawptr) target, cast(rawptr) linkpath) + ret := syscall(SYS_symlinkat, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath) return Errno(-ret) } else { ret := syscall(SYS_symlink, cast(rawptr) target, cast(rawptr) linkpath) @@ -1291,7 +1306,7 @@ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { */ chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { when ODIN_ARCH == .arm64 { - ret := syscall(SYS_fchmodat, cast(rawptr) name, transmute(u32) mode, 0) + ret := syscall(SYS_fchmodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } else { ret := syscall(SYS_chmod, cast(rawptr) name, transmute(u32) mode) @@ -2476,8 +2491,8 @@ tgkill :: proc "contextless" (tgid, tid: Pid, sig: Signal) -> (Errno) { Wait on process, process group or pid file descriptor. Available since Linux 2.6.10. */ -waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options) -> (Errno) { - ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options) +waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, options: Wait_Options, rusage: ^RUsage) -> (Errno) { + ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options, rusage) return Errno(-ret) } @@ -2504,7 +2519,7 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt Available since Linux 2.6.16. */ openat :: proc "contextless" (fd: Fd, name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { - ret := syscall(SYS_openat, fd, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode) + ret := syscall(SYS_openat, fd, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } @@ -2583,8 +2598,8 @@ linkat :: proc "contextless" (target_dirfd: Fd, oldpath: cstring, link_dirfd: Fd Create a symbolic link at specified dirfd. Available since Linux 2.6.16. */ -symlinkat :: proc "contextless" (dirfd: Fd, target: cstring, linkpath: cstring) -> (Errno) { - ret := syscall(SYS_symlinkat, dirfd, cast(rawptr) target, cast(rawptr) linkpath) +symlinkat :: proc "contextless" (target: cstring, dirfd: Fd, linkpath: cstring) -> (Errno) { + ret := syscall(SYS_symlinkat, cast(rawptr) target, dirfd, cast(rawptr) linkpath) return Errno(-ret) } @@ -2619,13 +2634,13 @@ faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> Wait for events on a file descriptor. Available since Linux 2.6.16. */ -ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (Errno) { +ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (i32, Errno) { when size_of(int) == 8 { ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) + return errno_unwrap(ret, i32) } else { ret := syscall(SYS_ppoll_time64, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) + return errno_unwrap(ret, i32) } } @@ -2808,8 +2823,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { - ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 5053e1e1c..e3fe67a9b 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -18,7 +18,7 @@ Gid :: distinct u32 /* Type for Process IDs, Thread IDs, Thread group ID. */ -Pid :: distinct int +Pid :: distinct i32 /* Type for any of: pid, pidfd, pgid. @@ -89,11 +89,11 @@ FD_Flags :: bit_set[FD_Flags_Bits; i32] Represents file's permission and status bits **Example:** When you're passing a value of this type the recommended usage is: - + ``` linux.Mode{.S_IXOTH, .S_IROTH} | linux.S_IRWXU | linux.S_IRWXG ``` - + This would generate a mode that has full permissions for the file's owner and group, and only "read" and "execute" bits for others. @@ -151,9 +151,9 @@ when ODIN_ARCH == .amd64 { size: i64, blksize: uint, blocks: u64, - atim: Time_Spec, - mtim: Time_Spec, - ctim: Time_Spec, + atime: Time_Spec, + mtime: Time_Spec, + ctime: Time_Spec, ino: Inode, } } @@ -495,16 +495,15 @@ Pid_FD_Flags :: bit_set[Pid_FD_Flags_Bits; i32] // 1. Odin's bitfields start from 0, whereas signals start from 1 // 2. It's unclear how bitfields act in terms of ABI (are they an array of ints or an array of longs?). // it makes a difference because ARM is big endian. -@private _SIGSET_NWORDS :: (1024 / (8 * size_of(uint))) +@private _SIGSET_NWORDS :: (8 / size_of(uint)) Sig_Set :: [_SIGSET_NWORDS]uint @private SI_MAX_SIZE :: 128 -@private SI_ARCH_PREAMBLE :: 3 * size_of(i32) -@private SI_PAD_SIZE :: (SI_MAX_SIZE - SI_ARCH_PREAMBLE) / size_of(i32) -@private SI_TIMER_PAD_SIZE :: size_of(Uid) - size_of(i32) +@private SI_ARCH_PREAMBLE :: 4 * size_of(i32) +@private SI_PAD_SIZE :: SI_MAX_SIZE - SI_ARCH_PREAMBLE Sig_Handler_Fn :: #type proc "c" (sig: Signal) -Sig_Restore_Fn :: #type proc "c" () +Sig_Restore_Fn :: #type proc "c" () -> ! Sig_Info :: struct #packed { signo: Signal, @@ -518,8 +517,9 @@ Sig_Info :: struct #packed { uid: Uid, /* sender's uid */ }, using _timer: struct { - timerid: i32, /* timer id */ + timerid: i32, /* timer id */ overrun: i32, /* overrun count */ + value: Sig_Val, /* timer value */ }, /* POSIX.1b signals */ using _rt: struct { @@ -528,8 +528,8 @@ Sig_Info :: struct #packed { }, /* SIGCHLD */ using _sigchld: struct { - _pid1: Pid, /* which child */ - _uid1: Uid, /* sender's uid */ + _pid1: Pid, /* which child */ + _uid1: Uid, /* sender's uid */ status: i32, /* exit code */ utime: uint, stime: uint, //clock_t @@ -537,7 +537,24 @@ Sig_Info :: struct #packed { /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ using _sigfault: struct { addr: rawptr, /* faulting insn/memory ref. */ - addr_lsb: i16, /* LSB of the reported address */ + using _: struct #raw_union { + trapno: i32, /* Trap number that caused signal */ + addr_lsb: i16, /* LSB of the reported address */ + using _addr_bnd: struct { + _pad2: u64, + lower: rawptr, /* lower bound during fault */ + upper: rawptr, /* upper bound during fault */ + }, + using _addr_pkey: struct { + _pad3: u64, + pkey: u32, /* protection key on PTE that faulted */ + }, + using _perf: struct { + perf_data: u64, + perf_type: u32, + perf_flags: u32, + }, + }, }, /* SIGPOLL */ using _sigpoll: struct { @@ -547,12 +564,43 @@ Sig_Info :: struct #packed { /* SIGSYS */ using _sigsys: struct { call_addr: rawptr, /* calling user insn */ - syscall: i32, /* triggering system call number */ - arch: u32, /* AUDIT_ARCH_* of syscall */ + syscall: i32, /* triggering system call number */ + arch: u32, /* AUDIT_ARCH_* of syscall */ }, }, } +#assert(size_of(Sig_Info) == 128) +when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + #assert(offset_of(Sig_Info, signo) == 0x00) + #assert(offset_of(Sig_Info, errno) == 0x04) + #assert(offset_of(Sig_Info, code) == 0x08) + #assert(offset_of(Sig_Info, pid) == 0x10) + #assert(offset_of(Sig_Info, uid) == 0x14) + #assert(offset_of(Sig_Info, timerid) == 0x10) + #assert(offset_of(Sig_Info, overrun) == 0x14) + #assert(offset_of(Sig_Info, value) == 0x18) + #assert(offset_of(Sig_Info, status) == 0x18) + #assert(offset_of(Sig_Info, utime) == 0x20) + #assert(offset_of(Sig_Info, stime) == 0x28) + #assert(offset_of(Sig_Info, addr) == 0x10) + #assert(offset_of(Sig_Info, addr_lsb) == 0x18) + #assert(offset_of(Sig_Info, trapno) == 0x18) + #assert(offset_of(Sig_Info, lower) == 0x20) + #assert(offset_of(Sig_Info, upper) == 0x28) + #assert(offset_of(Sig_Info, pkey) == 0x20) + #assert(offset_of(Sig_Info, perf_data) == 0x18) + #assert(offset_of(Sig_Info, perf_type) == 0x20) + #assert(offset_of(Sig_Info, perf_flags) == 0x24) + #assert(offset_of(Sig_Info, band) == 0x10) + #assert(offset_of(Sig_Info, fd) == 0x18) + #assert(offset_of(Sig_Info, call_addr) == 0x10) + #assert(offset_of(Sig_Info, syscall) == 0x18) + #assert(offset_of(Sig_Info, arch) == 0x1C) +} else { + // TODO +} + SIGEV_MAX_SIZE :: 64 SIGEV_PAD_SIZE :: ((SIGEV_MAX_SIZE-size_of(i32)*2+size_of(Sig_Val))/size_of(i32)) @@ -583,12 +631,20 @@ Sig_Stack :: struct { size: uintptr, } +Sig_Action_Special :: enum uint { + SIG_DFL = 0, + SIG_IGN = 1, + SIG_ERR = ~uint(0), +} + +Sig_Action_Flags :: bit_set[Sig_Action_Flag; uint] Sig_Action :: struct($T: typeid) { using _u: struct #raw_union { handler: Sig_Handler_Fn, sigaction: #type proc "c" (sig: Signal, si: ^Sig_Info, ctx: ^T), + special: Sig_Action_Special, }, - flags: uint, + flags: Sig_Action_Flags, restorer: Sig_Restore_Fn, mask: Sig_Set, } @@ -733,7 +789,7 @@ RLimit :: struct { /* Structure representing how much of each resource got used. -*/ +*/ RUsage :: struct { utime: Time_Val, stime: Time_Val, @@ -813,7 +869,7 @@ when size_of(int) == 8 || ODIN_ARCH == .i386 { cpid: Pid, lpid: Pid, nattach: uint, - _: [2]uint, + _: [2]uint, } } From f24f72c280df305175653f912f1a7a078c2d101b Mon Sep 17 00:00:00 2001 From: jason Date: Thu, 27 Jun 2024 17:14:48 -0400 Subject: [PATCH 2/5] convert all to use sys/linux over sys/unix; new implementations for pipe, process and env --- core/os/os2/env_linux.odin | 219 +++++++++++++++- core/os/os2/errors.odin | 16 ++ core/os/os2/errors_linux.odin | 287 +++++++++++---------- core/os/os2/file.odin | 7 +- core/os/os2/file_linux.odin | 388 ++++++++++++++++------------ core/os/os2/heap_linux.odin | 28 +- core/os/os2/path_linux.odin | 156 +++++------ core/os/os2/pipe_linux.odin | 12 +- core/os/os2/process.odin | 88 ++++--- core/os/os2/process_linux.odin | 428 +++++++++++++++++++++++++++++++ core/os/os2/process_windows.odin | 67 +++++ core/os/os2/stat_linux.odin | 116 ++------- 12 files changed, 1269 insertions(+), 543 deletions(-) create mode 100644 core/os/os2/process_linux.odin create mode 100644 core/os/os2/process_windows.odin diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index eb463f22c..99dd00d90 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -2,29 +2,230 @@ package os2 import "base:runtime" +import "base:intrinsics" + +import "core:sync" +import "core:slice" +import "core:strings" + +// TODO: IF NO_CRT: +// Override the libc environment functions' weak linkage to +// allow us to interact with 3rd party code that DOES link +// to libc. Otherwise, our environment can be out of sync. +// ELSE: +// Just use the libc. + +NOT_FOUND :: -1 + +// the environment is a 0 delimited list of = strings +_env: [dynamic]string + +_env_mutex: sync.Mutex + +// We need to be able to figure out if the environment variable +// is contained in the original environment or not. This also +// serves as a flag to determine if we have built _env. +_org_env_begin: uintptr +_org_env_end: uintptr + +// Returns value + index location into _env +// or -1 if not found +_lookup :: proc(key: string) -> (value: string, idx: int) { + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + for entry, i in _env { + if k, v := _kv_from_entry(entry); k == key { + return v, i + } + } + return "", -1 +} _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - //TODO + if _org_env_begin == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + found = true + value, _ = clone_string(v, allocator) + } return } -_set_env :: proc(key, value: string) -> bool { - //TODO - return false +_set_env :: proc(key, v_new: string) -> bool { + if _org_env_begin == 0 { + _build_env() + } + + // all key values are stored as "key=value\x00" + kv_size := len(key) + len(v_new) + 2 + if v_curr, idx := _lookup(key); idx != NOT_FOUND { + if v_curr == v_new { + return true + } + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + unordered_remove(&_env, idx) + + if !_is_in_org_env(v_curr) { + // We allocated this key-value. Possibly resize and + // overwrite the value only. Otherwise, treat as if it + // wasn't in the environment in the first place. + k_addr, v_addr := _kv_addr_from_val(v_curr, key) + if len(v_new) > len(v_curr) { + k_addr = ([^]u8)(heap_resize(k_addr, kv_size)) + if k_addr == nil { + return false + } + v_addr = &k_addr[len(key) + 1] + } + intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) + v_addr[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size])) + return true + } + } + + k_addr := ([^]u8)(heap_alloc(kv_size)) + if k_addr == nil { + return false + } + intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) + k_addr[len(key)] = '=' + + val_slice := k_addr[len(key) + 1:] + intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) + val_slice[len(v_new)] = 0 + + sync.mutex_lock(&_env_mutex) + append(&_env, string(k_addr[:kv_size - 1])) + sync.mutex_unlock(&_env_mutex) + return true } _unset_env :: proc(key: string) -> bool { - //TODO - return false + if _org_env_begin == 0 { + _build_env() + } + + v: string + i: int + if v, i = _lookup(key); i == -1 { + return false + } + + sync.mutex_lock(&_env_mutex) + unordered_remove(&_env, i) + sync.mutex_unlock(&_env_mutex) + + if _is_in_org_env(v) { + return true + } + + // if we got this far, the envrionment variable + // existed AND was allocated by us. + k_addr, _ := _kv_addr_from_val(v, key) + heap_free(k_addr) + return true } _clear_env :: proc() { - //TODO + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + + for kv in _env { + if !_is_in_org_env(kv) { + heap_free(raw_data(kv)) + } + } + clear(&_env) + + // nothing resides in the original environment either + _org_env_begin = ~uintptr(0) + _org_env_end = ~uintptr(0) } _environ :: proc(allocator: runtime.Allocator) -> []string { - //TODO - return nil + if _org_env_begin == 0 { + _build_env() + } + env := make([]string, len(_env), allocator) + + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + for entry, i in _env { + env[i], _ = clone_string(entry, allocator) + } + return env } +// The entire environment is stored as 0 terminated strings, +// so there is no need to clone/free individual variables +export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + if _org_env_begin == 0 { + // The environment has not been modified, so we can just + // send the original environment + org_env := _get_original_env() + n: int + for ; org_env[n] != nil; n += 1 {} + return slice.clone(org_env[:n + 1], allocator) + } + // NOTE: already terminated by nil pointer via + 1 + env := make([]cstring, len(_env) + 1, allocator) + + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + for entry, i in _env { + env[i] = cstring(raw_data(entry)) + } + return env +} + +_build_env :: proc() { + sync.mutex_lock(&_env_mutex) + defer sync.mutex_unlock(&_env_mutex) + if _org_env_begin != 0 { + return + } + + _env = make(type_of(_env), heap_allocator()) + cstring_env := _get_original_env() + _org_env_begin = uintptr(rawptr(cstring_env[0])) + for i := 0; cstring_env[i] != nil; i += 1 { + bytes := ([^]u8)(cstring_env[i]) + n := len(cstring_env[i]) + _org_env_end = uintptr(&bytes[n]) + append(&_env, string(bytes[:n])) + } +} + +_get_original_env :: #force_inline proc() -> [^]cstring { + // essentially &argv[argc] which should be a nil pointer! + #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] + assert(env[0] == nil) + return &env[1] +} + +_kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { + eq_idx := strings.index_byte(entry, '=') + if eq_idx == -1 { + return entry, "" + } + return entry[:eq_idx], entry[eq_idx + 1:] +} + +_kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { + v_addr := raw_data(val) + k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) + return k_addr, v_addr +} + +_is_in_org_env :: #force_inline proc(env_data: string) -> bool { + addr := uintptr(raw_data(env_data)) + return addr >= _org_env_begin && addr < _org_env_end +} diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 2c570170d..f7cfdbd3d 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -99,3 +99,19 @@ error_string :: proc(ferr: Error) -> string { return "unknown error" } + +print_error :: proc(ferr: Error, msg: string) { + TEMP_ALLOCATOR_GUARD() + err_str := error_string(ferr) + + // msg + ": " + err_str + '\n' + length := len(msg) + 2 + len(err_str) + 1 + buf := make([]u8, length, temp_allocator()) + + copy(buf, msg) + buf[len(msg)] = ':' + buf[len(msg) + 1] = ' ' + copy(buf[len(msg) + 2:], err_str) + buf[length - 1] = '\n' + write(stderr, buf) +} diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 053256fbd..503f10671 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,145 +1,164 @@ //+private package os2 -import "core:sys/unix" +import "core:sys/linux" -EPERM :: 1 -ENOENT :: 2 -ESRCH :: 3 -EINTR :: 4 -EIO :: 5 -ENXIO :: 6 -EBADF :: 9 -EAGAIN :: 11 -ENOMEM :: 12 -EACCES :: 13 -EFAULT :: 14 -EEXIST :: 17 -ENODEV :: 19 -ENOTDIR :: 20 -EISDIR :: 21 -EINVAL :: 22 -ENFILE :: 23 -EMFILE :: 24 -ETXTBSY :: 26 -EFBIG :: 27 -ENOSPC :: 28 -ESPIPE :: 29 -EROFS :: 30 -EPIPE :: 32 -ERANGE :: 34 /* Result too large */ -EDEADLK :: 35 /* Resource deadlock would occur */ -ENAMETOOLONG :: 36 /* File name too long */ -ENOLCK :: 37 /* No record locks available */ -ENOSYS :: 38 /* Invalid system call number */ -ENOTEMPTY :: 39 /* Directory not empty */ -ELOOP :: 40 /* Too many symbolic links encountered */ -EWOULDBLOCK :: EAGAIN /* Operation would block */ -ENOMSG :: 42 /* No message of desired type */ -EIDRM :: 43 /* Identifier removed */ -ECHRNG :: 44 /* Channel number out of range */ -EL2NSYNC :: 45 /* Level 2 not synchronized */ -EL3HLT :: 46 /* Level 3 halted */ -EL3RST :: 47 /* Level 3 reset */ -ELNRNG :: 48 /* Link number out of range */ -EUNATCH :: 49 /* Protocol driver not attached */ -ENOCSI :: 50 /* No CSI structure available */ -EL2HLT :: 51 /* Level 2 halted */ -EBADE :: 52 /* Invalid exchange */ -EBADR :: 53 /* Invalid request descriptor */ -EXFULL :: 54 /* Exchange full */ -ENOANO :: 55 /* No anode */ -EBADRQC :: 56 /* Invalid request code */ -EBADSLT :: 57 /* Invalid slot */ -EDEADLOCK :: EDEADLK -EBFONT :: 59 /* Bad font file format */ -ENOSTR :: 60 /* Device not a stream */ -ENODATA :: 61 /* No data available */ -ETIME :: 62 /* Timer expired */ -ENOSR :: 63 /* Out of streams resources */ -ENONET :: 64 /* Machine is not on the network */ -ENOPKG :: 65 /* Package not installed */ -EREMOTE :: 66 /* Object is remote */ -ENOLINK :: 67 /* Link has been severed */ -EADV :: 68 /* Advertise error */ -ESRMNT :: 69 /* Srmount error */ -ECOMM :: 70 /* Communication error on send */ -EPROTO :: 71 /* Protocol error */ -EMULTIHOP :: 72 /* Multihop attempted */ -EDOTDOT :: 73 /* RFS specific error */ -EBADMSG :: 74 /* Not a data message */ -EOVERFLOW :: 75 /* Value too large for defined data type */ -ENOTUNIQ :: 76 /* Name not unique on network */ -EBADFD :: 77 /* File descriptor in bad state */ -EREMCHG :: 78 /* Remote address changed */ -ELIBACC :: 79 /* Can not access a needed shared library */ -ELIBBAD :: 80 /* Accessing a corrupted shared library */ -ELIBSCN :: 81 /* .lib section in a.out corrupted */ -ELIBMAX :: 82 /* Attempting to link in too many shared libraries */ -ELIBEXEC :: 83 /* Cannot exec a shared library directly */ -EILSEQ :: 84 /* Illegal byte sequence */ -ERESTART :: 85 /* Interrupted system call should be restarted */ -ESTRPIPE :: 86 /* Streams pipe error */ -EUSERS :: 87 /* Too many users */ -ENOTSOCK :: 88 /* Socket operation on non-socket */ -EDESTADDRREQ :: 89 /* Destination address required */ -EMSGSIZE :: 90 /* Message too long */ -EPROTOTYPE :: 91 /* Protocol wrong type for socket */ -ENOPROTOOPT :: 92 /* Protocol not available */ -EPROTONOSUPPORT:: 93 /* Protocol not supported */ -ESOCKTNOSUPPORT:: 94 /* Socket type not supported */ -EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */ -EPFNOSUPPORT :: 96 /* Protocol family not supported */ -EAFNOSUPPORT :: 97 /* Address family not supported by protocol */ -EADDRINUSE :: 98 /* Address already in use */ -EADDRNOTAVAIL :: 99 /* Cannot assign requested address */ -ENETDOWN :: 100 /* Network is down */ -ENETUNREACH :: 101 /* Network is unreachable */ -ENETRESET :: 102 /* Network dropped connection because of reset */ -ECONNABORTED :: 103 /* Software caused connection abort */ -ECONNRESET :: 104 /* Connection reset by peer */ -ENOBUFS :: 105 /* No buffer space available */ -EISCONN :: 106 /* Transport endpoint is already connected */ -ENOTCONN :: 107 /* Transport endpoint is not connected */ -ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */ -ETOOMANYREFS :: 109 /* Too many references: cannot splice */ -ETIMEDOUT :: 110 /* Connection timed out */ -ECONNREFUSED :: 111 /* Connection refused */ -EHOSTDOWN :: 112 /* Host is down */ -EHOSTUNREACH :: 113 /* No route to host */ -EALREADY :: 114 /* Operation already in progress */ -EINPROGRESS :: 115 /* Operation now in progress */ -ESTALE :: 116 /* Stale file handle */ -EUCLEAN :: 117 /* Structure needs cleaning */ -ENOTNAM :: 118 /* Not a XENIX named type file */ -ENAVAIL :: 119 /* No XENIX semaphores available */ -EISNAM :: 120 /* Is a named type file */ -EREMOTEIO :: 121 /* Remote I/O error */ -EDQUOT :: 122 /* Quota exceeded */ -ENOMEDIUM :: 123 /* No medium found */ -EMEDIUMTYPE :: 124 /* Wrong medium type */ -ECANCELED :: 125 /* Operation Canceled */ -ENOKEY :: 126 /* Required key not available */ -EKEYEXPIRED :: 127 /* Key has expired */ -EKEYREVOKED :: 128 /* Key has been revoked */ -EKEYREJECTED :: 129 /* Key was rejected by service */ -EOWNERDEAD :: 130 /* Owner died */ -ENOTRECOVERABLE:: 131 /* State not recoverable */ -ERFKILL :: 132 /* Operation not possible due to RF-kill */ -EHWPOISON :: 133 /* Memory page has hardware error */ +_errno_strings : [int(max(linux.Errno)) + 1]string = { + linux.Errno.NONE = "Success", + linux.Errno.EPERM = "Operation not permitted", + linux.Errno.ENOENT = "No such file or directory", + linux.Errno.ESRCH = "No such process", + linux.Errno.EINTR = "Interrupted system call", + linux.Errno.EIO = "Input/output error", + linux.Errno.ENXIO = "No such device or address", + linux.Errno.E2BIG = "Argument list too long", + linux.Errno.ENOEXEC = "Exec format error", + linux.Errno.EBADF = "Bad file descriptor", + linux.Errno.ECHILD = "No child processes", + linux.Errno.EAGAIN = "Resource temporarily unavailable", + linux.Errno.ENOMEM = "Cannot allocate memory", + linux.Errno.EACCES = "Permission denied", + linux.Errno.EFAULT = "Bad address", + linux.Errno.ENOTBLK = "Block device required", + linux.Errno.EBUSY = "Device or resource busy", + linux.Errno.EEXIST = "File exists", + linux.Errno.EXDEV = "Invalid cross-device link", + linux.Errno.ENODEV = "No such device", + linux.Errno.ENOTDIR = "Not a directory", + linux.Errno.EISDIR = "Is a directory", + linux.Errno.EINVAL = "Invalid argument", + linux.Errno.ENFILE = "Too many open files in system", + linux.Errno.EMFILE = "Too many open files", + linux.Errno.ENOTTY = "Inappropriate ioctl for device", + linux.Errno.ETXTBSY = "Text file busy", + linux.Errno.EFBIG = "File too large", + linux.Errno.ENOSPC = "No space left on device", + linux.Errno.ESPIPE = "Illegal seek", + linux.Errno.EROFS = "Read-only file system", + linux.Errno.EMLINK = "Too many links", + linux.Errno.EPIPE = "Broken pipe", + linux.Errno.EDOM = "Numerical argument out of domain", + linux.Errno.ERANGE = "Numerical result out of range", + linux.Errno.EDEADLK = "Resource deadlock avoided", + linux.Errno.ENAMETOOLONG = "File name too long", + linux.Errno.ENOLCK = "No locks available", + linux.Errno.ENOSYS = "Function not implemented", + linux.Errno.ENOTEMPTY = "Directory not empty", + linux.Errno.ELOOP = "Too many levels of symbolic links", + 41 = "Unknown Error (41)", + linux.Errno.ENOMSG = "No message of desired type", + linux.Errno.EIDRM = "Identifier removed", + linux.Errno.ECHRNG = "Channel number out of range", + linux.Errno.EL2NSYNC = "Level 2 not synchronized", + linux.Errno.EL3HLT = "Level 3 halted", + linux.Errno.EL3RST = "Level 3 reset", + linux.Errno.ELNRNG = "Link number out of range", + linux.Errno.EUNATCH = "Protocol driver not attached", + linux.Errno.ENOCSI = "No CSI structure available", + linux.Errno.EL2HLT = "Level 2 halted", + linux.Errno.EBADE = "Invalid exchange", + linux.Errno.EBADR = "Invalid request descriptor", + linux.Errno.EXFULL = "Exchange full", + linux.Errno.ENOANO = "No anode", + linux.Errno.EBADRQC = "Invalid request code", + linux.Errno.EBADSLT = "Invalid slot", + 58 = "Unknown Error (58)", + linux.Errno.EBFONT = "Bad font file format", + linux.Errno.ENOSTR = "Device not a stream", + linux.Errno.ENODATA = "No data available", + linux.Errno.ETIME = "Timer expired", + linux.Errno.ENOSR = "Out of streams resources", + linux.Errno.ENONET = "Machine is not on the network", + linux.Errno.ENOPKG = "Package not installed", + linux.Errno.EREMOTE = "Object is remote", + linux.Errno.ENOLINK = "Link has been severed", + linux.Errno.EADV = "Advertise error", + linux.Errno.ESRMNT = "Srmount error", + linux.Errno.ECOMM = "Communication error on send", + linux.Errno.EPROTO = "Protocol error", + linux.Errno.EMULTIHOP = "Multihop attempted", + linux.Errno.EDOTDOT = "RFS specific error", + linux.Errno.EBADMSG = "Bad message", + linux.Errno.EOVERFLOW = "Value too large for defined data type", + linux.Errno.ENOTUNIQ = "Name not unique on network", + linux.Errno.EBADFD = "File descriptor in bad state", + linux.Errno.EREMCHG = "Remote address changed", + linux.Errno.ELIBACC = "Can not access a needed shared library", + linux.Errno.ELIBBAD = "Accessing a corrupted shared library", + linux.Errno.ELIBSCN = ".lib section in a.out corrupted", + linux.Errno.ELIBMAX = "Attempting to link in too many shared libraries", + linux.Errno.ELIBEXEC = "Cannot exec a shared library directly", + linux.Errno.EILSEQ = "Invalid or incomplete multibyte or wide character", + linux.Errno.ERESTART = "Interrupted system call should be restarted", + linux.Errno.ESTRPIPE = "Streams pipe error", + linux.Errno.EUSERS = "Too many users", + linux.Errno.ENOTSOCK = "Socket operation on non-socket", + linux.Errno.EDESTADDRREQ = "Destination address required", + linux.Errno.EMSGSIZE = "Message too long", + linux.Errno.EPROTOTYPE = "Protocol wrong type for socket", + linux.Errno.ENOPROTOOPT = "Protocol not available", + linux.Errno.EPROTONOSUPPORT = "Protocol not supported", + linux.Errno.ESOCKTNOSUPPORT = "Socket type not supported", + linux.Errno.EOPNOTSUPP = "Operation not supported", + linux.Errno.EPFNOSUPPORT = "Protocol family not supported", + linux.Errno.EAFNOSUPPORT = "Address family not supported by protocol", + linux.Errno.EADDRINUSE = "Address already in use", + linux.Errno.EADDRNOTAVAIL = "Cannot assign requested address", + linux.Errno.ENETDOWN = "Network is down", + linux.Errno.ENETUNREACH = "Network is unreachable", + linux.Errno.ENETRESET = "Network dropped connection on reset", + linux.Errno.ECONNABORTED = "Software caused connection abort", + linux.Errno.ECONNRESET = "Connection reset by peer", + linux.Errno.ENOBUFS = "No buffer space available", + linux.Errno.EISCONN = "Transport endpoint is already connected", + linux.Errno.ENOTCONN = "Transport endpoint is not connected", + linux.Errno.ESHUTDOWN = "Cannot send after transport endpoint shutdown", + linux.Errno.ETOOMANYREFS = "Too many references: cannot splice", + linux.Errno.ETIMEDOUT = "Connection timed out", + linux.Errno.ECONNREFUSED = "Connection refused", + linux.Errno.EHOSTDOWN = "Host is down", + linux.Errno.EHOSTUNREACH = "No route to host", + linux.Errno.EALREADY = "Operation already in progress", + linux.Errno.EINPROGRESS = "Operation now in progress", + linux.Errno.ESTALE = "Stale file handle", + linux.Errno.EUCLEAN = "Structure needs cleaning", + linux.Errno.ENOTNAM = "Not a XENIX named type file", + linux.Errno.ENAVAIL = "No XENIX semaphores available", + linux.Errno.EISNAM = "Is a named type file", + linux.Errno.EREMOTEIO = "Remote I/O error", + linux.Errno.EDQUOT = "Disk quota exceeded", + linux.Errno.ENOMEDIUM = "No medium found", + linux.Errno.EMEDIUMTYPE = "Wrong medium type", + linux.Errno.ECANCELED = "Operation canceled", + linux.Errno.ENOKEY = "Required key not available", + linux.Errno.EKEYEXPIRED = "Key has expired", + linux.Errno.EKEYREVOKED = "Key has been revoked", + linux.Errno.EKEYREJECTED = "Key was rejected by service", + linux.Errno.EOWNERDEAD = "Owner died", + linux.Errno.ENOTRECOVERABLE = "State not recoverable", + linux.Errno.ERFKILL = "Operation not possible due to RF-kill", + linux.Errno.EHWPOISON = "Memory page has hardware error", +} + + +_get_platform_error :: proc(errno: linux.Errno) -> Error { + #partial switch errno { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + } -_get_platform_error :: proc(res: int) -> Error { - errno := unix.get_errno(res) return Platform_Error(i32(errno)) } -_ok_or_error :: proc(res: int) -> Error { - return res >= 0 ? nil : _get_platform_error(res) -} - _error_string :: proc(errno: i32) -> string { - if errno == 0 { - return "" + if errno >= 0 && errno <= i32(max(linux.Errno)) { + return _errno_strings[errno] } - return "Error" + return "Unknown Error" } diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 4f4bd942e..98cfc0815 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -46,10 +46,9 @@ O_SPARSE :: File_Flags{.Sparse} O_CLOEXEC :: File_Flags{.Close_On_Exec} - -stdin: ^File = nil // OS-Specific -stdout: ^File = nil // OS-Specific -stderr: ^File = nil // OS-Specific +stdin: ^File = &_stdin +stdout: ^File = &_stdout +stderr: ^File = &_stderr @(require_results) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8f298be88..062c4aa0a 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -1,39 +1,60 @@ //+private package os2 -import "base:runtime" import "core:io" import "core:time" -import "core:sys/unix" - -INVALID_HANDLE :: -1 - -_O_RDONLY :: 0o00000000 -_O_WRONLY :: 0o00000001 -_O_RDWR :: 0o00000002 -_O_CREAT :: 0o00000100 -_O_EXCL :: 0o00000200 -_O_NOCTTY :: 0o00000400 -_O_TRUNC :: 0o00001000 -_O_APPEND :: 0o00002000 -_O_NONBLOCK :: 0o00004000 -_O_LARGEFILE :: 0o00100000 -_O_DIRECTORY :: 0o00200000 -_O_NOFOLLOW :: 0o00400000 -_O_SYNC :: 0o04010000 -_O_CLOEXEC :: 0o02000000 -_O_PATH :: 0o10000000 - -_AT_FDCWD :: -100 - -_CSTRING_NAME_HEAP_THRESHOLD :: 512 +import "base:runtime" +import "core:sys/linux" _File :: struct { name: string, - fd: int, + fd: linux.Fd, allocator: runtime.Allocator, } +_stdin : File = { + impl = { + name = "/proc/self/fd/0", + fd = 0, + allocator = _file_allocator(), + }, + stream = { + procedure = _file_stream_proc, + }, +} +_stdout : File = { + impl = { + name = "/proc/self/fd/1", + fd = 1, + allocator = _file_allocator(), + }, + stream = { + procedure = _file_stream_proc, + }, +} +_stderr : File = { + impl = { + name = "/proc/self/fd/2", + fd = 2, + allocator = _file_allocator(), + }, + stream = { + procedure = _file_stream_proc, + }, +} + +@init +_standard_stream_init :: proc() { + // cannot define these manually because cyclic reference + _stdin.stream.data = &_stdin + _stdout.stream.data = &_stdout + _stderr.stream.data = &_stderr +} + +_file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() +} + _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return @@ -41,40 +62,48 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while // allowing us to open serial devices. - flags_i: int = _O_NOCTTY + sys_flags: linux.Open_Flags = {.NOCTTY} switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = _O_RDONLY - case O_WRONLY: flags_i = _O_WRONLY - case O_RDWR: flags_i = _O_RDWR + case O_RDONLY: + case O_WRONLY: sys_flags += {.WRONLY} + case O_RDWR: sys_flags += {.RDWR} } - if .Append in flags { flags_i |= _O_APPEND } - if .Create in flags { flags_i |= _O_CREAT } - if .Excl in flags { flags_i |= _O_EXCL } - if .Sync in flags { flags_i |= _O_SYNC } - if .Trunc in flags { flags_i |= _O_TRUNC } - if .Close_On_Exec in flags { flags_i |= _O_CLOEXEC } + 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 .Close_On_Exec in flags { sys_flags += {.CLOEXEC} } - fd := unix.sys_open(name_cstr, flags_i, uint(perm)) - if fd < 0 { - return nil, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm))) + if errno != .NONE { + return nil, _get_platform_error(errno) } return _new_file(uintptr(fd), name), nil } -_new_file :: proc(fd: uintptr, _: string) -> ^File { +_new_file :: proc(fd: uintptr, _: string = "") -> ^File { file := new(File, file_allocator()) - file.impl.fd = int(fd) - file.impl.allocator = file_allocator() - file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator) - file.stream = { - data = file, - procedure = _file_stream_proc, - } + _construct_file(file, fd, "") return file } +_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") { + file^ = { + impl = { + fd = linux.Fd(fd), + allocator = file_allocator(), + name = _get_full_path(file.impl.fd, file.impl.allocator), + }, + stream = { + data = file, + procedure = _file_stream_proc, + }, + } +} + _destroy :: proc(f: ^File) -> Error { if f == nil { return nil @@ -86,12 +115,15 @@ _destroy :: proc(f: ^File) -> Error { _close :: proc(f: ^File) -> Error { - if f != nil { - res := unix.sys_close(f.impl.fd) - _destroy(f) - return _ok_or_error(res) + if f == nil { + return nil } - return nil + errno := linux.close(f.impl.fd) + if errno == .EBADF { // avoid possible double free + return _get_platform_error(errno) + } + _destroy(f) + return _get_platform_error(errno) } _fd :: proc(f: ^File) -> uintptr { @@ -106,20 +138,32 @@ _name :: proc(f: ^File) -> string { } _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - res := unix.sys_lseek(f.impl.fd, offset, int(whence)) - if res < 0 { - return -1, _get_platform_error(int(res)) + n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence)) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return res, nil + return n, nil } _read :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n := unix.sys_read(f.impl.fd, &p[0], len(p)) - if n < 0 { - return -1, _get_platform_error(n) + n, errno := linux.read(f.impl.fd, p[:]) + if errno != .NONE { + return -1, _get_platform_error(errno) + } + return i64(n), n == 0 ? io.Error.EOF : nil +} + +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + n, errno := linux.pread(f.impl.fd, p[:], offset) + if errno != .NONE { + return -1, _get_platform_error(errno) } if n == 0 { return 0, .EOF @@ -127,91 +171,67 @@ _read :: proc(f: ^File, p: []byte) -> (i64, Error) { return i64(n), nil } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := p, offset - for len(b) > 0 { - m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset) - if m < 0 { - return -1, _get_platform_error(m) - } - if m == 0 { - return 0, .EOF - } - n += i64(m) - b = b[m:] - offset += i64(m) - } - return -} - _write :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n := unix.sys_write(f.impl.fd, &p[0], uint(len(p))) - if n < 0 { - return -1, _get_platform_error(n) + n, errno := linux.write(f.impl.fd, p[:]) + if errno != .NONE { + return -1, _get_platform_error(errno) } return i64(n), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - b, offset := p, offset - for len(b) > 0 { - m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset) - if m < 0 { - return -1, _get_platform_error(m) - } - n += i64(m) - b = b[m:] - offset += i64(m) + n, errno := linux.pwrite(f.impl.fd, p[:], offset) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return + return i64(n), nil } _file_size :: proc(f: ^File) -> (n: i64, err: Error) { - s: _Stat = --- - res := unix.sys_fstat(f.impl.fd, &s) - if res < 0 { - return -1, _get_platform_error(res) + s: linux.Stat = --- + errno := linux.fstat(f.impl.fd, &s) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return s.size, nil + return i64(s.size), nil } _sync :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fsync(f.impl.fd)) + return _get_platform_error(linux.fsync(f.impl.fd)) } _flush :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fsync(f.impl.fd)) + return _get_platform_error(linux.fsync(f.impl.fd)) } _truncate :: proc(f: ^File, size: i64) -> Error { - return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size)) + return _get_platform_error(linux.ftruncate(f.impl.fd, size)) } _remove :: proc(name: string) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, int(File_Flags.Read)) - if fd < 0 { - return _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {.NOFOLLOW}) + #partial switch (errno) { + case .ELOOP: /* symlink */ + case .NONE: + defer linux.close(fd) + if _is_dir_fd(fd) { + return _get_platform_error(linux.rmdir(name_cstr)) + } + case: + return _get_platform_error(errno) } - defer unix.sys_close(fd) - if _is_dir_fd(fd) { - return _ok_or_error(unix.sys_rmdir(name_cstr)) - } - return _ok_or_error(unix.sys_unlink(name_cstr)) + return _get_platform_error(linux.unlink(name_cstr)) } _rename :: proc(old_name, new_name: string) -> Error { @@ -219,7 +239,7 @@ _rename :: proc(old_name, new_name: string) -> Error { old_name_cstr := temp_cstring(old_name) or_return new_name_cstr := temp_cstring(new_name) or_return - return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) + return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { @@ -227,148 +247,194 @@ _link :: proc(old_name, new_name: string) -> Error { old_name_cstr := temp_cstring(old_name) or_return new_name_cstr := temp_cstring(new_name) or_return - return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) + return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { TEMP_ALLOCATOR_GUARD() old_name_cstr := temp_cstring(old_name) or_return new_name_cstr := temp_cstring(new_name) or_return - - return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) + return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) } _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) { bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { - rc := unix.sys_readlink(name_cstr, &buf[0], bufsz) - if rc < 0 { - delete(buf) - return "", _get_platform_error(rc) - } else if rc == int(bufsz) { + sz, errno := linux.readlink(name_cstr, buf[:]) + if errno != .NONE { + delete(buf, allocator) + return "", _get_platform_error(errno) + } else if sz == int(bufsz) { bufsz *= 2 - delete(buf) + delete(buf, allocator) buf = make([]byte, bufsz, allocator) } else { - return string(buf[:rc]), nil + return string(buf[:sz]), nil } } } -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return return _read_link_cstr(name_cstr, allocator) } -_unlink :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return - return _ok_or_error(unix.sys_unlink(name_cstr)) -} - _chdir :: proc(name: string) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - return _ok_or_error(unix.sys_chdir(name_cstr)) + return _get_platform_error(linux.chdir(name_cstr)) } _fchdir :: proc(f: ^File) -> Error { - return _ok_or_error(unix.sys_fchdir(f.impl.fd)) + return _get_platform_error(linux.fchdir(f.impl.fd)) } _chmod :: proc(name: string, mode: File_Mode) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - return _ok_or_error(unix.sys_chmod(name_cstr, uint(mode))) + return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } _fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - return _ok_or_error(unix.sys_fchmod(f.impl.fd, uint(mode))) + return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode)))) } // NOTE: will throw error without super user priviledges _chown :: proc(name: string, uid, gid: int) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - return _ok_or_error(unix.sys_chown(name_cstr, uid, gid)) + return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _lchown :: proc(name: string, uid, gid: int) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) + return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _fchown :: proc(f: ^File, uid, gid: int) -> Error { - return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid)) + return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid))) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - times := [2]Unix_File_Time { - { atime._nsec, 0 }, - { mtime._nsec, 0 }, + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, } - return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, ×, 0)) + return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, ×[0], nil)) } _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - times := [2]Unix_File_Time { - { atime._nsec, 0 }, - { mtime._nsec, 0 }, + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, } - return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, ×, 0)) + return _get_platform_error(linux.utimensat(f.impl.fd, nil, ×[0], nil)) } _exists :: proc(name: string) -> bool { TEMP_ALLOCATOR_GUARD() name_cstr, _ := temp_cstring(name) - return unix.sys_access(name_cstr, F_OK) == 0 + res, errno := linux.access(name_cstr, linux.F_OK) + return !res && errno == .NONE } _is_file :: proc(name: string) -> bool { TEMP_ALLOCATOR_GUARD() name_cstr, _ := temp_cstring(name) - s: _Stat - res := unix.sys_stat(name_cstr, &s) - if res < 0 { + s: linux.Stat + if linux.stat(name_cstr, &s) != .NONE { return false } - return S_ISREG(s.mode) + return linux.S_ISREG(s.mode) } -_is_file_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error +_is_file_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { return false } - return S_ISREG(s.mode) + return linux.S_ISREG(s.mode) } _is_dir :: proc(name: string) -> bool { TEMP_ALLOCATOR_GUARD() name_cstr, _ := temp_cstring(name) - s: _Stat - res := unix.sys_stat(name_cstr, &s) - if res < 0 { + s: linux.Stat + if linux.stat(name_cstr, &s) != .NONE { return false } - return S_ISDIR(s.mode) + return linux.S_ISDIR(s.mode) } -_is_dir_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error +_is_dir_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { return false } - return S_ISDIR(s.mode) + return linux.S_ISDIR(s.mode) +} + +/* Certain files in the Linux file system are not actual + * files (e.g. everything in /proc/). Therefore, the + * read_entire_file procs fail to actually read anything + * since these "files" stat to a size of 0. Here, we just + * read until there is nothing left. + */ +_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } + +_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { + name_cstr := clone_to_cstring(name, allocator) or_return + defer delete(name, allocator) + return _read_entire_pseudo_file_cstring(name_cstr, allocator) +} + +_read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Allocator) -> ([]u8, Error) { + fd, errno := linux.open(name, {}) + if errno != .NONE { + return nil, _get_platform_error(errno) + } + defer linux.close(fd) + + BUF_SIZE_STEP :: 128 + contents := make([dynamic]u8, 0, BUF_SIZE_STEP, allocator) + + n: int + i: int + for { + resize(&contents, i + BUF_SIZE_STEP) + n, errno = linux.read(fd, contents[i:i+BUF_SIZE_STEP]) + if errno != .NONE { + delete(contents) + return nil, _get_platform_error(errno) + } + if n < BUF_SIZE_STEP { + break + } + i += BUF_SIZE_STEP + } + + resize(&contents, i + n) + + return contents[:], nil } @(private="package") diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index bb4acba13..11cf5ab41 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,7 +1,7 @@ //+private package os2 -import "core:sys/unix" +import "core:sys/linux" import "core:sync" import "core:mem" @@ -97,9 +97,8 @@ CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) -MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE -MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE - +MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE} +MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE} @thread_local _local_region: ^Region global_regions: ^Region @@ -324,11 +323,11 @@ heap_free :: proc(memory: rawptr) { // Regions // _new_region :: proc() -> ^Region #no_bounds_check { - res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) - if res < 0 { + ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) + if errno != .NONE { return nil } - new_region := (^Region)(uintptr(res)) + new_region := (^Region)(ptr) new_region.hdr.local_addr = CURRENTLY_ACTIVE new_region.hdr.reset_addr = &_local_region @@ -634,8 +633,8 @@ _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_chec // _direct_mmap_alloc :: proc(size: int) -> rawptr { mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) - new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) - if new_allocation < 0 && new_allocation > -4096 { + new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) + if errno != .NONE { return nil } @@ -655,13 +654,8 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr return mem.ptr_offset(alloc, 1) } - new_allocation := unix.sys_mremap( - alloc, - uint(old_mmap_size), - uint(new_mmap_size), - unix.MREMAP_MAYMOVE, - ) - if new_allocation < 0 && new_allocation > -4096 { + new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE}) + if errno != .NONE { return nil } @@ -702,7 +696,7 @@ _direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawp _direct_mmap_free :: proc(alloc: ^Allocation_Header) { requested := int(alloc.requested & REQUESTED_MASK) mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) - unix.sys_munmap(alloc, uint(mmap_size)) + linux.munmap(alloc, uint(mmap_size)) } // diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 81176c219..3c08eedee 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -3,104 +3,89 @@ package os2 import "core:strconv" import "base:runtime" -import "core:sys/unix" +import "core:sys/linux" _Path_Separator :: '/' _Path_Separator_String :: "/" _Path_List_Separator :: ':' -_S_IFMT :: 0o170000 // Type of file mask -_S_IFIFO :: 0o010000 // Named pipe (fifo) -_S_IFCHR :: 0o020000 // Character special -_S_IFDIR :: 0o040000 // Directory -_S_IFBLK :: 0o060000 // Block special -_S_IFREG :: 0o100000 // Regular -_S_IFLNK :: 0o120000 // Symbolic link -_S_IFSOCK :: 0o140000 // Socket - -_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC +_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC} _is_path_separator :: proc(c: byte) -> bool { return c == '/' } _mkdir :: proc(path: string, perm: File_Mode) -> Error { - // NOTE: These modes would require sys_mknod, however, that would require - // additional arguments to this function. + // TODO: These modes would require mknod, however, that would also + // require additional arguments to this function.. if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } TEMP_ALLOCATOR_GUARD() path_cstr := temp_cstring(path) or_return - return _ok_or_error(unix.sys_mkdir(path_cstr, uint(perm & 0o777))) + return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777))) } _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - _mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error { - if len(path) == 0 { - return _ok_or_error(unix.sys_close(dfd)) - } + mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { i: int - for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} + for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} + if i == 0 { + return _get_platform_error(linux.close(dfd)) + } path[i] = 0 - new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) - switch new_dfd { - case -ENOENT: - if res := unix.sys_mkdirat(dfd, cstring(&path[0]), uint(perm)); res < 0 { - return _get_platform_error(res) + new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) + #partial switch errno { + case .ENOENT: + if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE { + return _get_platform_error(errno) } has_created^ = true - if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { - return _get_platform_error(new_dfd) + if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE { + return _get_platform_error(errno) } fallthrough - case 0: - if res := unix.sys_close(dfd); res < 0 { - return _get_platform_error(res) + case .NONE: + if errno = linux.close(dfd); errno != .NONE { + return _get_platform_error(errno) } // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} - return _mkdirat(new_dfd, path[i:], perm, has_created) + return mkdirat(new_dfd, path[i:], perm, has_created) case: - return _get_platform_error(new_dfd) + return _get_platform_error(errno) } unreachable() } + // TODO if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } TEMP_ALLOCATOR_GUARD() - // need something we can edit, and use to generate cstrings - allocated: bool - path_bytes: []u8 - if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { - allocated = true - path_bytes = make([]u8, len(path) + 1) - } else { - path_bytes = make([]u8, len(path) + 1, temp_allocator()) - } + path_bytes := make([]u8, len(path) + 1, temp_allocator()) - // NULL terminate the byte slice to make it a valid cstring + // zero terminate the byte slice to make it a valid cstring copy(path_bytes, path) path_bytes[len(path)] = 0 - dfd: int + dfd: linux.Fd + errno: linux.Errno if path_bytes[0] == '/' { - dfd = unix.sys_open("/", _OPENDIR_FLAGS) + dfd, errno = linux.open("/", _OPENDIR_FLAGS) path_bytes = path_bytes[1:] } else { - dfd = unix.sys_open(".", _OPENDIR_FLAGS) + dfd, errno = linux.open(".", _OPENDIR_FLAGS) } - if dfd < 0 { - return _get_platform_error(dfd) + if errno != .NONE { + return _get_platform_error(errno) } has_created: bool - _mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return + mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return if has_created { return nil } @@ -119,28 +104,28 @@ dirent64 :: struct { _remove_all :: proc(path: string) -> Error { DT_DIR :: 4 - _remove_all_dir :: proc(dfd: int) -> Error { + remove_all_dir :: proc(dfd: linux.Fd) -> Error { n := 64 buf := make([]u8, n) defer delete(buf) loop: for { - getdents_res := unix.sys_getdents64(dfd, &buf[0], n) - switch getdents_res { - case -EINVAL: + buflen, errno := linux.getdents(dfd, buf[:]) + #partial switch errno { + case .EINVAL: delete(buf) n *= 2 buf = make([]u8, n) continue loop - case -4096..<0: - return _get_platform_error(getdents_res) - case 0: - break loop + case .NONE: + if buflen == 0 { break loop } + case: + return _get_platform_error(errno) } d: ^dirent64 - for i := 0; i < getdents_res; i += int(d.d_reclen) { + for i := 0; i < buflen; i += int(d.d_reclen) { d = (^dirent64)(rawptr(&buf[i])) d_name_cstr := cstring(&d.d_name[0]) @@ -156,23 +141,22 @@ _remove_all :: proc(path: string) -> Error { continue } - unlink_res: int - switch d.d_type { case DT_DIR: - new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS) - if new_dfd < 0 { - return _get_platform_error(new_dfd) + new_dfd: linux.Fd + new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS) + if errno != .NONE { + return _get_platform_error(errno) } - defer unix.sys_close(new_dfd) - _remove_all_dir(new_dfd) or_return - unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR)) + defer linux.close(new_dfd) + remove_all_dir(new_dfd) or_return + errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR}) case: - unlink_res = unix.sys_unlinkat(dfd, d_name_cstr) + errno = linux.unlinkat(dfd, d_name_cstr, nil) } - if unlink_res < 0 { - return _get_platform_error(unlink_res) + if errno != .NONE { + return _get_platform_error(errno) } } } @@ -182,17 +166,19 @@ _remove_all :: proc(path: string) -> Error { TEMP_ALLOCATOR_GUARD() path_cstr := temp_cstring(path) or_return - fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS) - switch fd { - case -ENOTDIR: - return _ok_or_error(unix.sys_unlink(path_cstr)) - case -4096..<0: - return _get_platform_error(fd) + fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + break + case .ENOTDIR: + return _get_platform_error(linux.unlink(path_cstr)) + case: + return _get_platform_error(errno) } - defer unix.sys_close(fd) - _remove_all_dir(fd) or_return - return _ok_or_error(unix.sys_rmdir(path_cstr)) + defer linux.close(fd) + remove_all_dir(fd) or_return + return _get_platform_error(linux.rmdir(path_cstr)) } _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { @@ -203,13 +189,12 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { PATH_MAX :: 4096 buf := make([dynamic]u8, PATH_MAX, allocator) for { - #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) - - if res >= 0 { - return string_from_null_terminated_bytes(buf[:]), nil + #no_bounds_check n, errno := linux.getcwd(buf[:]) + if errno == .NONE { + return string(buf[:n-1]), nil } - if res != -ERANGE { - return "", _get_platform_error(res) + if errno != .ERANGE { + return "", _get_platform_error(errno) } resize(&buf, len(buf)+PATH_MAX) } @@ -218,16 +203,16 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { _setwd :: proc(dir: string) -> Error { dir_cstr := temp_cstring(dir) or_return - return _ok_or_error(unix.sys_chdir(dir_cstr)) + return _get_platform_error(linux.chdir(dir_cstr)) } -_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string { +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { PROC_FD_PATH :: "/proc/self/fd/" buf: [32]u8 copy(buf[:], PROC_FD_PATH) - strconv.itoa(buf[len(PROC_FD_PATH):], fd) + strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) fullpath: string err: Error @@ -236,4 +221,3 @@ _get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string { } return fullpath } - diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index b66ff9663..5d42cca78 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,7 +1,17 @@ //+private package os2 +import "core:sys/linux" + _pipe :: proc() -> (r, w: ^File, err: Error) { - return nil, nil, nil + fds: [2]linux.Fd + errno := linux.pipe2(&fds, {.CLOEXEC}) + if errno != .NONE { + return nil, nil,_get_platform_error(errno) + } + + r = _new_file(uintptr(fds[0])) + w = _new_file(uintptr(fds[1])) + return } diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 862434b7b..099d1c19e 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -2,36 +2,42 @@ package os2 import "core:sync" import "core:time" -import "base:runtime" +import "core:c" -args: []string +args: []string = _alloc_command_line_arguments() exit :: proc "contextless" (code: int) -> ! { - runtime.trap() + _exit(code) } +@(require_results) get_uid :: proc() -> int { - return -1 + return _get_uid() } +@(require_results) get_euid :: proc() -> int { - return -1 + return _get_euid() } +@(require_results) get_gid :: proc() -> int { - return -1 + return _get_gid() } +@(require_results) get_egid :: proc() -> int { - return -1 + return _get_euid() } +@(require_results) get_pid :: proc() -> int { - return -1 + return _get_pid() } +@(require_results) get_ppid :: proc() -> int { - return -1 + return _get_ppid() } @@ -46,16 +52,12 @@ Process :: struct { Process_Attributes :: struct { dir: string, env: []string, - files: []^File, + stdin: ^File, + stdout: ^File, + stderr: ^File, sys: ^Process_Attributes_OS_Specific, } -Process_Attributes_OS_Specific :: struct{} - -Process_Error :: enum { - None, -} - Process_State :: struct { pid: int, exit_code: int, @@ -66,37 +68,53 @@ Process_State :: struct { sys: rawptr, } -Signal :: #type proc() - -Kill: Signal = nil -Interrupt: Signal = nil - - -find_process :: proc(pid: int) -> (^Process, Process_Error) { - return nil, .None +Signal :: enum { + Abort, + Floating_Point_Exception, + Illegal_Instruction, + Interrupt, + Segmentation_Fault, + Termination, } - -process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { - return nil, .None +Signal_Handler_Proc :: #type proc "c" (c.int) +Signal_Handler_Special :: enum { + Default, + Ignore, } -process_release :: proc(p: ^Process) -> Process_Error { - return .None +Signal_Handler :: union { + Signal_Handler_Proc, + Signal_Handler_Special, } -process_kill :: proc(p: ^Process) -> Process_Error { - return .None +@(require_results) +process_find :: proc(pid: int) -> (Process, Error) { + return _process_find(pid) } -process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { - return .None +@(require_results) +process_get_state :: proc(p: Process) -> (Process_State, Error) { + return _process_get_state(p) } -process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { - return {}, .None +@(require_results) +process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes = nil) -> (Process, Error) { + return _process_start(name, argv, attr) } +process_release :: proc(p: ^Process) -> Error { + return _process_release(p) +} +process_kill :: proc(p: ^Process) -> Error { + return _process_kill(p) +} +process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error { + return _process_signal(sig, h) +} +process_wait :: proc(p: ^Process, t: time.Duration = time.MAX_DURATION) -> (Process_State, Error) { + return _process_wait(p, t) +} diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin new file mode 100644 index 000000000..0484615ec --- /dev/null +++ b/core/os/os2/process_linux.odin @@ -0,0 +1,428 @@ +//+private +package os2 + +import "base:runtime" + +import "core:fmt" +import "core:mem" +import "core:time" +import "core:strings" +import "core:strconv" +import "core:sys/linux" +import "core:path/filepath" + +_alloc_command_line_arguments :: proc() -> []string { + res := make([]string, len(runtime.args__), heap_allocator()) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} + +_exit :: proc "contextless" (code: int) -> ! { + linux.exit_group(i32(code)) +} + +_get_uid :: proc() -> int { + return int(linux.getuid()) +} + +_get_euid :: proc() -> int { + return int(linux.geteuid()) +} + +_get_gid :: proc() -> int { + return int(linux.getgid()) +} + +_get_egid :: proc() -> int { + return int(linux.getegid()) +} + +_get_pid :: proc() -> int { + return int(linux.getpid()) +} + +_get_ppid :: proc() -> int { + return int(linux.getppid()) +} + +Process_Attributes_OS_Specific :: struct {} + +_process_find :: proc(pid: int) -> (Process, Error) { + TEMP_ALLOCATOR_GUARD() + pid_path := fmt.ctprintf("/proc/%d", pid) + + p: Process + dir_fd: linux.Fd + errno: linux.Errno + + #partial switch dir_fd, errno = linux.open(pid_path, _OPENDIR_FLAGS); errno { + case .NONE: + linux.close(dir_fd) + p.pid = pid + return p, nil + case .ENOTDIR: + return p, .Invalid_Dir + case .ENOENT: + return p, .Not_Exist + } + return p, _get_platform_error(errno) +} + +_process_get_state :: proc(p: Process) -> (state: Process_State, err: Error) { + TEMP_ALLOCATOR_GUARD() + + stat_name := fmt.ctprintf("/proc/%d/stat", p.pid) + stat_buf: []u8 + stat_buf, err = _read_entire_pseudo_file(stat_name, temp_allocator()) + + if err != nil { + return + } + + idx := strings.last_index_byte(string(stat_buf), ')') + stats := string(stat_buf[idx + 2:]) + + // utime and stime are the 12 and 13th items, respectively + // skip the first 11 items here. + for i := 0; i < 11; i += 1 { + stats = stats[strings.index_byte(stats, ' ') + 1:] + } + + idx = strings.index_byte(stats, ' ') + utime_str := stats[:idx] + + stats = stats[idx + 1:] + stime_str := stats[:strings.index_byte(stats, ' ')] + + utime, _ := strconv.parse_int(utime_str, 10) + stime, _ := strconv.parse_int(stime_str, 10) + + // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms + state.user_time = time.Duration(utime) * 10 * time.Millisecond + state.system_time = time.Duration(stime) * 10 * time.Millisecond + + return +} + +_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (child: Process, err: Error) { + TEMP_ALLOCATOR_GUARD() + + dir_fd := linux.AT_FDCWD + errno: linux.Errno + if attr != nil && attr.dir != "" { + dir_cstr := temp_cstring(attr.dir) or_return + if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { + return child, _get_platform_error(errno) + } + } + + // search PATH if just a plain name is provided + executable: cstring + if !strings.contains_rune(name, '/') { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) + found: bool + for dir in path_dirs { + executable = fmt.ctprintf("%s/%s", dir, name) + fail: bool + if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno == .NONE && !fail { + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + executable = fmt.ctprintf("./%s", name) + fail: bool + if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno != .NONE || fail { + return child, .Not_Exist + } + } + } else { + executable = temp_cstring(name) or_return + } + + not_exec: bool + if not_exec, errno = linux.faccessat(dir_fd, executable, linux.F_OK | linux.X_OK); errno != .NONE || not_exec { + return child, errno == .NONE ? .Permission_Denied : _get_platform_error(errno) + } + + // args and environment need to be a list of cstrings + // that are terminated by a nil pointer. + // The first argument is a copy of the executable name. + cargs := make([]cstring, len(argv) + 2, temp_allocator()) + cargs[0] = executable + for i := 0; i < len(argv); i += 1 { + cargs[i + 1] = temp_cstring(argv[i]) or_return + } + + // Use current process's environment if attributes not provided + env: [^]cstring + if attr == nil { + // take this process's current environment + env = raw_data(export_cstring_environment(temp_allocator())) + } else { + cenv := make([]cstring, len(attr.env) + 1, temp_allocator()) + for i := 0; i < len(attr.env); i += 1 { + cenv[i] = temp_cstring(attr.env[i]) or_return + } + env = &cenv[0] + } + + // TODO: This is the traditional textbook implementation with fork. + // A more efficient implementation with vfork: + // + // 1. retrieve signal handlers + // 2. block all signals + // 3. allocate some stack space + // 4. vfork (waits for child exit or execve); In child: + // a. set child signal handlers + // b. set up any necessary pipes + // c. execve + // 5. restore signal handlers + // + stdin_fds: [2]linux.Fd + stdout_fds: [2]linux.Fd + stderr_fds: [2]linux.Fd + if attr != nil && attr.stdin != nil { + if errno = linux.pipe2(&stdin_fds, nil); errno != .NONE { + return child, _get_platform_error(errno) + } + } + if attr != nil && attr.stdout != nil { + if errno = linux.pipe2(&stdout_fds, nil); errno != .NONE { + return child, _get_platform_error(errno) + } + } + if attr != nil && attr.stderr != nil { + if errno = linux.pipe2(&stderr_fds, nil); errno != .NONE { + return child, _get_platform_error(errno) + } + } + + pid: linux.Pid + if pid, errno = linux.fork(); errno != .NONE { + return child, _get_platform_error(errno) + } + + IN :: 1 + OUT :: 0 + + STDIN :: linux.Fd(0) + STDOUT :: linux.Fd(1) + STDERR :: linux.Fd(2) + + if pid == 0 { + // in child process now + if attr != nil && attr.stdin != nil { + if linux.close(stdin_fds[IN]) != .NONE { linux.exit(1) } + if _, errno = linux.dup2(stdin_fds[OUT], STDIN); errno != .NONE { linux.exit(1) } + if linux.close(stdin_fds[OUT]) != .NONE { linux.exit(1) } + } + if attr != nil && attr.stdout != nil { + if linux.close(stdout_fds[OUT]) != .NONE { linux.exit(1) } + if _, errno = linux.dup2(stdout_fds[IN], STDOUT); errno != .NONE { linux.exit(1) } + if linux.close(stdout_fds[IN]) != .NONE { linux.exit(1) } + } + if attr != nil && attr.stderr != nil { + if linux.close(stderr_fds[OUT]) != .NONE { linux.exit(1) } + if _, errno = linux.dup2(stderr_fds[IN], STDERR); errno != .NONE { linux.exit(1) } + if linux.close(stderr_fds[IN]) != .NONE { linux.exit(1) } + } + + if errno = linux.execveat(dir_fd, executable, &cargs[OUT], env); errno != .NONE { + print_error(_get_platform_error(errno), string(executable)) + panic("execve failed to replace process") + } + unreachable() + } + + // in parent process + if attr != nil && attr.stdin != nil { + linux.close(stdin_fds[OUT]) + _construct_file(attr.stdin, uintptr(stdin_fds[IN])) + } + if attr != nil && attr.stdout != nil { + linux.close(stdout_fds[IN]) + _construct_file(attr.stdout, uintptr(stdout_fds[OUT])) + } + if attr != nil && attr.stderr != nil { + linux.close(stderr_fds[IN]) + _construct_file(attr.stderr, uintptr(stderr_fds[OUT])) + } + + child.pid = int(pid) + return child, nil +} + +_process_release :: proc(p: ^Process) -> Error { + // We didn't allocate... + return nil +} + +_process_kill :: proc(p: ^Process) -> Error { + res := linux.kill(linux.Pid(p.pid), .SIGKILL) + return _get_platform_error(res) +} + +_process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error { + signo: linux.Signal + switch sig { + case .Abort: signo = .SIGABRT + case .Floating_Point_Exception: signo = .SIGFPE + case .Illegal_Instruction: signo = .SIGILL + case .Interrupt: signo = .SIGINT + case .Segmentation_Fault: signo = .SIGSEGV + case .Termination: signo = .SIGTERM + } + + sigact: linux.Sig_Action(int) + old: ^linux.Sig_Action(int) = nil + + switch v in h { + case Signal_Handler_Special: + switch v { + case .Default: + sigact.special = .SIG_DFL + case .Ignore: + sigact.special = .SIG_IGN + } + case Signal_Handler_Proc: + sigact.handler = (linux.Sig_Handler_Fn)(v) + } + + return _get_platform_error(linux.rt_sigaction(signo, &sigact, old)) +} + +_process_wait :: proc(p: ^Process, t: time.Duration) -> (state: Process_State, err: Error) { + safe_state :: proc(p: Process, state: Process_State = {}) -> (Process_State, Error) { + // process_get_state can fail, so we don't want to return it directly. + if new_state, err := _process_get_state(p); err == nil { + return new_state, nil + } + return state, nil + } + + state.pid = p.pid + + options: linux.Wait_Options + big_if: if t == 0 { + options += {.WNOHANG} + } else if t != time.MAX_DURATION { + ts: linux.Time_Spec = { + time_sec = uint(t / time.Second), + time_nsec = uint(t % time.Second), + } + + @static has_pidfd_open: bool = true + + // pidfd_open is fairly new, so don't error out on ENOSYS + pid_fd: linux.Pid_FD + errno: linux.Errno + if has_pidfd_open { + pid_fd, errno = linux.pidfd_open(linux.Pid(p.pid), nil) + if errno != .NONE && errno != .ENOSYS { + return state, _get_platform_error(errno) + } + } + + if has_pidfd_open && errno != .ENOSYS { + defer linux.close(linux.Fd(pid_fd)) + pollfd: [1]linux.Poll_Fd = { + { + fd = linux.Fd(pid_fd), + events = {.IN}, + }, + } + for { + n, e := linux.ppoll(pollfd[:], &ts, nil) + if e == .EINTR { + continue + } + if e != .NONE { + return state, _get_platform_error(errno) + } + if n == 0 { + return safe_state(p^, state) + } + break + } + } else { + has_pidfd_open = false + mask: bit_set[0..=63] + mask += { int(linux.Signal.SIGCHLD) - 1 } + + org_sigset: linux.Sig_Set + sigset: linux.Sig_Set + mem.copy(&sigset, &mask, size_of(mask)) + errno = linux.rt_sigprocmask(.SIG_BLOCK, &sigset, &org_sigset) + if errno != .NONE { + return state, _get_platform_error(errno) + } + defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) + + // In case there was a signal handler on SIGCHLD, avoid race + // condition by checking wait first. + options += {.WNOHANG} + waitid_options := options + {.WNOWAIT, .WEXITED} + info: linux.Sig_Info + errno = linux.waitid(.PID, linux.Id(p.pid), &info, waitid_options, nil) + if errno == .NONE && info.code != 0 { + break big_if + } + + loop: for { + sigset = {} + mem.copy(&sigset, &mask, size_of(mask)) + + _, errno = linux.rt_sigtimedwait(&sigset, &info, &ts) + #partial switch errno { + case .EAGAIN: // timeout + return safe_state(p^, state) + case .EINVAL: + return state, _get_platform_error(errno) + case .EINTR: + continue + case: + if int(info.pid) == p.pid { + break loop + } + } + } + } + } + + state, _ = safe_state(p^, state) + + status: u32 + errno: linux.Errno = .EINTR + for errno == .EINTR { + _, errno = linux.wait4(linux.Pid(p.pid), &status, options, nil) + if errno != .NONE { + return state, _get_platform_error(errno) + } + } + + // terminated by exit + if linux.WIFEXITED(status) { + p.is_done = true + state.exited = true + state.exit_code = int(linux.WEXITSTATUS(status)) + state.success = state.exit_code == 0 + return state, nil + } + + // terminated by signal + if linux.WIFSIGNALED(status) { + // NOTE: what's the correct behavior here?? + p.is_done = true + state.exited = false + state.exit_code = int(linux.WTERMSIG(status)) + state.success = false + return state, nil + } + + return safe_state(p^, state) +} diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin new file mode 100644 index 000000000..4b5e4bc02 --- /dev/null +++ b/core/os/os2/process_windows.odin @@ -0,0 +1,67 @@ +//+private +package os2 + +import "core:runtime" +import "core:time" + +_alloc_command_line_arguments :: proc() -> []string { + return nil +} + +_exit :: proc "contextless" (_: int) -> ! { + runtime.trap() +} + +_get_uid :: proc() -> int { + return -1 +} + +_get_euid :: proc() -> int { + return -1 +} + +_get_gid :: proc() -> int { + return -1 +} + +_get_egid :: proc() -> int { + return -1 +} + +_get_pid :: proc() -> int { + return -1 +} + +_get_ppid :: proc() -> int { + return -1 +} + +Process_Attributes_OS_Specific :: struct{} + +_process_find :: proc(pid: int) -> (Process, Error) { + return Process{}, nil +} + +_process_get_state :: proc(p: Process) -> (Process_State, Error) { + return Process_State{}, nil +} + +_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (Process, Error) { + return Process{}, nil +} + +_process_release :: proc(p: ^Process) -> Error { + return nil +} + +_process_kill :: proc(p: ^Process) -> Error { + return nil +} + +_process_signal :: proc(sig: Signal, handler: Signal_Handler) -> Error { + return nil +} + +_process_wait :: proc(p: ^Process, t: time.Duration) -> (Process_State, Error) { + return Process_State{}, nil +} diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index dc287cafe..c0b3088b4 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -3,108 +3,32 @@ package os2 import "core:time" import "base:runtime" -import "core:sys/unix" +import "core:sys/linux" import "core:path/filepath" -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - - // Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - - // Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - - -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0 // Test for file existance -X_OK :: 1 // Test for execute permission -W_OK :: 2 // Test for write permission -R_OK :: 4 // Test for read permission - -@private -Unix_File_Time :: struct { - seconds: i64, - nanoseconds: i64, -} - -@private -_Stat :: struct { - device_id: u64, // ID of device containing file - serial: u64, // File serial number - nlink: u64, // Number of hard links - mode: u32, // Mode of the file - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - _padding: i32, // 32 bits of padding - rdev: u64, // Device ID, if device - size: i64, // Size of the file, in bytes - block_size: i64, // Optimal bllocksize for I/O - blocks: i64, // Number of 512-byte blocks allocated - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - - _reserve1, - _reserve2, - _reserve3: i64, -} - - _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { return _fstat_internal(f.impl.fd, allocator) } -_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) { - s: _Stat - result := unix.sys_fstat(fd, &s) - if result < 0 { - return {}, _get_platform_error(result) +_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { + s: linux.Stat + errno := linux.fstat(fd, &s) + if errno != .NONE { + return {}, _get_platform_error(errno) } // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { fullpath = _get_full_path(fd, allocator), name = "", - size = s.size, + size = i64(s.size), mode = 0, - is_directory = S_ISDIR(s.mode), - modification_time = time.Time {s.modified.seconds}, - access_time = time.Time {s.last_access.seconds}, - creation_time = time.Time{0}, // regular stat does not provide this + is_directory = linux.S_ISDIR(s.mode), + modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } + fi.creation_time = fi.modification_time fi.name = filepath.base(fi.fullpath) return fi, nil @@ -115,11 +39,11 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, _O_RDONLY) - if fd < 0 { - return {}, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {}) + if errno != .NONE { + return {}, _get_platform_error(errno) } - defer unix.sys_close(fd) + defer linux.close(fd) return _fstat_internal(fd, allocator) } @@ -127,11 +51,11 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return - fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) - if fd < 0 { - return {}, _get_platform_error(fd) + fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) + if errno != .NONE { + return {}, _get_platform_error(errno) } - defer unix.sys_close(fd) + defer linux.close(fd) return _fstat_internal(fd, allocator) } From a15cbc474dd163457bb4677113ec0ee6cb9a12eb Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 28 Jun 2024 07:45:24 -0400 Subject: [PATCH 3/5] change error strings to an enumerated array in rodata; print_error takes a file argument --- core/os/os2/errors.odin | 4 +- core/os/os2/errors_linux.odin | 273 +++++++++++++++++---------------- core/os/os2/process_linux.odin | 2 +- core/sys/linux/bits.odin | 2 + 4 files changed, 142 insertions(+), 139 deletions(-) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index f7cfdbd3d..51d8314b4 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -100,7 +100,7 @@ error_string :: proc(ferr: Error) -> string { return "unknown error" } -print_error :: proc(ferr: Error, msg: string) { +print_error :: proc(f: ^File, ferr: Error, msg: string) { TEMP_ALLOCATOR_GUARD() err_str := error_string(ferr) @@ -113,5 +113,5 @@ print_error :: proc(ferr: Error, msg: string) { buf[len(msg) + 1] = ' ' copy(buf[len(msg) + 2:], err_str) buf[length - 1] = '\n' - write(stderr, buf) + write(f, buf) } diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 503f10671..d7234ce8b 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -3,141 +3,142 @@ package os2 import "core:sys/linux" -_errno_strings : [int(max(linux.Errno)) + 1]string = { - linux.Errno.NONE = "Success", - linux.Errno.EPERM = "Operation not permitted", - linux.Errno.ENOENT = "No such file or directory", - linux.Errno.ESRCH = "No such process", - linux.Errno.EINTR = "Interrupted system call", - linux.Errno.EIO = "Input/output error", - linux.Errno.ENXIO = "No such device or address", - linux.Errno.E2BIG = "Argument list too long", - linux.Errno.ENOEXEC = "Exec format error", - linux.Errno.EBADF = "Bad file descriptor", - linux.Errno.ECHILD = "No child processes", - linux.Errno.EAGAIN = "Resource temporarily unavailable", - linux.Errno.ENOMEM = "Cannot allocate memory", - linux.Errno.EACCES = "Permission denied", - linux.Errno.EFAULT = "Bad address", - linux.Errno.ENOTBLK = "Block device required", - linux.Errno.EBUSY = "Device or resource busy", - linux.Errno.EEXIST = "File exists", - linux.Errno.EXDEV = "Invalid cross-device link", - linux.Errno.ENODEV = "No such device", - linux.Errno.ENOTDIR = "Not a directory", - linux.Errno.EISDIR = "Is a directory", - linux.Errno.EINVAL = "Invalid argument", - linux.Errno.ENFILE = "Too many open files in system", - linux.Errno.EMFILE = "Too many open files", - linux.Errno.ENOTTY = "Inappropriate ioctl for device", - linux.Errno.ETXTBSY = "Text file busy", - linux.Errno.EFBIG = "File too large", - linux.Errno.ENOSPC = "No space left on device", - linux.Errno.ESPIPE = "Illegal seek", - linux.Errno.EROFS = "Read-only file system", - linux.Errno.EMLINK = "Too many links", - linux.Errno.EPIPE = "Broken pipe", - linux.Errno.EDOM = "Numerical argument out of domain", - linux.Errno.ERANGE = "Numerical result out of range", - linux.Errno.EDEADLK = "Resource deadlock avoided", - linux.Errno.ENAMETOOLONG = "File name too long", - linux.Errno.ENOLCK = "No locks available", - linux.Errno.ENOSYS = "Function not implemented", - linux.Errno.ENOTEMPTY = "Directory not empty", - linux.Errno.ELOOP = "Too many levels of symbolic links", - 41 = "Unknown Error (41)", - linux.Errno.ENOMSG = "No message of desired type", - linux.Errno.EIDRM = "Identifier removed", - linux.Errno.ECHRNG = "Channel number out of range", - linux.Errno.EL2NSYNC = "Level 2 not synchronized", - linux.Errno.EL3HLT = "Level 3 halted", - linux.Errno.EL3RST = "Level 3 reset", - linux.Errno.ELNRNG = "Link number out of range", - linux.Errno.EUNATCH = "Protocol driver not attached", - linux.Errno.ENOCSI = "No CSI structure available", - linux.Errno.EL2HLT = "Level 2 halted", - linux.Errno.EBADE = "Invalid exchange", - linux.Errno.EBADR = "Invalid request descriptor", - linux.Errno.EXFULL = "Exchange full", - linux.Errno.ENOANO = "No anode", - linux.Errno.EBADRQC = "Invalid request code", - linux.Errno.EBADSLT = "Invalid slot", - 58 = "Unknown Error (58)", - linux.Errno.EBFONT = "Bad font file format", - linux.Errno.ENOSTR = "Device not a stream", - linux.Errno.ENODATA = "No data available", - linux.Errno.ETIME = "Timer expired", - linux.Errno.ENOSR = "Out of streams resources", - linux.Errno.ENONET = "Machine is not on the network", - linux.Errno.ENOPKG = "Package not installed", - linux.Errno.EREMOTE = "Object is remote", - linux.Errno.ENOLINK = "Link has been severed", - linux.Errno.EADV = "Advertise error", - linux.Errno.ESRMNT = "Srmount error", - linux.Errno.ECOMM = "Communication error on send", - linux.Errno.EPROTO = "Protocol error", - linux.Errno.EMULTIHOP = "Multihop attempted", - linux.Errno.EDOTDOT = "RFS specific error", - linux.Errno.EBADMSG = "Bad message", - linux.Errno.EOVERFLOW = "Value too large for defined data type", - linux.Errno.ENOTUNIQ = "Name not unique on network", - linux.Errno.EBADFD = "File descriptor in bad state", - linux.Errno.EREMCHG = "Remote address changed", - linux.Errno.ELIBACC = "Can not access a needed shared library", - linux.Errno.ELIBBAD = "Accessing a corrupted shared library", - linux.Errno.ELIBSCN = ".lib section in a.out corrupted", - linux.Errno.ELIBMAX = "Attempting to link in too many shared libraries", - linux.Errno.ELIBEXEC = "Cannot exec a shared library directly", - linux.Errno.EILSEQ = "Invalid or incomplete multibyte or wide character", - linux.Errno.ERESTART = "Interrupted system call should be restarted", - linux.Errno.ESTRPIPE = "Streams pipe error", - linux.Errno.EUSERS = "Too many users", - linux.Errno.ENOTSOCK = "Socket operation on non-socket", - linux.Errno.EDESTADDRREQ = "Destination address required", - linux.Errno.EMSGSIZE = "Message too long", - linux.Errno.EPROTOTYPE = "Protocol wrong type for socket", - linux.Errno.ENOPROTOOPT = "Protocol not available", - linux.Errno.EPROTONOSUPPORT = "Protocol not supported", - linux.Errno.ESOCKTNOSUPPORT = "Socket type not supported", - linux.Errno.EOPNOTSUPP = "Operation not supported", - linux.Errno.EPFNOSUPPORT = "Protocol family not supported", - linux.Errno.EAFNOSUPPORT = "Address family not supported by protocol", - linux.Errno.EADDRINUSE = "Address already in use", - linux.Errno.EADDRNOTAVAIL = "Cannot assign requested address", - linux.Errno.ENETDOWN = "Network is down", - linux.Errno.ENETUNREACH = "Network is unreachable", - linux.Errno.ENETRESET = "Network dropped connection on reset", - linux.Errno.ECONNABORTED = "Software caused connection abort", - linux.Errno.ECONNRESET = "Connection reset by peer", - linux.Errno.ENOBUFS = "No buffer space available", - linux.Errno.EISCONN = "Transport endpoint is already connected", - linux.Errno.ENOTCONN = "Transport endpoint is not connected", - linux.Errno.ESHUTDOWN = "Cannot send after transport endpoint shutdown", - linux.Errno.ETOOMANYREFS = "Too many references: cannot splice", - linux.Errno.ETIMEDOUT = "Connection timed out", - linux.Errno.ECONNREFUSED = "Connection refused", - linux.Errno.EHOSTDOWN = "Host is down", - linux.Errno.EHOSTUNREACH = "No route to host", - linux.Errno.EALREADY = "Operation already in progress", - linux.Errno.EINPROGRESS = "Operation now in progress", - linux.Errno.ESTALE = "Stale file handle", - linux.Errno.EUCLEAN = "Structure needs cleaning", - linux.Errno.ENOTNAM = "Not a XENIX named type file", - linux.Errno.ENAVAIL = "No XENIX semaphores available", - linux.Errno.EISNAM = "Is a named type file", - linux.Errno.EREMOTEIO = "Remote I/O error", - linux.Errno.EDQUOT = "Disk quota exceeded", - linux.Errno.ENOMEDIUM = "No medium found", - linux.Errno.EMEDIUMTYPE = "Wrong medium type", - linux.Errno.ECANCELED = "Operation canceled", - linux.Errno.ENOKEY = "Required key not available", - linux.Errno.EKEYEXPIRED = "Key has expired", - linux.Errno.EKEYREVOKED = "Key has been revoked", - linux.Errno.EKEYREJECTED = "Key was rejected by service", - linux.Errno.EOWNERDEAD = "Owner died", - linux.Errno.ENOTRECOVERABLE = "State not recoverable", - linux.Errno.ERFKILL = "Operation not possible due to RF-kill", - linux.Errno.EHWPOISON = "Memory page has hardware error", +@(rodata) +_errno_strings : [linux.Errno]string = { + .NONE = "Success", + .EPERM = "Operation not permitted", + .ENOENT = "No such file or directory", + .ESRCH = "No such process", + .EINTR = "Interrupted system call", + .EIO = "Input/output error", + .ENXIO = "No such device or address", + .E2BIG = "Argument list too long", + .ENOEXEC = "Exec format error", + .EBADF = "Bad file descriptor", + .ECHILD = "No child processes", + .EAGAIN = "Resource temporarily unavailable", + .ENOMEM = "Cannot allocate memory", + .EACCES = "Permission denied", + .EFAULT = "Bad address", + .ENOTBLK = "Block device required", + .EBUSY = "Device or resource busy", + .EEXIST = "File exists", + .EXDEV = "Invalid cross-device link", + .ENODEV = "No such device", + .ENOTDIR = "Not a directory", + .EISDIR = "Is a directory", + .EINVAL = "Invalid argument", + .ENFILE = "Too many open files in system", + .EMFILE = "Too many open files", + .ENOTTY = "Inappropriate ioctl for device", + .ETXTBSY = "Text file busy", + .EFBIG = "File too large", + .ENOSPC = "No space left on device", + .ESPIPE = "Illegal seek", + .EROFS = "Read-only file system", + .EMLINK = "Too many links", + .EPIPE = "Broken pipe", + .EDOM = "Numerical argument out of domain", + .ERANGE = "Numerical result out of range", + .EDEADLK = "Resource deadlock avoided", + .ENAMETOOLONG = "File name too long", + .ENOLCK = "No locks available", + .ENOSYS = "Function not implemented", + .ENOTEMPTY = "Directory not empty", + .ELOOP = "Too many levels of symbolic links", + .EUNKNOWN_41 = "Unknown Error (41)", + .ENOMSG = "No message of desired type", + .EIDRM = "Identifier removed", + .ECHRNG = "Channel number out of range", + .EL2NSYNC = "Level 2 not synchronized", + .EL3HLT = "Level 3 halted", + .EL3RST = "Level 3 reset", + .ELNRNG = "Link number out of range", + .EUNATCH = "Protocol driver not attached", + .ENOCSI = "No CSI structure available", + .EL2HLT = "Level 2 halted", + .EBADE = "Invalid exchange", + .EBADR = "Invalid request descriptor", + .EXFULL = "Exchange full", + .ENOANO = "No anode", + .EBADRQC = "Invalid request code", + .EBADSLT = "Invalid slot", + .EUNKNOWN_58 = "Unknown Error (58)", + .EBFONT = "Bad font file format", + .ENOSTR = "Device not a stream", + .ENODATA = "No data available", + .ETIME = "Timer expired", + .ENOSR = "Out of streams resources", + .ENONET = "Machine is not on the network", + .ENOPKG = "Package not installed", + .EREMOTE = "Object is remote", + .ENOLINK = "Link has been severed", + .EADV = "Advertise error", + .ESRMNT = "Srmount error", + .ECOMM = "Communication error on send", + .EPROTO = "Protocol error", + .EMULTIHOP = "Multihop attempted", + .EDOTDOT = "RFS specific error", + .EBADMSG = "Bad message", + .EOVERFLOW = "Value too large for defined data type", + .ENOTUNIQ = "Name not unique on network", + .EBADFD = "File descriptor in bad state", + .EREMCHG = "Remote address changed", + .ELIBACC = "Can not access a needed shared library", + .ELIBBAD = "Accessing a corrupted shared library", + .ELIBSCN = ".lib section in a.out corrupted", + .ELIBMAX = "Attempting to link in too many shared libraries", + .ELIBEXEC = "Cannot exec a shared library directly", + .EILSEQ = "Invalid or incomplete multibyte or wide character", + .ERESTART = "Interrupted system call should be restarted", + .ESTRPIPE = "Streams pipe error", + .EUSERS = "Too many users", + .ENOTSOCK = "Socket operation on non-socket", + .EDESTADDRREQ = "Destination address required", + .EMSGSIZE = "Message too long", + .EPROTOTYPE = "Protocol wrong type for socket", + .ENOPROTOOPT = "Protocol not available", + .EPROTONOSUPPORT = "Protocol not supported", + .ESOCKTNOSUPPORT = "Socket type not supported", + .EOPNOTSUPP = "Operation not supported", + .EPFNOSUPPORT = "Protocol family not supported", + .EAFNOSUPPORT = "Address family not supported by protocol", + .EADDRINUSE = "Address already in use", + .EADDRNOTAVAIL = "Cannot assign requested address", + .ENETDOWN = "Network is down", + .ENETUNREACH = "Network is unreachable", + .ENETRESET = "Network dropped connection on reset", + .ECONNABORTED = "Software caused connection abort", + .ECONNRESET = "Connection reset by peer", + .ENOBUFS = "No buffer space available", + .EISCONN = "Transport endpoint is already connected", + .ENOTCONN = "Transport endpoint is not connected", + .ESHUTDOWN = "Cannot send after transport endpoint shutdown", + .ETOOMANYREFS = "Too many references: cannot splice", + .ETIMEDOUT = "Connection timed out", + .ECONNREFUSED = "Connection refused", + .EHOSTDOWN = "Host is down", + .EHOSTUNREACH = "No route to host", + .EALREADY = "Operation already in progress", + .EINPROGRESS = "Operation now in progress", + .ESTALE = "Stale file handle", + .EUCLEAN = "Structure needs cleaning", + .ENOTNAM = "Not a XENIX named type file", + .ENAVAIL = "No XENIX semaphores available", + .EISNAM = "Is a named type file", + .EREMOTEIO = "Remote I/O error", + .EDQUOT = "Disk quota exceeded", + .ENOMEDIUM = "No medium found", + .EMEDIUMTYPE = "Wrong medium type", + .ECANCELED = "Operation canceled", + .ENOKEY = "Required key not available", + .EKEYEXPIRED = "Key has expired", + .EKEYREVOKED = "Key has been revoked", + .EKEYREJECTED = "Key was rejected by service", + .EOWNERDEAD = "Owner died", + .ENOTRECOVERABLE = "State not recoverable", + .ERFKILL = "Operation not possible due to RF-kill", + .EHWPOISON = "Memory page has hardware error", } @@ -158,7 +159,7 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error { _error_string :: proc(errno: i32) -> string { if errno >= 0 && errno <= i32(max(linux.Errno)) { - return _errno_strings[errno] + return _errno_strings[linux.Errno(errno)] } return "Unknown Error" } diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 0484615ec..aec838d9d 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -233,7 +233,7 @@ _process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) } if errno = linux.execveat(dir_fd, executable, &cargs[OUT], env); errno != .NONE { - print_error(_get_platform_error(errno), string(executable)) + print_error(stderr, _get_platform_error(errno), string(executable)) panic("execve failed to replace process") } unreachable() diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index bf7ab4fae..bd686ed63 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -48,6 +48,7 @@ Errno :: enum i32 { ENOSYS = 38, ENOTEMPTY = 39, ELOOP = 40, + EUNKNOWN_41 = 41, ENOMSG = 42, EIDRM = 43, ECHRNG = 44, @@ -64,6 +65,7 @@ Errno :: enum i32 { ENOANO = 55, EBADRQC = 56, EBADSLT = 57, + EUNKNOWN_58 = 58, EBFONT = 59, ENOSTR = 60, ENODATA = 61, From dc954307d71b1602e50310d578070cb1547780e7 Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 28 Jun 2024 07:55:33 -0400 Subject: [PATCH 4/5] fix assumption about std handles in os2/file.odin --- core/os/os2/file.odin | 8 +++----- core/os/os2/file_linux.odin | 4 ++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 98cfc0815..236423163 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -45,11 +45,9 @@ O_TRUNC :: File_Flags{.Trunc} O_SPARSE :: File_Flags{.Sparse} O_CLOEXEC :: File_Flags{.Close_On_Exec} - -stdin: ^File = &_stdin -stdout: ^File = &_stdout -stderr: ^File = &_stderr - +stdin: ^File = nil // OS-Specific +stdout: ^File = nil // OS-Specific +stderr: ^File = nil // OS-Specific @(require_results) create :: proc(name: string) -> (^File, Error) { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 062c4aa0a..8e7db9751 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -49,6 +49,10 @@ _standard_stream_init :: proc() { _stdin.stream.data = &_stdin _stdout.stream.data = &_stdout _stderr.stream.data = &_stderr + + stdin = &_stdin + stdout = &_stdout + stderr = &_stderr } _file_allocator :: proc() -> runtime.Allocator { From 6a894195cbcf2caab2d84d75cc33f933e2390774 Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 28 Jun 2024 09:45:22 -0400 Subject: [PATCH 5/5] revert os2/process --- core/os/os2/process.odin | 88 +++---- core/os/os2/process_linux.odin | 428 ------------------------------- core/os/os2/process_windows.odin | 67 ----- 3 files changed, 35 insertions(+), 548 deletions(-) delete mode 100644 core/os/os2/process_linux.odin delete mode 100644 core/os/os2/process_windows.odin diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 099d1c19e..862434b7b 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -2,42 +2,36 @@ package os2 import "core:sync" import "core:time" -import "core:c" +import "base:runtime" -args: []string = _alloc_command_line_arguments() +args: []string exit :: proc "contextless" (code: int) -> ! { - _exit(code) + runtime.trap() } -@(require_results) get_uid :: proc() -> int { - return _get_uid() + return -1 } -@(require_results) get_euid :: proc() -> int { - return _get_euid() + return -1 } -@(require_results) get_gid :: proc() -> int { - return _get_gid() + return -1 } -@(require_results) get_egid :: proc() -> int { - return _get_euid() + return -1 } -@(require_results) get_pid :: proc() -> int { - return _get_pid() + return -1 } -@(require_results) get_ppid :: proc() -> int { - return _get_ppid() + return -1 } @@ -52,12 +46,16 @@ Process :: struct { Process_Attributes :: struct { dir: string, env: []string, - stdin: ^File, - stdout: ^File, - stderr: ^File, + files: []^File, sys: ^Process_Attributes_OS_Specific, } +Process_Attributes_OS_Specific :: struct{} + +Process_Error :: enum { + None, +} + Process_State :: struct { pid: int, exit_code: int, @@ -68,53 +66,37 @@ Process_State :: struct { sys: rawptr, } -Signal :: enum { - Abort, - Floating_Point_Exception, - Illegal_Instruction, - Interrupt, - Segmentation_Fault, - Termination, +Signal :: #type proc() + +Kill: Signal = nil +Interrupt: Signal = nil + + +find_process :: proc(pid: int) -> (^Process, Process_Error) { + return nil, .None } -Signal_Handler_Proc :: #type proc "c" (c.int) -Signal_Handler_Special :: enum { - Default, - Ignore, + +process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { + return nil, .None } -Signal_Handler :: union { - Signal_Handler_Proc, - Signal_Handler_Special, +process_release :: proc(p: ^Process) -> Process_Error { + return .None } -@(require_results) -process_find :: proc(pid: int) -> (Process, Error) { - return _process_find(pid) +process_kill :: proc(p: ^Process) -> Process_Error { + return .None } -@(require_results) -process_get_state :: proc(p: Process) -> (Process_State, Error) { - return _process_get_state(p) +process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { + return .None } -@(require_results) -process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes = nil) -> (Process, Error) { - return _process_start(name, argv, attr) +process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { + return {}, .None } -process_release :: proc(p: ^Process) -> Error { - return _process_release(p) -} -process_kill :: proc(p: ^Process) -> Error { - return _process_kill(p) -} -process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error { - return _process_signal(sig, h) -} -process_wait :: proc(p: ^Process, t: time.Duration = time.MAX_DURATION) -> (Process_State, Error) { - return _process_wait(p, t) -} diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin deleted file mode 100644 index aec838d9d..000000000 --- a/core/os/os2/process_linux.odin +++ /dev/null @@ -1,428 +0,0 @@ -//+private -package os2 - -import "base:runtime" - -import "core:fmt" -import "core:mem" -import "core:time" -import "core:strings" -import "core:strconv" -import "core:sys/linux" -import "core:path/filepath" - -_alloc_command_line_arguments :: proc() -> []string { - res := make([]string, len(runtime.args__), heap_allocator()) - for arg, i in runtime.args__ { - res[i] = string(arg) - } - return res -} - -_exit :: proc "contextless" (code: int) -> ! { - linux.exit_group(i32(code)) -} - -_get_uid :: proc() -> int { - return int(linux.getuid()) -} - -_get_euid :: proc() -> int { - return int(linux.geteuid()) -} - -_get_gid :: proc() -> int { - return int(linux.getgid()) -} - -_get_egid :: proc() -> int { - return int(linux.getegid()) -} - -_get_pid :: proc() -> int { - return int(linux.getpid()) -} - -_get_ppid :: proc() -> int { - return int(linux.getppid()) -} - -Process_Attributes_OS_Specific :: struct {} - -_process_find :: proc(pid: int) -> (Process, Error) { - TEMP_ALLOCATOR_GUARD() - pid_path := fmt.ctprintf("/proc/%d", pid) - - p: Process - dir_fd: linux.Fd - errno: linux.Errno - - #partial switch dir_fd, errno = linux.open(pid_path, _OPENDIR_FLAGS); errno { - case .NONE: - linux.close(dir_fd) - p.pid = pid - return p, nil - case .ENOTDIR: - return p, .Invalid_Dir - case .ENOENT: - return p, .Not_Exist - } - return p, _get_platform_error(errno) -} - -_process_get_state :: proc(p: Process) -> (state: Process_State, err: Error) { - TEMP_ALLOCATOR_GUARD() - - stat_name := fmt.ctprintf("/proc/%d/stat", p.pid) - stat_buf: []u8 - stat_buf, err = _read_entire_pseudo_file(stat_name, temp_allocator()) - - if err != nil { - return - } - - idx := strings.last_index_byte(string(stat_buf), ')') - stats := string(stat_buf[idx + 2:]) - - // utime and stime are the 12 and 13th items, respectively - // skip the first 11 items here. - for i := 0; i < 11; i += 1 { - stats = stats[strings.index_byte(stats, ' ') + 1:] - } - - idx = strings.index_byte(stats, ' ') - utime_str := stats[:idx] - - stats = stats[idx + 1:] - stime_str := stats[:strings.index_byte(stats, ' ')] - - utime, _ := strconv.parse_int(utime_str, 10) - stime, _ := strconv.parse_int(stime_str, 10) - - // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms - state.user_time = time.Duration(utime) * 10 * time.Millisecond - state.system_time = time.Duration(stime) * 10 * time.Millisecond - - return -} - -_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (child: Process, err: Error) { - TEMP_ALLOCATOR_GUARD() - - dir_fd := linux.AT_FDCWD - errno: linux.Errno - if attr != nil && attr.dir != "" { - dir_cstr := temp_cstring(attr.dir) or_return - if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { - return child, _get_platform_error(errno) - } - } - - // search PATH if just a plain name is provided - executable: cstring - if !strings.contains_rune(name, '/') { - path_env := get_env("PATH", temp_allocator()) - path_dirs := filepath.split_list(path_env, temp_allocator()) - found: bool - for dir in path_dirs { - executable = fmt.ctprintf("%s/%s", dir, name) - fail: bool - if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno == .NONE && !fail { - found = true - break - } - } - if !found { - // check in cwd to match windows behavior - executable = fmt.ctprintf("./%s", name) - fail: bool - if fail, errno = linux.faccessat(dir_fd, executable, linux.F_OK); errno != .NONE || fail { - return child, .Not_Exist - } - } - } else { - executable = temp_cstring(name) or_return - } - - not_exec: bool - if not_exec, errno = linux.faccessat(dir_fd, executable, linux.F_OK | linux.X_OK); errno != .NONE || not_exec { - return child, errno == .NONE ? .Permission_Denied : _get_platform_error(errno) - } - - // args and environment need to be a list of cstrings - // that are terminated by a nil pointer. - // The first argument is a copy of the executable name. - cargs := make([]cstring, len(argv) + 2, temp_allocator()) - cargs[0] = executable - for i := 0; i < len(argv); i += 1 { - cargs[i + 1] = temp_cstring(argv[i]) or_return - } - - // Use current process's environment if attributes not provided - env: [^]cstring - if attr == nil { - // take this process's current environment - env = raw_data(export_cstring_environment(temp_allocator())) - } else { - cenv := make([]cstring, len(attr.env) + 1, temp_allocator()) - for i := 0; i < len(attr.env); i += 1 { - cenv[i] = temp_cstring(attr.env[i]) or_return - } - env = &cenv[0] - } - - // TODO: This is the traditional textbook implementation with fork. - // A more efficient implementation with vfork: - // - // 1. retrieve signal handlers - // 2. block all signals - // 3. allocate some stack space - // 4. vfork (waits for child exit or execve); In child: - // a. set child signal handlers - // b. set up any necessary pipes - // c. execve - // 5. restore signal handlers - // - stdin_fds: [2]linux.Fd - stdout_fds: [2]linux.Fd - stderr_fds: [2]linux.Fd - if attr != nil && attr.stdin != nil { - if errno = linux.pipe2(&stdin_fds, nil); errno != .NONE { - return child, _get_platform_error(errno) - } - } - if attr != nil && attr.stdout != nil { - if errno = linux.pipe2(&stdout_fds, nil); errno != .NONE { - return child, _get_platform_error(errno) - } - } - if attr != nil && attr.stderr != nil { - if errno = linux.pipe2(&stderr_fds, nil); errno != .NONE { - return child, _get_platform_error(errno) - } - } - - pid: linux.Pid - if pid, errno = linux.fork(); errno != .NONE { - return child, _get_platform_error(errno) - } - - IN :: 1 - OUT :: 0 - - STDIN :: linux.Fd(0) - STDOUT :: linux.Fd(1) - STDERR :: linux.Fd(2) - - if pid == 0 { - // in child process now - if attr != nil && attr.stdin != nil { - if linux.close(stdin_fds[IN]) != .NONE { linux.exit(1) } - if _, errno = linux.dup2(stdin_fds[OUT], STDIN); errno != .NONE { linux.exit(1) } - if linux.close(stdin_fds[OUT]) != .NONE { linux.exit(1) } - } - if attr != nil && attr.stdout != nil { - if linux.close(stdout_fds[OUT]) != .NONE { linux.exit(1) } - if _, errno = linux.dup2(stdout_fds[IN], STDOUT); errno != .NONE { linux.exit(1) } - if linux.close(stdout_fds[IN]) != .NONE { linux.exit(1) } - } - if attr != nil && attr.stderr != nil { - if linux.close(stderr_fds[OUT]) != .NONE { linux.exit(1) } - if _, errno = linux.dup2(stderr_fds[IN], STDERR); errno != .NONE { linux.exit(1) } - if linux.close(stderr_fds[IN]) != .NONE { linux.exit(1) } - } - - if errno = linux.execveat(dir_fd, executable, &cargs[OUT], env); errno != .NONE { - print_error(stderr, _get_platform_error(errno), string(executable)) - panic("execve failed to replace process") - } - unreachable() - } - - // in parent process - if attr != nil && attr.stdin != nil { - linux.close(stdin_fds[OUT]) - _construct_file(attr.stdin, uintptr(stdin_fds[IN])) - } - if attr != nil && attr.stdout != nil { - linux.close(stdout_fds[IN]) - _construct_file(attr.stdout, uintptr(stdout_fds[OUT])) - } - if attr != nil && attr.stderr != nil { - linux.close(stderr_fds[IN]) - _construct_file(attr.stderr, uintptr(stderr_fds[OUT])) - } - - child.pid = int(pid) - return child, nil -} - -_process_release :: proc(p: ^Process) -> Error { - // We didn't allocate... - return nil -} - -_process_kill :: proc(p: ^Process) -> Error { - res := linux.kill(linux.Pid(p.pid), .SIGKILL) - return _get_platform_error(res) -} - -_process_signal :: proc(sig: Signal, h: Signal_Handler) -> Error { - signo: linux.Signal - switch sig { - case .Abort: signo = .SIGABRT - case .Floating_Point_Exception: signo = .SIGFPE - case .Illegal_Instruction: signo = .SIGILL - case .Interrupt: signo = .SIGINT - case .Segmentation_Fault: signo = .SIGSEGV - case .Termination: signo = .SIGTERM - } - - sigact: linux.Sig_Action(int) - old: ^linux.Sig_Action(int) = nil - - switch v in h { - case Signal_Handler_Special: - switch v { - case .Default: - sigact.special = .SIG_DFL - case .Ignore: - sigact.special = .SIG_IGN - } - case Signal_Handler_Proc: - sigact.handler = (linux.Sig_Handler_Fn)(v) - } - - return _get_platform_error(linux.rt_sigaction(signo, &sigact, old)) -} - -_process_wait :: proc(p: ^Process, t: time.Duration) -> (state: Process_State, err: Error) { - safe_state :: proc(p: Process, state: Process_State = {}) -> (Process_State, Error) { - // process_get_state can fail, so we don't want to return it directly. - if new_state, err := _process_get_state(p); err == nil { - return new_state, nil - } - return state, nil - } - - state.pid = p.pid - - options: linux.Wait_Options - big_if: if t == 0 { - options += {.WNOHANG} - } else if t != time.MAX_DURATION { - ts: linux.Time_Spec = { - time_sec = uint(t / time.Second), - time_nsec = uint(t % time.Second), - } - - @static has_pidfd_open: bool = true - - // pidfd_open is fairly new, so don't error out on ENOSYS - pid_fd: linux.Pid_FD - errno: linux.Errno - if has_pidfd_open { - pid_fd, errno = linux.pidfd_open(linux.Pid(p.pid), nil) - if errno != .NONE && errno != .ENOSYS { - return state, _get_platform_error(errno) - } - } - - if has_pidfd_open && errno != .ENOSYS { - defer linux.close(linux.Fd(pid_fd)) - pollfd: [1]linux.Poll_Fd = { - { - fd = linux.Fd(pid_fd), - events = {.IN}, - }, - } - for { - n, e := linux.ppoll(pollfd[:], &ts, nil) - if e == .EINTR { - continue - } - if e != .NONE { - return state, _get_platform_error(errno) - } - if n == 0 { - return safe_state(p^, state) - } - break - } - } else { - has_pidfd_open = false - mask: bit_set[0..=63] - mask += { int(linux.Signal.SIGCHLD) - 1 } - - org_sigset: linux.Sig_Set - sigset: linux.Sig_Set - mem.copy(&sigset, &mask, size_of(mask)) - errno = linux.rt_sigprocmask(.SIG_BLOCK, &sigset, &org_sigset) - if errno != .NONE { - return state, _get_platform_error(errno) - } - defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) - - // In case there was a signal handler on SIGCHLD, avoid race - // condition by checking wait first. - options += {.WNOHANG} - waitid_options := options + {.WNOWAIT, .WEXITED} - info: linux.Sig_Info - errno = linux.waitid(.PID, linux.Id(p.pid), &info, waitid_options, nil) - if errno == .NONE && info.code != 0 { - break big_if - } - - loop: for { - sigset = {} - mem.copy(&sigset, &mask, size_of(mask)) - - _, errno = linux.rt_sigtimedwait(&sigset, &info, &ts) - #partial switch errno { - case .EAGAIN: // timeout - return safe_state(p^, state) - case .EINVAL: - return state, _get_platform_error(errno) - case .EINTR: - continue - case: - if int(info.pid) == p.pid { - break loop - } - } - } - } - } - - state, _ = safe_state(p^, state) - - status: u32 - errno: linux.Errno = .EINTR - for errno == .EINTR { - _, errno = linux.wait4(linux.Pid(p.pid), &status, options, nil) - if errno != .NONE { - return state, _get_platform_error(errno) - } - } - - // terminated by exit - if linux.WIFEXITED(status) { - p.is_done = true - state.exited = true - state.exit_code = int(linux.WEXITSTATUS(status)) - state.success = state.exit_code == 0 - return state, nil - } - - // terminated by signal - if linux.WIFSIGNALED(status) { - // NOTE: what's the correct behavior here?? - p.is_done = true - state.exited = false - state.exit_code = int(linux.WTERMSIG(status)) - state.success = false - return state, nil - } - - return safe_state(p^, state) -} diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin deleted file mode 100644 index 4b5e4bc02..000000000 --- a/core/os/os2/process_windows.odin +++ /dev/null @@ -1,67 +0,0 @@ -//+private -package os2 - -import "core:runtime" -import "core:time" - -_alloc_command_line_arguments :: proc() -> []string { - return nil -} - -_exit :: proc "contextless" (_: int) -> ! { - runtime.trap() -} - -_get_uid :: proc() -> int { - return -1 -} - -_get_euid :: proc() -> int { - return -1 -} - -_get_gid :: proc() -> int { - return -1 -} - -_get_egid :: proc() -> int { - return -1 -} - -_get_pid :: proc() -> int { - return -1 -} - -_get_ppid :: proc() -> int { - return -1 -} - -Process_Attributes_OS_Specific :: struct{} - -_process_find :: proc(pid: int) -> (Process, Error) { - return Process{}, nil -} - -_process_get_state :: proc(p: Process) -> (Process_State, Error) { - return Process_State{}, nil -} - -_process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (Process, Error) { - return Process{}, nil -} - -_process_release :: proc(p: ^Process) -> Error { - return nil -} - -_process_kill :: proc(p: ^Process) -> Error { - return nil -} - -_process_signal :: proc(sig: Signal, handler: Signal_Handler) -> Error { - return nil -} - -_process_wait :: proc(p: ^Process, t: time.Duration) -> (Process_State, Error) { - return Process_State{}, nil -}