Browse Source

convert all to use sys/linux over sys/unix; new implementations for pipe, process and env

jason 1 year ago
parent
commit
f24f72c280

+ 210 - 9
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 <key>=<value> 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
+}

+ 16 - 0
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)
+}

+ 153 - 134
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 */
-
-_get_platform_error :: proc(res: int) -> Error {
-	errno := unix.get_errno(res)
-	return Platform_Error(i32(errno))
+_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",
 }
 
-_ok_or_error :: proc(res: int) -> Error {
-	return res >= 0 ? nil : _get_platform_error(res)
+
+_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
+	}
+
+	return Platform_Error(i32(errno))
 }
 
 _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"
 }

+ 3 - 4
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)

+ 224 - 158
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,112 +138,100 @@ _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)
 	}
-	if n == 0 {
-		return 0, .EOF
-	}
-	return i64(n), nil
+	return i64(n), n == 0 ? io.Error.EOF : nil
 }
 
-_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) {
+_read_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_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)
+	n, errno := linux.pread(f.impl.fd, p[:], offset)
+	if errno != .NONE {
+		return -1, _get_platform_error(errno)
 	}
-	return
+	if n == 0 {
+		return 0, .EOF
+	}
+	return i64(n), nil
 }
 
 _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, &times, 0))
+	return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, &times[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, &times, 0))
+	return _get_platform_error(linux.utimensat(f.impl.fd, nil, &times[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")

+ 11 - 17
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))
 }
 
 //

+ 70 - 86
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
 }
-

+ 11 - 1
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
 }
 

+ 53 - 35
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)
+}

+ 428 - 0
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)
+}

+ 67 - 0
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
+}

+ 20 - 96
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)
 }