Browse Source

Merge pull request #1792 from jasonKercher/os2_linux

Os2 linux
gingerBill 3 years ago
parent
commit
5609221831

+ 28 - 0
core/os/os2/env_linux.odin

@@ -0,0 +1,28 @@
+//+private
+package os2
+
+_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
+	//TODO
+	return
+}
+
+_set_env :: proc(key, value: string) -> bool {
+	//TODO
+	return false
+}
+
+_unset_env :: proc(key: string) -> bool {
+	//TODO
+	return false
+}
+
+_clear_env :: proc() {
+	//TODO
+}
+
+_environ :: proc(allocator := context.allocator) -> []string {
+	//TODO
+	return nil
+}
+
+

+ 145 - 0
core/os/os2/errors_linux.odin

@@ -0,0 +1,145 @@
+//+private
+package os2
+
+import "core:sys/unix"
+
+EPERM          :: 1
+ENOENT         :: 2
+ESRCH          :: 3
+EINTR          :: 4
+EIO            :: 5
+ENXIO          :: 6
+EBADF          :: 9
+EAGAIN         :: 11
+ENOMEM         :: 12
+EACCES         :: 13
+EFAULT         :: 14
+EEXIST         :: 17
+ENODEV         :: 19
+ENOTDIR        :: 20
+EISDIR         :: 21
+EINVAL         :: 22
+ENFILE         :: 23
+EMFILE         :: 24
+ETXTBSY        :: 26
+EFBIG          :: 27
+ENOSPC         :: 28
+ESPIPE         :: 29
+EROFS          :: 30
+EPIPE          :: 32
+ERANGE         :: 34   /* Result too large */
+EDEADLK        :: 35   /* Resource deadlock would occur */
+ENAMETOOLONG   :: 36   /* File name too long */
+ENOLCK         :: 37   /* No record locks available */
+ENOSYS         :: 38   /* Invalid system call number */
+ENOTEMPTY      :: 39   /* Directory not empty */
+ELOOP          :: 40   /* Too many symbolic links encountered */
+EWOULDBLOCK    :: EAGAIN /* Operation would block */
+ENOMSG         :: 42   /* No message of desired type */
+EIDRM          :: 43   /* Identifier removed */
+ECHRNG         :: 44   /* Channel number out of range */
+EL2NSYNC       :: 45   /* Level 2 not synchronized */
+EL3HLT         :: 46   /* Level 3 halted */
+EL3RST         :: 47   /* Level 3 reset */
+ELNRNG         :: 48   /* Link number out of range */
+EUNATCH        :: 49   /* Protocol driver not attached */
+ENOCSI         :: 50   /* No CSI structure available */
+EL2HLT         :: 51   /* Level 2 halted */
+EBADE          :: 52   /* Invalid exchange */
+EBADR          :: 53   /* Invalid request descriptor */
+EXFULL         :: 54   /* Exchange full */
+ENOANO         :: 55   /* No anode */
+EBADRQC        :: 56   /* Invalid request code */
+EBADSLT        :: 57   /* Invalid slot */
+EDEADLOCK      :: EDEADLK
+EBFONT         :: 59   /* Bad font file format */
+ENOSTR         :: 60   /* Device not a stream */
+ENODATA        :: 61   /* No data available */
+ETIME          :: 62   /* Timer expired */
+ENOSR          :: 63   /* Out of streams resources */
+ENONET         :: 64   /* Machine is not on the network */
+ENOPKG         :: 65   /* Package not installed */
+EREMOTE        :: 66   /* Object is remote */
+ENOLINK        :: 67   /* Link has been severed */
+EADV           :: 68   /* Advertise error */
+ESRMNT         :: 69   /* Srmount error */
+ECOMM          :: 70   /* Communication error on send */
+EPROTO         :: 71   /* Protocol error */
+EMULTIHOP      :: 72   /* Multihop attempted */
+EDOTDOT        :: 73   /* RFS specific error */
+EBADMSG        :: 74   /* Not a data message */
+EOVERFLOW      :: 75   /* Value too large for defined data type */
+ENOTUNIQ       :: 76   /* Name not unique on network */
+EBADFD         :: 77   /* File descriptor in bad state */
+EREMCHG        :: 78   /* Remote address changed */
+ELIBACC        :: 79   /* Can not access a needed shared library */
+ELIBBAD        :: 80   /* Accessing a corrupted shared library */
+ELIBSCN        :: 81   /* .lib section in a.out corrupted */
+ELIBMAX        :: 82   /* Attempting to link in too many shared libraries */
+ELIBEXEC       :: 83   /* Cannot exec a shared library directly */
+EILSEQ         :: 84   /* Illegal byte sequence */
+ERESTART       :: 85   /* Interrupted system call should be restarted */
+ESTRPIPE       :: 86   /* Streams pipe error */
+EUSERS         :: 87   /* Too many users */
+ENOTSOCK       :: 88   /* Socket operation on non-socket */
+EDESTADDRREQ   :: 89   /* Destination address required */
+EMSGSIZE       :: 90   /* Message too long */
+EPROTOTYPE     :: 91   /* Protocol wrong type for socket */
+ENOPROTOOPT    :: 92   /* Protocol not available */
+EPROTONOSUPPORT:: 93   /* Protocol not supported */
+ESOCKTNOSUPPORT:: 94   /* Socket type not supported */
+EOPNOTSUPP     :: 95   /* Operation not supported on transport endpoint */
+EPFNOSUPPORT   :: 96   /* Protocol family not supported */
+EAFNOSUPPORT   :: 97   /* Address family not supported by protocol */
+EADDRINUSE     :: 98   /* Address already in use */
+EADDRNOTAVAIL  :: 99   /* Cannot assign requested address */
+ENETDOWN       :: 100  /* Network is down */
+ENETUNREACH    :: 101  /* Network is unreachable */
+ENETRESET      :: 102  /* Network dropped connection because of reset */
+ECONNABORTED   :: 103  /* Software caused connection abort */
+ECONNRESET     :: 104  /* Connection reset by peer */
+ENOBUFS        :: 105  /* No buffer space available */
+EISCONN        :: 106  /* Transport endpoint is already connected */
+ENOTCONN       :: 107  /* Transport endpoint is not connected */
+ESHUTDOWN      :: 108  /* Cannot send after transport endpoint shutdown */
+ETOOMANYREFS   :: 109  /* Too many references: cannot splice */
+ETIMEDOUT      :: 110  /* Connection timed out */
+ECONNREFUSED   :: 111  /* Connection refused */
+EHOSTDOWN      :: 112  /* Host is down */
+EHOSTUNREACH   :: 113  /* No route to host */
+EALREADY       :: 114  /* Operation already in progress */
+EINPROGRESS    :: 115  /* Operation now in progress */
+ESTALE         :: 116  /* Stale file handle */
+EUCLEAN        :: 117  /* Structure needs cleaning */
+ENOTNAM        :: 118  /* Not a XENIX named type file */
+ENAVAIL        :: 119  /* No XENIX semaphores available */
+EISNAM         :: 120  /* Is a named type file */
+EREMOTEIO      :: 121  /* Remote I/O error */
+EDQUOT         :: 122  /* Quota exceeded */
+ENOMEDIUM      :: 123  /* No medium found */
+EMEDIUMTYPE    :: 124  /* Wrong medium type */
+ECANCELED      :: 125  /* Operation Canceled */
+ENOKEY         :: 126  /* Required key not available */
+EKEYEXPIRED    :: 127  /* Key has expired */
+EKEYREVOKED    :: 128  /* Key has been revoked */
+EKEYREJECTED   :: 129  /* Key was rejected by service */
+EOWNERDEAD     :: 130  /* Owner died */
+ENOTRECOVERABLE:: 131  /* State not recoverable */
+ERFKILL        :: 132  /* Operation not possible due to RF-kill */
+EHWPOISON      :: 133  /* Memory page has hardware error */
+
+_get_platform_error :: proc(res: int) -> Error {
+	errno := unix.get_errno(res)
+	return Platform_Error(i32(errno))
+}
+
+_ok_or_error :: proc(res: int) -> Error {
+	return res >= 0 ? nil : _get_platform_error(res)
+}
+
+_error_string :: proc(errno: i32) -> string {
+	if errno == 0 {
+		return ""
+	}
+	return "Error"
+}

