Browse Source

Merge pull request #3822 from jasonKercher/os2-rebase

os2 linux round 2
gingerBill 1 year ago
parent
commit
0c8924ea85

+ 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(f: ^File, 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(f, buf)
+}

+ 154 - 134
core/os/os2/errors_linux.odin

@@ -1,145 +1,165 @@
 //+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))
+@(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",
 }
 
-_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[linux.Errno(errno)]
 	}
-	return "Error"
+	return "Unknown Error"
 }

+ 0 - 3
core/os/os2/file.odin

@@ -45,13 +45,10 @@ O_TRUNC   :: File_Flags{.Trunc}
 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
 
-
 @(require_results)
 create :: proc(name: string) -> (^File, Error) {
 	return open(name, {.Read, .Write, .Create}, File_Mode(0o777))

+ 228 - 158
core/os/os2/file_linux.odin

@@ -1,39 +1,64 @@
 //+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
+
+	stdin  = &_stdin
+	stdout = &_stdout
+	stderr = &_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 +66,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 +119,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 +142,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 +243,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 +251,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
 }
 

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

+ 77 - 40
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,
@@ -150,44 +152,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,
-}
-
-// 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)
+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)
+
+} 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 +891,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 +970,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`
 */

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

+ 76 - 20
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,
 	}
 }