Browse Source

Merge pull request #4020 from odin-lang/bill/os-errno

`os.Error` to replace `os.Errno`
gingerBill 1 year ago
parent
commit
f427f040fd
49 changed files with 2865 additions and 1771 deletions
  1. 3 3
      core/crypto/rand_windows.odin
  2. 3 3
      core/encoding/csv/example.odin
  3. 1 1
      core/flags/errors.odin
  4. 2 2
      core/flags/internal_rtti.odin
  5. 1 1
      core/image/general_os.odin
  6. 1 1
      core/image/png/example.odin
  7. 1 1
      core/image/png/helpers.odin
  8. 2 2
      core/mem/virtual/file.odin
  9. 32 32
      core/net/socket_darwin.odin
  10. 9 18
      core/os/dir_unix.odin
  11. 11 10
      core/os/dir_windows.odin
  12. 19 19
      core/os/env_windows.odin
  13. 322 0
      core/os/errors.odin
  14. 83 93
      core/os/file_windows.odin
  15. 90 58
      core/os/os.odin
  16. 2 1
      core/os/os2/errors.odin
  17. 5 5
      core/os/os2/errors_linux.odin
  18. 20 5
      core/os/os2/file_util.odin
  19. 1 1
      core/os/os2/path_linux.odin
  20. 5 8
      core/os/os2/stat_windows.odin
  21. 379 230
      core/os/os_darwin.odin
  22. 29 24
      core/os/os_essence.odin
  23. 334 196
      core/os/os_freebsd.odin
  24. 101 69
      core/os/os_haiku.odin
  25. 106 61
      core/os/os_js.odin
  26. 293 258
      core/os/os_linux.odin
  27. 392 217
      core/os/os_netbsd.odin
  28. 342 205
      core/os/os_openbsd.odin
  29. 25 18
      core/os/os_wasi.odin
  30. 108 50
      core/os/os_windows.odin
  31. 18 38
      core/os/stat_unix.odin
  32. 36 36
      core/os/stat_windows.odin
  33. 16 33
      core/os/stream.odin
  34. 2 2
      core/path/filepath/match.odin
  35. 5 5
      core/path/filepath/path_windows.odin
  36. 14 21
      core/path/filepath/walk.odin
  37. 2 2
      core/prof/spall/spall.odin
  38. 3 12
      core/prof/spall/spall_linux.odin
  39. 4 9
      core/prof/spall/spall_unix.odin
  40. 4 5
      core/prof/spall/spall_windows.odin
  41. 5 6
      core/sync/futex_haiku.odin
  42. 1 1
      core/sys/haiku/os.odin
  43. 1 0
      core/sys/wasm/wasi/wasi_api.odin
  44. 2 2
      core/testing/runner.odin
  45. 1 1
      examples/demo/demo.odin
  46. 23 0
      src/check_expr.cpp
  47. 1 1
      tests/core/flags/test_core_flags.odin
  48. 4 4
      tests/core/os/os.odin
  49. 1 1
      tests/documentation/documentation_tester.odin

+ 3 - 3
core/crypto/rand_windows.odin