+ 422 - 0
core/os/os2/file_linux.odin

@@ -0,0 +1,422 @@
+//+private
+package os2
+
+import "core:io"
+import "core:time"
+import "core:strings"
+import "core:runtime"
+import "core:sys/unix"
+
+INVALID_HANDLE :: -1
+
+_O_RDONLY    :: 0o0
+_O_WRONLY    :: 0o1
+_O_RDWR      :: 0o2
+_O_CREAT     :: 0o100
+_O_EXCL      :: 0o200
+_O_TRUNC     :: 0o1000
+_O_APPEND    :: 0o2000
+_O_NONBLOCK  :: 0o4000
+_O_LARGEFILE :: 0o100000
+_O_DIRECTORY :: 0o200000
+_O_NOFOLLOW  :: 0o400000
+_O_SYNC      :: 0o4010000
+_O_CLOEXEC   :: 0o2000000
+_O_PATH      :: 0o10000000
+
+_AT_FDCWD :: -100
+
+_CSTRING_NAME_HEAP_THRESHOLD :: 512
+
+_File :: struct {
+	name: string,
+	fd: int,
+	allocator: runtime.Allocator,
+}
+
+_file_allocator :: proc() -> runtime.Allocator {
+	return heap_allocator()
+}
+
+_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+
+	flags_i: int
+	switch flags & O_RDONLY|O_WRONLY|O_RDWR {
+	case O_RDONLY: flags_i = _O_RDONLY
+	case O_WRONLY: flags_i = _O_WRONLY
+	case O_RDWR:   flags_i = _O_RDWR
+	}
+
+	flags_i |= (_O_APPEND * int(.Append in flags))
+	flags_i |= (_O_CREAT * int(.Create in flags))
+	flags_i |= (_O_EXCL * int(.Excl in flags))
+	flags_i |= (_O_SYNC * int(.Sync in flags))
+	flags_i |= (_O_TRUNC * int(.Trunc in flags))
+	flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags))
+
+	fd := unix.sys_open(name_cstr, flags_i, int(perm))
+	if fd < 0 {
+		return nil, _get_platform_error(fd)
+	}
+
+	return _new_file(uintptr(fd), name), nil
+}
+
+_new_file :: proc(fd: uintptr, _: string) -> ^File {
+	file := new(File, _file_allocator())
+	file.impl.fd = int(fd)
+	file.impl.allocator = _file_allocator()
+	file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator)
+	return file
+}
+
+_destroy :: proc(f: ^File) -> Error {
+	if f == nil {
+		return nil
+	}
+	delete(f.impl.name, f.impl.allocator)
+	free(f, f.impl.allocator)
+	return nil
+}
+
+
+_close :: proc(f: ^File) -> Error {
+	res := unix.sys_close(f.impl.fd)
+	return _ok_or_error(res)
+}
+
+_fd :: proc(f: ^File) -> uintptr {
+	if f == nil {
+		return ~uintptr(0)
+	}
+	return uintptr(f.impl.fd)
+}
+
+_name :: proc(f: ^File) -> string {
+	return f.impl.name if f != nil else ""
+}
+
+_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
+	res := unix.sys_lseek(f.impl.fd, offset, int(whence))
+	if res < 0 {
+		return -1, _get_platform_error(int(res))
+	}
+	return res, nil
+}
+
+_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
+	n = unix.sys_read(f.impl.fd, &p[0], len(p))
+	if n < 0 {
+		return -1, _get_platform_error(n)
+	}
+	return n, nil
+}
+
+_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
+	if offset < 0 {
+		return 0, .Invalid_Offset
+	}
+
+	b, offset := p, offset
+	for len(b) > 0 {
+		m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset)
+		if m < 0 {
+			return -1, _get_platform_error(m)
+		}
+		n += m
+		b = b[m:]
+		offset += i64(m)
+	}
+	return
+}
+
+_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
+	//TODO
+	return
+}
+
+_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
+	if len(p) == 0 {
+		return 0, nil
+	}
+	n = unix.sys_write(f.impl.fd, &p[0], uint(len(p)))
+	if n < 0 {
+		return -1, _get_platform_error(n)
+	}
+	return int(n), nil
+}
+
+_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
+	if offset < 0 {
+		return 0, .Invalid_Offset
+	}
+
+	b, offset := p, offset
+	for len(b) > 0 {
+		m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset)
+		if m < 0 {
+			return -1, _get_platform_error(m)
+		}
+		n += m
+		b = b[m:]
+		offset += i64(m)
+	}
+	return
+}
+
+_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
+	//TODO
+	return
+}
+
+_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
+	s: _Stat = ---
+	res := unix.sys_fstat(f.impl.fd, &s)
+	if res < 0 {
+		return -1, _get_platform_error(res)
+	}
+	return s.size, nil
+}
+
+_sync :: proc(f: ^File) -> Error {
+	return _ok_or_error(unix.sys_fsync(f.impl.fd))
+}
+
+_flush :: proc(f: ^File) -> Error {
+	return _ok_or_error(unix.sys_fsync(f.impl.fd))
+}
+
+_truncate :: proc(f: ^File, size: i64) -> Error {
+	return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size))
+}
+
+_remove :: proc(name: string) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+
+	fd := unix.sys_open(name_cstr, int(File_Flags.Read))
+	if fd < 0 {
+		return _get_platform_error(fd)
+	}
+	defer unix.sys_close(fd)
+
+	if _is_dir_fd(fd) {
+		return _ok_or_error(unix.sys_rmdir(name_cstr))
+	}
+	return _ok_or_error(unix.sys_unlink(name_cstr))
+}
+
+_rename :: proc(old_name, new_name: string) -> Error {
+	old_name_cstr, old_allocated := _name_to_cstring(old_name)
+	new_name_cstr, new_allocated := _name_to_cstring(new_name)
+	defer if old_allocated {
+		delete(old_name_cstr)
+	}
+	defer if new_allocated {
+		delete(new_name_cstr)
+	}
+
+	return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr))
+}
+
+_link :: proc(old_name, new_name: string) -> Error {
+	old_name_cstr, old_allocated := _name_to_cstring(old_name)
+	new_name_cstr, new_allocated := _name_to_cstring(new_name)
+	defer if old_allocated {
+		delete(old_name_cstr)
+	}
+	defer if new_allocated {
+		delete(new_name_cstr)
+	}
+
+	return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr))
+}
+
+_symlink :: proc(old_name, new_name: string) -> Error {
+	old_name_cstr, old_allocated := _name_to_cstring(old_name)
+	new_name_cstr, new_allocated := _name_to_cstring(new_name)
+	defer if old_allocated {
+		delete(old_name_cstr)
+	}
+	defer if new_allocated {
+		delete(new_name_cstr)
+	}
+
+	return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
+}
+
+_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) {
+	bufsz : uint = 256
+	buf := make([]byte, bufsz, allocator)
+	for {
+		rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz)
+		if rc < 0 {
+			delete(buf)
+			return "", _get_platform_error(rc)
+		} else if rc == int(bufsz) {
+			bufsz *= 2
+			delete(buf)
+			buf = make([]byte, bufsz, allocator)
+		} else {
+			return strings.string_from_ptr(&buf[0], rc), nil
+		}
+	}
+}
+
+_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _read_link_cstr(name_cstr, allocator)
+}
+
+_unlink :: proc(name: string) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _ok_or_error(unix.sys_unlink(name_cstr))
+}
+
+_chdir :: proc(name: string) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _ok_or_error(unix.sys_chdir(name_cstr))
+}
+
+_fchdir :: proc(f: ^File) -> Error {
+	return _ok_or_error(unix.sys_fchdir(f.impl.fd))
+}
+
+_chmod :: proc(name: string, mode: File_Mode) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _ok_or_error(unix.sys_chmod(name_cstr, int(mode)))
+}
+
+_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
+	return _ok_or_error(unix.sys_fchmod(f.impl.fd, int(mode)))
+}
+
+// NOTE: will throw error without super user priviledges
+_chown :: proc(name: string, uid, gid: int) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _ok_or_error(unix.sys_chown(name_cstr, uid, gid))
+}
+
+// NOTE: will throw error without super user priviledges
+_lchown :: proc(name: string, uid, gid: int) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid))
+}
+
+// NOTE: will throw error without super user priviledges
+_fchown :: proc(f: ^File, uid, gid: int) -> Error {
+	return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid))
+}
+
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	times := [2]Unix_File_Time {
+		{ atime._nsec, 0 },
+		{ mtime._nsec, 0 },
+	}
+	return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, &times, 0))
+}
+
+_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
+	times := [2]Unix_File_Time {
+		{ atime._nsec, 0 },
+		{ mtime._nsec, 0 },
+	}
+	return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, &times, 0))
+}
+
+_exists :: proc(name: string) -> bool {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	return unix.sys_access(name_cstr, F_OK) == 0
+}
+
+_is_file :: proc(name: string) -> bool {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	s: _Stat
+	res := unix.sys_stat(name_cstr, &s)
+	if res < 0 {
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+_is_file_fd :: proc(fd: int) -> bool {
+	s: _Stat
+	res := unix.sys_fstat(fd, &s)
+	if res < 0 { // error
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+_is_dir :: proc(name: string) -> bool {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	s: _Stat
+	res := unix.sys_stat(name_cstr, &s)
+	if res < 0 {
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+_is_dir_fd :: proc(fd: int) -> bool {
+	s: _Stat
+	res := unix.sys_fstat(fd, &s)
+	if res < 0 { // error
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+// Ideally we want to use the temp_allocator.  PATH_MAX on Linux is commonly
+// defined as 512, however, it is well known that paths can exceed that limit.
+// So, in theory you could have a path larger than the entire temp_allocator's
+// buffer. Therefor, any large paths will use context.allocator.
+_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
+	if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
+		cname = strings.clone_to_cstring(name)
+		allocated = true
+		return
+	}
+	cname = strings.clone_to_cstring(name, context.temp_allocator)
+	return
+}

+ 722 - 0
core/os/os2/heap_linux.odin

@@ -0,0 +1,722 @@
+//+private
+package os2
+
+import "core:sys/unix"
+import "core:sync"
+import "core:mem"
+
+// NOTEs
+//
+// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region
+// consists of a Region_Header and the memory that will be divided into allocations to
+// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes.
+// Allocation_Headers are used to navigate the memory in the region. The "next" member of
+// the Allocation_Header points to the next header, and the space between the headers
+// can be used to send to the user. This space between is referred to as "blocks" in the
+// code. The indexes in the header refer to these blocks instead of bytes.  This allows us
+// to index all the memory in the region with a u16.
+//
+// When an allocation request is made, it will use the first free block that can contain
+// the entire block.  If there is an excess number of blocks (as specified by the constant
+// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list.
+//
+// To keep the implementation simple, there can never exist 2 free blocks adjacent to each
+// other. Any freeing will result in attempting to merge the blocks before and after the
+// newly free'd blocks.
+//
+// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation
+// getting its own individual mmap. Individual mmaps will still get an Allocation_Header
+// that contains the size with the last bit set to 1 to indicate it is indeed a direct
+// mmap allocation.
+
+// Why not brk?
+// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation
+// does *not* utilize the brk system call to avoid possible conflicts with foreign C
+// code. Just because we aren't directly using libc, there is nothing stopping the user
+// from doing it.
+
+// What's with all the #no_bounds_check?
+// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere
+// until that region is written to by *you*.  So, when a new region is created, we call mmap
+// to get a pointer to some memory, and we claim that memory is a ^Region.  Therefor, the
+// region itself is never formally initialized by the compiler as this would result in writing
+// zeros to memory that we can already assume are 0. This would also have the effect of
+// actually commiting this data to memory whether it gets used or not.
+
+
+//
+// Some variables to play with
+//
+
+// Minimum blocks used for any one allocation
+MINIMUM_BLOCK_COUNT :: 2
+
+// Number of extra blocks beyond the requested amount where we would segment.
+// E.g. (blocks) |H0123456| 7 available
+//               |H01H0123| Ask for 2, now 4 available
+BLOCK_SEGMENT_THRESHOLD :: 4
+
+// Anything above this threshold will get its own memory map. Since regions
+// are indexed by 16 bit integers, this value should not surpass max(u16) * 6
+DIRECT_MMAP_THRESHOLD_USER :: int(max(u16))
+
+// The point at which we convert direct mmap to region. This should be a decent
+// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions.
+MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4
+
+// free_list is dynamic and is initialized in the begining of the region memory
+// when the region is initialized. Once resized, it can be moved anywhere.
+FREE_LIST_DEFAULT_CAP :: 32
+
+
+//
+// Other constants that should not be touched
+//
+
+// This universally seems to be 4096 outside of uncommon archs.
+PAGE_SIZE :: 4096
+
+// just rounding up to nearest PAGE_SIZE
+DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE
+
+// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well
+// as end right on a page boundary as to not waste space.
+SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE)
+
+// size of user memory blocks
+BLOCK_SIZE :: size_of(Allocation_Header)
+
+// number of allocation sections (call them blocks) of the region used for allocations
+BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE)
+
+// minimum amount of space that can used by any individual allocation (includes header)
+MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE
+
+// This is used as a boolean value for Region_Header.local_addr.
+CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0))
+
+FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16)
+
+MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE
+MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE
+
+
+@thread_local _local_region: ^Region
+global_regions: ^Region
+
+
+// There is no way of correctly setting the last bit of free_idx or
+// the last bit of requested, so we can safely use it as a flag to
+// determine if we are interacting with a direct mmap.
+REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF
+IS_DIRECT_MMAP :: 0x8000000000000000
+
+// Special free_idx value that does not index the free_list.
+NOT_FREE :: 0x7FFF
+Allocation_Header :: struct #raw_union {
+	using _:   struct {
+		// Block indicies
+		idx:      u16,
+		prev:     u16,
+		next:     u16,
+		free_idx: u16,
+	},
+	requested: u64,
+}
+
+Region_Header :: struct #align 16 {
+	next_region:   ^Region,  // points to next region in global_heap (linked list)
+	local_addr:    ^^Region, // tracks region ownership via address of _local_region
+	reset_addr:    ^^Region, // tracks old local addr for reset
+	free_list:     []u16,
+	free_list_len: u16,
+	free_blocks:   u16,      // number of free blocks in region (includes headers)
+	last_used:     u16,      // farthest back block that has been used (need zeroing?)
+	_reserved:     u16,
+}
+
+Region :: struct {
+	hdr: Region_Header,
+	memory: [BLOCKS_PER_REGION]Allocation_Header,
+}
+
+_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
+                            size, alignment: int,
+                            old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) {
+	//
+	// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment.
+	// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert
+	// padding. We also store the original pointer returned by heap_alloc right before
+	// the pointer we return to the user.
+	//
+
+	aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
+		a := max(alignment, align_of(rawptr))
+		space := size + a - 1
+
+		allocated_mem: rawptr
+		if old_ptr != nil {
+			original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^
+			allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
+		} else {
+			allocated_mem = heap_alloc(space+size_of(rawptr))
+		}
+		aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
+
+		ptr := uintptr(aligned_mem)
+		aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a)
+		diff := int(aligned_ptr - ptr)
+		if (size + diff) > space {
+			return nil, .Out_Of_Memory
+		}
+
+		aligned_mem = rawptr(aligned_ptr)
+		mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem
+
+		return mem.byte_slice(aligned_mem, size), nil
+	}
+
+	aligned_free :: proc(p: rawptr) {
+		if p != nil {
+			heap_free(mem.ptr_offset((^rawptr)(p), -1)^)
+		}
+	}
+
+	aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) {
+		if p == nil {
+			return nil, nil
+		}
+
+		return aligned_alloc(new_size, new_alignment, p)
+	}
+
+	switch mode {
+	case .Alloc:
+		return aligned_alloc(size, alignment)
+
+	case .Free:
+		aligned_free(old_memory)
+
+	case .Free_All:
+		return nil, .Mode_Not_Implemented
+
+	case .Resize:
+		if old_memory == nil {
+			return aligned_alloc(size, alignment)
+		}
+		return aligned_resize(old_memory, old_size, size, alignment)
+
+	case .Query_Features:
+		set := (^mem.Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Free, .Resize, .Query_Features}
+		}
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+
+	return nil, nil
+}
+
+heap_alloc :: proc(size: int) -> rawptr {
+	if size >= DIRECT_MMAP_THRESHOLD {
+		return _direct_mmap_alloc(size)
+	}
+
+	// atomically check if the local region has been stolen
+	if _local_region != nil {
+		res := sync.atomic_compare_exchange_strong_explicit(
+			&_local_region.hdr.local_addr,
+			&_local_region,
+			CURRENTLY_ACTIVE,
+			.Acquire,
+			.Relaxed,
+		)
+		if res != &_local_region {
+			// At this point, the region has been stolen and res contains the unexpected value
+			expected := res
+			if res != CURRENTLY_ACTIVE {
+				expected = res
+				res = sync.atomic_compare_exchange_strong_explicit(
+					&_local_region.hdr.local_addr,
+					expected,
+					CURRENTLY_ACTIVE,
+					.Acquire,
+					.Relaxed,
+				)
+			}
+			if res != expected {
+				_local_region = nil
+			}
+		}
+	}
+
+	size := size
+	size = _round_up_to_nearest(size, BLOCK_SIZE)
+	blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE))
+
+	// retrieve a region if new thread or stolen
+	if _local_region == nil {
+		_local_region, _ = _region_retrieve_with_space(blocks_needed)
+		if _local_region == nil {
+			return nil
+		}
+	}
+	defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+
+	// At this point we have a usable region. Let's find the user some memory
+	idx: u16
+	local_region_idx := _region_get_local_idx()
+	back_idx := -1
+	infinite: for {
+		for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 {
+			idx = _local_region.hdr.free_list[i]
+			#no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed {
+				break infinite
+			}
+		}
+		sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+		_local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx)
+	}
+	user_ptr, used := _region_get_block(_local_region, idx, blocks_needed)
+	_local_region.hdr.free_blocks -= (used + 1)
+
+	// If this memory was ever used before, it now needs to be zero'd.
+	if idx < _local_region.hdr.last_used {
+		mem.zero(user_ptr, int(used) * BLOCK_SIZE)
+	} else {
+		_local_region.hdr.last_used = idx + used
+	}
+
+	return user_ptr
+}
+
+heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check {
+	alloc := _get_allocation_header(old_memory)
+	if alloc.requested & IS_DIRECT_MMAP > 0 {
+		return _direct_mmap_resize(alloc, new_size)
+	}
+
+	if new_size > DIRECT_MMAP_THRESHOLD {
+		return _direct_mmap_from_region(alloc, new_size)
+	}
+
+	return _region_resize(alloc, new_size)
+}
+
+heap_free :: proc(memory: rawptr) {
+	alloc := _get_allocation_header(memory)
+	if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP {
+		_direct_mmap_free(alloc)
+		return
+	}
+
+	assert(alloc.free_idx == NOT_FREE)
+
+	_region_find_and_assign_local(alloc)
+	_region_local_free(alloc)
+	sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+}
+
+//
+// Regions
+//
+_new_region :: proc() -> ^Region #no_bounds_check {
+	res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0)
+	if res < 0 {
+		return nil
+	}
+	new_region := (^Region)(uintptr(res))
+
+	new_region.hdr.local_addr = CURRENTLY_ACTIVE
+	new_region.hdr.reset_addr = &_local_region
+
+	free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK)
+	_region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK)
+
+	// + 2 to account for free_list's allocation header
+	first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2
+
+	// first allocation header (this is a free list)
+	new_region.memory[0].next = u16(first_user_block)
+	new_region.memory[0].free_idx = NOT_FREE
+	new_region.memory[first_user_block].idx = u16(first_user_block)
+	new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1
+
+	// add the first user block to the free list
+	new_region.hdr.free_list[0] = u16(first_user_block)
+	new_region.hdr.free_list_len = 1
+	new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1
+
+	for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region);
+	    r != nil;
+	    r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {}
+
+	return new_region
+}
+
+_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check {
+	assert(alloc.free_idx == NOT_FREE)
+
+	old_memory := mem.ptr_offset(alloc, 1)
+
+	old_block_count := _get_block_count(alloc^)
+	new_block_count := u16(
+		max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE),
+	)
+	if new_block_count < old_block_count {
+		if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT {
+			_region_find_and_assign_local(alloc)
+			_region_segment(_local_region, alloc, new_block_count, alloc.free_idx)
+			new_block_count = _get_block_count(alloc^)
+			sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+		}
+		// need to zero anything within the new block that that lies beyond new_size
+		extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size
+		extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE)
+		mem.zero(extra_bytes_ptr, extra_bytes)
+		return old_memory
+	}
+
+	if !alloc_is_free_list {
+		_region_find_and_assign_local(alloc)
+	}
+	defer if !alloc_is_free_list {
+		sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+	}
+	
+	// First, let's see if we can grow in place.
+	if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE {
+		next_alloc := _local_region.memory[alloc.next]
+		total_available := old_block_count + _get_block_count(next_alloc) + 1
+		if total_available >= new_block_count {
+			alloc.next = next_alloc.next
+			_local_region.memory[alloc.next].prev = alloc.idx
+			if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD {
+				_region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx)
+			} else {
+				_region_free_list_remove(_local_region, next_alloc.free_idx)
+			}
+			mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE)
+			_local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used)
+			_local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count)
+			if alloc_is_free_list {
+				_region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^))
+			}
+			return old_memory
+		}
+	}
+
+	// If we made it this far, we need to resize, copy, zero and free.
+	region_iter := _local_region
+	local_region_idx := _region_get_local_idx()
+	back_idx := -1
+	idx: u16
+	infinite: for {
+		for i := 0; i < len(region_iter.hdr.free_list); i += 1 {
+			idx = region_iter.hdr.free_list[i]
+			if _get_block_count(region_iter.memory[idx]) >= new_block_count {
+				break infinite
+			}
+		}
+		if region_iter != _local_region {
+			sync.atomic_store_explicit(
+				&region_iter.hdr.local_addr,
+				region_iter.hdr.reset_addr,
+				.Release,
+			)
+		}
+		region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx)
+	}
+	if region_iter != _local_region {
+		sync.atomic_store_explicit(
+			&region_iter.hdr.local_addr,
+			region_iter.hdr.reset_addr,
+			.Release,
+		)
+	}
+
+	// copy from old memory
+	new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count)
+	mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE))
+
+	// zero any new memory
+	addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count)
+	new_blocks := used_blocks - old_block_count
+	mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE)
+
+	region_iter.hdr.free_blocks -= (used_blocks + 1)
+
+	// Set free_list before freeing.
+	if alloc_is_free_list {
+		_region_assign_free_list(_local_region, new_memory, used_blocks)
+	}
+
+	// free old memory
+	_region_local_free(alloc)
+	return new_memory
+}
+
+_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check {
+	alloc := alloc
+	add_to_free_list := true
+
+	_local_region.hdr.free_blocks += _get_block_count(alloc^) + 1
+
+	// try to merge with prev
+	if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE {
+		_local_region.memory[alloc.prev].next = alloc.next
+		_local_region.memory[alloc.next].prev = alloc.prev
+		alloc = &_local_region.memory[alloc.prev]
+		add_to_free_list = false
+	}
+
+	// try to merge with next
+	if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE {
+		old_next := alloc.next
+		alloc.next = _local_region.memory[old_next].next
+		_local_region.memory[alloc.next].prev = alloc.idx
+
+		if add_to_free_list {
+			_local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx
+			alloc.free_idx = _local_region.memory[old_next].free_idx
+		} else {
+			// NOTE: We have aleady merged with prev, and now merged with next.
+			//       Now, we are actually going to remove from the free_list.
+			_region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx)
+		}
+		add_to_free_list = false
+	}
+
+	// This is the only place where anything is appended to the free list.
+	if add_to_free_list {
+		fl := _local_region.hdr.free_list
+		alloc.free_idx = _local_region.hdr.free_list_len
+		fl[alloc.free_idx] = alloc.idx
+		_local_region.hdr.free_list_len += 1
+		if int(_local_region.hdr.free_list_len) == len(fl) {
+			free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list))
+			_region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true)
+		}
+	}
+}
+
+_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) {
+	raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list
+	raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK
+	raw_free_list.data = memory
+	region.hdr.free_list = transmute([]u16)(raw_free_list)
+}
+
+_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) {
+	r: ^Region
+	idx: int
+	for r = global_regions; r != nil; r = r.hdr.next_region {
+		if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks {
+			idx += 1
+			continue
+		}
+		idx += 1
+		local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr)
+		if local_addr != CURRENTLY_ACTIVE {
+			res := sync.atomic_compare_exchange_strong_explicit(
+				&r.hdr.local_addr,
+				local_addr,
+				CURRENTLY_ACTIVE,
+				.Acquire,
+				.Relaxed,
+			)
+			if res == local_addr {
+				r.hdr.reset_addr = local_addr
+				return r, idx
+			}
+		}
+	}
+
+	return _new_region(), idx
+}
+
+_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region {
+	r: ^Region
+	for r = global_regions; r != nil; r = r.hdr.next_region {
+		if _region_contains_mem(r, addr) {
+			return r
+		}
+	}
+	unreachable()
+}
+
+_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check {
+	alloc := &region.memory[idx]
+
+	assert(alloc.free_idx != NOT_FREE)
+	assert(alloc.next > 0)
+
+	block_count := _get_block_count(alloc^)
+	if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD {
+		_region_segment(region, alloc, blocks_needed, alloc.free_idx)
+	} else {
+		_region_free_list_remove(region, alloc.free_idx)
+	}
+
+	alloc.free_idx = NOT_FREE
+	return mem.ptr_offset(alloc, 1), _get_block_count(alloc^)
+}
+
+_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check {
+	old_next := alloc.next
+	alloc.next = alloc.idx + blocks + 1
+	region.memory[old_next].prev = alloc.next
+
+	// Initialize alloc.next allocation header here.
+	region.memory[alloc.next].prev = alloc.idx
+	region.memory[alloc.next].next = old_next
+	region.memory[alloc.next].idx = alloc.next
+	region.memory[alloc.next].free_idx = new_free_idx
+
+	// Replace our original spot in the free_list with new segment.
+	region.hdr.free_list[new_free_idx] = alloc.next
+}
+
+_region_get_local_idx :: proc() -> int {
+	idx: int
+	for r := global_regions; r != nil; r = r.hdr.next_region {
+		if r == _local_region {
+			return idx
+		}
+		idx += 1
+	}
+
+	return -1
+}
+
+_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) {
+	// Find the region that contains this memory
+	if !_region_contains_mem(_local_region, alloc) {
+		_local_region = _region_retrieve_from_addr(alloc)
+	}
+
+	// At this point, _local_region is set correctly. Spin until acquired
+	res: ^^Region
+	for res != &_local_region {
+		res = sync.atomic_compare_exchange_strong_explicit(
+			&_local_region.hdr.local_addr,
+			&_local_region,
+			CURRENTLY_ACTIVE,
+			.Acquire,
+			.Relaxed,
+		)
+	}
+}
+
+_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check {
+	if r == nil {
+		return false
+	}
+	mem_int := uintptr(memory)
+	return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1])
+}
+
+_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check {
+	// pop, swap and update allocation hdr
+	if n := region.hdr.free_list_len - 1; free_idx != n {
+		region.hdr.free_list[free_idx] = region.hdr.free_list[n]
+		alloc_idx := region.hdr.free_list[free_idx]
+		region.memory[alloc_idx].free_idx = free_idx
+	}
+	region.hdr.free_list_len -= 1
+}
+
+//
+// Direct mmap
+//
+_direct_mmap_alloc :: proc(size: int) -> rawptr {
+	mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE)
+	new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0)
+	if new_allocation < 0 && new_allocation > -4096 {
+		return nil
+	}
+
+	alloc := (^Allocation_Header)(uintptr(new_allocation))
+	alloc.requested = u64(size) // NOTE: requested = requested size
+	alloc.requested += IS_DIRECT_MMAP
+	return rawptr(mem.ptr_offset(alloc, 1))
+}
+
+_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
+	old_requested := int(alloc.requested & REQUESTED_MASK)
+	old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE)
+	new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE)
+	if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD {
+		return _direct_mmap_to_region(alloc, new_size)
+	} else if old_requested == new_size {
+		return mem.ptr_offset(alloc, 1)
+	}
+
+	new_allocation := unix.sys_mremap(
+		alloc,
+		uint(old_mmap_size),
+		uint(new_mmap_size),
+		unix.MREMAP_MAYMOVE,
+	)
+	if new_allocation < 0 && new_allocation > -4096 {
+		return nil
+	}
+
+	new_header := (^Allocation_Header)(uintptr(new_allocation))
+	new_header.requested = u64(new_size)
+	new_header.requested += IS_DIRECT_MMAP
+
+	if new_mmap_size > old_mmap_size {
+		// new section may not be pointer aligned, so cast to ^u8
+		new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE)
+		mem.zero(new_section, new_mmap_size - old_mmap_size)
+	}
+	return mem.ptr_offset(new_header, 1)
+
+}
+
+_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
+	new_memory := _direct_mmap_alloc(new_size)
+	if new_memory != nil {
+		old_memory := mem.ptr_offset(alloc, 1)
+		mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE)
+	}
+	_region_find_and_assign_local(alloc)
+	_region_local_free(alloc)
+	sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release)
+	return new_memory
+}
+
+_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr {
+	new_memory := heap_alloc(new_size)
+	if new_memory != nil {
+		mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size)
+		_direct_mmap_free(alloc)
+	}
+	return new_memory
+}
+
+_direct_mmap_free :: proc(alloc: ^Allocation_Header) {
+	requested := int(alloc.requested & REQUESTED_MASK)
+	mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE)
+	unix.sys_munmap(alloc, uint(mmap_size))
+}
+
+//
+// Util
+//
+
+_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 {
+	return alloc.next - alloc.idx - 1
+}
+
+_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header {
+	return mem.ptr_offset((^Allocation_Header)(raw_mem), -1)
+}
+
+_round_up_to_nearest :: #force_inline proc(size, round: int) -> int {
+	return (size-1) + round - (size-1) % round
+}

+ 247 - 0
core/os/os2/path_linux.odin

@@ -0,0 +1,247 @@
+//+private
+package os2
+
+import "core:strings"
+import "core:strconv"
+import "core:runtime"
+import "core:sys/unix"
+
+_Path_Separator      :: '/'
+_Path_List_Separator :: ':'
+
+_S_IFMT   :: 0o170000 // Type of file mask
+_S_IFIFO  :: 0o010000 // Named pipe (fifo)
+_S_IFCHR  :: 0o020000 // Character special
+_S_IFDIR  :: 0o040000 // Directory
+_S_IFBLK  :: 0o060000 // Block special
+_S_IFREG  :: 0o100000 // Regular
+_S_IFLNK  :: 0o120000 // Symbolic link
+_S_IFSOCK :: 0o140000 // Socket
+
+_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC
+
+_is_path_separator :: proc(c: byte) -> bool {
+	return c == '/'
+}
+
+_mkdir :: proc(path: string, perm: File_Mode) -> Error {
+	// NOTE: These modes would require sys_mknod, however, that would require
+	//       additional arguments to this function.
+	if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
+		return .Invalid_Argument
+	}
+
+	path_cstr, allocated := _name_to_cstring(path)
+	defer if allocated {
+		delete(path_cstr)
+	}
+	return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777)))
+}
+
+_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
+	_mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error {
+		if len(path) == 0 {
+			return _ok_or_error(unix.sys_close(dfd))
+		}
+		i: int
+		for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {}
+		path[i] = 0
+		new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
+		switch new_dfd {
+		case -ENOENT:
+			if res := unix.sys_mkdirat(dfd, cstring(&path[0]), perm); res < 0 {
+				return _get_platform_error(res)
+			}
+			has_created^ = true
+			if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 {
+				return _get_platform_error(new_dfd)
+			}
+			fallthrough
+		case 0:
+			if res := unix.sys_close(dfd); res < 0 {
+				return _get_platform_error(res)
+			}
+			// skip consecutive '/'
+			for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
+			return _mkdirat(new_dfd, path[i:], perm, has_created)
+		case:
+			return _get_platform_error(new_dfd)
+		}
+		unreachable()
+	}
+
+	if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 {
+		return .Invalid_Argument
+	}
+
+	// need something we can edit, and use to generate cstrings
+	allocated: bool
+	path_bytes: []u8
+	if len(path) > _CSTRING_NAME_HEAP_THRESHOLD {
+		allocated = true
+		path_bytes = make([]u8, len(path) + 1)
+	} else {
+		path_bytes = make([]u8, len(path) + 1, context.temp_allocator)
+	}
+	defer if allocated {
+		delete(path_bytes)
+	}
+
+	// NULL terminate the byte slice to make it a valid cstring
+	copy(path_bytes, path)
+	path_bytes[len(path)] = 0
+
+	dfd: int
+	if path_bytes[0] == '/' {
+		dfd = unix.sys_open("/", _OPENDIR_FLAGS)
+		path_bytes = path_bytes[1:]
+	} else {
+		dfd = unix.sys_open(".", _OPENDIR_FLAGS)
+	}
+	if dfd < 0 {
+		return _get_platform_error(dfd)
+	}
+	
+	has_created: bool
+	_mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
+	if has_created {
+		return nil
+	}
+	return .Exist
+	//return has_created ? nil : .Exist
+}
+
+dirent64 :: struct {
+	d_ino: u64,
+	d_off: u64,
+	d_reclen: u16,
+	d_type: u8,
+	d_name: [1]u8,
+}
+
+_remove_all :: proc(path: string) -> Error {
+	DT_DIR :: 4
+
+	_remove_all_dir :: proc(dfd: int) -> Error {
+		n := 64
+		buf := make([]u8, n)
+		defer delete(buf)
+
+		loop: for {
+			getdents_res := unix.sys_getdents64(dfd, &buf[0], n)
+			switch getdents_res {
+			case -EINVAL:
+				delete(buf)
+				n *= 2
+				buf = make([]u8, n)
+				continue loop
+			case -4096..<0:
+				return _get_platform_error(getdents_res)
+			case 0:
+				break loop
+			}
+
+			d: ^dirent64
+
+			for i := 0; i < getdents_res; i += int(d.d_reclen) {
+				d = (^dirent64)(rawptr(&buf[i]))
+				d_name_cstr := cstring(&d.d_name[0])
+
+				buf_len := uintptr(d.d_reclen) - offset_of(d.d_name)
+
+				/* check for current directory (.) */
+				#no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 {
+					continue
+				}
+
+				/* check for parent directory (..) */
+				#no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 {
+					continue
+				}
+
+				unlink_res: int
+
+				switch d.d_type {
+				case DT_DIR:
+					new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
+					if new_dfd < 0 {
+						return _get_platform_error(new_dfd)
+					}
+					defer unix.sys_close(new_dfd)
+					_remove_all_dir(new_dfd) or_return
+					unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR))
+				case:
+					unlink_res = unix.sys_unlinkat(dfd, d_name_cstr) 
+				}
+
+				if unlink_res < 0 {
+					return _get_platform_error(unlink_res)
+				}
+			}
+		}
+		return nil
+	}
+
+	path_cstr, allocated := _name_to_cstring(path)
+	defer if allocated {
+		delete(path_cstr)
+	}
+
+	fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS)
+	switch fd {
+	case -ENOTDIR:
+		return _ok_or_error(unix.sys_unlink(path_cstr))
+	case -4096..<0:
+		return _get_platform_error(fd)
+	}
+
+	defer unix.sys_close(fd)
+	_remove_all_dir(fd) or_return
+	return _ok_or_error(unix.sys_rmdir(path_cstr))
+}
+
+_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
+	// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
+	// an authoritative value for it across all systems.
+	// The largest value I could find was 4096, so might as well use the page size.
+	// NOTE(jason): Avoiding libc, so just use 4096 directly
+	PATH_MAX :: 4096
+	buf := make([dynamic]u8, PATH_MAX, allocator)
+	for {
+		#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
+
+		if res >= 0 {
+			return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil
+		}
+		if res != -ERANGE {
+			return "", _get_platform_error(res)
+		}
+		resize(&buf, len(buf)+PATH_MAX)
+	}
+	unreachable()
+}
+
+_setwd :: proc(dir: string) -> Error {
+	dir_cstr, allocated := _name_to_cstring(dir)
+	defer if allocated {
+		delete(dir_cstr)
+	}
+	return _ok_or_error(unix.sys_chdir(dir_cstr))
+}
+
+_get_full_path :: proc(fd: int, allocator := context.allocator) -> string {
+	PROC_FD_PATH :: "/proc/self/fd/"
+
+	buf: [32]u8
+	copy(buf[:], PROC_FD_PATH)
+
+	strconv.itoa(buf[len(PROC_FD_PATH):], fd)
+
+	fullpath: string
+	err: Error
+	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
+		return ""
+	}
+	return fullpath
+}
+