@@ -8,9 +8,9 @@ HAS_RAND_BYTES :: true
 
 @(private)
 _rand_bytes :: proc(dst: []byte) {
-	ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
-	if ret != os.ERROR_NONE {
-		switch ret {
+	ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
+	if ret != nil {
+		#partial switch ret {
 		case os.ERROR_INVALID_HANDLE:
 			// The handle to the first parameter is invalid.
 			// This should not happen here, since we explicitly pass nil to it

+ 3 - 3
core/encoding/csv/example.odin

@@ -38,9 +38,9 @@ iterate_csv_from_stream :: proc(filename: string) {
 	r.reuse_record_buffer = true // Without it you have to each of the fields within it
 	defer csv.reader_destroy(&r)
 
-	handle, errno := os.open(filename)
-	if errno != os.ERROR_NONE {
-		fmt.printfln("Error opening file: %v", filename)
+	handle, err := os.open(filename)
+	if err != nil {
+		fmt.eprintfln("Error opening file: %v", filename)
 		return
 	}
 	defer os.close(handle)

+ 1 - 1
core/flags/errors.odin

@@ -28,7 +28,7 @@ Parse_Error :: struct {
 // Provides more granular information than what just a string could hold.
 Open_File_Error :: struct {
 	filename: string,
-	errno: os.Errno,
+	errno: os.Error,
 	mode: int,
 	perms: int,
 }

+ 2 - 2
core/flags/internal_rtti.odin

@@ -254,8 +254,8 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type:
 		}
 
 		handle, errno := os.open(str, mode, perms)
-		if errno != 0 {
-			// NOTE(Feoramund): os.Errno is system-dependent, and there's
+		if errno != nil {
+			// NOTE(Feoramund): os.Error is system-dependent, and there's
 			// currently no good way to translate them all into strings.
 			//
 			// The upcoming `os2` package will hopefully solve this.

+ 1 - 1
core/image/general_os.odin

@@ -27,7 +27,7 @@ which :: proc{
 
 which_file :: proc(path: string) -> Which_File_Type {
 	f, err := os.open(path)
-	if err != 0 {
+	if err != nil {
 		return .Unknown
 	}
 	header: [128]byte

+ 1 - 1
core/image/png/example.odin

@@ -213,7 +213,7 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b
 	}
 
 	fd, err := open(filename, flags, mode)
-	if err != 0 {
+	if err != nil {
 		return false
 	}
 	defer close(fd)

+ 1 - 1
core/image/png/helpers.odin

@@ -450,7 +450,7 @@ when false {
 		}
 
 		fd, fderr := open(filename, flags, mode)
-		if fderr != 0 {
+		if fderr != nil {
 			return .Cannot_Open_File
 		}
 		defer close(fd)

+ 2 - 2
core/mem/virtual/file.odin

@@ -24,7 +24,7 @@ map_file :: proc{
 
 map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 	fd, err := os.open(filename, os.O_RDWR)
-	if err != 0 {
+	if err != nil {
 		return nil, .Open_Failure
 	}
 	defer os.close(fd)
@@ -34,7 +34,7 @@ map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []
 
 map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
 	size, os_err := os.file_size(os.Handle(fd))
-	if os_err != 0 {
+	if os_err != nil {
 		return nil, .Stat_Failure
 	}
 	if size < 0 {

+ 32 - 32
core/net/socket_darwin.odin

@@ -53,9 +53,9 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
 		unreachable()
 	}
 
-	sock, ok := os.socket(c_family, c_type, c_protocol)
-	if ok != os.ERROR_NONE {
-		err = Create_Socket_Error(ok)
+	sock, sock_err := os.socket(c_family, c_type, c_protocol)
+	if sock_err != nil {
+		err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1)
 		return
 	}
 
@@ -84,8 +84,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
 
 	sockaddr := _endpoint_to_sockaddr(endpoint)
 	res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
-	if res != os.ERROR_NONE {
-		err = Dial_Error(res)
+	if res != nil {
+		err = Dial_Error(os.is_platform_error(res) or_else -1)
 		return
 	}
 
@@ -100,11 +100,11 @@ _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
 	sockaddr := _endpoint_to_sockaddr(ep)
 	s := any_socket_to_socket(skt)
 	res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
-	if res != os.ERROR_NONE {
+	if res != nil {
 		if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT {
 			err = .Privileged_Port_Without_Root
 		} else {
-			err = Bind_Error(res)
+			err = Bind_Error(os.is_platform_error(res) or_else -1)
 		}
 	}
 	return
@@ -128,8 +128,8 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
 	bind(sock, interface_endpoint) or_return
 
 	res := os.listen(os.Socket(skt), backlog)
-	if res != os.ERROR_NONE {
-		err = Listen_Error(res)
+	if res != nil {
+		err = Listen_Error(os.is_platform_error(res) or_else -1)
 		return
 	}
 
@@ -141,9 +141,9 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
 	sockaddr: os.SOCKADDR_STORAGE_LH
 	sockaddrlen := c.int(size_of(sockaddr))
 
-	client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
-	if ok != os.ERROR_NONE {
-		err = Accept_Error(ok)
+	client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
+	if client_sock_err != nil {
+		err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1)
 		return
 	}
 	client = TCP_Socket(client_sock)
@@ -162,9 +162,9 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
 	if len(buf) <= 0 {
 		return
 	}
-	res, ok := os.recv(os.Socket(skt), buf, 0)
-	if ok != os.ERROR_NONE {
-		err = TCP_Recv_Error(ok)
+	res, res_err := os.recv(os.Socket(skt), buf, 0)
+	if res_err != nil {
+		err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1)
 		return
 	}
 	return int(res), nil
@@ -178,9 +178,9 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
 
 	from: os.SOCKADDR_STORAGE_LH
 	fromsize := c.int(size_of(from))
-	res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
-	if ok != os.ERROR_NONE {
-		err = UDP_Recv_Error(ok)
+	res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
+	if res_err != nil {
+		err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1)
 		return
 	}
 
@@ -194,9 +194,9 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
 	for bytes_written < len(buf) {
 		limit := min(int(max(i32)), len(buf) - bytes_written)
 		remaining := buf[bytes_written:][:limit]
-		res, ok := os.send(os.Socket(skt), remaining, 0)
-		if ok != os.ERROR_NONE {
-			err = TCP_Send_Error(ok)
+		res, res_err := os.send(os.Socket(skt), remaining, 0)
+		if res_err != nil {
+			err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1)
 			return
 		}
 		bytes_written += int(res)
@@ -210,9 +210,9 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
 	for bytes_written < len(buf) {
 		limit := min(1<<31, len(buf) - bytes_written)
 		remaining := buf[bytes_written:][:limit]
-		res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
-		if ok != os.ERROR_NONE {
-			err = UDP_Send_Error(ok)
+		res, res_err := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
+		if res_err != nil {
+			err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1)
 			return
 		}
 		bytes_written += int(res)
@@ -224,8 +224,8 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
 _shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
 	s := any_socket_to_socket(skt)
 	res := os.shutdown(os.Socket(s), int(manner))
-	if res != os.ERROR_NONE {
-		return Shutdown_Error(res)
+	if res != nil {
+		return Shutdown_Error(os.is_platform_error(res) or_else -1)
 	}
 	return
 }
@@ -302,8 +302,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
 
 	skt := any_socket_to_socket(s)
 	res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
-	if res != os.ERROR_NONE {
-		return Socket_Option_Error(res)
+	if res != nil {
+		return Socket_Option_Error(os.is_platform_error(res) or_else -1)
 	}
 
 	return nil
@@ -314,8 +314,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
 	socket := any_socket_to_socket(socket)
 
 	flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
-	if getfl_err != os.ERROR_NONE {
-		return Set_Blocking_Error(getfl_err)
+	if getfl_err != nil {
+		return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1)
 	}
 
 	if should_block {
@@ -325,8 +325,8 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
 	}
 
 	_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
-	if setfl_err != os.ERROR_NONE {
-		return Set_Blocking_Error(setfl_err)
+	if setfl_err != nil {
+		return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1)
 	}
 
 	return nil

+ 9 - 18
core/os/dir_unix.odin

@@ -3,21 +3,12 @@ package os
 
 import "core:strings"
 
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
-	dirp: Dir
-	dirp, err = _fdopendir(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
+	dirp := _fdopendir(fd) or_return
 	defer _closedir(dirp)
 
-	dirpath: string
-	dirpath, err = absolute_path_from_handle(fd)
-	if err != ERROR_NONE {
-		return
-	}
-
+	dirpath := absolute_path_from_handle(fd) or_return
 	defer delete(dirpath)
 
 	n := n
@@ -27,8 +18,8 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		size = 100
 	}
 
-	dfi := make([dynamic]File_Info, 0, size, allocator)
-	defer if err != ERROR_NONE {
+	dfi := make([dynamic]File_Info, 0, size, allocator) or_return
+	defer if err != nil {
 		for fi_ in dfi {
 			file_info_delete(fi_, allocator)
 		}
@@ -39,7 +30,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		entry: Dirent
 		end_of_stream: bool
 		entry, err, end_of_stream = _readdir(dirp)
-		if err != ERROR_NONE {
+		if err != nil {
 			return
 		} else if end_of_stream {
 			break
@@ -56,7 +47,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 
 		s: OS_Stat
 		s, err = _lstat(fullpath)
-		if err != ERROR_NONE {
+		if err != nil {
 			delete(fullpath, allocator)
 			return
 		}
@@ -67,5 +58,5 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		append(&dfi, fi_)
 	}
 
-	return dfi[:], ERROR_NONE
+	return dfi[:], nil
 }

+ 11 - 10
core/os/dir_windows.odin

@@ -4,7 +4,9 @@ import win32 "core:sys/windows"
 import "core:strings"
 import "base:runtime"
 
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
+	@(require_results)
 	find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
 		// Ignore "." and ".."
 		if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
@@ -57,7 +59,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 
 	dir_fi, _ := file_info_from_get_file_information_by_handle("", h)
 	if !dir_fi.is_dir {
-		return nil, ERROR_FILE_IS_NOT_DIR
+		return nil, .Not_Dir
 	}
 
 	n := n
@@ -68,15 +70,14 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 	}
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
-	wpath: []u16
-	wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
-	if len(wpath) == 0 || err != ERROR_NONE {
+	wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
+	if len(wpath) == 0 {
 		return
 	}
 
-	dfi := make([dynamic]File_Info, 0, size)
+	dfi := make([dynamic]File_Info, 0, size) or_return
 
-	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
+	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+1] = '*'
@@ -88,7 +89,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 	find_data := &win32.WIN32_FIND_DATAW{}
 	find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
 	if find_handle == win32.INVALID_HANDLE_VALUE {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 		return dfi[:], err
 	}
 	defer win32.FindClose(find_handle)
@@ -101,7 +102,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		}
 
 		if !win32.FindNextFileW(find_handle, find_data) {
-			e := Errno(win32.GetLastError())
+			e := get_last_error()
 			if e == ERROR_NO_MORE_FILES {
 				break
 			}
@@ -109,5 +110,5 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
 		}
 	}
 
-	return dfi[:], ERROR_NONE
+	return dfi[:], nil
 }

+ 19 - 19
core/os/env_windows.odin

@@ -7,27 +7,22 @@ import "base:runtime"
 // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
 // Otherwise the returned value will be empty and the boolean will be false
 // NOTE: the value will be allocated with the supplied allocator
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	if key == "" {
 		return
 	}
 	wkey := win32.utf8_to_wstring(key)
 	n := win32.GetEnvironmentVariableW(wkey, nil, 0)
-	if n == 0 {
-		err := win32.GetLastError()
-		if err == u32(ERROR_ENVVAR_NOT_FOUND) {
-			return "", false
-		}
+	if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
+		return "", false
 	}
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
-	b := make([dynamic]u16, n, context.temp_allocator)
+	b, _ := make([dynamic]u16, n, context.temp_allocator)
 	n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
-	if n == 0 {
-		err := win32.GetLastError()
-		if err == u32(ERROR_ENVVAR_NOT_FOUND) {
-			return "", false
-		}
+	if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
+		return "", false
 	}
 	value, _ = win32.utf16_to_utf8(b[:n], allocator)
 	found = true
@@ -39,41 +34,46 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 // It returns the value, which will be empty if the variable is not present
 // To distinguish between an empty value and an unset value, use lookup_env
 // NOTE: the value will be allocated with the supplied allocator
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
 // set_env sets the value of the environment variable named by the key
-set_env :: proc(key, value: string) -> Errno {
+set_env :: proc(key, value: string) -> Error {
 	k := win32.utf8_to_wstring(key)
 	v := win32.utf8_to_wstring(value)
 
 	if !win32.SetEnvironmentVariableW(k, v) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return 0
+	return nil
 }
 
 // unset_env unsets a single environment variable
-unset_env :: proc(key: string) -> Errno {
+unset_env :: proc(key: string) -> Error {
 	k := win32.utf8_to_wstring(key)
 	if !win32.SetEnvironmentVariableW(k, nil) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return 0
+	return nil
 }
 
 // environ returns a copy of strings representing the environment, in the form "key=value"
 // NOTE: the slice of strings and the strings with be allocated using the supplied allocator
+@(require_results)
 environ :: proc(allocator := context.allocator) -> []string {
-	envs := cast([^]win32.WCHAR)(win32.GetEnvironmentStringsW())
+	envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW())
 	if envs == nil {
 		return nil
 	}
 	defer win32.FreeEnvironmentStringsW(envs)
 
-	r := make([dynamic]string, 0, 50, allocator)
+	r, err := make([dynamic]string, 0, 50, allocator)
+	if err != nil {
+		return nil
+	}
 	for from, i := 0, 0; true; i += 1 {
 		if c := envs[i]; c == 0 {
 			if i <= from {

+ 322 - 0
core/os/errors.odin

@@ -0,0 +1,322 @@
+package os
+
+import "base:intrinsics"
+import "base:runtime"
+import "core:io"
+
+Platform_Error :: _Platform_Error
+#assert(size_of(Platform_Error) <= 4)
+#assert(intrinsics.type_has_nil(Platform_Error))
+
+General_Error :: enum u32 {
+	None,
+
+	Permission_Denied,
+	Exist,
+	Not_Exist,
+	Closed,
+
+	Timeout,
+
+	Broken_Pipe,
+
+	// Indicates that an attempt to retrieve a file's size was made, but the
+	// file doesn't have a size.
+	No_Size,
+
+	Invalid_File,
+	Invalid_Dir,
+	Invalid_Path,
+	Invalid_Callback,
+
+	Pattern_Has_Separator,
+
+	Unsupported,
+
+	File_Is_Pipe,
+	Not_Dir,
+}
+
+
+Errno :: Error // alias for legacy use
+
+Error :: union #shared_nil {
+	General_Error,
+	io.Error,
+	runtime.Allocator_Error,
+	Platform_Error,
+}
+#assert(size_of(Error) == 8)
+
+ERROR_NONE :: Error{}
+
+ERROR_EOF :: io.Error.EOF
+
+@(require_results)
+is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
+	v := ferr.(Platform_Error) or_else {}
+	return i32(v), i32(v) != 0
+}
+
+@(require_results)
+error_string :: proc "contextless" (ferr: Error) -> string {
+	if ferr == nil {
+		return ""
+	}
+	switch e in ferr {
+	case General_Error:
+		switch e {
+		case .None: return ""
+		case .Permission_Denied: return "permission denied"
+		case .Exist:             return "file already exists"
+		case .Not_Exist:         return "file does not exist"
+		case .Closed:            return "file already closed"
+		case .Timeout:           return "i/o timeout"
+		case .Broken_Pipe:       return "Broken pipe"
+		case .No_Size:           return "file has no definite size"
+		case .Invalid_File:      return "invalid file"
+		case .Invalid_Dir:       return "invalid directory"
+		case .Invalid_Path:      return "invalid path"
+		case .Invalid_Callback:  return "invalid callback"
+		case .Unsupported:       return "unsupported"
+		case .Pattern_Has_Separator: return "pattern has separator"
+		case .File_Is_Pipe:      return "file is pipe"
+		case .Not_Dir:           return "file is not directory"
+		}
+	case io.Error:
+		switch e {
+		case .None: return ""
+		case .EOF:               return "eof"
+		case .Unexpected_EOF:    return "unexpected eof"
+		case .Short_Write:       return "short write"
+		case .Invalid_Write:     return "invalid write result"
+		case .Short_Buffer:      return "short buffer"
+		case .No_Progress:       return "multiple read calls return no data or error"
+		case .Invalid_Whence:    return "invalid whence"
+		case .Invalid_Offset:    return "invalid offset"
+		case .Invalid_Unread:    return "invalid unread"
+		case .Negative_Read:     return "negative read"
+		case .Negative_Write:    return "negative write"
+		case .Negative_Count:    return "negative count"
+		case .Buffer_Full:       return "buffer full"
+		case .Unknown, .Empty: //
+		}
+	case runtime.Allocator_Error:
+		switch e {
+		case .None:                 return ""
+		case .Out_Of_Memory:        return "out of memory"
+		case .Invalid_Pointer:      return "invalid allocator pointer"
+		case .Invalid_Argument:     return "invalid allocator argument"
+		case .Mode_Not_Implemented: return "allocator mode not implemented"
+		}
+	case Platform_Error:
+		return _error_string(e)
+	}
+
+	return "unknown error"
+}
+
+print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
+	err_str := error_string(ferr)
+
+	// msg + ": " + err_str + '\n'
+	length := len(msg) + 2 + len(err_str) + 1
+	buf_ := intrinsics.alloca(length, 1)
+	buf := buf_[:length]
+
+	copy(buf, msg)
+	buf[len(msg)] = ':'
+	buf[len(msg) + 1] = ' '
+	copy(buf[len(msg) + 2:], err_str)
+	buf[length - 1] = '\n'
+	return write(f, buf)
+}
+
+
+@(require_results, private)
+_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) {
+	if e == nil {
+		return ""
+	}
+
+	when ODIN_OS == .Darwin {
+		if s := string(_darwin_string_error(i32(e))); s != "" {
+			return s
+		}
+	}
+
+	when ODIN_OS != .Linux {
+		@(require_results)
+		binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check {
+			n := len(array)
+			left, right := 0, n
+			for left < right {
+				mid := int(uint(left+right) >> 1)
+				if array[mid] < key {
+					left = mid+1
+				} else {
+					// equal or greater
+					right = mid
+				}
+			}
+			return left, left < n && array[left] == key
+		}
+
+		err := runtime.Type_Info_Enum_Value(e)
+
+		ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum)
+		if idx, ok := binary_search(ti.values, err); ok {
+			return ti.names[idx]
+		}
+	} else {
+		@(rodata, static)
+		pe_strings := [Platform_Error]string{
+			.NONE            = "",
+			.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",
+		}
+		if Platform_Error.NONE <= e && e <= max(Platform_Error) {
+			return pe_strings[e]
+		}
+	}
+	return "<unknown platform error>"
+}
+
+@(private, require_results)
+error_to_io_error :: proc(ferr: Error) -> io.Error {
+	if ferr == nil {
+		return .None
+	}
+	return ferr.(io.Error) or_else .Unknown
+}

+ 83 - 93
core/os/file_windows.odin

@@ -5,13 +5,15 @@ import "base:intrinsics"
 import "base:runtime"
 import "core:unicode/utf16"
 
+@(require_results)
 is_path_separator :: proc(c: byte) -> bool {
 	return c == '/' || c == '\\'
 }
 
-open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
 	if len(path) == 0 {
-		return INVALID_HANDLE, ERROR_FILE_NOT_FOUND
+		return INVALID_HANDLE, General_Error.Not_Exist
 	}
 
 	access: u32
@@ -52,32 +54,31 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	wide_path := win32.utf8_to_wstring(path)
 	handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
 	if handle != INVALID_HANDLE {
-		return handle, ERROR_NONE
+		return handle, nil
 	}
 
-	err := Errno(win32.GetLastError())
-	return INVALID_HANDLE, err
+	return INVALID_HANDLE, get_last_error()
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	if !win32.CloseHandle(win32.HANDLE(fd)) {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-flush :: proc(fd: Handle) -> (err: Errno) {
+flush :: proc(fd: Handle) -> (err: Error) {
 	if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	single_write_length: win32.DWORD
@@ -90,25 +91,24 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 
 		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
 		if single_write_length <= 0 || !e {
-			err := Errno(win32.GetLastError())
-			return int(total_write), err
+			return int(total_write), get_last_error()
 		}
 		total_write += i64(single_write_length)
 	}
-	return int(total_write), ERROR_NONE
+	return int(total_write), nil
 }
 
-@(private="file")
-read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
+@(private="file", require_results)
+read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
 	if len(b) == 0 {
-		return 0, 0
+		return 0, nil
 	}
 	
 	BUF_SIZE :: 386
 	buf16: [BUF_SIZE]u16
 	buf8: [4*BUF_SIZE]u8
 
-	for n < len(b) && err == 0 {
+	for n < len(b) && err == nil {
 		min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
 		max_read := u32(min(BUF_SIZE, min_read))
 		if max_read == 0 {
@@ -118,7 +118,7 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 		single_read_length: u32
 		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
 		if !ok {
-			err = Errno(win32.GetLastError())
+			err = get_last_error()
 		}
 
 		buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
@@ -149,9 +149,9 @@ read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) {
 	return
 }
 
-read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
+read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 	
 	handle := win32.HANDLE(fd)
@@ -165,7 +165,7 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
 
 	if is_console {
 		total_read, err = read_console(handle, data[total_read:][:to_read])
-		if err != 0 {
+		if err != nil {
 			return total_read, err
 		}
 	} else {
@@ -175,18 +175,18 @@ read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Errno) {
 			// Successful read can mean two things, including EOF, see:
 			// https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
 			if bytes_read == 0 {
-				return 0, ERROR_HANDLE_EOF
+				return 0, .EOF
 			} else {
-				return int(bytes_read), ERROR_NONE
+				return int(bytes_read), nil
 			}
 		} else {
-			return 0, Errno(win32.GetLastError())
+			return 0, get_last_error()
 		}
 	}
-	return total_read, ERROR_NONE
+	return total_read, nil
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	w: u32
 	switch whence {
 	case 0: w = win32.FILE_BEGIN
@@ -197,22 +197,23 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
 	lo := i32(offset)
 	ft := win32.GetFileType(win32.HANDLE(fd))
 	if ft == win32.FILE_TYPE_PIPE {
-		return 0, ERROR_FILE_IS_PIPE
+		return 0, .File_Is_Pipe
 	}
 
 	dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
 	if dw_ptr == win32.INVALID_SET_FILE_POINTER {
-		err := Errno(win32.GetLastError())
+		err := get_last_error()
 		return 0, err
 	}
-	return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE
+	return i64(hi)<<32 + i64(dw_ptr), nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	length: win32.LARGE_INTEGER
-	err: Errno
+	err: Error
 	if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return i64(length), err
 }
@@ -220,10 +221,9 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 
 @(private)
 MAX_RW :: 1<<30
-ERROR_EOF :: 38
 
 @(private)
-pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	buf := data
 	if len(buf) > MAX_RW {
 		buf = buf[:MAX_RW]
@@ -239,15 +239,15 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 
 	h := win32.HANDLE(fd)
 	done: win32.DWORD
-	e: Errno
+	e: Error
 	if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		done = 0
 	}
 	return int(done), e
 }
 @(private)
-pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	buf := data
 	if len(buf) > MAX_RW {
 		buf = buf[:MAX_RW]
@@ -261,9 +261,9 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 
 	h := win32.HANDLE(fd)
 	done: win32.DWORD
-	e: Errno
+	e: Error
 	if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		done = 0
 	}
 	return int(done), e
@@ -279,19 +279,19 @@ on Windows, read_at changes the position of the file cursor, on *nix, it does no
 
 will read from the location twice on *nix, and from two different locations on Windows
 */
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	if offset < 0 {
-		return 0, ERROR_NEGATIVE_OFFSET
+		return 0, .Invalid_Offset
 	}
 
 	b, offset := data, offset
 	for len(b) > 0 {
 		m, e := pread(fd, b, offset)
 		if e == ERROR_EOF {
-			err = 0
+			err = nil
 			break
 		}
-		if e != 0 {
+		if e != nil {
 			err = e
 			break
 		}
@@ -311,18 +311,14 @@ on Windows, write_at changes the position of the file cursor, on *nix, it does n
 
 will write to the location twice on *nix, and to two different locations on Windows
 */
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	if offset < 0 {
-		return 0, ERROR_NEGATIVE_OFFSET
+		return 0, .Invalid_Offset
 	}
 
 	b, offset := data, offset
 	for len(b) > 0 {
-		m, e := pwrite(fd, b, offset)
-		if e != 0 {
-			err = e
-			break
-		}
+		m := pwrite(fd, b, offset) or_return
 		n += m
 		b = b[m:]
 		offset += i64(m)
@@ -338,6 +334,7 @@ stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
 stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
 
 
+@(require_results)
 get_std_handle :: proc "contextless" (h: uint) -> Handle {
 	fd := win32.GetStdHandle(win32.DWORD(h))
 	return Handle(fd)
@@ -352,6 +349,7 @@ exists :: proc(path: string) -> bool {
 	return attribs != win32.INVALID_FILE_ATTRIBUTES
 }
 
+@(require_results)
 is_file :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
@@ -363,6 +361,7 @@ is_file :: proc(path: string) -> bool {
 	return false
 }
 
+@(require_results)
 is_dir :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
@@ -377,13 +376,14 @@ is_dir :: proc(path: string) -> bool {
 // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
 @private cwd_lock := win32.SRWLOCK{} // zero is initialized
 
+@(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
 	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
-	dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
+	dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
 
 	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
 	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -393,14 +393,14 @@ get_current_directory :: proc(allocator := context.allocator) -> string {
 	return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wstr := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	win32.AcquireSRWLockExclusive(&cwd_lock)
 
 	if !win32.SetCurrentDirectoryW(wstr) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 
 	win32.ReleaseSRWLockExclusive(&cwd_lock)
@@ -409,31 +409,31 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
 }
 change_directory :: set_current_directory
 
-make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
+make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	// Mode is unused on Windows, but is needed on *nix
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.CreateDirectoryW(wpath, nil) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
-remove_directory :: proc(path: string) -> (err: Errno) {
+remove_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.RemoveDirectoryW(wpath) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-@(private)
+@(private, require_results)
 is_abs :: proc(path: string) -> bool {
 	if len(path) > 0 && path[0] == '/' {
 		return true
@@ -449,7 +449,7 @@ is_abs :: proc(path: string) -> bool {
 	return false
 }
 
-@(private)
+@(private, require_results)
 fix_long_path :: proc(path: string) -> string {
 	if len(path) < 248 {
 		return path
@@ -464,7 +464,7 @@ fix_long_path :: proc(path: string) -> string {
 
 	prefix :: `\\?`
 
-	path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
+	path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
 	copy(path_buf, prefix)
 	n := len(path)
 	r, w := 0, len(prefix)
@@ -494,80 +494,69 @@ fix_long_path :: proc(path: string) -> string {
 }
 
 
-link :: proc(old_name, new_name: string) -> (err: Errno) {
+link :: proc(old_name, new_name: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	n := win32.utf8_to_wstring(fix_long_path(new_name))
 	o := win32.utf8_to_wstring(fix_long_path(old_name))
-	return Errno(win32.CreateHardLinkW(n, o, nil))
+	return Platform_Error(win32.CreateHardLinkW(n, o, nil))
 }
 
-unlink :: proc(path: string) -> (err: Errno) {
+unlink :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
 
 	if !win32.DeleteFileW(wpath) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
 
-rename :: proc(old_path, new_path: string) -> (err: Errno) {
+rename :: proc(old_path, new_path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	from := win32.utf8_to_wstring(old_path, context.temp_allocator)
 	to := win32.utf8_to_wstring(new_path, context.temp_allocator)
 
 	if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }
 
 
-ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
-	curr_off, e := seek(fd, 0, 1)
-	if e != 0 {
-		return e
-	}
+ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
+	curr_off := seek(fd, 0, 1) or_return
 	defer seek(fd, curr_off, 0)
-	_, e = seek(fd, length, 0)
-	if e != 0 {
-		return e
-	}
+	_= seek(fd, length, 0) or_return
 	ok := win32.SetEndOfFile(win32.HANDLE(fd))
 	if !ok {
-		return Errno(win32.GetLastError())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-truncate :: proc(path: string, length: i64) -> (err: Errno) {
-	fd: Handle
-	fd, err = open(path, O_WRONLY|O_CREATE, 0o666)
-	if err != 0 {
-		return
-	}
+truncate :: proc(path: string, length: i64) -> (err: Error) {
+	fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return
 	defer close(fd)
-	err = ftruncate(fd, length)
-	return
+	return ftruncate(fd, length)
 }
 
 
-remove :: proc(name: string) -> Errno {
+remove :: proc(name: string) -> Error {
 	p := win32.utf8_to_wstring(fix_long_path(name))
 	err, err1: win32.DWORD
 	if !win32.DeleteFileW(p) {
 		err = win32.GetLastError()
 	}
 	if err == 0 {
-		return 0
+		return nil
 	}
 	if !win32.RemoveDirectoryW(p) {
 		err1 = win32.GetLastError()
 	}
 	if err1 == 0 {
-		return 0
+		return nil
 	}
 
 	if err != err1 {
@@ -588,16 +577,17 @@ remove :: proc(name: string) -> Errno {
 		}
 	}
 
-	return Errno(err)
+	return Platform_Error(err)
 }
 
 
-pipe :: proc() -> (r, w: Handle, err: Errno) {
+@(require_results)
+pipe :: proc() -> (r, w: Handle, err: Error) {
 	sa: win32.SECURITY_ATTRIBUTES
 	sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
 	sa.bInheritHandle = true
 	if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
-		err = Errno(win32.GetLastError())
+		err = get_last_error()
 	}
 	return
 }

+ 90 - 58
core/os/os.odin

@@ -1,6 +1,8 @@
 package os
 
+import "base:intrinsics"
 import "base:runtime"
+import "core:io"
 import "core:strconv"
 import "core:unicode/utf8"
 
@@ -13,15 +15,15 @@ SEEK_SET :: 0
 SEEK_CUR :: 1
 SEEK_END :: 2
 
-write_string :: proc(fd: Handle, str: string) -> (int, Errno) {
+write_string :: proc(fd: Handle, str: string) -> (int, Error) {
 	return write(fd, transmute([]byte)str)
 }
 
-write_byte :: proc(fd: Handle, b: byte) -> (int, Errno) {
+write_byte :: proc(fd: Handle, b: byte) -> (int, Error) {
 	return write(fd, []byte{b})
 }
 
-write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) {
+write_rune :: proc(fd: Handle, r: rune) -> (int, Error) {
 	if r < utf8.RUNE_SELF {
 		return write_byte(fd, byte(r))
 	}
@@ -30,113 +32,145 @@ write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) {
 	return write(fd, b[:n])
 }
 
-write_encoded_rune :: proc(fd: Handle, r: rune) {
-	write_byte(fd, '\'')
+write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
+	wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
+		n^ += m
+		if merr != nil {
+			err^ = merr
+			return true
+		}
+		return false
+	}
+
+	if wrap(write_byte(f, '\''), &n, &err) { return }
 
 	switch r {
-	case '\a': write_string(fd, "\\a")
-	case '\b': write_string(fd, "\\b")
-	case '\e': write_string(fd, "\\e")
-	case '\f': write_string(fd, "\\f")
-	case '\n': write_string(fd, "\\n")
-	case '\r': write_string(fd, "\\r")
-	case '\t': write_string(fd, "\\t")
-	case '\v': write_string(fd, "\\v")
+	case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return }
+	case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return }
+	case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return }
+	case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return }
+	case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return }
+	case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return }
+	case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return }
+	case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return }
 	case:
 		if r < 32 {
-			write_string(fd, "\\x")
+			if wrap(write_string(f, "\\x"), &n, &err) { return }
 			b: [2]byte
 			s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
-			case 0: write_string(fd, "00")
-			case 1: write_rune(fd, '0')
-			case 2: write_string(fd, s)
+			case 0: if wrap(write_string(f, "00"), &n, &err) { return }
+			case 1: if wrap(write_rune(f, '0'), &n, &err)    { return }
+			case 2: if wrap(write_string(f, s), &n, &err)    { return }
 			}
 		} else {
-			write_rune(fd, r)
+			if wrap(write_rune(f, r), &n, &err) { return }
 		}
 	}
-	write_byte(fd, '\'')
+	_ = wrap(write_byte(f, '\''), &n, &err)
+	return
 }
 
-read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Errno) {
+read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) {
 	if len(buf) < min {
-		return 0, -1
+		return 0, io.Error.Short_Buffer
 	}
 	nn := max(int)
-	for nn > 0 && n < min && err == 0 {
+	for nn > 0 && n < min && err == nil {
 		nn, err = read(fd, buf[n:])
 		n += nn
 	}
 	if n >= min {
-		err = 0
+		err = nil
 	}
 	return
 }
 
-read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Errno) {
+read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) {
 	return read_at_least(fd, buf, len(buf))
 }
 
 
+@(require_results)
 file_size_from_path :: proc(path: string) -> i64 {
 	fd, err := open(path, O_RDONLY, 0)
-	if err != 0 {
+	if err != nil {
 		return -1
 	}
 	defer close(fd)
 
 	length: i64
-	if length, err = file_size(fd); err != 0 {
+	if length, err = file_size(fd); err != nil {
 		return -1
 	}
 	return length
 }
 
+@(require_results)
 read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+	err: Error
+	data, err = read_entire_file_from_filename_or_err(name, allocator, loc)
+	success = err == nil
+	return
+}
+
+@(require_results)
+read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+	err: Error
+	data, err = read_entire_file_from_handle_or_err(fd, allocator, loc)
+	success = err == nil
+	return
+}
+
+read_entire_file :: proc {
+	read_entire_file_from_filename,
+	read_entire_file_from_handle,
+}
+
+@(require_results)
+read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
 	context.allocator = allocator
 
-	fd, err := open(name, O_RDONLY, 0)
-	if err != 0 {
-		return nil, false
-	}
+	fd := open(name, O_RDONLY, 0) or_return
 	defer close(fd)
 
-	return read_entire_file_from_handle(fd, allocator, loc)
+	return read_entire_file_from_handle_or_err(fd, allocator, loc)
 }
 
-read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
+@(require_results)
+read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
 	context.allocator = allocator
 
-	length: i64
-	err: Errno
-	if length, err = file_size(fd); err != 0 {
-		return nil, false
-	}
-
+	length := file_size(fd) or_return
 	if length <= 0 {
-		return nil, true
+		return nil, nil
 	}
 
-	data = make([]byte, int(length), allocator, loc)
+	data = make([]byte, int(length), allocator, loc) or_return
 	if data == nil {
-		return nil, false
+		return nil, nil
 	}
-
-	bytes_read, read_err := read_full(fd, data)
-	if read_err != ERROR_NONE {
-		delete(data)
-		return nil, false
+	defer if err != nil {
+		delete(data, allocator)
 	}
-	return data[:bytes_read], true
+
+	bytes_read := read_full(fd, data) or_return
+	data = data[:bytes_read]
+	return
 }
 
-read_entire_file :: proc {
-	read_entire_file_from_filename,
-	read_entire_file_from_handle,
+read_entire_file_or_err :: proc {
+	read_entire_file_from_filename_or_err,
+	read_entire_file_from_handle_or_err,
 }
 
+
 write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) {
+	return write_entire_file_or_err(name, data, truncate) == nil
+}
+
+@(require_results)
+write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error {
 	flags: int = O_WRONLY|O_CREATE
 	if truncate {
 		flags |= O_TRUNC
@@ -148,21 +182,18 @@ write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (succ
 		mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
 	}
 
-	fd, err := open(name, flags, mode)
-	if err != 0 {
-		return false
-	}
+	fd := open(name, flags, mode) or_return
 	defer close(fd)
 
-	_, write_err := write(fd, data)
-	return write_err == 0
+	_ = write(fd, data) or_return
+	return nil
 }
 
-write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
+write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
 	return write(fd, ([^]byte)(data)[:len])
 }
 
-read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) {
+read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
 	return read(fd, ([^]byte)(data)[:len])
 }
 
@@ -173,6 +204,7 @@ heap_alloc  :: runtime.heap_alloc
 heap_resize :: runtime.heap_resize
 heap_free   :: runtime.heap_free
 
+@(require_results)
 processor_core_count :: proc() -> int {
 	return _processor_core_count()
 }

+ 2 - 1
core/os/os2/errors.odin

@@ -42,13 +42,14 @@ Error :: union #shared_nil {
 ERROR_NONE :: Error{}
 
 
-
+@(require_results)
 is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
 	v := ferr.(Platform_Error) or_else {}
 	return i32(v), i32(v) != 0
 }
 
 
+@(require_results)
 error_string :: proc(ferr: Error) -> string {
 	if ferr == nil {
 		return ""

+ 5 - 5
core/os/os2/errors_linux.odin

@@ -4,8 +4,8 @@ package os2
 import "core:sys/linux"
 
 @(rodata)
-_errno_strings : [linux.Errno]string = {
-	.NONE            = "Success",
+_errno_strings := [linux.Error]string{
+	.NONE            = "",
 	.EPERM           = "Operation not permitted",
 	.ENOENT          = "No such file or directory",
 	.ESRCH           = "No such process",
@@ -142,7 +142,7 @@ _errno_strings : [linux.Errno]string = {
 }
 
 
-_get_platform_error :: proc(errno: linux.Errno) -> Error {
+_get_platform_error :: proc(errno: linux.Error) -> Error {
 	#partial switch errno {
 	case .NONE:
 		return nil
@@ -158,8 +158,8 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error {
 }
 
 _error_string :: proc(errno: i32) -> string {
-	if errno >= 0 && errno <= i32(max(linux.Errno)) {
-		return _errno_strings[linux.Errno(errno)]
+	if errno >= 0 && errno <= i32(max(linux.Error)) {
+		return _errno_strings[linux.Error(errno)]
 	}
 	return "Unknown Error"
 }

+ 20 - 5
core/os/os2/file_util.odin

@@ -73,6 +73,24 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
 	return
 }
 
+read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) {
+	if len(buf) < min {
+		return 0, .Short_Buffer
+	}
+	nn := max(int)
+	for nn > 0 && n < min && err == nil {
+		nn, err = read(f, buf[n:])
+		n += nn
+	}
+	if n >= min {
+		err = nil
+	}
+	return
+}
+
+read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) {
+	return read_at_least(f, buf, len(buf))
+}
 
 write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
 	return write(f, ([^]byte)(data)[:len])
@@ -155,11 +173,8 @@ write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := tru
 	if truncate {
 		flags |= O_TRUNC
 	}
-	f, err := open(name, flags, perm)
-	if err != nil {
-		return err
-	}
-	_, err = write(f, data)
+	f := open(name, flags, perm) or_return
+	_, err := write(f, data)
 	if cerr := close(f); cerr != nil && err == nil {
 		err = cerr
 	}

+ 1 - 1
core/os/os2/path_linux.odin

@@ -59,7 +59,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
 	path_bytes[len(path)] = 0
 
 	dfd: linux.Fd
-	errno: linux.Errno
+	errno: linux.Error
 	if path_bytes[0] == '/' {
 		dfd, errno = linux.open("/", _OPENDIR_FLAGS)
 		path_bytes = path_bytes[1:]

+ 5 - 8
core/os/os2/stat_windows.odin

@@ -6,25 +6,22 @@ import "core:time"
 import "core:strings"
 import win32 "core:sys/windows"
 
-_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
+_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 	if f == nil || (^File_Impl)(f.impl).fd == nil {
-		return {}, nil
+		return
 	}
 
-	path, err := _cleanpath_from_handle(f, allocator)
-	if err != nil {
-		return {}, err
-	}
+	path := _cleanpath_from_handle(f, allocator) or_return
 
 	h := _handle(f)
 	switch win32.GetFileType(h) {
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
-		fi := File_Info {
+		fi = File_Info {
 			fullpath = path,
 			name = basename(path),
 			type = file_type(h),
 		}
-		return fi, nil
+		return
 	}
 
 	return _file_info_from_get_file_information_by_handle(path, h, allocator)

File diff suppressed because it is too large
+ 379 - 230
core/os/os_darwin.odin


+ 29 - 24
core/os/os_essence.odin

@@ -2,54 +2,59 @@ package os
 
 import "core:sys/es"
 
-Handle :: distinct int;
-Errno :: distinct int;
+Handle :: distinct int
+_Platform_Error :: enum i32 {NONE}
 
-ERROR_NONE :: (Errno) (es.SUCCESS);
+// ERROR_NONE :: Error(es.SUCCESS)
 
-O_RDONLY :: 0x1;
-O_WRONLY :: 0x2;
-O_CREATE :: 0x4;
-O_TRUNC  :: 0x8;
+O_RDONLY :: 0x1
+O_WRONLY :: 0x2
+O_CREATE :: 0x4
+O_TRUNC  :: 0x8
 
-stderr : Handle = 0; 
+stderr : Handle = 0
 
 current_thread_id :: proc "contextless" () -> int {
-	return (int) (es.ThreadGetID(es.CURRENT_THREAD));
+	return (int) (es.ThreadGetID(es.CURRENT_THREAD))
 }
 
 heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
-	return es.HeapAllocate(size, zero_memory);
+	return es.HeapAllocate(size, zero_memory)
 }
 
 heap_free :: proc(ptr: rawptr) {
-	es.HeapFree(ptr);
+	es.HeapFree(ptr)
 }
 
 heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
-	return es.HeapReallocate(ptr, new_size, false);
+	return es.HeapReallocate(ptr, new_size, false)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
-	return (Handle) (0), (Errno) (1);
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
+	return (Handle) (0), (Error) (1)
 }
 
-close :: proc(fd: Handle) -> Errno {
-	return (Errno) (1);
+close :: proc(fd: Handle) -> Error {
+	return (Error) (1)
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	return (i64) (0), (Errno) (1);
+file_size :: proc(fd: Handle) -> (i64, Error) {
+	return (i64) (0), (Error) (1)
 }
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
-	return (int) (0), (Errno) (1);
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
+	return (int) (0), (Error) (1)
 }
 
-write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
-	return (int) (0), (Errno) (1);
+write :: proc(fd: Handle, data: []u8) -> (int, Error) {
+	return (int) (0), (Error) (1)
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
-	return (i64) (0), (Errno) (1);
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
+	return (i64) (0), (Error) (1)
 }
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
+}

+ 334 - 196
core/os/os_freebsd.odin

@@ -9,105 +9,200 @@ import "core:c"
 
 Handle :: distinct i32
 File_Time :: distinct u64
-Errno :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:      Errno : 0
-EPERM:           Errno : 1
-ENOENT:          Errno : 2
-ESRCH:           Errno : 3
-EINTR:           Errno : 4
-EIO:             Errno : 5
-ENXIO:           Errno : 6
-E2BIG:           Errno : 7
-ENOEXEC:         Errno : 8
-EBADF:           Errno : 9
-ECHILD:          Errno : 10
-EBEADLK:         Errno : 11
-ENOMEM:          Errno : 12
-EACCESS:         Errno : 13
-EFAULT:          Errno : 14
-ENOTBLK:         Errno : 15
-EBUSY:           Errno : 16
-EEXIST:          Errno : 17
-EXDEV:           Errno : 18
-ENODEV:          Errno : 19
-ENOTDIR:         Errno : 20
-EISDIR:          Errno : 21
-EINVAL:          Errno : 22
-ENFILE:          Errno : 23
-EMFILE:          Errno : 24
-ENOTTY:          Errno : 25
-ETXTBSY:         Errno : 26
-EFBIG:           Errno : 27
-ENOSPC:          Errno : 28
-ESPIPE:          Errno : 29
-EROFS:           Errno : 30
-EMLINK:          Errno : 31
-EPIPE:           Errno : 32
-EDOM:            Errno : 33
-ERANGE:          Errno : 34 /* Result too large */
-EAGAIN:          Errno : 35
-EINPROGRESS:     Errno : 36
-EALREADY:        Errno : 37
-ENOTSOCK:        Errno : 38
-EDESTADDRREQ:    Errno : 39
-EMSGSIZE:        Errno : 40
-EPROTOTYPE:      Errno : 41
-ENOPROTOOPT:     Errno : 42
-EPROTONOSUPPORT: Errno : 43
-ESOCKTNOSUPPORT: Errno : 44
-EOPNOTSUPP:      Errno : 45
-EPFNOSUPPORT:    Errno : 46
-EAFNOSUPPORT:    Errno : 47
-EADDRINUSE:      Errno : 48
-EADDRNOTAVAIL:   Errno : 49
-ENETDOWN:        Errno : 50
-ENETUNREACH:     Errno : 51
-ENETRESET:       Errno : 52
-ECONNABORTED:    Errno : 53
-ECONNRESET:      Errno : 54
-ENOBUFS:         Errno : 55
-EISCONN:         Errno : 56
-ENOTCONN:        Errno : 57
-ESHUTDOWN:       Errno : 58
-ETIMEDOUT:       Errno : 60
-ECONNREFUSED:    Errno : 61
-ELOOP:           Errno : 62
-ENAMETOOLING:    Errno : 63
-EHOSTDOWN:       Errno : 64
-EHOSTUNREACH:    Errno : 65
-ENOTEMPTY:       Errno : 66
-EPROCLIM:        Errno : 67
-EUSERS:          Errno : 68
-EDQUOT:          Errno : 69
-ESTALE:          Errno : 70
-EBADRPC:         Errno : 72
-ERPCMISMATCH:    Errno : 73
-EPROGUNAVAIL:    Errno : 74
-EPROGMISMATCH:   Errno : 75
-EPROCUNAVAIL:    Errno : 76
-ENOLCK:          Errno : 77
-ENOSYS:          Errno : 78
-EFTYPE:          Errno : 79
-EAUTH:           Errno : 80
-ENEEDAUTH:       Errno : 81
-EIDRM:           Errno : 82
-ENOMSG:          Errno : 83
-EOVERFLOW:       Errno : 84
-ECANCELED:       Errno : 85
-EILSEQ:          Errno : 86
-ENOATTR:         Errno : 87
-EDOOFUS:         Errno : 88
-EBADMSG:         Errno : 89
-EMULTIHOP:       Errno : 90
-ENOLINK:         Errno : 91
-EPROTO:          Errno : 92
-ENOTCAPABLE:     Errno : 93
-ECAPMODE:        Errno : 94
-ENOTRECOVERABLE: Errno : 95
-EOWNERDEAD:      Errno : 96
+_Platform_Error :: enum i32 {
+	NONE            = 0,
+	EPERM           = 1,
+	ENOENT          = 2,
+	ESRCH           = 3,
+	EINTR           = 4,
+	EIO             = 5,
+	ENXIO           = 6,
+	E2BIG           = 7,
+	ENOEXEC         = 8,
+	EBADF           = 9,
+	ECHILD          = 10,
+	EBEADLK         = 11,
+	ENOMEM          = 12,
+	EACCESS         = 13,
+	EFAULT          = 14,
+	ENOTBLK         = 15,
+	EBUSY           = 16,
+	EEXIST          = 17,
+	EXDEV           = 18,
+	ENODEV          = 19,
+	ENOTDIR         = 20,
+	EISDIR          = 21,
+	EINVAL          = 22,
+	ENFILE          = 23,
+	EMFILE          = 24,
+	ENOTTY          = 25,
+	ETXTBSY         = 26,
+	EFBIG           = 27,
+	ENOSPC          = 28,
+	ESPIPE          = 29,
+	EROFS           = 30,
+	EMLINK          = 31,
+	EPIPE           = 32,
+	EDOM            = 33,
+	ERANGE          = 34, /* Result too large */
+	EAGAIN          = 35,
+	EINPROGRESS     = 36,
+	EALREADY        = 37,
+	ENOTSOCK        = 38,
+	EDESTADDRREQ    = 39,
+	EMSGSIZE        = 40,
+	EPROTOTYPE      = 41,
+	ENOPROTOOPT     = 42,
+	EPROTONOSUPPORT = 43,
+	ESOCKTNOSUPPORT = 44,
+	EOPNOTSUPP      = 45,
+	EPFNOSUPPORT    = 46,
+	EAFNOSUPPORT    = 47,
+	EADDRINUSE      = 48,
+	EADDRNOTAVAIL   = 49,
+	ENETDOWN        = 50,
+	ENETUNREACH     = 51,
+	ENETRESET       = 52,
+	ECONNABORTED    = 53,
+	ECONNRESET      = 54,
+	ENOBUFS         = 55,
+	EISCONN         = 56,
+	ENOTCONN        = 57,
+	ESHUTDOWN       = 58,
+	ETIMEDOUT       = 60,
+	ECONNREFUSED    = 61,
+	ELOOP           = 62,
+	ENAMETOOLING    = 63,
+	EHOSTDOWN       = 64,
+	EHOSTUNREACH    = 65,
+	ENOTEMPTY       = 66,
+	EPROCLIM        = 67,
+	EUSERS          = 68,
+	EDQUOT          = 69,
+	ESTALE          = 70,
+	EBADRPC         = 72,
+	ERPCMISMATCH    = 73,
+	EPROGUNAVAIL    = 74,
+	EPROGMISMATCH   = 75,
+	EPROCUNAVAIL    = 76,
+	ENOLCK          = 77,
+	ENOSYS          = 78,
+	EFTYPE          = 79,
+	EAUTH           = 80,
+	ENEEDAUTH       = 81,
+	EIDRM           = 82,
+	ENOMSG          = 83,
+	EOVERFLOW       = 84,
+	ECANCELED       = 85,
+	EILSEQ          = 86,
+	ENOATTR         = 87,
+	EDOOFUS         = 88,
+	EBADMSG         = 89,
+	EMULTIHOP       = 90,
+	ENOLINK         = 91,
+	EPROTO          = 92,
+	ENOTCAPABLE     = 93,
+	ECAPMODE        = 94,
+	ENOTRECOVERABLE = 95,
+	EOWNERDEAD      = 96,
+}
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+E2BIG           :: Platform_Error.E2BIG
+ENOEXEC         :: Platform_Error.ENOEXEC
+EBADF           :: Platform_Error.EBADF
+ECHILD          :: Platform_Error.ECHILD
+EBEADLK         :: Platform_Error.EBEADLK
+ENOMEM          :: Platform_Error.ENOMEM
+EACCESS         :: Platform_Error.EACCESS
+EFAULT          :: Platform_Error.EFAULT
+ENOTBLK         :: Platform_Error.ENOTBLK
+EBUSY           :: Platform_Error.EBUSY
+EEXIST          :: Platform_Error.EEXIST
+EXDEV           :: Platform_Error.EXDEV
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ENOTTY          :: Platform_Error.ENOTTY
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EMLINK          :: Platform_Error.EMLINK
+EPIPE           :: Platform_Error.EPIPE
+EDOM            :: Platform_Error.EDOM
+ERANGE          :: Platform_Error.ERANGE
+EAGAIN          :: Platform_Error.EAGAIN
+EINPROGRESS     :: Platform_Error.EINPROGRESS
+EALREADY        :: Platform_Error.EALREADY
+ENOTSOCK        :: Platform_Error.ENOTSOCK
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ
+EMSGSIZE        :: Platform_Error.EMSGSIZE
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT
+EADDRINUSE      :: Platform_Error.EADDRINUSE
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL
+ENETDOWN        :: Platform_Error.ENETDOWN
+ENETUNREACH     :: Platform_Error.ENETUNREACH
+ENETRESET       :: Platform_Error.ENETRESET
+ECONNABORTED    :: Platform_Error.ECONNABORTED
+ECONNRESET      :: Platform_Error.ECONNRESET
+ENOBUFS         :: Platform_Error.ENOBUFS
+EISCONN         :: Platform_Error.EISCONN
+ENOTCONN        :: Platform_Error.ENOTCONN
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED
+ELOOP           :: Platform_Error.ELOOP
+ENAMETOOLING    :: Platform_Error.ENAMETOOLING
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY
+EPROCLIM        :: Platform_Error.EPROCLIM
+EUSERS          :: Platform_Error.EUSERS
+EDQUOT          :: Platform_Error.EDQUOT
+ESTALE          :: Platform_Error.ESTALE
+EBADRPC         :: Platform_Error.EBADRPC
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL
+ENOLCK          :: Platform_Error.ENOLCK
+ENOSYS          :: Platform_Error.ENOSYS
+EFTYPE          :: Platform_Error.EFTYPE
+EAUTH           :: Platform_Error.EAUTH
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH
+EIDRM           :: Platform_Error.EIDRM
+ENOMSG          :: Platform_Error.ENOMSG
+EOVERFLOW       :: Platform_Error.EOVERFLOW
+ECANCELED       :: Platform_Error.ECANCELED
+EILSEQ          :: Platform_Error.EILSEQ
+ENOATTR         :: Platform_Error.ENOATTR
+EDOOFUS         :: Platform_Error.EDOOFUS
+EBADMSG         :: Platform_Error.EBADMSG
+EMULTIHOP       :: Platform_Error.EMULTIHOP
+ENOLINK         :: Platform_Error.ENOLINK
+EPROTO          :: Platform_Error.EPROTO
+ENOTCAPABLE     :: Platform_Error.ENOTCAPABLE
+ECAPMODE        :: Platform_Error.ECAPMODE
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD
 
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
@@ -258,13 +353,13 @@ S_ISGID :: 0o2000 // Set group id on execution
 S_ISVTX :: 0o1000 // Directory restrcted delete
 
 
-S_ISLNK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -274,7 +369,7 @@ R_OK :: 4 // Test for read permission
 F_KINFO :: 22
 
 foreign libc {
-	@(link_name="__error")		__errno_location :: proc() -> ^c.int ---
+	@(link_name="__error")		__Error_location :: proc() -> ^c.int ---
 
 	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---
 	@(link_name="close")            _unix_close         :: proc(fd: Handle) -> c.int ---
@@ -320,30 +415,38 @@ foreign dl {
 	@(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__Error_location()^)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`.
@@ -354,124 +457,148 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s := _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -490,38 +617,40 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}                                                                  
 last_write_time_by_name :: proc(name: string) -> File_Time {}                                                        
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return 0, err
 	}
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	s, err := _stat(name)
-	if err != ERROR_NONE {
+	if err != nil {
 		return 0, err
 	}
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	s: OS_Stat = ---
 	result := _unix_lstat(cstr, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	
@@ -529,54 +658,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
 
 	if result == nil {
 		end_of_stream = true
@@ -586,8 +714,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -598,20 +726,21 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 
-	return "", Errno{}
+	return "", Error{}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 	// NOTE(Feoramund): The situation isn't ideal, but this was the best way I
 	// could find to implement this. There are a couple outstanding bug reports
 	// regarding the desire to retrieve an absolute path from a handle, but to
@@ -626,14 +755,15 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
 
 	res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo)
 	if res == -1 {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 
 	path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path))
-	return path, ERROR_NONE
+	return path, nil
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -644,27 +774,28 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
@@ -676,11 +807,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -692,7 +825,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -701,14 +834,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -716,16 +849,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return cast(int) pthread_getthreadid_np()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -741,6 +877,7 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -753,7 +890,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	count : int = 0
 	count_size := size_of(count)
@@ -767,6 +904,7 @@ _processor_core_count :: proc() -> int {
 }
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 101 - 69
core/os/os_haiku.odin

@@ -10,16 +10,14 @@ import "core:sys/haiku"
 Handle    :: i32
 Pid       :: i32
 File_Time :: i64
-Errno     :: i32
+_Platform_Error :: haiku.Errno
 
 MAX_PATH :: haiku.PATH_MAX
 
-ENOSYS :: int(haiku.Errno.POSIX_ERROR_BASE) + 9
+ENOSYS :: _Platform_Error(i32(haiku.Errno.POSIX_ERROR_BASE) + 9)
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE: Errno: 0
-
 stdin:  Handle = 0
 stdout: Handle = 1
 stderr: Handle = 2
@@ -121,7 +119,7 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK
 
 
 foreign libc {
-	@(link_name="_errnop")	__error		:: proc() -> ^c.int ---
+	@(link_name="_errorp")	__error		:: proc() -> ^c.int ---
 
 	@(link_name="fork")	_unix_fork	:: proc() -> pid_t ---
 	@(link_name="getthrid")	_unix_getthrid	:: proc() -> int ---
@@ -179,38 +177,47 @@ Dirent :: struct {
 
 Dir :: distinct rawptr // DIR*
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__error()^)
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := _unix_fork()
 	if pid == -1 {
-		return Pid(-1), Errno(get_last_error())
+		return Pid(-1), get_last_error()
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // In practice a read/write call would probably never read/write these big buffers all at once,
@@ -220,47 +227,69 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return -1, err
 	}
-	return s.size, ERROR_NONE
+	return s.size, nil
 }
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -269,8 +298,8 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -278,13 +307,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_stat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -292,55 +321,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	res := _unix_fstat(fd, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
 
 	if result == nil {
 		end_of_stream = true
@@ -350,8 +378,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -361,22 +389,24 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	return "", Errno(ENOSYS)
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
+	return "", Error(ENOSYS)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -387,26 +417,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
-	path_cstr := transmute(cstring)path_ptr
-	path = strings.clone( string(path_cstr) )
+	path_cstr := cstring(path_ptr)
+	path = strings.clone(string(path_cstr))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cstr, c.int(mask))
 	if res == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -417,12 +448,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	info: haiku.system_info
 	haiku.get_system_info(&info)

+ 106 - 61
core/os/os_js.odin

@@ -3,42 +3,45 @@ package os
 
 import "base:runtime"
 
+@(require_results)
 is_path_separator :: proc(c: byte) -> bool {
 	return c == '/' || c == '\\'
 }
 
-open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-flush :: proc(fd: Handle) -> (err: Errno) {
+flush :: proc(fd: Handle) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 @(private="file")
-read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) {
+read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
@@ -47,38 +50,42 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
 MAX_RW :: 1<<30
 
 @(private)
-pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 @(private)
-pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 stdout: Handle = 1
 stderr: Handle = 2
 
+@(require_results)
 get_std_handle :: proc "contextless" (h: uint) -> Handle {
 	context = runtime.default_context()
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
+@(require_results)
 is_file :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
+@(require_results)
 is_dir :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
@@ -86,82 +93,118 @@ is_dir :: proc(path: string) -> bool {
 // NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
 //@private cwd_lock := win32.SRWLOCK{} // zero is initialized
 
+@(require_results)
 get_current_directory :: proc(allocator := context.allocator) -> string {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-change_directory :: proc(path: string) -> (err: Errno) {
+change_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
+make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-remove_directory :: proc(path: string) -> (err: Errno) {
+remove_directory :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-@(private)
+@(private, require_results)
 is_abs :: proc(path: string) -> bool {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-@(private)
+@(private, require_results)
 fix_long_path :: proc(path: string) -> string {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-link :: proc(old_name, new_name: string) -> (err: Errno) {
+link :: proc(old_name, new_name: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-unlink :: proc(path: string) -> (err: Errno) {
+unlink :: proc(path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
 
-rename :: proc(old_path, new_path: string) -> (err: Errno) {
+rename :: proc(old_path, new_path: string) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
+ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-truncate :: proc(path: string, length: i64) -> (err: Errno) {
+truncate :: proc(path: string, length: i64) -> (err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-remove :: proc(name: string) -> Errno {
+remove :: proc(name: string) -> Error {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
-pipe :: proc() -> (r, w: Handle, err: Errno) {
+@(require_results)
+pipe :: proc() -> (r, w: Handle, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+@(require_results)
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
-Errno     :: distinct int
+
+_Platform_Error :: enum i32 {
+	NONE = 0,
+	FILE_NOT_FOUND      = 2,
+	PATH_NOT_FOUND      = 3,
+	ACCESS_DENIED       = 5,
+	INVALID_HANDLE      = 6,
+	NOT_ENOUGH_MEMORY   = 8,
+	NO_MORE_FILES       = 18,
+	HANDLE_EOF          = 38,
+	NETNAME_DELETED     = 64,
+	FILE_EXISTS         = 80,
+	INVALID_PARAMETER   = 87,
+	BROKEN_PIPE         = 109,
+	BUFFER_OVERFLOW     = 111,
+	INSUFFICIENT_BUFFER = 122,
+	MOD_NOT_FOUND       = 126,
+	PROC_NOT_FOUND      = 127,
+	DIR_NOT_EMPTY       = 145,
+	ALREADY_EXISTS      = 183,
+	ENVVAR_NOT_FOUND    = 203,
+	MORE_DATA           = 234,
+	OPERATION_ABORTED   = 995,
+	IO_PENDING          = 997,
+	NOT_FOUND           = 1168,
+	PRIVILEGE_NOT_HELD  = 1314,
+	WSAEACCES             = 10013,
+	WSAECONNRESET         = 10054,
+
+	// Windows reserves errors >= 1<<29 for application use
+	FILE_IS_PIPE    = 1<<29 + 0,
+	FILE_IS_NOT_DIR = 1<<29 + 1,
+	NEGATIVE_OFFSET = 1<<29 + 2,
+}
 
 
 INVALID_HANDLE :: ~Handle(0)
@@ -182,37 +225,34 @@ O_ASYNC    :: 0x02000
 O_CLOEXEC  :: 0x80000
 
 
-ERROR_NONE:                   Errno : 0
-ERROR_FILE_NOT_FOUND:         Errno : 2
-ERROR_PATH_NOT_FOUND:         Errno : 3
-ERROR_ACCESS_DENIED:          Errno : 5
-ERROR_INVALID_HANDLE:         Errno : 6
-ERROR_NOT_ENOUGH_MEMORY:      Errno : 8
-ERROR_NO_MORE_FILES:          Errno : 18
-ERROR_HANDLE_EOF:             Errno : 38
-ERROR_NETNAME_DELETED:        Errno : 64
-ERROR_FILE_EXISTS:            Errno : 80
-ERROR_INVALID_PARAMETER:      Errno : 87
-ERROR_BROKEN_PIPE:            Errno : 109
-ERROR_BUFFER_OVERFLOW:        Errno : 111
-ERROR_INSUFFICIENT_BUFFER:    Errno : 122
-ERROR_MOD_NOT_FOUND:          Errno : 126
-ERROR_PROC_NOT_FOUND:         Errno : 127
-ERROR_DIR_NOT_EMPTY:          Errno : 145
-ERROR_ALREADY_EXISTS:         Errno : 183
-ERROR_ENVVAR_NOT_FOUND:       Errno : 203
-ERROR_MORE_DATA:              Errno : 234
-ERROR_OPERATION_ABORTED:      Errno : 995
-ERROR_IO_PENDING:             Errno : 997
-ERROR_NOT_FOUND:              Errno : 1168
-ERROR_PRIVILEGE_NOT_HELD:     Errno : 1314
-WSAEACCES:                    Errno : 10013
-WSAECONNRESET:                Errno : 10054
-
-// Windows reserves errors >= 1<<29 for application use
-ERROR_FILE_IS_PIPE:           Errno : 1<<29 + 0
-ERROR_FILE_IS_NOT_DIR:        Errno : 1<<29 + 1
-ERROR_NEGATIVE_OFFSET:        Errno : 1<<29 + 2
+ERROR_FILE_NOT_FOUND      :: Platform_Error.FILE_NOT_FOUND
+ERROR_PATH_NOT_FOUND      :: Platform_Error.PATH_NOT_FOUND
+ERROR_ACCESS_DENIED       :: Platform_Error.ACCESS_DENIED
+ERROR_INVALID_HANDLE      :: Platform_Error.INVALID_HANDLE
+ERROR_NOT_ENOUGH_MEMORY   :: Platform_Error.NOT_ENOUGH_MEMORY
+ERROR_NO_MORE_FILES       :: Platform_Error.NO_MORE_FILES
+ERROR_HANDLE_EOF          :: Platform_Error.HANDLE_EOF
+ERROR_NETNAME_DELETED     :: Platform_Error.NETNAME_DELETED
+ERROR_FILE_EXISTS         :: Platform_Error.FILE_EXISTS
+ERROR_INVALID_PARAMETER   :: Platform_Error.INVALID_PARAMETER
+ERROR_BROKEN_PIPE         :: Platform_Error.BROKEN_PIPE
+ERROR_BUFFER_OVERFLOW     :: Platform_Error.BUFFER_OVERFLOW
+ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER
+ERROR_MOD_NOT_FOUND       :: Platform_Error.MOD_NOT_FOUND
+ERROR_PROC_NOT_FOUND      :: Platform_Error.PROC_NOT_FOUND
+ERROR_DIR_NOT_EMPTY       :: Platform_Error.DIR_NOT_EMPTY
+ERROR_ALREADY_EXISTS      :: Platform_Error.ALREADY_EXISTS
+ERROR_ENVVAR_NOT_FOUND    :: Platform_Error.ENVVAR_NOT_FOUND
+ERROR_MORE_DATA           :: Platform_Error.MORE_DATA
+ERROR_OPERATION_ABORTED   :: Platform_Error.OPERATION_ABORTED
+ERROR_IO_PENDING          :: Platform_Error.IO_PENDING
+ERROR_NOT_FOUND           :: Platform_Error.NOT_FOUND
+ERROR_PRIVILEGE_NOT_HELD  :: Platform_Error.PRIVILEGE_NOT_HELD
+WSAEACCES                 :: Platform_Error.WSAEACCES
+WSAECONNRESET             :: Platform_Error.WSAECONNRESET
+
+ERROR_FILE_IS_PIPE        :: General_Error.File_Is_Pipe
+ERROR_FILE_IS_NOT_DIR     :: General_Error.Not_Dir
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
@@ -221,20 +261,23 @@ args := _alloc_command_line_arguments()
 
 
 
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
 
+@(require_results)
 get_page_size :: proc() -> int {
 	unimplemented("core:os procedure not supported on JS target")
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	unimplemented("core:os procedure not supported on JS target")
 }
@@ -246,6 +289,7 @@ exit :: proc "contextless" (code: int) -> ! {
 
 
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	context = runtime.default_context()
 	unimplemented("core:os procedure not supported on JS target")
@@ -253,6 +297,7 @@ current_thread_id :: proc "contextless" () -> int {
 
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	return nil
 }

+ 293 - 258
core/os/os_linux.odin

@@ -20,148 +20,148 @@ import "base:intrinsics"
 // all that about compatibility. But we don't want to push experimental changes
 // and have people's code break while it's still work in progress.
 import unix "core:sys/unix"
+import linux "core:sys/linux"
 
 Handle    :: distinct i32
 Pid       :: distinct i32
 File_Time :: distinct u64
-Errno     :: distinct i32
 Socket    :: distinct int
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:     Errno : 0
-EPERM:          Errno : 1
-ENOENT:         Errno : 2
-ESRCH:          Errno : 3
-EINTR:          Errno : 4
-EIO:            Errno : 5
-ENXIO:          Errno : 6
-EBADF:          Errno : 9
-EAGAIN:         Errno : 11
-ENOMEM:         Errno : 12
-EACCES:         Errno : 13
-EFAULT:         Errno : 14
-EEXIST:         Errno : 17
-ENODEV:         Errno : 19
-ENOTDIR:        Errno : 20
-EISDIR:         Errno : 21
-EINVAL:         Errno : 22
-ENFILE:         Errno : 23
-EMFILE:         Errno : 24
-ETXTBSY:        Errno : 26
-EFBIG:          Errno : 27
-ENOSPC:         Errno : 28
-ESPIPE:         Errno : 29
-EROFS:          Errno : 30
-EPIPE:          Errno : 32
-
-ERANGE:         Errno : 34 /* Result too large */
-EDEADLK:        Errno : 35 /* Resource deadlock would occur */
-ENAMETOOLONG:   Errno : 36 /* File name too long */
-ENOLCK:         Errno : 37 /* No record locks available */
-
-ENOSYS:         Errno : 38	/* Invalid system call number */
-
-ENOTEMPTY:      Errno : 39	/* Directory not empty */
-ELOOP:          Errno : 40	/* Too many symbolic links encountered */
-EWOULDBLOCK:    Errno : EAGAIN /* Operation would block */
-ENOMSG:         Errno : 42	/* No message of desired type */
-EIDRM:          Errno : 43	/* Identifier removed */
-ECHRNG:         Errno : 44	/* Channel number out of range */
-EL2NSYNC:       Errno : 45	/* Level 2 not synchronized */
-EL3HLT:         Errno : 46	/* Level 3 halted */
-EL3RST:         Errno : 47	/* Level 3 reset */
-ELNRNG:         Errno : 48	/* Link number out of range */
-EUNATCH:        Errno : 49	/* Protocol driver not attached */
-ENOCSI:         Errno : 50	/* No CSI structure available */
-EL2HLT:         Errno : 51	/* Level 2 halted */
-EBADE:          Errno : 52	/* Invalid exchange */
-EBADR:          Errno : 53	/* Invalid request descriptor */
-EXFULL:         Errno : 54	/* Exchange full */
-ENOANO:         Errno : 55	/* No anode */
-EBADRQC:        Errno : 56	/* Invalid request code */
-EBADSLT:        Errno : 57	/* Invalid slot */
-EDEADLOCK:      Errno : EDEADLK
-EBFONT:         Errno : 59	/* Bad font file format */
-ENOSTR:         Errno : 60	/* Device not a stream */
-ENODATA:        Errno : 61	/* No data available */
-ETIME:          Errno : 62	/* Timer expired */
-ENOSR:          Errno : 63	/* Out of streams resources */
-ENONET:         Errno : 64	/* Machine is not on the network */
-ENOPKG:         Errno : 65	/* Package not installed */
-EREMOTE:        Errno : 66	/* Object is remote */
-ENOLINK:        Errno : 67	/* Link has been severed */
-EADV:           Errno : 68	/* Advertise error */
-ESRMNT:         Errno : 69	/* Srmount error */
-ECOMM:          Errno : 70	/* Communication error on send */
-EPROTO:         Errno : 71	/* Protocol error */
-EMULTIHOP:      Errno : 72	/* Multihop attempted */
-EDOTDOT:        Errno : 73	/* RFS specific error */
-EBADMSG:        Errno : 74	/* Not a data message */
-EOVERFLOW:      Errno : 75	/* Value too large for defined data type */
-ENOTUNIQ:       Errno : 76	/* Name not unique on network */
-EBADFD:         Errno : 77	/* File descriptor in bad state */
-EREMCHG:        Errno : 78	/* Remote address changed */
-ELIBACC:        Errno : 79	/* Can not access a needed shared library */
-ELIBBAD:        Errno : 80	/* Accessing a corrupted shared library */
-ELIBSCN:        Errno : 81	/* .lib section in a.out corrupted */
-ELIBMAX:        Errno : 82	/* Attempting to link in too many shared libraries */
-ELIBEXEC:       Errno : 83	/* Cannot exec a shared library directly */
-EILSEQ:         Errno : 84	/* Illegal byte sequence */
-ERESTART:       Errno : 85	/* Interrupted system call should be restarted */
-ESTRPIPE:       Errno : 86	/* Streams pipe error */
-EUSERS:         Errno : 87	/* Too many users */
-ENOTSOCK:       Errno : 88	/* Socket operation on non-socket */
-EDESTADDRREQ:   Errno : 89	/* Destination address required */
-EMSGSIZE:       Errno : 90	/* Message too long */
-EPROTOTYPE:     Errno : 91	/* Protocol wrong type for socket */
-ENOPROTOOPT:    Errno : 92	/* Protocol not available */
-EPROTONOSUPPORT:Errno : 93	/* Protocol not supported */
-ESOCKTNOSUPPORT:Errno : 94	/* Socket type not supported */
-EOPNOTSUPP: 	Errno : 95	/* Operation not supported on transport endpoint */
-EPFNOSUPPORT: 	Errno : 96	/* Protocol family not supported */
-EAFNOSUPPORT: 	Errno : 97	/* Address family not supported by protocol */
-EADDRINUSE: 	Errno : 98	/* Address already in use */
-EADDRNOTAVAIL: 	Errno : 99	/* Cannot assign requested address */
-ENETDOWN: 		Errno : 100	/* Network is down */
-ENETUNREACH: 	Errno : 101	/* Network is unreachable */
-ENETRESET: 		Errno : 102	/* Network dropped connection because of reset */
-ECONNABORTED: 	Errno : 103	/* Software caused connection abort */
-ECONNRESET: 	Errno : 104	/* Connection reset by peer */
-ENOBUFS: 		Errno : 105	/* No buffer space available */
-EISCONN: 		Errno : 106	/* Transport endpoint is already connected */
-ENOTCONN: 		Errno : 107	/* Transport endpoint is not connected */
-ESHUTDOWN: 		Errno : 108	/* Cannot send after transport endpoint shutdown */
-ETOOMANYREFS: 	Errno : 109	/* Too many references: cannot splice */
-ETIMEDOUT: 		Errno : 110	/* Connection timed out */
-ECONNREFUSED: 	Errno : 111	/* Connection refused */
-EHOSTDOWN: 		Errno : 112	/* Host is down */
-EHOSTUNREACH: 	Errno : 113	/* No route to host */
-EALREADY: 		Errno : 114	/* Operation already in progress */
-EINPROGRESS: 	Errno : 115	/* Operation now in progress */
-ESTALE: 		Errno : 116	/* Stale file handle */
-EUCLEAN: 		Errno : 117	/* Structure needs cleaning */
-ENOTNAM: 		Errno : 118	/* Not a XENIX named type file */
-ENAVAIL: 		Errno : 119	/* No XENIX semaphores available */
-EISNAM: 		Errno : 120	/* Is a named type file */
-EREMOTEIO: 		Errno : 121	/* Remote I/O error */
-EDQUOT: 		Errno : 122	/* Quota exceeded */
-
-ENOMEDIUM: 		Errno : 123	/* No medium found */
-EMEDIUMTYPE: 	Errno : 124	/* Wrong medium type */
-ECANCELED: 		Errno : 125	/* Operation Canceled */
-ENOKEY: 		Errno : 126	/* Required key not available */
-EKEYEXPIRED: 	Errno : 127	/* Key has expired */
-EKEYREVOKED: 	Errno : 128	/* Key has been revoked */
-EKEYREJECTED: 	Errno : 129	/* Key was rejected by service */
+_Platform_Error :: linux.Errno
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+EBADF           :: Platform_Error.EBADF
+EAGAIN          :: Platform_Error.EAGAIN
+ENOMEM          :: Platform_Error.ENOMEM
+EACCES          :: Platform_Error.EACCES
+EFAULT          :: Platform_Error.EFAULT
+EEXIST          :: Platform_Error.EEXIST
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EPIPE           :: Platform_Error.EPIPE
+
+ERANGE          :: Platform_Error.ERANGE          /* Result too large */
+EDEADLK         :: Platform_Error.EDEADLK         /* Resource deadlock would occur */
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG    /* File name too long */
+ENOLCK          :: Platform_Error.ENOLCK          /* No record locks available */
+
+ENOSYS          :: Platform_Error.ENOSYS          /* Invalid system call number */
+
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY       /* Directory not empty */
+ELOOP           :: Platform_Error.ELOOP           /* Too many symbolic links encountered */
+EWOULDBLOCK     :: Platform_Error.EWOULDBLOCK     /* Operation would block */
+ENOMSG          :: Platform_Error.ENOMSG          /* No message of desired type */
+EIDRM           :: Platform_Error.EIDRM           /* Identifier removed */
+ECHRNG          :: Platform_Error.ECHRNG          /* Channel number out of range */
+EL2NSYNC        :: Platform_Error.EL2NSYNC        /* Level 2 not synchronized */
+EL3HLT          :: Platform_Error.EL3HLT          /* Level 3 halted */
+EL3RST          :: Platform_Error.EL3RST          /* Level 3 reset */
+ELNRNG          :: Platform_Error.ELNRNG          /* Link number out of range */
+EUNATCH         :: Platform_Error.EUNATCH         /* Protocol driver not attached */
+ENOCSI          :: Platform_Error.ENOCSI          /* No CSI structure available */
+EL2HLT          :: Platform_Error.EL2HLT          /* Level 2 halted */
+EBADE           :: Platform_Error.EBADE           /* Invalid exchange */
+EBADR           :: Platform_Error.EBADR           /* Invalid request descriptor */
+EXFULL          :: Platform_Error.EXFULL          /* Exchange full */
+ENOANO          :: Platform_Error.ENOANO          /* No anode */
+EBADRQC         :: Platform_Error.EBADRQC         /* Invalid request code */
+EBADSLT         :: Platform_Error.EBADSLT         /* Invalid slot */
+EDEADLOCK       :: Platform_Error.EDEADLOCK
+EBFONT          :: Platform_Error.EBFONT          /* Bad font file format */
+ENOSTR          :: Platform_Error.ENOSTR          /* Device not a stream */
+ENODATA         :: Platform_Error.ENODATA         /* No data available */
+ETIME           :: Platform_Error.ETIME           /* Timer expired */
+ENOSR           :: Platform_Error.ENOSR           /* Out of streams resources */
+ENONET          :: Platform_Error.ENONET          /* Machine is not on the network */
+ENOPKG          :: Platform_Error.ENOPKG          /* Package not installed */
+EREMOTE         :: Platform_Error.EREMOTE         /* Object is remote */
+ENOLINK         :: Platform_Error.ENOLINK         /* Link has been severed */
+EADV            :: Platform_Error.EADV            /* Advertise error */
+ESRMNT          :: Platform_Error.ESRMNT          /* Srmount error */
+ECOMM           :: Platform_Error.ECOMM           /* Communication error on send */
+EPROTO          :: Platform_Error.EPROTO          /* Protocol error */
+EMULTIHOP       :: Platform_Error.EMULTIHOP       /* Multihop attempted */
+EDOTDOT         :: Platform_Error.EDOTDOT         /* RFS specific error */
+EBADMSG         :: Platform_Error.EBADMSG         /* Not a data message */
+EOVERFLOW       :: Platform_Error.EOVERFLOW       /* Value too large for defined data type */
+ENOTUNIQ        :: Platform_Error.ENOTUNIQ        /* Name not unique on network */
+EBADFD          :: Platform_Error.EBADFD          /* File descriptor in bad state */
+EREMCHG         :: Platform_Error.EREMCHG         /* Remote address changed */
+ELIBACC         :: Platform_Error.ELIBACC         /* Can not access a needed shared library */
+ELIBBAD         :: Platform_Error.ELIBBAD         /* Accessing a corrupted shared library */
+ELIBSCN         :: Platform_Error.ELIBSCN         /* .lib section in a.out corrupted */
+ELIBMAX         :: Platform_Error.ELIBMAX         /* Attempting to link in too many shared libraries */
+ELIBEXEC        :: Platform_Error.ELIBEXEC        /* Cannot exec a shared library directly */
+EILSEQ          :: Platform_Error.EILSEQ          /* Illegal byte sequence */
+ERESTART        :: Platform_Error.ERESTART        /* Interrupted system call should be restarted */
+ESTRPIPE        :: Platform_Error.ESTRPIPE        /* Streams pipe error */
+EUSERS          :: Platform_Error.EUSERS          /* Too many users */
+ENOTSOCK        :: Platform_Error.ENOTSOCK        /* Socket operation on non-socket */
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ    /* Destination address required */
+EMSGSIZE        :: Platform_Error.EMSGSIZE        /* Message too long */
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE      /* Protocol wrong type for socket */
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT     /* Protocol not available */
+EPROTONOSUPPOR  :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */
+ESOCKTNOSUPPOR  :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP      /* Operation not supported on transport endpoint */
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT    /* Protocol family not supported */
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT    /* Address family not supported by protocol */
+EADDRINUSE      :: Platform_Error.EADDRINUSE      /* Address already in use */
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL   /* Cannot assign requested address */
+ENETDOWN        :: Platform_Error.ENETDOWN        /* Network is down */
+ENETUNREACH     :: Platform_Error.ENETUNREACH     /* Network is unreachable */
+ENETRESET       :: Platform_Error.ENETRESET       /* Network dropped connection because of reset */
+ECONNABORTED    :: Platform_Error.ECONNABORTED    /* Software caused connection abort */
+ECONNRESET      :: Platform_Error.ECONNRESET      /* Connection reset by peer */
+ENOBUFS         :: Platform_Error.ENOBUFS         /* No buffer space available */
+EISCONN         :: Platform_Error.EISCONN         /* Transport endpoint is already connected */
+ENOTCONN        :: Platform_Error.ENOTCONN        /* Transport endpoint is not connected */
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN       /* Cannot send after transport endpoint shutdown */
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS    /* Too many references: cannot splice */
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT       /* Connection timed out */
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED    /* Connection refused */
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN       /* Host is down */
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH    /* No route to host */
+EALREADY        :: Platform_Error.EALREADY        /* Operation already in progress */
+EINPROGRESS     :: Platform_Error.EINPROGRESS     /* Operation now in progress */
+ESTALE          :: Platform_Error.ESTALE          /* Stale file handle */
+EUCLEAN         :: Platform_Error.EUCLEAN         /* Structure needs cleaning */
+ENOTNAM         :: Platform_Error.ENOTNAM         /* Not a XENIX named type file */
+ENAVAIL         :: Platform_Error.ENAVAIL         /* No XENIX semaphores available */
+EISNAM          :: Platform_Error.EISNAM          /* Is a named type file */
+EREMOTEIO       :: Platform_Error.EREMOTEIO       /* Remote I/O error */
+EDQUOT          :: Platform_Error.EDQUOT          /* Quota exceeded */
+
+ENOMEDIUM       :: Platform_Error.ENOMEDIUM       /* No medium found */
+EMEDIUMTYPE     :: Platform_Error.EMEDIUMTYPE     /* Wrong medium type */
+ECANCELED       :: Platform_Error.ECANCELED       /* Operation Canceled */
+ENOKEY          :: Platform_Error.ENOKEY          /* Required key not available */
+EKEYEXPIRED     :: Platform_Error.EKEYEXPIRED     /* Key has expired */
+EKEYREVOKED     :: Platform_Error.EKEYREVOKED     /* Key has been revoked */
+EKEYREJECTED    :: Platform_Error.EKEYREJECTED    /* Key was rejected by service */
 
 /* for robust mutexes */
-EOWNERDEAD: 	Errno : 130	/* Owner died */
-ENOTRECOVERABLE: Errno : 131	/* State not recoverable */
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD      /* Owner died */
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */
 
-ERFKILL: 		Errno : 132	/* Operation not possible due to RF-kill */
+ERFKILL   :: Platform_Error.ERFKILL               /* Operation not possible due to RF-kill */
 
-EHWPOISON: 		Errno : 133	/* Memory page has hardware error */
+EHWPOISON :: Platform_Error.EHWPOISON             /* Memory page has hardware error */
 
 ADDR_NO_RANDOMIZE :: 0x40000
 
@@ -448,13 +448,13 @@ 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 }
+@(require_results) S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) 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
@@ -506,41 +506,55 @@ foreign dl {
 	@(link_name="freeifaddrs")      _freeifaddrs        :: proc(ifa: ^ifaddrs) ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
 // determine errno from syscall return value
-@private
-_get_errno :: proc(res: int) -> Errno {
+@(private, require_results)
+_get_errno :: proc(res: int) -> Error {
 	if res < 0 && res > -4096 {
-		return Errno(-res)
+		return Platform_Error(-res)
 	}
-	return 0
+	return nil
 }
 
 // get errno from libc
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	err := Platform_Error(__errno_location()^)
+	#partial switch err {
+	case .NONE:
+		return nil
+	case .EPERM:
+		return .Permission_Denied
+	case .EEXIST:
+		return .Exist
+	case .ENOENT:
+		return .Not_Exist
+	}
+	return err
 }
 
-personality :: proc(persona: u64) -> (Errno) {
+personality :: proc(persona: u64) -> Error {
 	res := unix.sys_personality(persona)
 	if res == -1 {
 		return _get_errno(res)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := unix.sys_fork()
 	if pid == -1 {
 		return -1, _get_errno(pid)
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-execvp :: proc(path: string, args: []string) -> Errno {
+execvp :: proc(path: string, args: []string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -551,24 +565,30 @@ execvp :: proc(path: string, args: []string) -> Errno {
 	}
 
 	_unix_execvp(path_cstr, raw_data(args_cstrs))
-	return Errno(get_last_error())
+	return get_last_error()
 }
 
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := unix.sys_open(cstr, flags, uint(mode))
 	if handle < 0 {
 		return INVALID_HANDLE, _get_errno(handle)
 	}
-	return Handle(handle), ERROR_NONE
+	return Handle(handle), nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	return _get_errno(unix.sys_close(int(fd)))
 }
 
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
+}
+
 // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error).
 // `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin.
 // In practice a read/write call would probably never read/write these big buffers all at once,
@@ -578,9 +598,9 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_read := min(uint(len(data)), MAX_RW)
@@ -589,12 +609,12 @@ read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if bytes_read < 0 {
 		return -1, _get_errno(bytes_read)
 	}
-	return bytes_read, ERROR_NONE
+	return bytes_read, nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write := min(uint(len(data)), MAX_RW)
@@ -603,12 +623,12 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	if bytes_written < 0 {
 		return -1, _get_errno(bytes_written)
 	}
-	return bytes_written, ERROR_NONE
+	return bytes_written, nil
 }
 
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_read := min(uint(len(data)), MAX_RW)
@@ -617,12 +637,12 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	if bytes_read < 0 {
 		return -1, _get_errno(bytes_read)
 	}
-	return bytes_read, ERROR_NONE
+	return bytes_read, nil
 }
 
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write := min(uint(len(data)), MAX_RW)
@@ -631,92 +651,97 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	if bytes_written < 0 {
 		return -1, _get_errno(bytes_written)
 	}
-	return bytes_written, ERROR_NONE
+	return bytes_written, nil
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := unix.sys_lseek(int(fd), offset, whence)
 	if res < 0 {
 		return -1, _get_errno(int(res))
 	}
-	return i64(res), ERROR_NONE
+	return i64(res), nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	result := unix.sys_fstat(int(fd), rawptr(&s))
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return max(s.size, 0), ERROR_NONE
+	return max(s.size, 0), nil
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr))
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_unlink(path_cstr))
 }
 
-make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: u32 = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_mkdir(path_cstr, uint(mode)))
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	return _get_errno(unix.sys_rmdir(path_cstr))
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -725,6 +750,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
@@ -742,26 +768,22 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}
 last_write_time_by_name :: proc(name: string) -> File_Time {}
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -771,11 +793,11 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -785,53 +807,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized; the syscall fills this buffer for us
 	s: OS_Stat = ---
 	result := unix.sys_fstat(int(fd), rawptr(&s))
 	if result < 0 {
 		return s, _get_errno(result)
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -842,8 +864,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -860,12 +882,13 @@ _readlink :: proc(path: string) -> (string, Errno) {
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}
 	}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 	buf : [256]byte
 	fd_str := strconv.itoa( buf[:], cast(int)fd )
 
@@ -875,7 +898,8 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
 	return _readlink(procfs_path)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -886,25 +910,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := unix.sys_access(cstr, mask)
 	if result < 0 {
 		return false, _get_errno(result)
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -916,33 +941,35 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
-set_env :: proc(key, value: string) -> Errno {
+set_env :: proc(key, value: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
 	value_cstring := strings.clone_to_cstring(value, context.temp_allocator)
 	// NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly
 	res := _unix_setenv(key_cstring, value_cstring, 1)
 	if res < 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-unset_env :: proc(key: string) -> Errno {
+unset_env :: proc(key: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	s := strings.clone_to_cstring(key, context.temp_allocator)
 	res := _unix_putenv(s)
 	if res < 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -964,14 +991,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := unix.sys_chdir(cstr)
 	if res < 0 {
 		return _get_errno(res)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -979,16 +1006,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return unix.sys_gettid()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -1004,6 +1034,7 @@ dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -1016,11 +1047,12 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return int(_unix_get_nprocs())
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {
@@ -1029,117 +1061,120 @@ _alloc_command_line_arguments :: proc() -> []string {
 	return res
 }
 
-socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
+@(require_results)
+socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
 	result := unix.sys_socket(domain, type, protocol)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return Socket(result), ERROR_NONE
+	return Socket(result), nil
 }
 
-bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error {
 	result := unix.sys_bind(int(sd), addr, len)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 
-connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
+connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error {
 	result := unix.sys_connect(int(sd), addr, len)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
+accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) {
 	result := unix.sys_accept(int(sd), rawptr(addr), len)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return Socket(result), ERROR_NONE
+	return Socket(result), nil
 }
 
-listen :: proc(sd: Socket, backlog: int) -> (Errno) {
+listen :: proc(sd: Socket, backlog: int) -> Error {
 	result := unix.sys_listen(int(sd), backlog)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
+setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error {
 	result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
 
-recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
+recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) {
 	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size))
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
 	result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
 
-sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
+sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) {
 	result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
+send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) {
 	result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
 	if result < 0 {
 		return 0, _get_errno(int(result))
 	}
-	return u32(result), ERROR_NONE
+	return u32(result), nil
 }
 
-shutdown :: proc(sd: Socket, how: int) -> (Errno) {
+shutdown :: proc(sd: Socket, how: int) -> Error {
 	result := unix.sys_shutdown(int(sd), how)
 	if result < 0 {
 		return _get_errno(result)
 	}
-	return ERROR_NONE
+	return nil
 }
 
-fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) {
 	result := unix.sys_fcntl(fd, cmd, arg)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }
 
-poll :: proc(fds: []pollfd, timeout: int) -> (int, Errno) {
+@(require_results)
+poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) {
 	result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout)
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }
 
-ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) {
+@(require_results)
+ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) {
 	result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t))
 	if result < 0 {
 		return 0, _get_errno(result)
 	}
-	return result, ERROR_NONE
+	return result, nil
 }

+ 392 - 217
core/os/os_netbsd.odin

@@ -9,150 +9,289 @@ import "core:c"
 
 Handle :: distinct i32
 File_Time :: distinct u64
-Errno :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:     Errno : 0 		/* No error */
-EPERM:          Errno : 1		/* Operation not permitted */
-ENOENT:         Errno : 2		/* No such file or directory */
-EINTR:          Errno : 4		/* Interrupted system call */
-ESRCH:          Errno : 3		/* No such process */
-EIO:            Errno : 5		/* Input/output error */
-ENXIO:          Errno : 6		/* Device not configured */
-E2BIG:          Errno : 7		/* Argument list too long */
-ENOEXEC:        Errno : 8		/* Exec format error */
-EBADF:          Errno : 9		/* Bad file descriptor */
-ECHILD:         Errno : 10		/* No child processes */
-EDEADLK:        Errno : 11		/* Resource deadlock avoided. 11 was EAGAIN */
-ENOMEM:         Errno : 12		/* Cannot allocate memory */
-EACCES:         Errno : 13		/* Permission denied */
-EFAULT:         Errno : 14		/* Bad address */
-ENOTBLK:        Errno : 15		/* Block device required */
-EBUSY:          Errno : 16		/* Device busy */
-EEXIST:         Errno : 17		/* File exists */
-EXDEV:          Errno : 18		/* Cross-device link */
-ENODEV:         Errno : 19		/* Operation not supported by device */
-ENOTDIR:        Errno : 20		/* Not a directory */
-EISDIR:         Errno : 21		/* Is a directory */
-EINVAL:         Errno : 22		/* Invalid argument */
-ENFILE:         Errno : 23		/* Too many open files in system */
-EMFILE:         Errno : 24		/* Too many open files */
-ENOTTY:         Errno : 25		/* Inappropriate ioctl for device */
-ETXTBSY:        Errno : 26		/* Text file busy */
-EFBIG:          Errno : 27		/* File too large */
-ENOSPC:         Errno : 28		/* No space left on device */
-ESPIPE:         Errno : 29		/* Illegal seek */
-EROFS:          Errno : 30		/* Read-only file system */
-EMLINK:         Errno : 31		/* Too many links */
-EPIPE:          Errno : 32		/* Broken pipe */
+_Platform_Error :: enum i32 {
+	NONE            = 0,
+	EPERM           = 1,          /* Operation not permitted */
+	ENOENT          = 2,          /* No such file or directory */
+	EINTR           = 4,          /* Interrupted system call */
+	ESRCH           = 3,          /* No such process */
+	EIO             = 5,          /* Input/output error */
+	ENXIO           = 6,          /* Device not configured */
+	E2BIG           = 7,          /* Argument list too long */
+	ENOEXEC         = 8,          /* Exec format error */
+	EBADF           = 9,          /* Bad file descriptor */
+	ECHILD          = 10,         /* No child processes */
+	EDEADLK         = 11,         /* Resource deadlock avoided. 11 was EAGAIN */
+	ENOMEM          = 12,         /* Cannot allocate memory */
+	EACCES          = 13,         /* Permission denied */
+	EFAULT          = 14,         /* Bad address */
+	ENOTBLK         = 15,         /* Block device required */
+	EBUSY           = 16,         /* Device busy */
+	EEXIST          = 17,         /* File exists */
+	EXDEV           = 18,         /* Cross-device link */
+	ENODEV          = 19,         /* Operation not supported by device */
+	ENOTDIR         = 20,         /* Not a directory */
+	EISDIR          = 21,         /* Is a directory */
+	EINVAL          = 22,         /* Invalid argument */
+	ENFILE          = 23,         /* Too many open files in system */
+	EMFILE          = 24,         /* Too many open files */
+	ENOTTY          = 25,         /* Inappropriate ioctl for device */
+	ETXTBSY         = 26,         /* Text file busy */
+	EFBIG           = 27,         /* File too large */
+	ENOSPC          = 28,         /* No space left on device */
+	ESPIPE          = 29,         /* Illegal seek */
+	EROFS           = 30,         /* Read-only file system */
+	EMLINK          = 31,         /* Too many links */
+	EPIPE           = 32,         /* Broken pipe */
+
+	/* math software */
+	EDOM            = 33,         /* Numerical argument out of domain */
+	ERANGE          = 34,         /* Result too large or too small */
+
+	/* non-blocking and interrupt i/o */
+	EAGAIN          = 35,         /* Resource temporarily unavailable */
+	EWOULDBLOCK     = EAGAIN,     /*  Operation would block */
+	EINPROGRESS     = 36,         /* Operation now in progress */
+	EALREADY        = 37,         /* Operation already in progress */
+
+	/* ipc/network software -- argument errors */
+	ENOTSOCK        = 38,         /* Socket operation on non-socket */
+	EDESTADDRREQ    = 39,         /* Destination address required */
+	EMSGSIZE        = 40,         /* Message too long */
+	EPROTOTYPE      = 41,         /* Protocol wrong type for socket */
+	ENOPROTOOPT     = 42,         /* Protocol option not available */
+	EPROTONOSUPPORT = 43,         /* Protocol not supported */
+	ESOCKTNOSUPPORT = 44,         /* Socket type not supported */
+	EOPNOTSUPP      = 45,         /* Operation not supported */
+	EPFNOSUPPORT    = 46,         /* Protocol family not supported */
+	EAFNOSUPPORT    = 47,         /* Address family not supported by protocol family */
+	EADDRINUSE      = 48,         /* Address already in use */
+	EADDRNOTAVAIL   = 49,         /* Can't assign requested address */
+
+	/* ipc/network software -- operational errors */
+	ENETDOWN        = 50,         /* Network is down */
+	ENETUNREACH     = 51,         /* Network is unreachable */
+	ENETRESET       = 52,         /* Network dropped connection on reset */
+	ECONNABORTED    = 53,         /* Software caused connection abort */
+	ECONNRESET      = 54,         /* Connection reset by peer */
+	ENOBUFS         = 55,         /* No buffer space available */
+	EISCONN         = 56,         /* Socket is already connected */
+	ENOTCONN        = 57,         /* Socket is not connected */
+	ESHUTDOWN       = 58,         /* Can't send after socket shutdown */
+	ETOOMANYREFS    = 59,         /* Too many references: can't splice */
+	ETIMEDOUT       = 60,         /* Operation timed out */
+	ECONNREFUSED    = 61,         /* Connection refused */
+
+	ELOOP           = 62,         /* Too many levels of symbolic links */
+	ENAMETOOLONG    = 63,         /* File name too long */
+
+	/* should be rearranged */
+	EHOSTDOWN       = 64,         /* Host is down */
+	EHOSTUNREACH    = 65,         /* No route to host */
+	ENOTEMPTY       = 66,         /* Directory not empty */
+
+	/* quotas & mush */
+	EPROCLIM        = 67,         /* Too many processes */
+	EUSERS          = 68,         /* Too many users */
+	EDQUOT          = 69,         /* Disc quota exceeded */
+
+	/* Network File System */
+	ESTALE          = 70,         /* Stale NFS file handle */
+	EREMOTE         = 71,         /* Too many levels of remote in path */
+	EBADRPC         = 72,         /* RPC struct is bad */
+	ERPCMISMATCH    = 73,         /* RPC version wrong */
+	EPROGUNAVAIL    = 74,         /* RPC prog. not avail */
+	EPROGMISMATCH   = 75,         /* Program version wrong */
+	EPROCUNAVAIL    = 76,         /* Bad procedure for program */
+
+	ENOLCK          = 77,         /* No locks available */
+	ENOSYS          = 78,         /* Function not implemented */
+
+	EFTYPE          = 79,         /* Inappropriate file type or format */
+	EAUTH           = 80,         /* Authentication error */
+	ENEEDAUTH       = 81,         /* Need authenticator */
+
+	/* SystemV IPC */
+	EIDRM           = 82,         /* Identifier removed */
+	ENOMSG          = 83,         /* No message of desired type */
+	EOVERFLOW       = 84,         /* Value too large to be stored in data type */
+
+	/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
+	EILSEQ          = 85,         /* Illegal byte sequence */
+
+	/* From IEEE Std 1003.1-2001 */
+	/* Base, Realtime, Threads or Thread Priority Scheduling option errors */
+	ENOTSUP         = 86,         /* Not supported */
+
+	/* Realtime option errors */
+	ECANCELED       = 87,         /* Operation canceled */
+
+	/* Realtime, XSI STREAMS option errors */
+	EBADMSG         = 88,         /* Bad or Corrupt message */
+
+	/* XSI STREAMS option errors  */
+	ENODATA         = 89,         /* No message available */
+	ENOSR           = 90,         /* No STREAM resources */
+	ENOSTR          = 91,         /* Not a STREAM */
+	ETIME           = 92,         /* STREAM ioctl timeout */
+
+	/* File system extended attribute errors */
+	ENOATTR         = 93,         /* Attribute not found */
+
+	/* Realtime, XSI STREAMS option errors */
+	EMULTIHOP       = 94,         /* Multihop attempted */
+	ENOLINK         = 95,         /* Link has been severed */
+	EPROTO          = 96,         /* Protocol error */
+
+	/* Robust mutexes */
+	EOWNERDEAD      = 97,         /* Previous owner died */
+	ENOTRECOVERABLE = 98,         /* State not recoverable */
+
+	ELAST           = 98,         /* Must equal largest Error */
+}
+
+EPERM           :: Platform_Error.EPERM           /* Operation not permitted */
+ENOENT          :: Platform_Error.ENOENT          /* No such file or directory */
+EINTR           :: Platform_Error.EINTR           /* Interrupted system call */
+ESRCH           :: Platform_Error.ESRCH           /* No such process */
+EIO             :: Platform_Error.EIO             /* Input/output error */
+ENXIO           :: Platform_Error.ENXIO           /* Device not configured */
+E2BIG           :: Platform_Error.E2BIG           /* Argument list too long */
+ENOEXEC         :: Platform_Error.ENOEXEC         /* Exec format error */
+EBADF           :: Platform_Error.EBADF           /* Bad file descriptor */
+ECHILD          :: Platform_Error.ECHILD          /* No child processes */
+EDEADLK         :: Platform_Error.EDEADLK         /* Resource deadlock avoided. 11 was EAGAIN */
+ENOMEM          :: Platform_Error.ENOMEM          /* Cannot allocate memory */
+EACCES          :: Platform_Error.EACCES          /* Permission denied */
+EFAULT          :: Platform_Error.EFAULT          /* Bad address */
+ENOTBLK         :: Platform_Error.ENOTBLK         /* Block device required */
+EBUSY           :: Platform_Error.EBUSY           /* Device busy */
+EEXIST          :: Platform_Error.EEXIST          /* File exists */
+EXDEV           :: Platform_Error.EXDEV           /* Cross-device link */
+ENODEV          :: Platform_Error.ENODEV          /* Operation not supported by device */
+ENOTDIR         :: Platform_Error.ENOTDIR         /* Not a directory */
+EISDIR          :: Platform_Error.EISDIR          /* Is a directory */
+EINVAL          :: Platform_Error.EINVAL          /* Invalid argument */
+ENFILE          :: Platform_Error.ENFILE          /* Too many open files in system */
+EMFILE          :: Platform_Error.EMFILE          /* Too many open files */
+ENOTTY          :: Platform_Error.ENOTTY          /* Inappropriate ioctl for device */
+ETXTBSY         :: Platform_Error.ETXTBSY         /* Text file busy */
+EFBIG           :: Platform_Error.EFBIG           /* File too large */
+ENOSPC          :: Platform_Error.ENOSPC          /* No space left on device */
+ESPIPE          :: Platform_Error.ESPIPE          /* Illegal seek */
+EROFS           :: Platform_Error.EROFS           /* Read-only file system */
+EMLINK          :: Platform_Error.EMLINK          /* Too many links */
+EPIPE           :: Platform_Error.EPIPE           /* Broken pipe */
 
 /* math software */
-EDOM:           Errno : 33		/* Numerical argument out of domain */
-ERANGE:         Errno : 34		/* Result too large or too small */
+EDOM            :: Platform_Error.EDOM            /* Numerical argument out of domain */
+ERANGE          :: Platform_Error.ERANGE          /* Result too large or too small */
 
 /* non-blocking and interrupt i/o */
-EAGAIN:         Errno : 35		/* Resource temporarily unavailable */
-EWOULDBLOCK:    Errno : EAGAIN	/* Operation would block */
-EINPROGRESS:    Errno : 36		/* Operation now in progress */
-EALREADY:       Errno : 37		/* Operation already in progress */
+EAGAIN          :: Platform_Error.EAGAIN          /* Resource temporarily unavailable */
+EWOULDBLOCK     :: EAGAIN        /*  Operation would block */
+EINPROGRESS     :: Platform_Error.EINPROGRESS     /* Operation now in progress */
+EALREADY        :: Platform_Error.EALREADY        /* Operation already in progress */
 
 /* ipc/network software -- argument errors */
-ENOTSOCK:       Errno : 38		/* Socket operation on non-socket */
-EDESTADDRREQ:   Errno : 39		/* Destination address required */
-EMSGSIZE:       Errno : 40		/* Message too long */
-EPROTOTYPE:     Errno : 41		/* Protocol wrong type for socket */
-ENOPROTOOPT:    Errno : 42		/* Protocol option not available */
-EPROTONOSUPPORT:    Errno : 43		/* Protocol not supported */
-ESOCKTNOSUPPORT:    Errno : 44		/* Socket type not supported */
-EOPNOTSUPP:     Errno : 45		/* Operation not supported */
-EPFNOSUPPORT:   Errno : 46		/* Protocol family not supported */
-EAFNOSUPPORT:   Errno : 47		/* Address family not supported by protocol family */
-EADDRINUSE:     Errno : 48		/* Address already in use */
-EADDRNOTAVAIL:  Errno : 49		/* Can't assign requested address */
+ENOTSOCK        :: Platform_Error.ENOTSOCK        /* Socket operation on non-socket */
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ    /* Destination address required */
+EMSGSIZE        :: Platform_Error.EMSGSIZE        /* Message too long */
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE      /* Protocol wrong type for socket */
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT     /* Protocol option not available */
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP      /* Operation not supported */
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT    /* Protocol family not supported */
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT    /* Address family not supported by protocol family */
+EADDRINUSE      :: Platform_Error.EADDRINUSE      /* Address already in use */
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL   /* Can't assign requested address */
 
 /* ipc/network software -- operational errors */
-ENETDOWN:       Errno : 50		/* Network is down */
-ENETUNREACH:    Errno : 51		/* Network is unreachable */
-ENETRESET:      Errno : 52		/* Network dropped connection on reset */
-ECONNABORTED:   Errno : 53		/* Software caused connection abort */
-ECONNRESET:     Errno : 54		/* Connection reset by peer */
-ENOBUFS:        Errno : 55		/* No buffer space available */
-EISCONN:        Errno : 56		/* Socket is already connected */
-ENOTCONN:       Errno : 57		/* Socket is not connected */
-ESHUTDOWN:      Errno : 58		/* Can't send after socket shutdown */
-ETOOMANYREFS:   Errno : 59		/* Too many references: can't splice */
-ETIMEDOUT:      Errno : 60		/* Operation timed out */
-ECONNREFUSED:   Errno : 61		/* Connection refused */
-
-ELOOP:          Errno : 62		/* Too many levels of symbolic links */
-ENAMETOOLONG:   Errno : 63		/* File name too long */
+ENETDOWN        :: Platform_Error.ENETDOWN        /* Network is down */
+ENETUNREACH     :: Platform_Error.ENETUNREACH     /* Network is unreachable */
+ENETRESET       :: Platform_Error.ENETRESET       /* Network dropped connection on reset */
+ECONNABORTED    :: Platform_Error.ECONNABORTED    /* Software caused connection abort */
+ECONNRESET      :: Platform_Error.ECONNRESET      /* Connection reset by peer */
+ENOBUFS         :: Platform_Error.ENOBUFS         /* No buffer space available */
+EISCONN         :: Platform_Error.EISCONN         /* Socket is already connected */
+ENOTCONN        :: Platform_Error.ENOTCONN        /* Socket is not connected */
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN       /* Can't send after socket shutdown */
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS    /* Too many references: can't splice */
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT       /* Operation timed out */
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED    /* Connection refused */
+
+ELOOP           :: Platform_Error.ELOOP           /* Too many levels of symbolic links */
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG    /* File name too long */
 
 /* should be rearranged */
-EHOSTDOWN:      Errno : 64		/* Host is down */
-EHOSTUNREACH:   Errno : 65		/* No route to host */
-ENOTEMPTY:      Errno : 66		/* Directory not empty */
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN       /* Host is down */
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH    /* No route to host */
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY       /* Directory not empty */
 
 /* quotas & mush */
-EPROCLIM:       Errno : 67		/* Too many processes */
-EUSERS:         Errno : 68		/* Too many users */
-EDQUOT:         Errno : 69		/* Disc quota exceeded */
+EPROCLIM        :: Platform_Error.EPROCLIM        /* Too many processes */
+EUSERS          :: Platform_Error.EUSERS          /* Too many users */
+EDQUOT          :: Platform_Error.EDQUOT          /* Disc quota exceeded */
 
 /* Network File System */
-ESTALE:         Errno : 70		/* Stale NFS file handle */
-EREMOTE:        Errno : 71		/* Too many levels of remote in path */
-EBADRPC:        Errno : 72		/* RPC struct is bad */
-ERPCMISMATCH:   Errno : 73		/* RPC version wrong */
-EPROGUNAVAIL:   Errno : 74		/* RPC prog. not avail */
-EPROGMISMATCH:  Errno : 75		/* Program version wrong */
-EPROCUNAVAIL:   Errno : 76		/* Bad procedure for program */
+ESTALE          :: Platform_Error.ESTALE          /* Stale NFS file handle */
+EREMOTE         :: Platform_Error.EREMOTE         /* Too many levels of remote in path */
+EBADRPC         :: Platform_Error.EBADRPC         /* RPC struct is bad */
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH    /* RPC version wrong */
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL    /* RPC prog. not avail */
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH   /* Program version wrong */
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL    /* Bad procedure for program */
 
-ENOLCK:         Errno : 77		/* No locks available */
-ENOSYS:         Errno : 78		/* Function not implemented */
+ENOLCK          :: Platform_Error.ENOLCK          /* No locks available */
+ENOSYS          :: Platform_Error.ENOSYS          /* Function not implemented */
 
-EFTYPE:         Errno : 79		/* Inappropriate file type or format */
-EAUTH:          Errno : 80		/* Authentication error */
-ENEEDAUTH:      Errno : 81		/* Need authenticator */
+EFTYPE          :: Platform_Error.EFTYPE          /* Inappropriate file type or format */
+EAUTH           :: Platform_Error.EAUTH           /* Authentication error */
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH       /* Need authenticator */
 
 /* SystemV IPC */
-EIDRM:          Errno : 82		/* Identifier removed */
-ENOMSG:         Errno : 83		/* No message of desired type */
-EOVERFLOW:      Errno : 84		/* Value too large to be stored in data type */
+EIDRM           :: Platform_Error.EIDRM           /* Identifier removed */
+ENOMSG          :: Platform_Error.ENOMSG          /* No message of desired type */
+EOVERFLOW       :: Platform_Error.EOVERFLOW       /* Value too large to be stored in data type */
 
 /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */
-EILSEQ:         Errno : 85		/* Illegal byte sequence */
+EILSEQ          :: Platform_Error.EILSEQ          /* Illegal byte sequence */
 
 /* From IEEE Std 1003.1-2001 */
 /* Base, Realtime, Threads or Thread Priority Scheduling option errors */
-ENOTSUP:        Errno : 86		/* Not supported */
+ENOTSUP         :: Platform_Error.ENOTSUP         /* Not supported */
 
 /* Realtime option errors */
-ECANCELED:      Errno : 87		/* Operation canceled */
+ECANCELED       :: Platform_Error.ECANCELED       /* Operation canceled */
 
 /* Realtime, XSI STREAMS option errors */
-EBADMSG:        Errno : 88		/* Bad or Corrupt message */
+EBADMSG         :: Platform_Error.EBADMSG         /* Bad or Corrupt message */
 
 /* XSI STREAMS option errors  */
-ENODATA:        Errno : 89		/* No message available */
-ENOSR:          Errno : 90		/* No STREAM resources */
-ENOSTR:         Errno : 91		/* Not a STREAM */
-ETIME:          Errno : 92		/* STREAM ioctl timeout */
+ENODATA         :: Platform_Error.ENODATA         /* No message available */
+ENOSR           :: Platform_Error.ENOSR           /* No STREAM resources */
+ENOSTR          :: Platform_Error.ENOSTR          /* Not a STREAM */
+ETIME           :: Platform_Error.ETIME           /* STREAM ioctl timeout */
 
 /* File system extended attribute errors */
-ENOATTR:        Errno : 93		/* Attribute not found */
+ENOATTR         :: Platform_Error.ENOATTR         /* Attribute not found */
 
 /* Realtime, XSI STREAMS option errors */
-EMULTIHOP:      Errno : 94		/* Multihop attempted */
-ENOLINK:        Errno : 95		/* Link has been severed */
-EPROTO:         Errno : 96		/* Protocol error */
+EMULTIHOP       :: Platform_Error.EMULTIHOP       /* Multihop attempted */
+ENOLINK         :: Platform_Error.ENOLINK         /* Link has been severed */
+EPROTO          :: Platform_Error.EPROTO          /* Protocol error */
 
 /* Robust mutexes */
-EOWNERDEAD:     Errno : 97		/* Previous owner died */
-ENOTRECOVERABLE:    Errno : 98		/* State not recoverable */
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD      /* Previous owner died */
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */
 
-ELAST:          Errno : 98		/* Must equal largest errno */
+ELAST           :: Platform_Error.ELAST           /* Must equal largest Error */
 
-/* end of errno */
+/* end of Error */
 
 O_RDONLY   :: 0x000000000
 O_WRONLY   :: 0x000000001
@@ -268,13 +407,13 @@ 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 "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
-S_ISREG  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
-S_ISDIR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
-S_ISCHR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
-S_ISBLK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
-S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
-S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
+@(require_results) S_ISLNK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0 // Test for file existance
 X_OK :: 1 // Test for execute permission
@@ -334,154 +473,186 @@ foreign libc {
 
 // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end.
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__errno_location()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__errno_location()^)
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // We set a max of 1GB to keep alignment and to be safe.
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s := _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -490,6 +661,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 is_file :: proc {is_file_path, is_file_handle}
 is_dir :: proc {is_dir_path, is_dir_handle}
 
+@(require_results)
 exists :: proc(path: string) -> bool {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cpath := strings.clone_to_cstring(path, context.temp_allocator)
@@ -497,12 +669,13 @@ exists :: proc(path: string) -> bool {
 	return res == 0
 }
 
-fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
+@(require_results)
+fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) {
 	result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg))
 	if result < 0 {
-		return 0, Errno(get_last_error())
+		return 0, get_last_error()
 	}
-	return int(result), ERROR_NONE
+	return int(result), nil
 }
 
 // NOTE(bill): Uses startup to initialize it
@@ -511,38 +684,34 @@ stdin: Handle  = 0
 stdout: Handle = 1
 stderr: Handle = 2
 
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	s: OS_Stat = ---
 	result := _unix_lstat(cstr, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	
@@ -550,54 +719,54 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	s: OS_Stat = ---
 	result := _unix_fstat(fd, &s)
 	if result == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -607,8 +776,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -619,31 +788,28 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 
-	return "", Errno{}
+	return "", Error{}
 }
 
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) {
 	buf: [MAX_PATH]byte
-	_, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0])))
-	if err != ERROR_NONE {
-		return "", err
-	}
-
-	path := strings.clone_from_cstring(cstring(&buf[0]))
-	return path, err
+	_ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return
+	return strings.clone_from_cstring(cstring(&buf[0]))
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -654,26 +820,27 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	result := _unix_access(cstr, c.int(mask))
 	if result == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 
@@ -685,11 +852,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
 	// an authoritative value for it across all systems.
@@ -701,7 +870,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -710,14 +879,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -725,10 +894,12 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return int(_lwp_self())
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
@@ -736,6 +907,7 @@ dlopen :: proc(filename: string, flags: int) -> rawptr {
 	return handle
 }
 
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -749,10 +921,12 @@ dlclose :: proc(handle: rawptr) -> bool {
 	return _unix_dlclose(handle) == 0
 }
 
+@(require_results)
 dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -765,7 +939,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	count : int = 0
 	count_size := size_of(count)
@@ -778,6 +952,7 @@ _processor_core_count :: proc() -> int {
 	return 1
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 342 - 205
core/os/os_openbsd.odin

@@ -9,108 +9,205 @@ import "base:runtime"
 Handle    :: distinct i32
 Pid       :: distinct i32
 File_Time :: distinct u64
-Errno     :: distinct i32
 
 INVALID_HANDLE :: ~Handle(0)
 
-ERROR_NONE:	Errno: 0
-
-EPERM:		Errno: 1
-ENOENT:		Errno: 2
-ESRCH:		Errno: 3
-EINTR:		Errno: 4
-EIO:		Errno: 5
-ENXIO:		Errno: 6
-E2BIG:		Errno: 7
-ENOEXEC:	Errno: 8
-EBADF:		Errno: 9
-ECHILD:		Errno: 10
-EDEADLK:	Errno: 11
-ENOMEM:		Errno: 12
-EACCES:		Errno: 13
-EFAULT:		Errno: 14
-ENOTBLK:	Errno: 15
-EBUSY:		Errno: 16
-EEXIST:		Errno: 17
-EXDEV:		Errno: 18
-ENODEV:		Errno: 19
-ENOTDIR:	Errno: 20
-EISDIR:		Errno: 21
-EINVAL:		Errno: 22
-ENFILE:		Errno: 23
-EMFILE:		Errno: 24
-ENOTTY:		Errno: 25
-ETXTBSY:	Errno: 26
-EFBIG:		Errno: 27
-ENOSPC:		Errno: 28
-ESPIPE:		Errno: 29
-EROFS:		Errno: 30
-EMLINK:		Errno: 31
-EPIPE:		Errno: 32
-EDOM:		Errno: 33
-ERANGE:		Errno: 34
-EAGAIN:		Errno: 35
-EWOULDBLOCK:	Errno: EAGAIN
-EINPROGRESS:	Errno: 36
-EALREADY:	Errno: 37
-ENOTSOCK:	Errno: 38
-EDESTADDRREQ:	Errno: 39
-EMSGSIZE:	Errno: 40
-EPROTOTYPE:	Errno: 41
-ENOPROTOOPT:	Errno: 42
-EPROTONOSUPPORT: Errno: 43
-ESOCKTNOSUPPORT: Errno: 44
-EOPNOTSUPP:	Errno: 45
-EPFNOSUPPORT:	Errno: 46
-EAFNOSUPPORT:	Errno: 47
-EADDRINUSE:	Errno: 48
-EADDRNOTAVAIL:	Errno: 49
-ENETDOWN:	Errno: 50
-ENETUNREACH:	Errno: 51
-ENETRESET:	Errno: 52
-ECONNABORTED:	Errno: 53
-ECONNRESET:	Errno: 54
-ENOBUFS:	Errno: 55
-EISCONN:	Errno: 56
-ENOTCONN:	Errno: 57
-ESHUTDOWN:	Errno: 58
-ETOOMANYREFS:	Errno: 59
-ETIMEDOUT:	Errno: 60
-ECONNREFUSED:	Errno: 61
-ELOOP:		Errno: 62
-ENAMETOOLONG:	Errno: 63
-EHOSTDOWN:	Errno: 64
-EHOSTUNREACH:	Errno: 65
-ENOTEMPTY:	Errno: 66
-EPROCLIM:	Errno: 67
-EUSERS:		Errno: 68
-EDQUOT:		Errno: 69
-ESTALE:		Errno: 70
-EREMOTE:	Errno: 71
-EBADRPC:	Errno: 72
-ERPCMISMATCH:	Errno: 73
-EPROGUNAVAIL:	Errno: 74
-EPROGMISMATCH:	Errno: 75
-EPROCUNAVAIL:	Errno: 76
-ENOLCK:		Errno: 77
-ENOSYS:		Errno: 78
-EFTYPE:		Errno: 79
-EAUTH:		Errno: 80
-ENEEDAUTH:	Errno: 81
-EIPSEC:		Errno: 82
-ENOATTR:	Errno: 83
-EILSEQ:		Errno: 84
-ENOMEDIUM:	Errno: 85
-EMEDIUMTYPE:	Errno: 86
-EOVERFLOW:	Errno: 87
-ECANCELED:	Errno: 88
-EIDRM:		Errno: 89
-ENOMSG:		Errno: 90
-ENOTSUP:	Errno: 91
-EBADMSG:	Errno: 92
-ENOTRECOVERABLE: Errno: 93
-EOWNERDEAD:	Errno: 94
-EPROTO:		Errno: 95
+_Platform_Error :: enum i32 {
+	NONE = 0,
+	EPERM           = 1,
+	ENOENT          = 2,
+	ESRCH           = 3,
+	EINTR           = 4,
+	EIO             = 5,
+	ENXIO           = 6,
+	E2BIG           = 7,
+	ENOEXEC         = 8,
+	EBADF           = 9,
+	ECHILD          = 10,
+	EDEADLK         = 11,
+	ENOMEM          = 12,
+	EACCES          = 13,
+	EFAULT          = 14,
+	ENOTBLK         = 15,
+	EBUSY           = 16,
+	EEXIST          = 17,
+	EXDEV           = 18,
+	ENODEV          = 19,
+	ENOTDIR         = 20,
+	EISDIR          = 21,
+	EINVAL          = 22,
+	ENFILE          = 23,
+	EMFILE          = 24,
+	ENOTTY          = 25,
+	ETXTBSY         = 26,
+	EFBIG           = 27,
+	ENOSPC          = 28,
+	ESPIPE          = 29,
+	EROFS           = 30,
+	EMLINK          = 31,
+	EPIPE           = 32,
+	EDOM            = 33,
+	ERANGE          = 34,
+	EAGAIN          = 35,
+	EWOULDBLOCK     = EAGAIN,
+	EINPROGRESS     = 36,
+	EALREADY        = 37,
+	ENOTSOCK        = 38,
+	EDESTADDRREQ    = 39,
+	EMSGSIZE        = 40,
+	EPROTOTYPE      = 41,
+	ENOPROTOOPT     = 42,
+	EPROTONOSUPPORT = 43,
+	ESOCKTNOSUPPORT = 44,
+	EOPNOTSUPP      = 45,
+	EPFNOSUPPORT    = 46,
+	EAFNOSUPPORT    = 47,
+	EADDRINUSE      = 48,
+	EADDRNOTAVAIL   = 49,
+	ENETDOWN        = 50,
+	ENETUNREACH     = 51,
+	ENETRESET       = 52,
+	ECONNABORTED    = 53,
+	ECONNRESET      = 54,
+	ENOBUFS         = 55,
+	EISCONN         = 56,
+	ENOTCONN        = 57,
+	ESHUTDOWN       = 58,
+	ETOOMANYREFS    = 59,
+	ETIMEDOUT       = 60,
+	ECONNREFUSED    = 61,
+	ELOOP           = 62,
+	ENAMETOOLONG    = 63,
+	EHOSTDOWN       = 64,
+	EHOSTUNREACH    = 65,
+	ENOTEMPTY       = 66,
+	EPROCLIM        = 67,
+	EUSERS          = 68,
+	EDQUOT          = 69,
+	ESTALE          = 70,
+	EREMOTE         = 71,
+	EBADRPC         = 72,
+	ERPCMISMATCH    = 73,
+	EPROGUNAVAIL    = 74,
+	EPROGMISMATCH   = 75,
+	EPROCUNAVAIL    = 76,
+	ENOLCK          = 77,
+	ENOSYS          = 78,
+	EFTYPE          = 79,
+	EAUTH           = 80,
+	ENEEDAUTH       = 81,
+	EIPSEC          = 82,
+	ENOATTR         = 83,
+	EILSEQ          = 84,
+	ENOMEDIUM       = 85,
+	EMEDIUMTYPE     = 86,
+	EOVERFLOW       = 87,
+	ECANCELED       = 88,
+	EIDRM           = 89,
+	ENOMSG          = 90,
+	ENOTSUP         = 91,
+	EBADMSG         = 92,
+	ENOTRECOVERABLE = 93,
+	EOWNERDEAD      = 94,
+	EPROTO          = 95,
+}
+
+EPERM           :: Platform_Error.EPERM
+ENOENT          :: Platform_Error.ENOENT
+ESRCH           :: Platform_Error.ESRCH
+EINTR           :: Platform_Error.EINTR
+EIO             :: Platform_Error.EIO
+ENXIO           :: Platform_Error.ENXIO
+E2BIG           :: Platform_Error.E2BIG
+ENOEXEC         :: Platform_Error.ENOEXEC
+EBADF           :: Platform_Error.EBADF
+ECHILD          :: Platform_Error.ECHILD
+EDEADLK         :: Platform_Error.EDEADLK
+ENOMEM          :: Platform_Error.ENOMEM
+EACCES          :: Platform_Error.EACCES
+EFAULT          :: Platform_Error.EFAULT
+ENOTBLK         :: Platform_Error.ENOTBLK
+EBUSY           :: Platform_Error.EBUSY
+EEXIST          :: Platform_Error.EEXIST
+EXDEV           :: Platform_Error.EXDEV
+ENODEV          :: Platform_Error.ENODEV
+ENOTDIR         :: Platform_Error.ENOTDIR
+EISDIR          :: Platform_Error.EISDIR
+EINVAL          :: Platform_Error.EINVAL
+ENFILE          :: Platform_Error.ENFILE
+EMFILE          :: Platform_Error.EMFILE
+ENOTTY          :: Platform_Error.ENOTTY
+ETXTBSY         :: Platform_Error.ETXTBSY
+EFBIG           :: Platform_Error.EFBIG
+ENOSPC          :: Platform_Error.ENOSPC
+ESPIPE          :: Platform_Error.ESPIPE
+EROFS           :: Platform_Error.EROFS
+EMLINK          :: Platform_Error.EMLINK
+EPIPE           :: Platform_Error.EPIPE
+EDOM            :: Platform_Error.EDOM
+ERANGE          :: Platform_Error.ERANGE
+EAGAIN          :: Platform_Error.EAGAIN
+EWOULDBLOCK     :: Platform_Error.EWOULDBLOCK
+EINPROGRESS     :: Platform_Error.EINPROGRESS
+EALREADY        :: Platform_Error.EALREADY
+ENOTSOCK        :: Platform_Error.ENOTSOCK
+EDESTADDRREQ    :: Platform_Error.EDESTADDRREQ
+EMSGSIZE        :: Platform_Error.EMSGSIZE
+EPROTOTYPE      :: Platform_Error.EPROTOTYPE
+ENOPROTOOPT     :: Platform_Error.ENOPROTOOPT
+EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
+ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
+EOPNOTSUPP      :: Platform_Error.EOPNOTSUPP
+EPFNOSUPPORT    :: Platform_Error.EPFNOSUPPORT
+EAFNOSUPPORT    :: Platform_Error.EAFNOSUPPORT
+EADDRINUSE      :: Platform_Error.EADDRINUSE
+EADDRNOTAVAIL   :: Platform_Error.EADDRNOTAVAIL
+ENETDOWN        :: Platform_Error.ENETDOWN
+ENETUNREACH     :: Platform_Error.ENETUNREACH
+ENETRESET       :: Platform_Error.ENETRESET
+ECONNABORTED    :: Platform_Error.ECONNABORTED
+ECONNRESET      :: Platform_Error.ECONNRESET
+ENOBUFS         :: Platform_Error.ENOBUFS
+EISCONN         :: Platform_Error.EISCONN
+ENOTCONN        :: Platform_Error.ENOTCONN
+ESHUTDOWN       :: Platform_Error.ESHUTDOWN
+ETOOMANYREFS    :: Platform_Error.ETOOMANYREFS
+ETIMEDOUT       :: Platform_Error.ETIMEDOUT
+ECONNREFUSED    :: Platform_Error.ECONNREFUSED
+ELOOP           :: Platform_Error.ELOOP
+ENAMETOOLONG    :: Platform_Error.ENAMETOOLONG
+EHOSTDOWN       :: Platform_Error.EHOSTDOWN
+EHOSTUNREACH    :: Platform_Error.EHOSTUNREACH
+ENOTEMPTY       :: Platform_Error.ENOTEMPTY
+EPROCLIM        :: Platform_Error.EPROCLIM
+EUSERS          :: Platform_Error.EUSERS
+EDQUOT          :: Platform_Error.EDQUOT
+ESTALE          :: Platform_Error.ESTALE
+EREMOTE         :: Platform_Error.EREMOTE
+EBADRPC         :: Platform_Error.EBADRPC
+ERPCMISMATCH    :: Platform_Error.ERPCMISMATCH
+EPROGUNAVAIL    :: Platform_Error.EPROGUNAVAIL
+EPROGMISMATCH   :: Platform_Error.EPROGMISMATCH
+EPROCUNAVAIL    :: Platform_Error.EPROCUNAVAIL
+ENOLCK          :: Platform_Error.ENOLCK
+ENOSYS          :: Platform_Error.ENOSYS
+EFTYPE          :: Platform_Error.EFTYPE
+EAUTH           :: Platform_Error.EAUTH
+ENEEDAUTH       :: Platform_Error.ENEEDAUTH
+EIPSEC          :: Platform_Error.EIPSEC
+ENOATTR         :: Platform_Error.ENOATTR
+EILSEQ          :: Platform_Error.EILSEQ
+ENOMEDIUM       :: Platform_Error.ENOMEDIUM
+EMEDIUMTYPE     :: Platform_Error.EMEDIUMTYPE
+EOVERFLOW       :: Platform_Error.EOVERFLOW
+ECANCELED       :: Platform_Error.ECANCELED
+EIDRM           :: Platform_Error.EIDRM
+ENOMSG          :: Platform_Error.ENOMSG
+ENOTSUP         :: Platform_Error.ENOTSUP
+EBADMSG         :: Platform_Error.EBADMSG
+ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
+EOWNERDEAD      :: Platform_Error.EOWNERDEAD
+EPROTO          :: Platform_Error.EPROTO
 
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
@@ -225,13 +322,13 @@ S_ISUID :: 0o4000 // Set user id on execution
 S_ISGID :: 0o2000 // Set group id on execution
 S_ISTXT :: 0o1000 // Sticky bit
 
-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 }
+@(require_results) S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+@(require_results) S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+@(require_results) S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+@(require_results) S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+@(require_results) S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
 
 F_OK :: 0x00 // Test for file existance
 X_OK :: 0x01 // Test for execute permission
@@ -291,38 +388,47 @@ foreign libc {
 	@(link_name="dlerror")	_unix_dlerror	:: proc() -> cstring ---
 }
 
+@(require_results)
 is_path_separator :: proc(r: rune) -> bool {
 	return r == '/'
 }
 
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	return Platform_Error(__error()^)
 }
 
-fork :: proc() -> (Pid, Errno) {
+@(require_results)
+fork :: proc() -> (Pid, Error) {
 	pid := _unix_fork()
 	if pid == -1 {
-		return Pid(-1), Errno(get_last_error())
+		return Pid(-1), get_last_error()
 	}
-	return Pid(pid), ERROR_NONE
+	return Pid(pid), nil
 }
 
-open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+@(require_results)
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	handle := _unix_open(cstr, c.int(flags), c.int(mode))
 	if handle == -1 {
-		return INVALID_HANDLE, Errno(get_last_error())
+		return INVALID_HANDLE, get_last_error()
 	}
-	return handle, ERROR_NONE
+	return handle, nil
 }
 
-close :: proc(fd: Handle) -> Errno {
+close :: proc(fd: Handle) -> Error {
 	result := _unix_close(fd)
 	if result == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
 
 // If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`.
@@ -333,124 +439,148 @@ close :: proc(fd: Handle) -> Errno {
 @(private)
 MAX_RW :: 1 << 30
 
-read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+read :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	to_read    := min(c.size_t(len(data)), MAX_RW)
 	bytes_read := _unix_read(fd, &data[0], to_read)
 	if bytes_read == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return int(bytes_read), ERROR_NONE
+	return int(bytes_read), nil
 }
 
-write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
 	if len(data) == 0 {
-		return 0, ERROR_NONE
+		return 0, nil
 	}
 
 	to_write      := min(c.size_t(len(data)), MAX_RW)
 	bytes_written := _unix_write(fd, &data[0], to_write)
 	if bytes_written == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
+	}
+	return int(bytes_written), nil
+}
+
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = read(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
 	}
-	return int(bytes_written), ERROR_NONE
+	return
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr := seek(fd, offset, SEEK_CUR) or_return
+	n, err = write(fd, data)
+	_, err1 := seek(fd, curr, SEEK_SET)
+	if err1 != nil && err == nil {
+		err = err1
+	}
+	return
 }
 
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
 	res := _unix_seek(fd, offset, c.int(whence))
 	if res == -1 {
-		return -1, Errno(get_last_error())
+		return -1, get_last_error()
 	}
-	return res, ERROR_NONE
+	return res, nil
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return -1, err
-	}
-	return s.size, ERROR_NONE
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
+	size = -1
+	s :=  _fstat(fd) or_return
+	size = s.size
+	return
 }
 
-rename :: proc(old_path, new_path: string) -> Errno {
+rename :: proc(old_path, new_path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
 	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
 	res := _unix_rename(old_path_cstr, new_path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove :: proc(path: string) -> Errno {
+remove :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_unlink(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_mkdir(path_cstr, mode)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-remove_directory :: proc(path: string) -> Errno {
+remove_directory :: proc(path: string) -> Error {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_rmdir(path_cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
+@(require_results)
 is_file_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISREG(s.mode)
 }
 
+@(require_results)
 is_dir_handle :: proc(fd: Handle) -> bool {
 	s, err := _fstat(fd)
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
 }
 
+@(require_results)
 is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
 	s: OS_Stat
-	err: Errno
+	err: Error
 	if follow_links {
 		s, err = _stat(path)
 	} else {
 		s, err = _lstat(path)
 	}
-	if err != ERROR_NONE {
+	if err != nil {
 		return false
 	}
 	return S_ISDIR(s.mode)
@@ -469,26 +599,22 @@ stderr: Handle = 2
 last_write_time :: proc(fd: Handle) -> File_Time {}                                                               
 last_write_time_by_name :: proc(name: string) -> File_Time {}                                                     
 */
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
-	s, err := _fstat(fd)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
+	s := _fstat(fd) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
-	s, err := _stat(name)
-	if err != ERROR_NONE {
-		return 0, err
-	}
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
+	s := _stat(name) or_return
 	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
-	return File_Time(modified), ERROR_NONE
+	return File_Time(modified), nil
 }
 
-@private
-_stat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_stat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -496,13 +622,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_stat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+@(private, require_results)
+_lstat :: proc(path: string) -> (OS_Stat, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -510,55 +636,55 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
 	s: OS_Stat = ---
 	res := _unix_lstat(cstr, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+@(private, require_results)
+_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
 	// deliberately uninitialized
 	s: OS_Stat = ---
 	res := _unix_fstat(fd, &s)
 	if res == -1 {
-		return s, Errno(get_last_error())
+		return s, get_last_error()
 	}
-	return s, ERROR_NONE
+	return s, nil
 }
 
-@private
-_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+@(private, require_results)
+_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
 	dirp := _unix_fdopendir(fd)
 	if dirp == cast(Dir)nil {
-		return nil, Errno(get_last_error())
+		return nil, get_last_error()
 	}
-	return dirp, ERROR_NONE
+	return dirp, nil
 }
 
-@private
-_closedir :: proc(dirp: Dir) -> Errno {
+@(private)
+_closedir :: proc(dirp: Dir) -> Error {
 	rc := _unix_closedir(dirp)
 	if rc != 0 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
-@private
+@(private)
 _rewinddir :: proc(dirp: Dir) {
 	_unix_rewinddir(dirp)
 }
 
-@private
-_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+@(private, require_results)
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
 	result: ^Dirent
 	rc := _unix_readdir_r(dirp, &entry, &result)
 
 	if rc != 0 {
-		err = Errno(get_last_error())
+		err = get_last_error()
 		return
 	}
-	err = ERROR_NONE
+	err = nil
 
 	if result == nil {
 		end_of_stream = true
@@ -568,8 +694,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
 	return
 }
 
-@private
-_readlink :: proc(path: string) -> (string, Errno) {
+@(private, require_results)
+_readlink :: proc(path: string) -> (string, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
 	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
 
@@ -579,23 +705,25 @@ _readlink :: proc(path: string) -> (string, Errno) {
 		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
 		if rc == -1 {
 			delete(buf)
-			return "", Errno(get_last_error())
+			return "", get_last_error()
 		} else if rc == int(bufsz) {
 			bufsz += MAX_PATH
 			delete(buf)
 			buf = make([]byte, bufsz)
 		} else {
-			return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
+			return strings.string_from_ptr(&buf[0], rc), nil
 		}	
 	}
 }
 
 // XXX OpenBSD
-absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
-	return "", Errno(ENOSYS)
+@(require_results)
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
+	return "", Error(ENOSYS)
 }
 
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+@(require_results)
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -606,25 +734,26 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
 
 	path_ptr := _unix_realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", Errno(get_last_error())
+		return "", get_last_error()
 	}
 	defer _unix_free(path_ptr)
 
 	path = strings.clone(string(cstring(path_ptr)))
 
-	return path, ERROR_NONE
+	return path, nil
 }
 
-access :: proc(path: string, mask: int) -> (bool, Errno) {
+access :: proc(path: string, mask: int) -> (bool, Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_access(cstr, c.int(mask))
 	if res == -1 {
-		return false, Errno(get_last_error())
+		return false, get_last_error()
 	}
-	return true, ERROR_NONE
+	return true, nil
 }
 
+@(require_results)
 lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
 	path_str := strings.clone_to_cstring(key, context.temp_allocator)
@@ -635,11 +764,13 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
 	return strings.clone(string(cstr), allocator), true
 }
 
+@(require_results)
 get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
 	value, _ = lookup_env(key, allocator)
 	return
 }
 
+@(require_results)
 get_current_directory :: proc() -> string {
 	buf := make([dynamic]u8, MAX_PATH)
 	for {
@@ -647,7 +778,7 @@ get_current_directory :: proc() -> string {
 		if cwd != nil {
 			return string(cwd)
 		}
-		if Errno(get_last_error()) != ERANGE {
+		if get_last_error() != ERANGE {
 			delete(buf)
 			return ""
 		}
@@ -656,14 +787,14 @@ get_current_directory :: proc() -> string {
 	unreachable()
 }
 
-set_current_directory :: proc(path: string) -> (err: Errno) {
+set_current_directory :: proc(path: string) -> (err: Error) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(path, context.temp_allocator)
 	res := _unix_chdir(cstr)
 	if res == -1 {
-		return Errno(get_last_error())
+		return get_last_error()
 	}
-	return ERROR_NONE
+	return nil
 }
 
 exit :: proc "contextless" (code: int) -> ! {
@@ -671,16 +802,19 @@ exit :: proc "contextless" (code: int) -> ! {
 	_unix_exit(c.int(code))
 }
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return _unix_getthrid()
 }
 
+@(require_results)
 dlopen :: proc(filename: string, flags: int) -> rawptr {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
 	handle := _unix_dlopen(cstr, c.int(flags))
 	return handle
 }
+@(require_results)
 dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
 	assert(handle != nil)
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
@@ -692,10 +826,12 @@ dlclose :: proc(handle: rawptr) -> bool {
 	assert(handle != nil)
 	return _unix_dlclose(handle) == 0
 }
+@(require_results)
 dlerror :: proc() -> string {
 	return string(_unix_dlerror())
 }
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -710,11 +846,12 @@ get_page_size :: proc() -> int {
 
 _SC_NPROCESSORS_ONLN :: 503
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return int(_sysconf(_SC_NPROCESSORS_ONLN))
 }
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	res := make([]string, len(runtime.args__))
 	for arg, i in runtime.args__ {

+ 25 - 18
core/os/os_wasi.odin

@@ -4,12 +4,10 @@ import "core:sys/wasm/wasi"
 import "base:runtime"
 
 Handle :: distinct i32
-Errno :: distinct i32
+_Platform_Error :: wasi.errno_t
 
 INVALID_HANDLE :: -1
 
-ERROR_NONE :: Errno(wasi.errno_t.SUCCESS)
-
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
 O_RDWR     :: 0x00002
@@ -29,6 +27,7 @@ stderr: Handle = 2
 
 args := _alloc_command_line_arguments()
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> (args: []string) {
 	args = make([]string, len(runtime.args__))
 	for &arg, i in args {
@@ -93,8 +92,9 @@ init_preopens :: proc() {
 	preopens = dyn_preopens[:]
 }
 
+@(require_results)
 wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
-
+	@(require_results)
 	prefix_matches :: proc(prefix, path: string) -> bool {
 		// Empty is valid for any relative path.
 		if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
@@ -148,23 +148,24 @@ wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
 write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
 	iovs := wasi.iovec_t(data)
 	n, err := wasi.fd_read(wasi.fd_t(fd), {iovs})
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	iovs := wasi.ciovec_t(data)
 	n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
 read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
 	iovs := wasi.iovec_t(data)
 	n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
-	return int(n), Errno(err)
+	return int(n), Platform_Error(err)
 }
+@(require_results)
 open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
 	oflags: wasi.oflags_t
 	if mode & O_CREATE == O_CREATE {
@@ -201,30 +202,36 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn
 	}
 
 	fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
-	return Handle(fd), Errno(err)
+	return Handle(fd), Platform_Error(err)
 }
 close :: proc(fd: Handle) -> Errno {
 	err := wasi.fd_close(wasi.fd_t(fd))
-	return Errno(err)
+	return Platform_Error(err)
+}
+
+flush :: proc(fd: Handle) -> Error {
+	// do nothing
+	return nil
 }
+
 seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
 	n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence))
-	return i64(n), Errno(err)
+	return i64(n), Platform_Error(err)
 }
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return 0
 }
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	return 1
 }
 
-file_size :: proc(fd: Handle) -> (i64, Errno) {
-	stat, err := wasi.fd_filestat_get(wasi.fd_t(fd))
-	if err != nil {
-		return 0, Errno(err)
-	}
-	return i64(stat.size), 0
+@(require_results)
+file_size :: proc(fd: Handle) -> (size: i64, err: Errno) {
+	stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return
+	size = i64(stat.size)
+	return
 }
 
 

+ 108 - 50
core/os/os_windows.odin

@@ -7,7 +7,6 @@ import "base:intrinsics"
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
-Errno     :: distinct int
 
 
 INVALID_HANDLE :: ~Handle(0)
@@ -27,70 +26,119 @@ O_SYNC     :: 0x01000
 O_ASYNC    :: 0x02000
 O_CLOEXEC  :: 0x80000
 
-
-ERROR_NONE:                   Errno : 0
-ERROR_FILE_NOT_FOUND:         Errno : 2
-ERROR_PATH_NOT_FOUND:         Errno : 3
-ERROR_ACCESS_DENIED:          Errno : 5
-ERROR_INVALID_HANDLE:         Errno : 6
-ERROR_NOT_ENOUGH_MEMORY:      Errno : 8
-ERROR_NO_MORE_FILES:          Errno : 18
-ERROR_HANDLE_EOF:             Errno : 38
-ERROR_NETNAME_DELETED:        Errno : 64
-ERROR_FILE_EXISTS:            Errno : 80
-ERROR_INVALID_PARAMETER:      Errno : 87
-ERROR_BROKEN_PIPE:            Errno : 109
-ERROR_BUFFER_OVERFLOW:        Errno : 111
-ERROR_INSUFFICIENT_BUFFER:    Errno : 122
-ERROR_MOD_NOT_FOUND:          Errno : 126
-ERROR_PROC_NOT_FOUND:         Errno : 127
-ERROR_DIR_NOT_EMPTY:          Errno : 145
-ERROR_ALREADY_EXISTS:         Errno : 183
-ERROR_ENVVAR_NOT_FOUND:       Errno : 203
-ERROR_MORE_DATA:              Errno : 234
-ERROR_OPERATION_ABORTED:      Errno : 995
-ERROR_IO_PENDING:             Errno : 997
-ERROR_NOT_FOUND:              Errno : 1168
-ERROR_PRIVILEGE_NOT_HELD:     Errno : 1314
-WSAEACCES:                    Errno : 10013
-WSAECONNRESET:                Errno : 10054
-
-// Windows reserves errors >= 1<<29 for application use
-ERROR_FILE_IS_PIPE:           Errno : 1<<29 + 0
-ERROR_FILE_IS_NOT_DIR:        Errno : 1<<29 + 1
-ERROR_NEGATIVE_OFFSET:        Errno : 1<<29 + 2
+_Platform_Error :: win32.System_Error
+
+ERROR_FILE_NOT_FOUND      :: _Platform_Error(2)
+ERROR_PATH_NOT_FOUND      :: _Platform_Error(3)
+ERROR_ACCESS_DENIED       :: _Platform_Error(5)
+ERROR_INVALID_HANDLE      :: _Platform_Error(6)
+ERROR_NOT_ENOUGH_MEMORY   :: _Platform_Error(8)
+ERROR_NO_MORE_FILES       :: _Platform_Error(18)
+ERROR_HANDLE_EOF          :: _Platform_Error(38)
+ERROR_NETNAME_DELETED     :: _Platform_Error(64)
+ERROR_FILE_EXISTS         :: _Platform_Error(80)
+ERROR_INVALID_PARAMETER   :: _Platform_Error(87)
+ERROR_BROKEN_PIPE         :: _Platform_Error(109)
+ERROR_BUFFER_OVERFLOW     :: _Platform_Error(111)
+ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122)
+ERROR_MOD_NOT_FOUND       :: _Platform_Error(126)
+ERROR_PROC_NOT_FOUND      :: _Platform_Error(127)
+ERROR_DIR_NOT_EMPTY       :: _Platform_Error(145)
+ERROR_ALREADY_EXISTS      :: _Platform_Error(183)
+ERROR_ENVVAR_NOT_FOUND    :: _Platform_Error(203)
+ERROR_MORE_DATA           :: _Platform_Error(234)
+ERROR_OPERATION_ABORTED   :: _Platform_Error(995)
+ERROR_IO_PENDING          :: _Platform_Error(997)
+ERROR_NOT_FOUND           :: _Platform_Error(1168)
+ERROR_PRIVILEGE_NOT_HELD  :: _Platform_Error(1314)
+WSAEACCES                 :: _Platform_Error(10013)
+WSAECONNRESET             :: _Platform_Error(10054)
+
+ERROR_FILE_IS_PIPE        :: General_Error.File_Is_Pipe
+ERROR_FILE_IS_NOT_DIR     :: General_Error.Not_Dir
 
 // "Argv" arguments converted to Odin strings
 args := _alloc_command_line_arguments()
 
+@(require_results, no_instrumentation)
+get_last_error :: proc "contextless" () -> Error {
+	err := win32.GetLastError()
+	if err == 0 {
+		return nil
+	}
+	switch err {
+	case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION:
+		return .Permission_Denied
+
+	case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS:
+		return .Exist
+
+	case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND:
+		return .Not_Exist
+
+	case win32.ERROR_NO_DATA:
+		return .Closed
+
+	case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT:
+		return .Timeout
+
+	case win32.ERROR_NOT_SUPPORTED:
+		return .Unsupported
+
+	case win32.ERROR_HANDLE_EOF:
+		return .EOF
+
+	case win32.ERROR_INVALID_HANDLE:
+		return .Invalid_File
+
+	case
+		win32.ERROR_BAD_ARGUMENTS,
+		win32.ERROR_INVALID_PARAMETER,
+		win32.ERROR_NOT_ENOUGH_MEMORY,
+		win32.ERROR_NO_MORE_FILES,
+		win32.ERROR_LOCK_VIOLATION,
+		win32.ERROR_BROKEN_PIPE,
+		win32.ERROR_CALL_NOT_IMPLEMENTED,
+		win32.ERROR_INSUFFICIENT_BUFFER,
+		win32.ERROR_INVALID_NAME,
+		win32.ERROR_LOCK_FAILED,
+		win32.ERROR_ENVVAR_NOT_FOUND,
+		win32.ERROR_OPERATION_ABORTED,
+		win32.ERROR_IO_PENDING,
+		win32.ERROR_NO_UNICODE_TRANSLATION:
+		// fallthrough
+	}
+	return Platform_Error(err)
+}
 
 
-
-
-last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+@(require_results)
+last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
 	file_info: win32.BY_HANDLE_FILE_INFORMATION
 	if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) {
-		return 0, Errno(win32.GetLastError())
+		return 0, get_last_error()
 	}
 	lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime)
 	hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime)
-	return lo | hi << 32, ERROR_NONE
+	return lo | hi << 32, nil
 }
 
-last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+@(require_results)
+last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
 	data: win32.WIN32_FILE_ATTRIBUTE_DATA
 
 	wide_path := win32.utf8_to_wstring(name)
 	if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) {
-		return 0, Errno(win32.GetLastError())
+		return 0, get_last_error()
 	}
 
 	l := File_Time(data.ftLastWriteTime.dwLowDateTime)
 	h := File_Time(data.ftLastWriteTime.dwHighDateTime)
-	return l | h << 32, ERROR_NONE
+	return l | h << 32, nil
 }
 
 
+@(require_results)
 get_page_size :: proc() -> int {
 	// NOTE(tetra): The page size never changes, so why do anything complicated
 	// if we don't have to.
@@ -105,7 +153,7 @@ get_page_size :: proc() -> int {
 	return page_size
 }
 
-@(private)
+@(private, require_results)
 _processor_core_count :: proc() -> int {
 	length : win32.DWORD = 0
 	result := win32.GetLogicalProcessorInformation(nil, &length)
@@ -136,12 +184,14 @@ exit :: proc "contextless" (code: int) -> ! {
 
 
 
+@(require_results)
 current_thread_id :: proc "contextless" () -> int {
 	return int(win32.GetCurrentThreadId())
 }
 
 
 
+@(require_results)
 _alloc_command_line_arguments :: proc() -> []string {
 	arg_count: i32
 	arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -175,44 +225,52 @@ _alloc_command_line_arguments :: proc() -> []string {
 */
 WINDOWS_11_BUILD_CUTOFF :: 22_000
 
-get_windows_version_w :: proc() -> win32.OSVERSIONINFOEXW {
+@(require_results)
+get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW {
 	osvi : win32.OSVERSIONINFOEXW
 	osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW)
 	win32.RtlGetVersion(&osvi)
 	return osvi
 }
 
-is_windows_xp :: proc() -> bool {
+@(require_results)
+is_windows_xp :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
 }
 
-is_windows_vista :: proc() -> bool {
+@(require_results)
+is_windows_vista :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
 }
 
-is_windows_7 :: proc() -> bool {
+@(require_results)
+is_windows_7 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
 }
 
-is_windows_8 :: proc() -> bool {
+@(require_results)
+is_windows_8 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2)
 }
 
-is_windows_8_1 :: proc() -> bool {
+@(require_results)
+is_windows_8_1 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3)
 }
 
-is_windows_10 :: proc() -> bool {
+@(require_results)
+is_windows_10 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber <  WINDOWS_11_BUILD_CUTOFF)
 }
 
-is_windows_11 :: proc() -> bool {
+@(require_results)
+is_windows_11 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF)
 }

+ 18 - 38
core/os/stat_unix.odin

@@ -50,14 +50,14 @@ File_Info :: struct {
 }
 */
 
-@private
+@(private, require_results)
 _make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time {
 	return time.Time{
 		_nsec = uft.nanoseconds + uft.seconds * 1_000_000_000,
 	}
 }
 
-@private
+@(private)
 _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 	fi.size = s.size
 	fi.mode = cast(File_Mode)s.mode
@@ -71,7 +71,7 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
 }
 
 
-@private
+@(private, require_results)
 path_base :: proc(path: string) -> string {
 	is_separator :: proc(c: byte) -> bool {
 		return c == '/'
@@ -100,55 +100,35 @@ path_base :: proc(path: string) -> string {
 }
 
 
-lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
-
+@(require_results)
+lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _lstat(name)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _lstat(name) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_relative(name)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_relative(name) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }
 
-stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
+@(require_results)
+stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _stat(name)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _stat(name) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_relative(name)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_relative(name) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }
 
-fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
-
+@(require_results)
+fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) {
 	context.allocator = allocator
 
-	s: OS_Stat
-	s, err = _fstat(fd)
-	if err != ERROR_NONE {
-		return fi, err
-	}
+	s := _fstat(fd) or_return
 	_fill_file_info_from_stat(&fi, s)
-	fi.fullpath, err = absolute_path_from_handle(fd)
-	if err != ERROR_NONE {
-		return
-	}
+	fi.fullpath = absolute_path_from_handle(fd) or_return
 	fi.name = path_base(fi.fullpath)
-	return fi, ERROR_NONE
+	return
 }

+ 36 - 36
core/os/stat_windows.odin

@@ -4,7 +4,7 @@ import "core:time"
 import "base:runtime"
 import win32 "core:sys/windows"
 
-@(private)
+@(private, require_results)
 full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) {
 	context.allocator = allocator
 	
@@ -19,10 +19,10 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
 	for {
 		n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 		if n == 0 {
-			return "", Errno(win32.GetLastError())
+			return "", get_last_error()
 		}
 		if n <= u32(len(buf)) {
-			return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE
+			return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil
 		}
 		resize(&buf, len(buf)*2)
 	}
@@ -30,7 +30,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
 	return
 }
 
-@(private)
+@(private, require_results)
 _stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) {
 	if len(name) == 0 {
 		return {}, ERROR_PATH_NOT_FOUND
@@ -54,7 +54,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 		fd: win32.WIN32_FIND_DATAW
 		sh := win32.FindFirstFileW(wname, &fd)
 		if sh == win32.INVALID_HANDLE_VALUE {
-			e = Errno(win32.GetLastError())
+			e = get_last_error()
 			return
 		}
 		win32.FindClose(sh)
@@ -64,7 +64,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 
 	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
 	if h == win32.INVALID_HANDLE_VALUE {
-		e = Errno(win32.GetLastError())
+		e = get_last_error()
 		return
 	}
 	defer win32.CloseHandle(h)
@@ -72,26 +72,29 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
 }
 
 
+@(require_results)
 lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
 	attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
 	attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT
 	return _stat(name, attrs, allocator)
 }
 
+@(require_results)
 stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
 	attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
 	return _stat(name, attrs, allocator)
 }
 
-fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) {
+@(require_results)
+fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
 	if fd == 0 {
-		return {}, ERROR_INVALID_HANDLE
+		err = ERROR_INVALID_HANDLE
 	}
 	context.allocator = allocator
 
-	path, err := cleanpath_from_handle(fd)
-	if err != ERROR_NONE {
-		return {}, err
+	path := cleanpath_from_handle(fd) or_return
+	defer if err != nil {
+		delete(path)
 	}
 
 	h := win32.HANDLE(fd)
@@ -99,16 +102,16 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err
 	case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
 		fi.name = basename(path)
 		fi.mode |= file_type_mode(h)
-		errno = ERROR_NONE
+		err = nil
 	case:
-		fi, errno = file_info_from_get_file_information_by_handle(path, h)
+		fi  = file_info_from_get_file_information_by_handle(path, h) or_return
 	}
 	fi.fullpath = path
 	return
 }
 
 
-@(private)
+@(private, require_results)
 cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	buf := buf
 	N := 0
@@ -133,16 +136,13 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
 	return buf
 }
 
-@(private)
-cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
+@(private, require_results)
+cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
-	buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator)
-	if err != 0 {
-		return "", err
-	}
-	return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
+	buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
+	return win32.utf16_to_utf8(buf, context.allocator)
 }
-@(private)
+@(private, require_results)
 cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
 	if fd == 0 {
 		return nil, ERROR_INVALID_HANDLE
@@ -151,20 +151,20 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> (
 
 	n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
 	if n == 0 {
-		return nil, Errno(win32.GetLastError())
+		return nil, get_last_error()
 	}
 	buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
 	buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
-	return buf[:buf_len], ERROR_NONE
+	return buf[:buf_len], nil
 }
-@(private)
+@(private, require_results)
 cleanpath_from_buf :: proc(buf: []u16) -> string {
 	buf := buf
 	buf = cleanpath_strip_prefix(buf)
 	return win32.utf16_to_utf8(buf, context.allocator) or_else ""
 }
 
-@(private)
+@(private, require_results)
 basename :: proc(name: string) -> (base: string) {
 	name := name
 	if len(name) > 3 && name[:3] == `\\?` {
@@ -190,7 +190,7 @@ basename :: proc(name: string) -> (base: string) {
 	return name
 }
 
-@(private)
+@(private, require_results)
 file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
 	switch win32.GetFileType(h) {
 	case win32.FILE_TYPE_PIPE:
@@ -202,7 +202,7 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
 }
 
 
-@(private)
+@(private, require_results)
 file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
 	if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 		mode |= 0o444
@@ -239,7 +239,7 @@ windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) {
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
 }
 
-@(private)
+@(private, require_results)
 file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 
@@ -254,7 +254,7 @@ file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_
 	return
 }
 
-@(private)
+@(private, require_results)
 file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) {
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 
@@ -269,20 +269,20 @@ file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string)
 	return
 }
 
-@(private)
+@(private, require_results)
 file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) {
 	d: win32.BY_HANDLE_FILE_INFORMATION
 	if !win32.GetFileInformationByHandle(h, &d) {
-		err := Errno(win32.GetLastError())
+		err := get_last_error()
 		return {}, err
 
 	}
 
 	ti: win32.FILE_ATTRIBUTE_TAG_INFO
 	if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
-		err := win32.GetLastError()
-		if err != u32(ERROR_INVALID_PARAMETER) {
-			return {}, Errno(err)
+		err := get_last_error()
+		if err != ERROR_INVALID_PARAMETER {
+			return {}, err
 		}
 		// Indicate this is a symlink on FAT file systems
 		ti.ReparseTag = 0
@@ -299,5 +299,5 @@ file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HAN
 
 	windows_set_file_info_times(&fi, &d)
 
-	return fi, ERROR_NONE
+	return fi, nil
 }

+ 16 - 33
core/os/stream.odin

@@ -14,44 +14,36 @@ stream_from_handle :: proc(fd: Handle) -> io.Stream {
 _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
 	fd := Handle(uintptr(stream_data))
 	n_int: int
-	os_err: Errno
+	os_err: Error
 	switch mode {
 	case .Close:
-		close(fd)
+		os_err = close(fd)
 	case .Flush:
-		when ODIN_OS == .Windows {
-			flush(fd)
-		} else {
-			// TOOD(bill): other operating systems
-		}
+		os_err = flush(fd)
 	case .Read:
 		n_int, os_err = read(fd, p)
 		n = i64(n_int)
-		if n == 0 && os_err == 0 {
+		if n == 0 && os_err == nil {
 			err = .EOF
 		}
 
 	case .Read_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
-			n_int, os_err = read_at(fd, p, offset)
-			n = i64(n_int)
-			if n == 0 && os_err == 0 {
-				err = .EOF
-			}
+		n_int, os_err = read_at(fd, p, offset)
+		n = i64(n_int)
+		if n == 0 && os_err == nil {
+			err = .EOF
 		}
 	case .Write:
 		n_int, os_err = write(fd, p)
 		n = i64(n_int)
-		if n == 0 && os_err == 0 {
+		if n == 0 && os_err == nil {
 			err = .EOF
 		}
 	case .Write_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) {
-			n_int, os_err = write_at(fd, p, offset)
-			n = i64(n_int)
-			if n == 0 && os_err == 0 {
-				err = .EOF
-			}
+		n_int, os_err = write_at(fd, p, offset)
+		n = i64(n_int)
+		if n == 0 && os_err == nil {
+			err = .EOF
 		}
 	case .Seek:
 		n, os_err = seek(fd, offset, int(whence))
@@ -60,20 +52,11 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 	case .Destroy:
 		err = .Empty
 	case .Query:
-		when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
-			return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query})
-		} else {
-			return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
-		}
+		return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
 	}
 
-	if err == nil && os_err != 0 {
-		when ODIN_OS == .Windows {
-			if os_err == ERROR_HANDLE_EOF {
-				return n, .EOF
-			}
-		}
-		err = .Unknown
+	if err == nil && os_err != nil {
+		err = error_to_io_error(os_err)
 	}
 	return
 }

+ 2 - 2
core/path/filepath/match.odin

@@ -271,7 +271,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont
 
 
 	d, derr := os.open(dir, os.O_RDONLY)
-	if derr != 0 {
+	if derr != nil {
 		return
 	}
 	defer os.close(d)
@@ -280,7 +280,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont
 		file_info, ferr := os.fstat(d)
 		defer os.file_info_delete(file_info)
 
-		if ferr != 0 {
+		if ferr != nil {
 			return
 		}
 		if !file_info.is_dir {

+ 5 - 5
core/path/filepath/path_windows.odin

@@ -52,7 +52,7 @@ is_abs :: proc(path: string) -> bool {
 
 
 @(private)
-temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
+temp_full_path :: proc(name: string) -> (path: string, err: os.Error) {
 	ta := context.temp_allocator
 
 	name := name
@@ -63,17 +63,17 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
 	p := win32.utf8_to_utf16(name, ta)
 	n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
 	if n == 0 {
-		return "", os.Errno(win32.GetLastError())
+		return "", os.get_last_error()
 	}
 
 	buf := make([]u16, n, ta)
 	n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
 	if n == 0 {
 		delete(buf)
-		return "", os.Errno(win32.GetLastError())
+		return "", os.get_last_error()
 	}
 
-	return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
+	return win32.utf16_to_utf8(buf[:n], ta)
 }
 
 
@@ -81,7 +81,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
 abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
 	full_path, err := temp_full_path(path)
-	if err != 0 {
+	if err != nil {
 		return "", false
 	}
 	p := clean(full_path, allocator)

+ 14 - 21
core/path/filepath/walk.odin

@@ -14,7 +14,7 @@ import "core:slice"
 // The sole exception is if 'skip_dir' is returned as true:
 // 	when 'skip_dir' is invoked on a directory. 'walk' skips directory contents
 // 	when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory
-Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) -> (err: os.Errno, skip_dir: bool)
+Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool)
 
 // walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root'
 // All errors that happen visiting files and directories are filtered by walk_proc
@@ -22,44 +22,44 @@ Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr)
 // NOTE: Walking large directories can be inefficient due to the lexical sort
 // NOTE: walk does not follow symbolic links
 // NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done
-walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Errno {
+walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error {
 	info, err := os.lstat(root, context.temp_allocator)
 	defer os.file_info_delete(info, context.temp_allocator)
 
 	skip_dir: bool
-	if err != 0 {
+	if err != nil {
 		err, skip_dir = walk_proc(info, err, user_data)
 	} else {
 		err, skip_dir = _walk(info, walk_proc, user_data)
 	}
-	return 0 if skip_dir else err
+	return nil if skip_dir else err
 }
 
 
 @(private)
-_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) {
+_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) {
 	if !info.is_dir {
 		if info.fullpath == "" && info.name == "" {
 			// ignore empty things
 			return
 		}
-		return walk_proc(info, 0, user_data)
+		return walk_proc(info, nil, user_data)
 	}
 
 	fis: []os.File_Info
-	err1: os.Errno
+	err1: os.Error
 	fis, err = read_dir(info.fullpath, context.temp_allocator)
 	defer os.file_info_slice_delete(fis, context.temp_allocator)
 
 	err1, skip_dir = walk_proc(info, err, user_data)
-	if err != 0 || err1 != 0 || skip_dir {
+	if err != nil || err1 != nil || skip_dir {
 		err = err1
 		return
 	}
 
 	for fi in fis {
 		err, skip_dir = _walk(fi, walk_proc, user_data)
-		if err != 0 || skip_dir {
+		if err != nil || skip_dir {
 			if !fi.is_dir || !skip_dir {
 				return
 			}
@@ -70,19 +70,12 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (e
 }
 
 @(private)
-read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) {
-	f, err := os.open(dir_name, os.O_RDONLY)
-	if err != 0 {
-		return nil, err
-	}
-	fis: []os.File_Info
-	fis, err = os.read_dir(f, -1, allocator)
-	os.close(f)
-	if err != 0 {
-		return nil, err
-	}
+read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) {
+	f := os.open(dir_name, os.O_RDONLY) or_return
+	defer os.close(f)
+	fis = os.read_dir(f, -1, allocator) or_return
 	slice.sort_by(fis, proc(a, b: os.File_Info) -> bool {
 		return a.name < b.name
 	})
-	return fis, 0
+	return
 }

+ 2 - 2
core/prof/spall/spall.odin

@@ -68,7 +68,7 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000
 
 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok {
 	fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
-	if err != os.ERROR_NONE {
+	if err != nil {
 		return
 	}
 
@@ -227,7 +227,7 @@ _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_ch
 }
 
 @(no_instrumentation)
-write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Errno) {
+write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) {
 	return _write(fd, buf)
 }
 

+ 3 - 12
core/prof/spall/spall_linux.odin

@@ -10,21 +10,12 @@ import "core:sys/linux"
 MAX_RW :: 0x7fffffff
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
-	if len(data) == 0 {
-		return 0, os.ERROR_NONE
-	}
-	
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	for n < len(data) {
 		chunk := data[:min(len(data), MAX_RW)]
-		written, errno := linux.write(linux.Fd(fd), chunk)
-		if errno != .NONE {
-			return n, os.Errno(errno)
-		}
-		n += written
+		n += linux.write(linux.Fd(fd), chunk) or_return
 	}
-
-	return n, os.ERROR_NONE
+	return
 }
 
 CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.

+ 4 - 9
core/prof/spall/spall_unix.odin

@@ -22,29 +22,24 @@ foreign libc {
 	@(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 ---
 }
 
-@(no_instrumentation)
-get_last_error :: proc "contextless" () -> int {
-	return int(__error()^)
-}
-
 MAX_RW :: 0x7fffffff
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	if len(data) == 0 {
-		return 0, os.ERROR_NONE
+		return 0, nil
 	}
 
 	for n < len(data) {
 		chunk := data[:min(len(data), MAX_RW)]
 		written := _unix_write(fd, raw_data(chunk), len(chunk))
 		if written < 0 {
-			return n, os.Errno(get_last_error())
+			return n, os.get_last_error()
 		}
 		n += written
 	}
 
-	return n, os.ERROR_NONE
+	return n, nil
 }
 
 CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.

+ 4 - 5
core/prof/spall/spall_windows.odin

@@ -10,9 +10,9 @@ import win32 "core:sys/windows"
 MAX_RW :: 1<<30
 
 @(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #no_bounds_check /* bounds check would segfault instrumentation */ {
+_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
 	if len(data) == 0 {
-		return 0, os.ERROR_NONE
+		return 0, nil
 	}
 
 	single_write_length: win32.DWORD
@@ -25,12 +25,11 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Errno) #n
 
 		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
 		if single_write_length <= 0 || !e {
-			err := os.Errno(win32.GetLastError())
-			return int(total_write), err
+			return int(total_write), os.get_last_error()
 		}
 		total_write += i64(single_write_length)
 	}
-	return int(total_write), os.ERROR_NONE
+	return int(total_write), nil
 }
 
 @(no_instrumentation)

+ 5 - 6
core/sync/futex_haiku.odin

@@ -2,7 +2,6 @@
 package sync
 
 import "core:c"
-import "base:runtime"
 import "core:sys/haiku"
 import "core:sys/unix"
 import "core:time"
@@ -86,10 +85,10 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) {
 	waiter.prev.next = waiter.next
 	waiter.next.prev = waiter.prev
 
- 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+	_ = unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
 
  	// FIXME: Add error handling!
- 	return
+	return
 }
 
 _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) {
@@ -133,10 +132,10 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration
 	waiter.prev.next = waiter.next
 	waiter.next.prev = waiter.prev
 
- 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
 
- 	// FIXME: Add error handling!
- 	return 
+	// FIXME: Add error handling!
+	return
 }
 
 _futex_signal :: proc "contextless" (f: ^Futex) {

+ 1 - 1
core/sys/haiku/os.odin

@@ -399,7 +399,7 @@ cpu_topology_node_info :: struct {
 		},
 		_package: struct {
 			vendor:          cpu_vendor,
-			cache_line_size: u32
+			cache_line_size: u32,
 		},
 		_core: struct {
 			model:             u32,

+ 1 - 0
core/sys/wasm/wasi/wasi_api.odin

@@ -16,6 +16,7 @@ CLOCK_REALTIME           :: clockid_t(2)
 CLOCK_THREAD_CPUTIME_ID  :: clockid_t(3)
 
 errno_t :: enum u16 {
+	NONE = 0,
 	// No error occurred. System call completed successfully.
 	SUCCESS = 0,
 	// Argument list too long.

+ 2 - 2
core/testing/runner.odin

@@ -868,8 +868,8 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
 		when ODIN_OS != .Windows {
 			mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH
 		}
-		json_fd, errno := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
-		fmt.assertf(errno == os.ERROR_NONE, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, errno)
+		json_fd, err := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
+		fmt.assertf(err == nil, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, err)
 		defer os.close(json_fd)
 
 		for test, i in report.all_tests {

+ 1 - 1
examples/demo/demo.odin

@@ -359,7 +359,7 @@ control_flow :: proc() {
 
 		if false {
 			f, err := os.open("my_file.txt")
-			if err != os.ERROR_NONE {
+			if err != nil {
 				// handle error
 			}
 			defer os.close(f)

+ 23 - 0
src/check_expr.cpp

@@ -127,6 +127,8 @@ gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finis
 
 gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y);
 
+gb_internal bool is_exact_value_zero(ExactValue const &v);
+
 enum LoadDirectiveResult {
 	LoadDirective_Success  = 0,
 	LoadDirective_Error    = 1,
@@ -4457,6 +4459,27 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar
 		
 
 	case Type_Union:
+		// IMPORTANT NOTE HACK(bill): This is just to allow for comparisons against `0` with the `os.Error` type
+		// as a kind of transition period
+		if (!build_context.strict_style &&
+		    operand->mode == Addressing_Constant &&
+		    target_type->kind == Type_Named &&
+		    (c->pkg == nullptr || c->pkg->name != "os") &&
+		    target_type->Named.name == "Error") {
+			Entity *e = target_type->Named.type_name;
+			if (e->pkg && e->pkg->name == "os") {
+				if (is_exact_value_zero(operand->value) &&
+				    (operand->value.kind == ExactValue_Integer ||
+				     operand->value.kind == ExactValue_Float)) {
+					operand->mode = Addressing_Value;
+					target_type = t_untyped_nil;
+				     	operand->value = empty_exact_value;
+					update_untyped_expr_value(c, operand->expr, operand->value);
+					break;
+				}
+			}
+		}
+		// "fallthrough"
 		if (!is_operand_nil(*operand) && !is_operand_uninit(*operand)) {
 			TEMPORARY_ALLOCATOR_GUARD();
 

+ 1 - 1
tests/core/flags/test_core_flags.odin

@@ -1117,7 +1117,7 @@ test_os_handle :: proc(t: ^testing.T) {
 		// Delete the file now that we're done.
 		//
 		// This is not done all the time, just in case the file is useful to debugging.
-		testing.expect_value(t, os.remove(TEMPORARY_FILENAME), os.ERROR_NONE)
+		testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil)
 	}
 
 	TEMPORARY_FILENAME :: "test_core_flags_write_test_output_data"

+ 4 - 4
tests/core/os/os.odin

@@ -7,12 +7,12 @@ import "core:testing"
 
 @(test)
 read_dir :: proc(t: ^testing.T) {
-	fd, errno := os.open(#directory + "/dir")
-	testing.expect_value(t, errno, os.ERROR_NONE)
+	fd, err := os.open(#directory + "/dir")
+	testing.expect_value(t, err, nil)
 	defer os.close(fd)
 
-	dir, errno2 := os.read_dir(fd, -1)
-	testing.expect_value(t, errno2, os.ERROR_NONE)
+	dir, err2 := os.read_dir(fd, -1)
+	testing.expect_value(t, err2, nil)
 	defer os.file_info_slice_delete(dir)
 
 	slice.sort_by_key(dir, proc(fi: os.File_Info) -> string { return fi.name })

+ 1 - 1
tests/documentation/documentation_tester.odin

@@ -439,7 +439,7 @@ main :: proc() {
 		}
 		save_path := fmt.tprintf("verify/test_%v_%v.odin", test.package_name, code_test_name)
 
-		test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != 0 {
+		test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != nil {
 			fmt.eprintf("We could not open the file to the path %q for writing\n", save_path)
 			g_bad_doc = true
 			continue

Some files were not shown because too many files changed in this diff