+ 7 - 0
core/os/os2/pipe_linux.odin

@@ -0,0 +1,7 @@
+//+private
+package os2
+
+_pipe :: proc() -> (r, w: ^File, err: Error) {
+	return nil, nil, nil
+}
+

+ 152 - 0
core/os/os2/stat_linux.odin

@@ -0,0 +1,152 @@
+//+private
+package os2
+
+import "core:time"
+import "core:runtime"
+import "core:sys/unix"
+import "core:path/filepath"
+
+// File type
+S_IFMT   :: 0o170000 // Type of file mask
+S_IFIFO  :: 0o010000 // Named pipe (fifo)
+S_IFCHR  :: 0o020000 // Character special
+S_IFDIR  :: 0o040000 // Directory
+S_IFBLK  :: 0o060000 // Block special
+S_IFREG  :: 0o100000 // Regular
+S_IFLNK  :: 0o120000 // Symbolic link
+S_IFSOCK :: 0o140000 // Socket
+
+// File mode
+// Read, write, execute/search by owner
+S_IRWXU :: 0o0700 // RWX mask for owner
+S_IRUSR :: 0o0400 // R for owner
+S_IWUSR :: 0o0200 // W for owner
+S_IXUSR :: 0o0100 // X for owner
+
+	// Read, write, execute/search by group
+S_IRWXG :: 0o0070 // RWX mask for group
+S_IRGRP :: 0o0040 // R for group
+S_IWGRP :: 0o0020 // W for group
+S_IXGRP :: 0o0010 // X for group
+
+	// Read, write, execute/search by others
+S_IRWXO :: 0o0007 // RWX mask for other
+S_IROTH :: 0o0004 // R for other
+S_IWOTH :: 0o0002 // W for other
+S_IXOTH :: 0o0001 // X for other
+
+S_ISUID :: 0o4000 // Set user id on execution
+S_ISGID :: 0o2000 // Set group id on execution
+S_ISVTX :: 0o1000 // Directory restrcted delete
+
+
+S_ISLNK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK  }
+S_ISREG  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG  }
+S_ISDIR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR  }
+S_ISCHR  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR  }
+S_ISBLK  :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK  }
+S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO  }
+S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
+
+F_OK :: 0 // Test for file existance
+X_OK :: 1 // Test for execute permission
+W_OK :: 2 // Test for write permission
+R_OK :: 4 // Test for read permission
+
+@private
+Unix_File_Time :: struct {
+	seconds:     i64,
+	nanoseconds: i64,
+}
+
+@private
+_Stat :: struct {
+	device_id:     u64, // ID of device containing file
+	serial:        u64, // File serial number
+	nlink:         u64, // Number of hard links
+	mode:          u32, // Mode of the file
+	uid:           u32, // User ID of the file's owner
+	gid:           u32, // Group ID of the file's group
+	_padding:      i32, // 32 bits of padding
+	rdev:          u64, // Device ID, if device
+	size:          i64, // Size of the file, in bytes
+	block_size:    i64, // Optimal bllocksize for I/O
+	blocks:        i64, // Number of 512-byte blocks allocated
+
+	last_access:   Unix_File_Time, // Time of last access
+	modified:      Unix_File_Time, // Time of last modification
+	status_change: Unix_File_Time, // Time of last status change
+
+	_reserve1,
+	_reserve2,
+	_reserve3:     i64,
+}
+
+
+_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
+	return _fstat_internal(f.impl.fd, allocator)
+}
+
+_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) {
+	s: _Stat
+	result := unix.sys_fstat(fd, &s)
+	if result < 0 {
+		return {}, _get_platform_error(result)
+	}
+
+	// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
+	fi := File_Info {
+		fullpath = _get_full_path(fd, allocator),
+		name = "",
+		size = s.size,
+		mode = 0,
+		is_dir = S_ISDIR(s.mode),
+		modification_time = time.Time {s.modified.seconds},
+		access_time = time.Time {s.last_access.seconds},
+		creation_time = time.Time{0}, // regular stat does not provide this
+	}
+
+	fi.name = filepath.base(fi.fullpath)
+	return fi, nil
+}
+
+// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
+_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+
+	fd := unix.sys_open(name_cstr, _O_RDONLY)
+	if fd < 0 {
+		return {}, _get_platform_error(fd)
+	}
+	defer unix.sys_close(fd)
+	return _fstat_internal(fd, allocator)
+}
+
+_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW)
+	if fd < 0 {
+		return {}, _get_platform_error(fd)
+	}
+	defer unix.sys_close(fd)
+	return _fstat_internal(fd, allocator)
+}
+
+_same_file :: proc(fi1, fi2: File_Info) -> bool {
+	return fi1.fullpath == fi2.fullpath
+}
+
+_stat_internal :: proc(name: string) -> (s: _Stat, res: int) {
+	name_cstr, allocated := _name_to_cstring(name)
+	defer if allocated {
+		delete(name_cstr)
+	}
+	res = unix.sys_stat(name_cstr, &s)
+	return
+}

+ 20 - 0
core/os/os2/temp_file_linux.odin

@@ -0,0 +1,20 @@
+//+private
+package os2
+
+import "core:runtime"
+
+
+_create_temp :: proc(dir, pattern: string) -> (^File, Error) {
+	//TODO
+	return nil, nil
+}
+
+_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) {
+	//TODO
+	return "", nil
+}
+
+_temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) {
+	//TODO
+	return "", nil
+}

+ 329 - 2
core/sys/unix/syscalls_linux.odin

@@ -33,8 +33,8 @@ when ODIN_ARCH == .amd64 {
 	SYS_rt_sigprocmask : uintptr : 14
 	SYS_rt_sigreturn : uintptr : 15
 	SYS_ioctl : uintptr : 16
-	SYS_pread : uintptr : 17
-	SYS_pwrite : uintptr : 18
+	SYS_pread64 : uintptr : 17
+	SYS_pwrite64 : uintptr : 18
 	SYS_readv : uintptr : 19
 	SYS_writev : uintptr : 20
 	SYS_access : uintptr : 21
@@ -1518,6 +1518,51 @@ when ODIN_ARCH == .amd64 {
 	#panic("Unsupported architecture")
 }
 
+// syscall related constants
+AT_FDCWD            :: ~uintptr(99)
+AT_REMOVEDIR        :: uintptr(0x200)
+AT_SYMLINK_FOLLOW   :: uintptr(0x400)
+AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
+
+// mmap flags
+PROT_NONE      :: 0x0
+PROT_READ      :: 0x1
+PROT_WRITE     :: 0x2
+PROT_EXEC      :: 0x4
+PROT_GROWSDOWN :: 0x01000000
+PROT_GROWSUP   :: 0x02000000
+
+MAP_FIXED           :: 0x10
+MAP_SHARED          :: 0x1
+MAP_PRIVATE         :: 0x2
+MAP_SHARED_VALIDATE :: 0x3
+MAP_ANONYMOUS       :: 0x20
+
+// mremap flags
+MREMAP_MAYMOVE   :: 1
+MREMAP_FIXED     :: 2
+MREMAP_DONTUNMAP :: 4
+
+// madvise flags
+MADV_NORMAL      :: 0
+MADV_RANDOM      :: 1
+MADV_SEQUENTIAL  :: 2
+MADV_WILLNEED    :: 3
+MADV_DONTNEED    :: 4
+MADV_FREE        :: 8
+MADV_REMOVE      :: 9
+MADV_DONTFORK    :: 10
+MADV_DOFORK      :: 11
+MADV_MERGEABLE   :: 12
+MADV_UNMERGEABLE :: 13
+MADV_HUGEPAGE    :: 14
+MADV_NOHUGEPAGE  :: 15
+MADV_DONTDUMP    :: 16
+MADV_DODUMP      :: 17
+MADV_WIPEONFORK  :: 18
+MADV_KEEPONFORK  :: 19
+MADV_HWPOISON    :: 100
+
 sys_gettid :: proc "contextless" () -> int {
 	return cast(int)intrinsics.syscall(SYS_gettid)
 }
@@ -1525,3 +1570,285 @@ sys_gettid :: proc "contextless" () -> int {
 sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: int, flags: uint) -> int {
 	return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags))
 }
+
+sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
+	} else { // NOTE: arm64 does not have open
+		return int(intrinsics.syscall(SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
+	}
+}
+
+sys_openat :: proc "contextless" (dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int {
+	return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
+}
+
+sys_close :: proc "contextless" (fd: int) -> int {
+	return int(intrinsics.syscall(SYS_close, uintptr(fd)))
+}
+
+sys_read :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int {
+	return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size)))
+}
+
+sys_pread :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset)))
+	} else {
+		low := uintptr(offset & 0xFFFFFFFF)
+		high := uintptr(offset >> 32)
+		return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), high, low))
+	}
+}
+
+sys_write :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int {
+	return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size)))
+}
+
+sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset)))
+	} else {
+		low := uintptr(offset & 0xFFFFFFFF)
+		high := uintptr(offset >> 32)
+		return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), high, low))
+	}
+}
+
+sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence)))
+	} else {
+		low := uintptr(offset & 0xFFFFFFFF)
+		high := uintptr(offset >> 32)
+		result: i64
+		res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence)))
+		return res if res < 0 else result
+	}
+}
+
+sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int {
+	when ODIN_ARCH == .amd64 {
+		return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat)))
+	} else when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat)))
+	} else { // NOTE: arm64 does not have stat
+		return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0))
+	}
+}
+
+sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat)))
+	} else {
+		return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat)))
+	}
+}
+
+sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int {
+	when ODIN_ARCH == .amd64 {
+		return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat)))
+	} else when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat)))
+	} else { // NOTE: arm64 does not have any lstat
+		return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
+	}
+}
+
+sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
+	} else { // NOTE: arm64 does not have readlink
+		return int(intrinsics.syscall(SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
+	}
+}
+
+sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name))))
+	} else { // NOTE: arm64 does not have symlink
+		return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name))))
+	}
+}
+
+sys_access :: proc "contextless" (path: cstring, mask: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask)))
+	} else { // NOTE: arm64 does not have access
+		return int(intrinsics.syscall(SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask)))
+	}
+}
+
+sys_getcwd :: proc "contextless" (buf: rawptr, size: uint) -> int {
+	return int(intrinsics.syscall(SYS_getcwd, uintptr(buf), uintptr(size)))
+}
+
+sys_chdir :: proc "contextless" (path: cstring) -> int {
+	return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path))))
+}
+
+sys_fchdir :: proc "contextless" (fd: int) -> int {
+	return int(intrinsics.syscall(SYS_fchdir, uintptr(fd)))
+}
+
+sys_chmod :: proc "contextless" (path: cstring, mode: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode)))
+	} else { // NOTE: arm64 does not have chmod
+		return int(intrinsics.syscall(SYS_fchmodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
+	}
+}
+
+sys_fchmod :: proc "contextless" (fd: int, mode: int) -> int {
+	return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode)))
+}
+
+sys_chown :: proc "contextless" (path: cstring, user: int, group: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group)))
+	} else { // NOTE: arm64 does not have chown
+		return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), 0))
+	}
+}
+
+sys_fchown :: proc "contextless" (fd: int, user: int, group: int) -> int {
+	return int(intrinsics.syscall(SYS_fchown, uintptr(fd), uintptr(user), uintptr(group)))
+}
+
+sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group)))
+	} else { // NOTE: arm64 does not have lchown
+		return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW))
+	}
+}
+
+sys_rename :: proc "contextless" (old, new: cstring) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new))))
+	} else { // NOTE: arm64 does not have rename
+		return int(intrinsics.syscall(SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new))))
+	}
+}
+
+sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name))))
+	} else { // NOTE: arm64 does not have link
+		return int(intrinsics.syscall(SYS_linkat, AT_FDCWD, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW))
+	}
+}
+
+sys_unlink :: proc "contextless" (path: cstring) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path))))
+	} else { // NOTE: arm64 does not have unlink
+		return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0))
+	}
+}
+
+sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> int {
+	return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag))
+}
+
+sys_rmdir :: proc "contextless" (path: cstring) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path))))
+	} else { // NOTE: arm64 does not have rmdir
+		return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR))
+	}
+}
+
+sys_mkdir :: proc "contextless" (path: cstring, mode: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
+	} else { // NOTE: arm64 does not have mkdir
+		return int(intrinsics.syscall(SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
+	}
+}
+
+sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: int) -> int {
+	return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode)))
+}
+
+sys_mknod :: proc "contextless" (path: cstring, mode: int, dev: int) -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev)))
+	} else { // NOTE: arm64 does not have mknod
+		return int(intrinsics.syscall(SYS_mknodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode), uintptr(dev)))
+	}
+}
+
+sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: int, dev: int) -> int {
+	return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev)))
+}
+
+sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length)))
+	} else {
+		low := uintptr(length & 0xFFFFFFFF)
+		high := uintptr(length >> 32)
+		return int(intrinsics.syscall(SYS_truncate64, uintptr(rawptr(path)), high, low))
+	}
+}
+
+sys_ftruncate :: proc "contextless" (fd: int, length: i64) -> int {
+	when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
+		return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length)))
+	} else {
+		low := uintptr(length & 0xFFFFFFFF)
+		high := uintptr(length >> 32)
+		return int(intrinsics.syscall(SYS_ftruncate64, uintptr(fd), high, low))
+	}
+}
+
+sys_fsync :: proc "contextless" (fd: int) -> int {
+	return int(intrinsics.syscall(SYS_fsync, uintptr(fd)))
+}
+
+sys_getdents64 :: proc "contextless" (fd: int, dirent: rawptr, count: int) -> int {
+	return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count)))
+}
+
+sys_fork :: proc "contextless" () -> int {
+	when ODIN_ARCH != .arm64 {
+		return int(intrinsics.syscall(SYS_fork))
+	} else {
+		return int(intrinsics.syscall(SYS_clone, SIGCHLD))
+	}
+}
+
+sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int {
+	return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset))
+}
+
+sys_mremap :: proc "contextless" (addr: rawptr, old_length, new_length: uint, flags: int, new_addr: rawptr = nil) -> int {
+	return int(intrinsics.syscall(SYS_mremap, uintptr(addr), uintptr(old_length), uintptr(new_length), uintptr(flags), uintptr(new_addr)))
+}
+
+sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int {
+	return int(intrinsics.syscall(SYS_munmap, uintptr(addr), uintptr(length)))
+}
+
+sys_mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: int) -> int {
+	return int(intrinsics.syscall(SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot)))
+}
+
+sys_madvise :: proc "contextless" (addr: rawptr, length: uint, advice: int) -> int {
+	return int(intrinsics.syscall(SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice)))
+}
+
+
+// NOTE: Unsure about if this works directly on 32 bit archs. It may need 32 bit version of the time struct.
+//       As of Linux 5.1, there is a utimensat_time64 function.  Maybe use this in the future?
+sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, flags: int) -> int {
+	return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags)))
+}
+
+get_errno :: proc "contextless" (res: int) -> i32 {
+	if res < 0 && res > -4096 {
+		return i32(-res)
+	}
+	return 0
+}

+ 1 - 1
core/sys/windows/kernel32.odin

@@ -794,4 +794,4 @@ Control_Event :: enum DWORD {
 	close     = 2,
 	logoff    = 5,
 	shutdown  = 6,
-}
+}