Browse Source

Merge pull request #3230 from avanspector/haiku

Add Haiku OS support
gingerBill 1 year ago
parent
commit
53ce945034

+ 1 - 1
base/runtime/entry_unix.odin

@@ -1,5 +1,5 @@
 //+private
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 //+no-instrumentation
 package runtime
 

+ 2 - 2
base/runtime/heap_allocator_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 //+private
 package runtime
 
@@ -35,4 +35,4 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
 
 _heap_free :: proc(ptr: rawptr) {
 	_unix_free(ptr)
-}
+}

+ 21 - 0
base/runtime/os_specific_haiku.odin

@@ -0,0 +1,21 @@
+//+build haiku
+//+private
+package runtime
+
+foreign import libc "system:c"
+
+foreign libc {
+	@(link_name="write")
+	_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
+
+	_errnop :: proc() -> ^i32 ---
+}
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	ret := _unix_write(2, raw_data(data), len(data))
+	if ret < len(data) {
+		err := _errnop()
+		return int(ret), _OS_Errno(err^ if err != nil else 0)
+	}
+	return int(ret), 0
+}

+ 5 - 0
build_odin.sh

@@ -82,6 +82,11 @@ OpenBSD)
 	LDFLAGS="$LDFLAGS -liconv"
 	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
 	;;
+Haiku)
+	CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
+	LDFLAGS="$LDFLAGS -liconv"
+	LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
+	;;
 *)
 	error "Platform \"$OS_NAME\" unsupported"
 	;;

+ 18 - 0
core/c/libc/errno.odin

@@ -80,6 +80,24 @@ when ODIN_OS == .Darwin {
 	ERANGE :: 34
 }
 
+when ODIN_OS == .Haiku {
+	@(private="file")
+	@(default_calling_convention="c")
+	foreign libc {
+		@(link_name="_errnop")
+		_get_errno :: proc() -> ^int ---
+	}
+
+	@(private="file")
+	B_GENERAL_ERROR_BASE :: min(i32)
+	@(private="file")
+	B_POSIX_ERROR_BASE   :: B_GENERAL_ERROR_BASE + 0x7000
+
+	EDOM   :: B_POSIX_ERROR_BASE + 16
+	EILSEQ :: B_POSIX_ERROR_BASE + 38
+	ERANGE :: B_POSIX_ERROR_BASE + 17
+}
+
 // Odin has no way to make an identifier "errno" behave as a function call to
 // read the value, or to produce an lvalue such that you can assign a different
 // error value to errno. To work around this, just expose it as a function like

+ 30 - 0
core/c/libc/stdio.odin

@@ -163,6 +163,36 @@ when ODIN_OS == .Darwin {
 	}
 }
 
+when ODIN_OS == .Haiku {
+	fpos_t :: distinct i64
+	
+	_IOFBF        :: 0
+	_IOLBF        :: 1
+	_IONBF        :: 2
+
+	BUFSIZ        :: 8192
+
+	EOF           :: int(-1)
+
+	FOPEN_MAX     :: 128
+
+	FILENAME_MAX  :: 256
+
+	L_tmpnam      :: 512
+
+	SEEK_SET      :: 0
+	SEEK_CUR      :: 1
+	SEEK_END      :: 2
+
+	TMP_MAX       :: 32768
+
+	foreign libc {
+		stderr: ^FILE
+		stdin:  ^FILE
+		stdout: ^FILE
+	}
+}
+
 @(default_calling_convention="c")
 foreign libc {
 	// 7.21.4 Operations on files

+ 1 - 1
core/c/libc/time.odin

@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
 	}
 }
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku {
 	@(default_calling_convention="c")
 	foreign libc {
 		// 7.27.2 Time manipulation functions

+ 5 - 1
core/c/libc/wctype.odin

@@ -29,7 +29,11 @@ when ODIN_OS == .Windows {
 } else when ODIN_OS == .FreeBSD {
 	wctrans_t :: distinct int
 	wctype_t  :: distinct ulong
-	
+
+} else when ODIN_OS == .Haiku {
+	wctrans_t :: distinct i32
+	wctype_t  :: distinct i32
+
 }
 
 @(default_calling_convention="c")

+ 435 - 0
core/os/os_haiku.odin

@@ -0,0 +1,435 @@
+package os
+
+foreign import libc "system:c"
+
+import "base:runtime"
+import "core:c"
+import "core:strings"
+import "core:sys/haiku"
+
+Handle    :: i32
+Pid       :: i32
+File_Time :: i64
+Errno     :: i32
+
+MAX_PATH :: haiku.PATH_MAX
+
+ENOSYS :: int(haiku.Errno.POSIX_ERROR_BASE) + 9
+
+INVALID_HANDLE :: ~Handle(0)
+
+ERROR_NONE: Errno: 0
+
+stdin:  Handle = 0
+stdout: Handle = 1
+stderr: Handle = 2
+
+pid_t     :: haiku.pid_t
+off_t     :: haiku.off_t
+dev_t     :: haiku.dev_t
+ino_t     :: haiku.ino_t
+mode_t    :: haiku.mode_t
+nlink_t   :: haiku.nlink_t
+uid_t     :: haiku.uid_t
+gid_t     :: haiku.gid_t
+blksize_t :: haiku.blksize_t
+blkcnt_t  :: haiku.blkcnt_t
+time_t    :: haiku.time_t
+
+
+Unix_File_Time :: struct {
+	seconds:     time_t,
+	nanoseconds: c.long,
+}
+
+OS_Stat :: struct {
+	device_id: dev_t,		// device ID that this file resides on
+	serial: ino_t,			// this file's serial inode ID
+	mode: mode_t,			// file mode (rwx for user, group, etc)
+	nlink: nlink_t,			// number of hard links to this file
+	uid: uid_t,			// user ID of the file's owner
+	gid: gid_t,			// group ID of the file's group
+	size: off_t,			// file size, in bytes
+	rdev: dev_t,			// device type (not used)
+	block_size:	blksize_t,	// optimal blocksize for I/O
+	
+	last_access: Unix_File_Time,	// time of last access
+	modified: Unix_File_Time,	// time of last data modification
+	status_change: Unix_File_Time,	// time of last file status change
+	birthtime:	Unix_File_Time,	// time of file creation
+
+	type: u32,                      // attribute/index type
+
+	blocks: blkcnt_t,		// blocks allocated for file
+}
+
+/* file access modes for open() */
+O_RDONLY         :: 0x0000		/* read only */
+O_WRONLY         :: 0x0001		/* write only */
+O_RDWR           :: 0x0002		/* read and write */
+O_ACCMODE        :: 0x0003		/* mask to get the access modes above */
+O_RWMASK         :: O_ACCMODE
+
+/* flags for open() */
+O_EXCL           :: 0x0100		/* exclusive creat */
+O_CREATE         :: 0x0200		/* create and open file */
+O_TRUNC          :: 0x0400		/* open with truncation */
+O_NOCTTY         :: 0x1000		/* don't make tty the controlling tty */
+O_NOTRAVERSE     :: 0x2000		/* do not traverse leaf link */
+
+// 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
+S_ISVTX  :: 0o001000 // Save swapped text even after use
+
+// 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_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 }
+
+
+foreign libc {
+	@(link_name="_errnop")	__error		:: proc() -> ^c.int ---
+
+	@(link_name="fork")	_unix_fork	:: proc() -> pid_t ---
+	@(link_name="getthrid")	_unix_getthrid	:: proc() -> 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 ---
+	@(link_name="read")	_unix_read	:: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="write")	_unix_write	:: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
+	@(link_name="lseek")	_unix_seek	:: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t ---
+	@(link_name="stat")	_unix_stat	:: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
+	@(link_name="fstat")	_unix_fstat	:: proc(fd: Handle, sb: ^OS_Stat) -> c.int ---
+	@(link_name="lstat")	_unix_lstat	:: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
+	@(link_name="readlink")	_unix_readlink	:: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t ---
+	@(link_name="access")	_unix_access	:: proc(path: cstring, mask: c.int) -> c.int ---
+	@(link_name="getcwd")	_unix_getcwd	:: proc(buf: cstring, len: c.size_t) -> cstring ---
+	@(link_name="chdir")	_unix_chdir	:: proc(path: cstring) -> c.int ---
+	@(link_name="rename")	_unix_rename	:: proc(old, new: cstring) -> c.int ---
+	@(link_name="unlink")	_unix_unlink	:: proc(path: cstring) -> c.int ---
+	@(link_name="rmdir")	_unix_rmdir	:: proc(path: cstring) -> c.int ---
+	@(link_name="mkdir")	_unix_mkdir	:: proc(path: cstring, mode: mode_t) -> c.int ---
+
+	@(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int ---
+	@(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long ---
+	@(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir ---
+	@(link_name="closedir")	_unix_closedir	:: proc(dirp: Dir) -> c.int ---
+	@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---
+	@(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
+
+	@(link_name="malloc")	_unix_malloc	:: proc(size: c.size_t) -> rawptr ---
+	@(link_name="calloc")	_unix_calloc	:: proc(num, size: c.size_t) -> rawptr ---
+	@(link_name="free")	_unix_free	:: proc(ptr: rawptr) ---
+	@(link_name="realloc")	_unix_realloc	:: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
+
+	@(link_name="getenv")	_unix_getenv	:: proc(cstring) -> cstring ---
+	@(link_name="realpath")	_unix_realpath	:: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
+
+	@(link_name="exit")	_unix_exit	:: proc(status: c.int) -> ! ---
+
+	@(link_name="dlopen")	_unix_dlopen	:: proc(filename: cstring, flags: c.int) -> rawptr ---
+	@(link_name="dlsym")	_unix_dlsym	:: proc(handle: rawptr, symbol: cstring) -> rawptr ---
+	@(link_name="dlclose")	_unix_dlclose	:: proc(handle: rawptr) -> c.int ---
+	@(link_name="dlerror")	_unix_dlerror	:: proc() -> cstring ---
+}
+
+MAXNAMLEN :: haiku.NAME_MAX
+
+Dirent :: struct {
+	dev:      dev_t,
+	pdef:     dev_t,
+	ino:      ino_t,
+	pino:     ino_t,
+	reclen:   u16,
+	name:     [MAXNAMLEN + 1]byte, // name
+}
+
+Dir :: distinct rawptr // DIR*
+
+is_path_separator :: proc(r: rune) -> bool {
+	return r == '/'
+}
+
+get_last_error :: proc "contextless" () -> int {
+	return int(__error()^)
+}
+
+fork :: proc() -> (Pid, Errno) {
+	pid := _unix_fork()
+	if pid == -1 {
+		return Pid(-1), Errno(get_last_error())
+	}
+	return Pid(pid), ERROR_NONE
+}
+
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
+	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 handle, ERROR_NONE
+}
+
+close :: proc(fd: Handle) -> Errno {
+	result := _unix_close(fd)
+	if result == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+// In practice a read/write call would probably never read/write these big buffers all at once,
+// which is why the number of bytes is returned and why there are procs that will call this in a
+// loop for you.
+// 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) {
+	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 int(bytes_read), ERROR_NONE
+}
+
+write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	if len(data) == 0 {
+		return 0, ERROR_NONE
+	}
+
+	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 int(bytes_written), ERROR_NONE
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+	res := _unix_seek(fd, offset, c.int(whence))
+	if res == -1 {
+		return -1, Errno(get_last_error())
+	}
+	return res, ERROR_NONE
+}
+
+file_size :: proc(fd: Handle) -> (i64, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return -1, err
+	}
+	return s.size, ERROR_NONE
+}
+
+// "Argv" arguments converted to Odin strings
+args := _alloc_command_line_arguments()
+
+_alloc_command_line_arguments :: proc() -> []string {
+	res := make([]string, len(runtime.args__))
+	for arg, i in runtime.args__ {
+		res[i] = string(arg)
+	}
+	return res
+}
+
+@private
+_stat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_stat(cstr, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_lstat :: proc(path: string) -> (OS_Stat, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_lstat(cstr, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
+	// deliberately uninitialized
+	s: OS_Stat = ---
+	res := _unix_fstat(fd, &s)
+	if res == -1 {
+		return s, Errno(get_last_error())
+	}
+	return s, ERROR_NONE
+}
+
+@private
+_fdopendir :: proc(fd: Handle) -> (Dir, Errno) {
+	dirp := _unix_fdopendir(fd)
+	if dirp == cast(Dir)nil {
+		return nil, Errno(get_last_error())
+	}
+	return dirp, ERROR_NONE
+}
+
+@private
+_closedir :: proc(dirp: Dir) -> Errno {
+	rc := _unix_closedir(dirp)
+	if rc != 0 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+@private
+_rewinddir :: proc(dirp: Dir) {
+	_unix_rewinddir(dirp)
+}
+
+@private
+_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
+	result: ^Dirent
+	rc := _unix_readdir_r(dirp, &entry, &result)
+
+	if rc != 0 {
+		err = Errno(get_last_error())
+		return
+	}
+	err = ERROR_NONE
+
+	if result == nil {
+		end_of_stream = true
+		return
+	}
+
+	return
+}
+
+@private
+_readlink :: proc(path: string) -> (string, Errno) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+
+	bufsz : uint = MAX_PATH
+	buf := make([]byte, MAX_PATH)
+	for {
+		rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
+		if rc == -1 {
+			delete(buf)
+			return "", Errno(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
+		}	
+	}
+}
+
+absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
+	return "", Errno(ENOSYS)
+}
+
+absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
+	rel := rel
+	if rel == "" {
+		rel = "."
+	}
+
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
+	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
+
+	path_ptr := _unix_realpath(rel_cstr, nil)
+	if path_ptr == nil {
+		return "", Errno(get_last_error())
+	}
+	defer _unix_free(path_ptr)
+
+	path_cstr := transmute(cstring)path_ptr
+	path = strings.clone( string(path_cstr) )
+
+	return path, ERROR_NONE
+}
+
+access :: proc(path: string, mask: int) -> (bool, Errno) {
+	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 true, ERROR_NONE
+}
+
+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)
+	cstr := _unix_getenv(path_str)
+	if cstr == nil {
+		return "", false
+	}
+	return strings.clone(string(cstr), allocator), true
+}
+
+get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
+	value, _ = lookup_env(key, allocator)
+	return
+}
+
+@(private)
+_processor_core_count :: proc() -> int {
+	info: haiku.system_info
+	haiku.get_system_info(&info)
+	return int(info.cpu_count)
+}
+
+exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
+	_unix_exit(i32(code))
+}

+ 1 - 1
core/os/stat_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 package os
 
 import "core:time"

+ 3 - 3
core/os/stream.odin

@@ -32,7 +32,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 		}
 
 	case .Read_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) {
+		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) {
 			n_int, os_err = read_at(fd, p, offset)
 			n = i64(n_int)
 			if n == 0 && os_err == 0 {
@@ -46,7 +46,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
 			err = .EOF
 		}
 	case .Write_At:
-		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) {
+		when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) {
 			n_int, os_err = write_at(fd, p, offset)
 			n = i64(n_int)
 			if n == 0 && os_err == 0 {
@@ -60,7 +60,7 @@ _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 {
+		when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || 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})

+ 167 - 0
core/sync/futex_haiku.odin

@@ -0,0 +1,167 @@
+//+private
+package sync
+
+import "core:c"
+import "core:runtime"
+import "core:sys/haiku"
+import "core:sys/unix"
+import "core:time"
+
+@(private="file")
+Wait_Node :: struct {
+	thread:     unix.pthread_t,
+	futex:      ^Futex,
+	prev, next: ^Wait_Node,
+}
+@(private="file")
+atomic_flag :: distinct bool
+@(private="file")
+Wait_Queue :: struct {
+	lock: atomic_flag,
+	list: Wait_Node,
+}
+@(private="file")
+waitq_lock :: proc "contextless" (waitq: ^Wait_Queue) {
+	for cast(bool)atomic_exchange_explicit(&waitq.lock, atomic_flag(true), .Acquire) {
+		cpu_relax() // spin...
+	}
+}
+@(private="file")
+waitq_unlock :: proc "contextless" (waitq: ^Wait_Queue) {
+	atomic_store_explicit(&waitq.lock, atomic_flag(false), .Release)
+}
+
+// FIXME: This approach may scale badly in the future,
+// possible solution - hash map (leads to deadlocks now).
+@(private="file")
+g_waitq: Wait_Queue
+
+@(init, private="file")
+g_waitq_init :: proc() {
+	g_waitq = {
+		list = {
+			prev = &g_waitq.list,
+			next = &g_waitq.list,
+		},
+	}
+}
+
+@(private="file")
+get_waitq :: #force_inline proc "contextless" (f: ^Futex) -> ^Wait_Queue {
+	_ = f
+	return &g_waitq
+}
+
+_futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) {
+	waitq := get_waitq(f)
+	waitq_lock(waitq)
+	defer waitq_unlock(waitq)
+
+	head   := &waitq.list
+	waiter := Wait_Node{
+		thread = unix.pthread_self(),
+		futex  = f,
+		prev   = head,
+		next   = head.next,
+	}
+
+	waiter.prev.next = &waiter
+	waiter.next.prev = &waiter
+
+	old_mask, mask: haiku.sigset_t
+	haiku.sigemptyset(&mask)
+	haiku.sigaddset(&mask, haiku.SIGCONT)
+	unix.pthread_sigmask(haiku.SIG_BLOCK, &mask, &old_mask)
+
+	if u32(atomic_load_explicit(f, .Acquire)) == expect {
+		waitq_unlock(waitq)
+		defer waitq_lock(waitq)
+		
+		sig: c.int
+		haiku.sigwait(&mask, &sig)
+		errno := haiku.errno() 
+		ok = errno == .OK
+	}
+
+	waiter.prev.next = waiter.next
+	waiter.next.prev = waiter.prev
+
+ 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+
+ 	// FIXME: Add error handling!
+ 	return
+}
+
+_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) {
+	if duration <= 0 {
+		return false
+	}
+	waitq := get_waitq(f)
+	waitq_lock(waitq)
+	defer waitq_unlock(waitq)
+
+	head   := &waitq.list
+	waiter := Wait_Node{
+		thread = unix.pthread_self(),
+		futex  = f,
+		prev   = head,
+		next   = head.next,
+	}
+
+	waiter.prev.next = &waiter
+	waiter.next.prev = &waiter
+
+	old_mask, mask: haiku.sigset_t
+	haiku.sigemptyset(&mask)
+	haiku.sigaddset(&mask, haiku.SIGCONT)
+	unix.pthread_sigmask(haiku.SIG_BLOCK, &mask, &old_mask)
+
+	if u32(atomic_load_explicit(f, .Acquire)) == expect {
+		waitq_unlock(waitq)
+		defer waitq_lock(waitq)
+		
+		info: haiku.siginfo_t
+		ts := unix.timespec{
+			tv_sec  = i64(duration / 1e9),
+			tv_nsec = i64(duration % 1e9),
+		}
+		haiku.sigtimedwait(&mask, &info, &ts)
+		errno := haiku.errno() 
+		ok = errno == .EAGAIN || errno == .OK
+	}
+
+	waiter.prev.next = waiter.next
+	waiter.next.prev = waiter.prev
+
+ 	unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil)
+
+ 	// FIXME: Add error handling!
+ 	return 
+}
+
+_futex_signal :: proc "contextless" (f: ^Futex) {
+	waitq := get_waitq(f)
+	waitq_lock(waitq)
+	defer waitq_unlock(waitq)
+
+	head := &waitq.list
+	for waiter := head.next; waiter != head; waiter = waiter.next {
+		if waiter.futex == f {
+			unix.pthread_kill(waiter.thread, haiku.SIGCONT)
+			break
+		}
+	}
+}
+
+_futex_broadcast :: proc "contextless" (f: ^Futex) {
+	waitq := get_waitq(f)
+	waitq_lock(waitq)
+	defer waitq_unlock(waitq)
+
+	head := &waitq.list
+	for waiter := head.next; waiter != head; waiter = waiter.next {
+		if waiter.futex == f {
+			unix.pthread_kill(waiter.thread, haiku.SIGCONT)
+		}
+	}
+}

+ 8 - 0
core/sync/primitives_haiku.odin

@@ -0,0 +1,8 @@
+//+private
+package sync
+
+import "core:sys/haiku"
+
+_current_thread_id :: proc "contextless" () -> int {
+	return int(haiku.find_thread(nil))
+}

+ 239 - 0
core/sys/haiku/errors.odin

@@ -0,0 +1,239 @@
+//+build haiku
+package sys_haiku
+
+import "core:c"
+
+Errno :: enum c.int {
+	// Error baselines
+	GENERAL_ERROR_BASE     = min(c.int),
+	OS_ERROR_BASE          = GENERAL_ERROR_BASE + 0x1000,
+	APP_ERROR_BASE         = GENERAL_ERROR_BASE + 0x2000,
+	INTERFACE_ERROR_BASE   = GENERAL_ERROR_BASE + 0x3000,
+	MEDIA_ERROR_BASE       = GENERAL_ERROR_BASE + 0x4000,
+	TRANSLATION_ERROR_BASE = GENERAL_ERROR_BASE + 0x4800,
+	MIDI_ERROR_BASE        = GENERAL_ERROR_BASE + 0x5000,
+	STORAGE_ERROR_BASE     = GENERAL_ERROR_BASE + 0x6000,
+	POSIX_ERROR_BASE       = GENERAL_ERROR_BASE + 0x7000,
+	MAIL_ERROR_BASE        = GENERAL_ERROR_BASE + 0x8000,
+	PRINT_ERROR_BASE       = GENERAL_ERROR_BASE + 0x9000,
+	DEVICE_ERROR_BASE      = GENERAL_ERROR_BASE + 0xa000,
+
+	// Developer-defined errors start at (ERRORS_END+1)
+	ERRORS_END             = GENERAL_ERROR_BASE + 0xffff,
+
+	// General Errors
+	NO_MEMORY              = GENERAL_ERROR_BASE + 0,
+	IO_ERROR               = GENERAL_ERROR_BASE + 1,
+	PERMISSION_DENIED      = GENERAL_ERROR_BASE + 2,
+	BAD_INDEX              = GENERAL_ERROR_BASE + 3,
+	BAD_TYPE               = GENERAL_ERROR_BASE + 4,
+	BAD_VALUE              = GENERAL_ERROR_BASE + 5,
+	MISMATCHED_VALUES      = GENERAL_ERROR_BASE + 6,
+	NAME_NOT_FOUND         = GENERAL_ERROR_BASE + 7,
+	NAME_IN_USE            = GENERAL_ERROR_BASE + 8,
+	TIMED_OUT              = GENERAL_ERROR_BASE + 9,
+	INTERRUPTED            = GENERAL_ERROR_BASE + 10,
+	WOULD_BLOCK            = GENERAL_ERROR_BASE + 11,
+	CANCELED               = GENERAL_ERROR_BASE + 12,
+	NO_INIT                = GENERAL_ERROR_BASE + 13,
+	NOT_INITIALIZED        = GENERAL_ERROR_BASE + 13,
+	BUSY                   = GENERAL_ERROR_BASE + 14,
+	NOT_ALLOWED            = GENERAL_ERROR_BASE + 15,
+	BAD_DATA               = GENERAL_ERROR_BASE + 16,
+	DONT_DO_THAT           = GENERAL_ERROR_BASE + 17,
+
+	ERROR                  = -1,
+	OK                     = 0,
+	NO_ERROR               = 0,
+
+	// Kernel Kit Errors
+	BAD_SEM_ID                        = OS_ERROR_BASE + 0,
+	NO_MORE_SEMS                      = OS_ERROR_BASE + 1,
+	BAD_THREAD_ID                     = OS_ERROR_BASE + 0x100,
+	NO_MORE_THREADS                   = OS_ERROR_BASE + 0x101,
+	BAD_THREAD_STATE                  = OS_ERROR_BASE + 0x102,
+	BAD_TEAM_ID                       = OS_ERROR_BASE + 0x103,
+	NO_MORE_TEAMS                     = OS_ERROR_BASE + 0x104,
+	BAD_PORT_ID                       = OS_ERROR_BASE + 0x200,
+	NO_MORE_PORTS                     = OS_ERROR_BASE + 0x201,
+	BAD_IMAGE_ID                      = OS_ERROR_BASE + 0x300,
+	BAD_ADDRESS                       = OS_ERROR_BASE + 0x301,
+	NOT_AN_EXECUTABLE                 = OS_ERROR_BASE + 0x302,
+	MISSING_LIBRARY                   = OS_ERROR_BASE + 0x303,
+	MISSING_SYMBOL                    = OS_ERROR_BASE + 0x304,
+	UNKNOWN_EXECUTABLE                = OS_ERROR_BASE + 0x305,
+	LEGACY_EXECUTABLE                 = OS_ERROR_BASE + 0x306,
+
+	DEBUGGER_ALREADY_INSTALLED        = OS_ERROR_BASE + 0x400,
+
+	// Application Kit Errors
+	BAD_REPLY                         = APP_ERROR_BASE + 0,
+	DUPLICATE_REPLY                   = APP_ERROR_BASE + 1,
+	MESSAGE_TO_SELF                   = APP_ERROR_BASE + 2,
+	BAD_HANDLER                       = APP_ERROR_BASE + 3,
+	ALREADY_RUNNING                   = APP_ERROR_BASE + 4,
+	LAUNCH_FAILED                     = APP_ERROR_BASE + 5,
+	AMBIGUOUS_APP_LAUNCH              = APP_ERROR_BASE + 6,
+	UNKNOWN_MIME_TYPE                 = APP_ERROR_BASE + 7,
+	BAD_SCRIPT_SYNTAX                 = APP_ERROR_BASE + 8,
+	LAUNCH_FAILED_NO_RESOLVE_LINK     = APP_ERROR_BASE + 9,
+	LAUNCH_FAILED_EXECUTABLE          = APP_ERROR_BASE + 10,
+	LAUNCH_FAILED_APP_NOT_FOUND       = APP_ERROR_BASE + 11,
+	LAUNCH_FAILED_APP_IN_TRASH        = APP_ERROR_BASE + 12,
+	LAUNCH_FAILED_NO_PREFERRED_APP    = APP_ERROR_BASE + 13,
+	LAUNCH_FAILED_FILES_APP_NOT_FOUND = APP_ERROR_BASE + 14,
+	BAD_MIME_SNIFFER_RULE             = APP_ERROR_BASE + 15,
+	NOT_A_MESSAGE                     = APP_ERROR_BASE + 16,
+	SHUTDOWN_CANCELLED                = APP_ERROR_BASE + 17,
+	SHUTTING_DOWN                     = APP_ERROR_BASE + 18,
+
+	// Storage Kit/File System Errors
+	FILE_ERROR                        = STORAGE_ERROR_BASE + 0,
+	// 1 was B_FILE_NOT_FOUND (deprecated)
+	FILE_EXISTS                       = STORAGE_ERROR_BASE + 2,
+	ENTRY_NOT_FOUND                   = STORAGE_ERROR_BASE + 3,
+	NAME_TOO_LONG                     = STORAGE_ERROR_BASE + 4,
+	NOT_A_DIRECTORY                   = STORAGE_ERROR_BASE + 5,
+	DIRECTORY_NOT_EMPTY               = STORAGE_ERROR_BASE + 6,
+	DEVICE_FULL                       = STORAGE_ERROR_BASE + 7,
+	READ_ONLY_DEVICE                  = STORAGE_ERROR_BASE + 8,
+	IS_A_DIRECTORY                    = STORAGE_ERROR_BASE + 9,
+	NO_MORE_FDS                       = STORAGE_ERROR_BASE + 10,
+	CROSS_DEVICE_LINK                 = STORAGE_ERROR_BASE + 11,
+	LINK_LIMIT                        = STORAGE_ERROR_BASE + 12,
+	BUSTED_PIPE                       = STORAGE_ERROR_BASE + 13,
+	UNSUPPORTED                       = STORAGE_ERROR_BASE + 14,
+	PARTITION_TOO_SMALL               = STORAGE_ERROR_BASE + 15,
+	PARTIAL_READ                      = STORAGE_ERROR_BASE + 16,
+	PARTIAL_WRITE                     = STORAGE_ERROR_BASE + 17,
+
+	// Some POSIX errors
+	E2BIG                             = POSIX_ERROR_BASE + 1,
+	EFBIG                             = POSIX_ERROR_BASE + 4,
+	ENODEV                            = POSIX_ERROR_BASE + 7,
+	ERANGE                            = POSIX_ERROR_BASE + 17,
+	EOVERFLOW                         = POSIX_ERROR_BASE + 41,
+	EOPNOTSUPP                        = POSIX_ERROR_BASE + 43,
+
+	ENOSYS                            = POSIX_ERROR_BASE + 9,
+	EAGAIN                            = WOULD_BLOCK,
+
+	// New error codes that can be mapped to POSIX errors
+	TOO_MANY_ARGS_NEG                 = E2BIG,
+	FILE_TOO_LARGE_NEG                = EFBIG,
+	DEVICE_NOT_FOUND_NEG              = ENODEV,
+	RESULT_NOT_REPRESENTABLE_NEG      = ERANGE,
+	BUFFER_OVERFLOW_NEG               = EOVERFLOW,
+	NOT_SUPPORTED_NEG                 = EOPNOTSUPP,
+
+	TOO_MANY_ARGS_POS                 = -E2BIG,
+	FILE_TOO_LARGE_POS                = -EFBIG,
+	DEVICE_NOT_FOUND_POS              = -ENODEV,
+	RESULT_NOT_REPRESENTABLE_POS      = -ERANGE,
+	BUFFER_OVERFLOW_POS               = -EOVERFLOW,
+	NOT_SUPPORTED_POS                 = -EOPNOTSUPP,
+
+	// Media Kit Errors
+	STREAM_NOT_FOUND             = MEDIA_ERROR_BASE + 0,
+	SERVER_NOT_FOUND             = MEDIA_ERROR_BASE + 1,
+	RESOURCE_NOT_FOUND           = MEDIA_ERROR_BASE + 2,
+	RESOURCE_UNAVAILABLE         = MEDIA_ERROR_BASE + 3,
+	BAD_SUBSCRIBER               = MEDIA_ERROR_BASE + 4,
+	SUBSCRIBER_NOT_ENTERED       = MEDIA_ERROR_BASE + 5,
+	BUFFER_NOT_AVAILABLE         = MEDIA_ERROR_BASE + 6,
+	LAST_BUFFER_ERROR            = MEDIA_ERROR_BASE + 7,
+	MEDIA_SYSTEM_FAILURE         = MEDIA_ERROR_BASE + 100,
+	MEDIA_BAD_NODE               = MEDIA_ERROR_BASE + 101,
+	MEDIA_NODE_BUSY              = MEDIA_ERROR_BASE + 102,
+	MEDIA_BAD_FORMAT             = MEDIA_ERROR_BASE + 103,
+	MEDIA_BAD_BUFFER             = MEDIA_ERROR_BASE + 104,
+	MEDIA_TOO_MANY_NODES         = MEDIA_ERROR_BASE + 105,
+	MEDIA_TOO_MANY_BUFFERS       = MEDIA_ERROR_BASE + 106,
+	MEDIA_NODE_ALREADY_EXISTS    = MEDIA_ERROR_BASE + 107,
+	MEDIA_BUFFER_ALREADY_EXISTS  = MEDIA_ERROR_BASE + 108,
+	MEDIA_CANNOT_SEEK            = MEDIA_ERROR_BASE + 109,
+	MEDIA_CANNOT_CHANGE_RUN_MODE = MEDIA_ERROR_BASE + 110,
+	MEDIA_APP_ALREADY_REGISTERED = MEDIA_ERROR_BASE + 111,
+	MEDIA_APP_NOT_REGISTERED     = MEDIA_ERROR_BASE + 112,
+	MEDIA_CANNOT_RECLAIM_BUFFERS = MEDIA_ERROR_BASE + 113,
+	MEDIA_BUFFERS_NOT_RECLAIMED  = MEDIA_ERROR_BASE + 114,
+	MEDIA_TIME_SOURCE_STOPPED    = MEDIA_ERROR_BASE + 115,
+	MEDIA_TIME_SOURCE_BUSY       = MEDIA_ERROR_BASE + 116,
+	MEDIA_BAD_SOURCE             = MEDIA_ERROR_BASE + 117,
+	MEDIA_BAD_DESTINATION        = MEDIA_ERROR_BASE + 118,
+	MEDIA_ALREADY_CONNECTED      = MEDIA_ERROR_BASE + 119,
+	MEDIA_NOT_CONNECTED          = MEDIA_ERROR_BASE + 120,
+	MEDIA_BAD_CLIP_FORMAT        = MEDIA_ERROR_BASE + 121,
+	MEDIA_ADDON_FAILED           = MEDIA_ERROR_BASE + 122,
+	MEDIA_ADDON_DISABLED         = MEDIA_ERROR_BASE + 123,
+	MEDIA_CHANGE_IN_PROGRESS     = MEDIA_ERROR_BASE + 124,
+	MEDIA_STALE_CHANGE_COUNT     = MEDIA_ERROR_BASE + 125,
+	MEDIA_ADDON_RESTRICTED       = MEDIA_ERROR_BASE + 126,
+	MEDIA_NO_HANDLER             = MEDIA_ERROR_BASE + 127,
+	MEDIA_DUPLICATE_FORMAT       = MEDIA_ERROR_BASE + 128,
+	MEDIA_REALTIME_DISABLED      = MEDIA_ERROR_BASE + 129,
+	MEDIA_REALTIME_UNAVAILABLE   = MEDIA_ERROR_BASE + 130,
+
+	// Mail Kit Errors
+	MAIL_NO_DAEMON               = MAIL_ERROR_BASE + 0,
+	MAIL_UNKNOWN_USER            = MAIL_ERROR_BASE + 1,
+	MAIL_WRONG_PASSWORD          = MAIL_ERROR_BASE + 2,
+	MAIL_UNKNOWN_HOST            = MAIL_ERROR_BASE + 3,
+	MAIL_ACCESS_ERROR            = MAIL_ERROR_BASE + 4,
+	MAIL_UNKNOWN_FIELD           = MAIL_ERROR_BASE + 5,
+	MAIL_NO_RECIPIENT            = MAIL_ERROR_BASE + 6,
+	MAIL_INVALID_MAIL            = MAIL_ERROR_BASE + 7,
+
+	// Printing Errors
+	NO_PRINT_SERVER              = PRINT_ERROR_BASE + 0,
+
+	// Device Kit Errors
+	DEV_INVALID_IOCTL            = DEVICE_ERROR_BASE + 0,
+	DEV_NO_MEMORY                = DEVICE_ERROR_BASE + 1,
+	DEV_BAD_DRIVE_NUM            = DEVICE_ERROR_BASE + 2,
+	DEV_NO_MEDIA                 = DEVICE_ERROR_BASE + 3,
+	DEV_UNREADABLE               = DEVICE_ERROR_BASE + 4,
+	DEV_FORMAT_ERROR             = DEVICE_ERROR_BASE + 5,
+	DEV_TIMEOUT                  = DEVICE_ERROR_BASE + 6,
+	DEV_RECALIBRATE_ERROR        = DEVICE_ERROR_BASE + 7,
+	DEV_SEEK_ERROR               = DEVICE_ERROR_BASE + 8,
+	DEV_ID_ERROR                 = DEVICE_ERROR_BASE + 9,
+	DEV_READ_ERROR               = DEVICE_ERROR_BASE + 10,
+	DEV_WRITE_ERROR              = DEVICE_ERROR_BASE + 11,
+	DEV_NOT_READY                = DEVICE_ERROR_BASE + 12,
+	DEV_MEDIA_CHANGED            = DEVICE_ERROR_BASE + 13,
+	DEV_MEDIA_CHANGE_REQUESTED   = DEVICE_ERROR_BASE + 14,
+	DEV_RESOURCE_CONFLICT        = DEVICE_ERROR_BASE + 15,
+	DEV_CONFIGURATION_ERROR      = DEVICE_ERROR_BASE + 16,
+	DEV_DISABLED_BY_USER         = DEVICE_ERROR_BASE + 17,
+	DEV_DOOR_OPEN                = DEVICE_ERROR_BASE + 18,
+	DEV_INVALID_PIPE             = DEVICE_ERROR_BASE + 19,
+	DEV_CRC_ERROR                = DEVICE_ERROR_BASE + 20,
+	DEV_STALLED                  = DEVICE_ERROR_BASE + 21,
+	DEV_BAD_PID                  = DEVICE_ERROR_BASE + 22,
+	DEV_UNEXPECTED_PID           = DEVICE_ERROR_BASE + 23,
+	DEV_DATA_OVERRUN             = DEVICE_ERROR_BASE + 24,
+	DEV_DATA_UNDERRUN            = DEVICE_ERROR_BASE + 25,
+	DEV_FIFO_OVERRUN             = DEVICE_ERROR_BASE + 26,
+	DEV_FIFO_UNDERRUN            = DEVICE_ERROR_BASE + 27,
+	DEV_PENDING                  = DEVICE_ERROR_BASE + 28,
+	DEV_MULTIPLE_ERRORS          = DEVICE_ERROR_BASE + 29,
+	DEV_TOO_LATE                 = DEVICE_ERROR_BASE + 30,
+
+	// Translation Kit Errors
+	TRANSLATION_BASE_ERROR       = TRANSLATION_ERROR_BASE + 0,
+	NO_TRANSLATOR                = TRANSLATION_ERROR_BASE + 1,
+	ILLEGAL_DATA                 = TRANSLATION_ERROR_BASE + 2,
+}
+
+errno :: #force_inline proc "contextless" () -> Errno {
+	return Errno(_errnop()^)
+}
+
+foreign import libroot "system:c"
+foreign libroot {
+	_to_positive_error :: proc(error: c.int) -> c.int ---
+	_to_negative_error :: proc(error: c.int) -> c.int ---
+
+	_errnop :: proc() -> ^c.int ---
+}

+ 168 - 0
core/sys/haiku/find_directory.odin

@@ -0,0 +1,168 @@
+//+build haiku
+package sys_haiku
+
+import "core:c"
+
+directory_which :: enum c.int {
+	// Per volume directories
+	DESKTOP_DIRECTORY = 0,
+	TRASH_DIRECTORY,
+
+	// System directories
+	SYSTEM_DIRECTORY        = 1000,
+	SYSTEM_ADDONS_DIRECTORY = 1002,
+	SYSTEM_BOOT_DIRECTORY,
+	SYSTEM_FONTS_DIRECTORY,
+	SYSTEM_LIB_DIRECTORY,
+	SYSTEM_SERVERS_DIRECTORY,
+	SYSTEM_APPS_DIRECTORY,
+	SYSTEM_BIN_DIRECTORY,
+	SYSTEM_DOCUMENTATION_DIRECTORY = 1010,
+	SYSTEM_PREFERENCES_DIRECTORY,
+	SYSTEM_TRANSLATORS_DIRECTORY,
+	SYSTEM_MEDIA_NODES_DIRECTORY,
+	SYSTEM_SOUNDS_DIRECTORY,
+	SYSTEM_DATA_DIRECTORY,
+	SYSTEM_DEVELOP_DIRECTORY,
+	SYSTEM_PACKAGES_DIRECTORY,
+	SYSTEM_HEADERS_DIRECTORY,
+	SYSTEM_ETC_DIRECTORY      = 2008,
+	SYSTEM_SETTINGS_DIRECTORY = 2010,
+	SYSTEM_LOG_DIRECTORY      = 2012,
+	SYSTEM_SPOOL_DIRECTORY,
+	SYSTEM_TEMP_DIRECTORY,
+	SYSTEM_VAR_DIRECTORY,
+	SYSTEM_CACHE_DIRECTORY       = 2020,
+	SYSTEM_NONPACKAGED_DIRECTORY = 2023,
+	SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
+	SYSTEM_NONPACKAGED_TRANSLATORS_DIRECTORY,
+	SYSTEM_NONPACKAGED_MEDIA_NODES_DIRECTORY,
+	SYSTEM_NONPACKAGED_BIN_DIRECTORY,
+	SYSTEM_NONPACKAGED_DATA_DIRECTORY,
+	SYSTEM_NONPACKAGED_FONTS_DIRECTORY,
+	SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY,
+	SYSTEM_NONPACKAGED_DOCUMENTATION_DIRECTORY,
+	SYSTEM_NONPACKAGED_LIB_DIRECTORY,
+	SYSTEM_NONPACKAGED_HEADERS_DIRECTORY,
+	SYSTEM_NONPACKAGED_DEVELOP_DIRECTORY,
+
+	// User directories. These are interpreted in the context of the user making the find_directory call.
+	USER_DIRECTORY = 3000,
+	USER_CONFIG_DIRECTORY,
+	USER_ADDONS_DIRECTORY,
+	USER_BOOT_DIRECTORY,
+	USER_FONTS_DIRECTORY,
+	USER_LIB_DIRECTORY,
+	USER_SETTINGS_DIRECTORY,
+	USER_DESKBAR_DIRECTORY,
+	USER_PRINTERS_DIRECTORY,
+	USER_TRANSLATORS_DIRECTORY,
+	USER_MEDIA_NODES_DIRECTORY,
+	USER_SOUNDS_DIRECTORY,
+	USER_DATA_DIRECTORY,
+	USER_CACHE_DIRECTORY,
+	USER_PACKAGES_DIRECTORY,
+	USER_HEADERS_DIRECTORY,
+	USER_NONPACKAGED_DIRECTORY,
+	USER_NONPACKAGED_ADDONS_DIRECTORY,
+	USER_NONPACKAGED_TRANSLATORS_DIRECTORY,
+	USER_NONPACKAGED_MEDIA_NODES_DIRECTORY,
+	USER_NONPACKAGED_BIN_DIRECTORY,
+	USER_NONPACKAGED_DATA_DIRECTORY,
+	USER_NONPACKAGED_FONTS_DIRECTORY,
+	USER_NONPACKAGED_SOUNDS_DIRECTORY,
+	USER_NONPACKAGED_DOCUMENTATION_DIRECTORY,
+	USER_NONPACKAGED_LIB_DIRECTORY,
+	USER_NONPACKAGED_HEADERS_DIRECTORY,
+	USER_NONPACKAGED_DEVELOP_DIRECTORY,
+	USER_DEVELOP_DIRECTORY,
+	USER_DOCUMENTATION_DIRECTORY,
+	USER_SERVERS_DIRECTORY,
+	USER_APPS_DIRECTORY,
+	USER_BIN_DIRECTORY,
+	USER_PREFERENCES_DIRECTORY,
+	USER_ETC_DIRECTORY,
+	USER_LOG_DIRECTORY,
+	USER_SPOOL_DIRECTORY,
+	USER_VAR_DIRECTORY,
+
+	// Global directories
+	APPS_DIRECTORY = 4000,
+	PREFERENCES_DIRECTORY,
+	UTILITIES_DIRECTORY,
+	PACKAGE_LINKS_DIRECTORY,
+
+	// Obsolete: Legacy BeOS definition to be phased out
+	BEOS_DIRECTORY = 1000,
+	BEOS_SYSTEM_DIRECTORY,
+	BEOS_ADDONS_DIRECTORY,
+	BEOS_BOOT_DIRECTORY,
+	BEOS_FONTS_DIRECTORY,
+	BEOS_LIB_DIRECTORY,
+	BEOS_SERVERS_DIRECTORY,
+	BEOS_APPS_DIRECTORY,
+	BEOS_BIN_DIRECTORY,
+	BEOS_ETC_DIRECTORY,
+	BEOS_DOCUMENTATION_DIRECTORY,
+	BEOS_PREFERENCES_DIRECTORY,
+	BEOS_TRANSLATORS_DIRECTORY,
+	BEOS_MEDIA_NODES_DIRECTORY,
+	BEOS_SOUNDS_DIRECTORY,
+}
+
+find_path_flags :: enum c.int {
+	CREATE_DIRECTORY        = 0x0001,
+	CREATE_PARENT_DIRECTORY = 0x0002,
+	EXISTING_ONLY           = 0x0004,
+	
+	// find_paths() only!
+	SYSTEM_ONLY             = 0x0010,
+	USER_ONLY               = 0x0020,
+}
+
+path_base_directory :: enum c.int {
+	INSTALLATION_LOCATION_DIRECTORY,
+	ADD_ONS_DIRECTORY,
+	APPS_DIRECTORY,
+	BIN_DIRECTORY,
+	BOOT_DIRECTORY,
+	CACHE_DIRECTORY,
+	DATA_DIRECTORY,
+	DEVELOP_DIRECTORY,
+	DEVELOP_LIB_DIRECTORY,
+	DOCUMENTATION_DIRECTORY,
+	ETC_DIRECTORY,
+	FONTS_DIRECTORY,
+	HEADERS_DIRECTORY,
+	LIB_DIRECTORY,
+	LOG_DIRECTORY,
+	MEDIA_NODES_DIRECTORY,
+	PACKAGES_DIRECTORY,
+	PREFERENCES_DIRECTORY,
+	SERVERS_DIRECTORY,
+	SETTINGS_DIRECTORY,
+	SOUNDS_DIRECTORY,
+	SPOOL_DIRECTORY,
+	TRANSLATORS_DIRECTORY,
+	VAR_DIRECTORY,
+
+	// find_path() only!
+	IMAGE_PATH = 1000,
+	PACKAGE_PATH,
+}
+
+// value that can be used instead of a pointer to a symbol in the program image
+APP_IMAGE_SYMBOL :: rawptr(addr_t(0))
+// pointer to a symbol in the callers image (same as B_CURRENT_IMAGE_SYMBOL)
+current_image_symbol :: proc() -> rawptr { return rawptr(current_image_symbol) }
+
+foreign import libroot "system:c"
+foreign libroot {
+	find_directory         :: proc(which: directory_which, volume: dev_t, createIt: bool, pathString: [^]c.char, length: i32) -> status_t ---
+	find_path              :: proc(codePointer: rawptr, baseDirectory: path_base_directory, subPath: cstring, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t ---
+	find_path_etc          :: proc(codePointer: rawptr, dependency: cstring, architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t ---
+	find_path_for_path     :: proc(path: cstring, baseDirectory: path_base_directory, subPath: cstring, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t ---
+	find_path_for_path_etc :: proc(path: cstring, dependency: cstring, architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t ---
+	find_paths             :: proc(baseDirectory: path_base_directory, subPath: cstring, _paths: ^[^][^]c.char, _pathCount: ^c.size_t) -> status_t ---
+	find_paths_etc         :: proc(architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, _paths: ^[^][^]c.char, _pathCount: ^c.size_t) -> status_t ---
+}

+ 502 - 0
core/sys/haiku/os.odin

@@ -0,0 +1,502 @@
+//+build haiku
+package sys_haiku
+
+import "core:c"
+import "core:sys/unix"
+
+foreign import libroot "system:c"
+
+PATH_MAX   :: 1024
+NAME_MAX   :: 256
+MAXPATHLEN :: PATH_MAX
+
+FILE_NAME_LENGTH :: NAME_MAX
+PATH_NAME_LENGTH :: MAXPATHLEN
+OS_NAME_LENGTH   :: 32
+
+// Areas
+
+area_info :: struct {
+	area:       area_id,
+	name:       [OS_NAME_LENGTH]c.char,
+	size:       c.size_t,
+	lock:       u32,
+	protection: u32,
+	team:       team_id,
+	ram_size:   u32,
+	copy_count: u32,
+	in_count:   u32,
+	out_count:  u32,
+	address:    rawptr,
+}
+
+area_locking :: enum u32 {
+	NO_LOCK           = 0,
+	LAZY_LOCK         = 1,
+	FULL_LOCK         = 2,
+	CONTIGUOUS        = 3,
+	LOMEM             = 4, // CONTIGUOUS, < 16 MB physical address
+	_32_BIT_FULL_LOCK  = 5, // FULL_LOCK, < 4 GB physical addresses
+	_32_BIT_CONTIGUOUS = 6, // CONTIGUOUS, < 4 GB physical address
+}
+
+// for create_area() and clone_area()
+address_spec :: enum u32 {
+	ANY_ADDRESS             = 0,
+	EXACT_ADDRESS           = 1,
+	BASE_ADDRESS            = 2,
+	CLONE_ADDRESS           = 3,
+	ANY_KERNEL_ADDRESS      = 4,
+	// ANY_KERNEL_BLOCK_ADDRESS = 5,
+	RANDOMIZED_ANY_ADDRESS  = 6,
+	RANDOMIZED_BASE_ADDRESS = 7,
+}
+
+area_protection_flags :: enum u32 {
+	READ_AREA      = 1 << 0,
+	WRITE_AREA     = 1 << 1,
+	EXECUTE_AREA   = 1 << 2,
+	// "stack" protection is not available on most platforms - it's used
+	// to only commit memory as needed, and have guard pages at the
+	// bottom of the stack.
+	STACK_AREA     = 1 << 3,
+	CLONEABLE_AREA = 1 << 8,
+}
+
+foreign libroot {
+	create_area         :: proc(name: cstring, startAddress: ^rawptr, addressSpec: address_spec, size: c.size_t, lock: area_locking, protection: area_protection_flags) -> area_id ---
+	clone_area          :: proc(name: cstring, destAddress: ^rawptr, addressSpec: address_spec, protection: area_protection_flags, source: area_id) -> area_id ---
+	find_area           :: proc(name: cstring) -> area_id ---
+	area_for            :: proc(address: rawptr) -> area_id ---
+	delete_area         :: proc(id: area_id) -> status_t ---
+	resize_area         :: proc(id: area_id, newSize: c.size_t) -> status_t ---
+	set_area_protection :: proc(id: area_id, newProtection: area_protection_flags) -> status_t ---
+	_get_area_info      :: proc(id: area_id, areaInfo: ^area_info, size: c.size_t) -> status_t ---
+	_get_next_area_info :: proc(team: team_id, cookie: ^c.ssize_t, areaInfo: ^area_info, size: c.size_t) -> status_t ---
+}
+
+// Ports
+
+port_info :: struct {
+	port:        port_id,
+	team:        team_id,
+	name:        [OS_NAME_LENGTH]c.char,
+	capacity:    i32, // queue depth
+	queue_count: i32, // # msgs waiting to be read
+	total_count: i32, // total # msgs read so far
+}
+
+port_flags :: enum u32 {
+	USE_USER_MEMCPY   = 0x80000000,
+	// read the message, but don't remove it; kernel-only; memory must be locked
+	PEEK_PORT_MESSAGE = 0x100,
+}
+
+foreign libroot {
+	create_port          :: proc(capacity: i32, name: cstring) -> port_id ---
+	find_port            :: proc(name: cstring) -> port_id ---
+	read_port            :: proc(port: port_id, code: ^i32, buffer: rawptr, bufferSize: c.size_t) -> c.ssize_t ---
+	read_port_etc        :: proc(port: port_id, code: ^i32, buffer: rawptr, bufferSize: c.size_t, flags: port_flags, timeout: bigtime_t) -> c.ssize_t ---
+	write_port           :: proc(port: port_id, code: i32, buffer: rawptr, bufferSize: c.size_t) -> status_t ---
+	write_port_etc       :: proc(port: port_id, code: i32, buffer: rawptr, bufferSize: c.size_t, flags: port_flags, timeout: bigtime_t) -> status_t ---
+	close_port           :: proc(port: port_id) -> status_t ---
+	delete_port          :: proc(port: port_id) -> status_t ---
+	port_buffer_size     :: proc(port: port_id) -> c.ssize_t ---
+	port_buffer_size_etc :: proc(port: port_id, flags: port_flags, timeout: bigtime_t) -> c.ssize_t ---
+	port_count           :: proc(port: port_id) -> c.ssize_t ---
+	set_port_owner       :: proc(port: port_id, team: team_id) -> status_t ---
+	_get_port_info       :: proc(port: port_id, portInfo: ^port_info, portInfoSize: c.size_t) -> status_t ---
+	_get_next_port_info  :: proc(team: team_id, cookie: ^i32, portInfo: ^port_info, portInfoSize: c.size_t) -> status_t ---
+}
+
+// Semaphores
+
+sem_info :: struct {
+	sem:           sem_id,
+	team:          team_id,
+	name:          [OS_NAME_LENGTH]c.char,
+	count:         i32,
+	latest_holder: thread_id,
+}
+
+semaphore_flags :: enum u32 {
+	CAN_INTERRUPT      = 0x01, // acquisition of the semaphore can be interrupted (system use only)
+	CHECK_PERMISSION   = 0x04, // ownership will be checked (system use only)
+	KILL_CAN_INTERRUPT = 0x20, // acquisition of the semaphore can be interrupted by SIGKILL[THR], even if not CAN_INTERRUPT (system use only)
+	
+	// release_sem_etc() only flags
+	DO_NOT_RESCHEDULE       = 0x02, // thread is not rescheduled
+	RELEASE_ALL             = 0x08, // all waiting threads will be woken up, count will be zeroed
+	RELEASE_IF_WAITING_ONLY	= 0x10, // release count only if there are any threads waiting
+}
+
+foreign libroot {
+	create_sem         :: proc(count: i32, name: cstring) -> sem_id ---
+	delete_sem         :: proc(id: sem_id) -> status_t ---
+	acquire_sem        :: proc(id: sem_id) -> status_t ---
+	acquire_sem_etc    :: proc(id: sem_id, count: i32, flags: semaphore_flags, timeout: bigtime_t) -> status_t ---
+	release_sem        :: proc(id: sem_id) -> status_t ---
+	release_sem_etc    :: proc(id: sem_id, count: i32, flags: semaphore_flags) -> status_t ---
+	switch_sem         :: proc(semToBeReleased: sem_id) -> status_t ---
+	switch_sem_etc     :: proc(semToBeReleased: sem_id, id: sem_id, count: i32, flags: semaphore_flags, timeout: bigtime_t) -> status_t ---
+	get_sem_count      :: proc(id: sem_id, threadCount: ^i32) -> status_t ---
+	set_sem_owner      :: proc(id: sem_id, team: team_id) -> status_t ---
+	_get_sem_info      :: proc(id: sem_id, info: ^sem_info, infoSize: c.size_t) -> status_t ---
+	_get_next_sem_info :: proc(team: team_id, cookie: ^i32, info: ^sem_info, infoSize: c.size_t) -> status_t ---
+}
+
+// Teams
+
+team_info :: struct {
+	team:                team_id,
+	thread_count:        i32,
+	image_count:         i32,
+	area_count:          i32,
+	debugger_nub_thread: thread_id,
+	debugger_nub_port:   port_id,
+	argc:                i32,
+	args:                [64]c.char,
+	uid:                 uid_t,
+	gid:                 gid_t,
+
+	// Haiku R1 extensions
+	real_uid:            uid_t,
+	real_gid:            gid_t,
+	group_id:            pid_t,
+	session_id:          pid_t,
+	parent:              team_id,
+	name:                [OS_NAME_LENGTH]c.char,
+	start_time:          bigtime_t,
+}
+
+CURRENT_TEAM :: 0
+SYSTEM_TEAM  :: 1
+
+team_usage_info :: struct {
+	user_time:   bigtime_t,
+	kernel_time: bigtime_t,
+}
+
+team_usage_who :: enum i32 {
+	// compatible to sys/resource.h RUSAGE_SELF and RUSAGE_CHILDREN
+	SELF     = 0,
+	CHILDREN = -1,
+}
+
+foreign libroot {
+	// see also: send_signal()
+	kill_team            :: proc(team: team_id) -> status_t ---
+	_get_team_info       :: proc(id: team_id, info: ^team_info, size: c.size_t) -> status_t ---
+	_get_next_team_info  :: proc(cookie: ^i32, info: ^team_info, size: c.size_t) -> status_t ---
+	_get_team_usage_info :: proc(id: team_id, who: team_usage_who, info: ^team_usage_info, size: c.size_t) -> status_t ---
+}
+
+// Threads
+
+thread_state :: enum c.int {
+	RUNNING = 1,
+	READY,
+	RECEIVING,
+	ASLEEP,
+	SUSPENDED,
+	WAITING,
+}
+
+thread_info :: struct {
+	thread:      thread_id,
+	team:        team_id,
+	name:        [OS_NAME_LENGTH]c.char,
+	state:       thread_state,
+	priority:    thread_priority,
+	sem:         sem_id,
+	user_time:   bigtime_t,
+	kernel_time: bigtime_t,
+	stack_base:  rawptr,
+	stack_end:   rawptr,
+}
+
+thread_priority :: enum i32 {
+	IDLE_PRIORITY              = 0,
+	LOWEST_ACTIVE_PRIORITY     = 1,
+	LOW_PRIORITY               = 5,
+	NORMAL_PRIORITY            = 10,
+	DISPLAY_PRIORITY           = 15,
+	URGENT_DISPLAY_PRIORITY    = 20,
+	REAL_TIME_DISPLAY_PRIORITY = 100,
+	URGENT_PRIORITY            = 110,
+	REAL_TIME_PRIORITY         = 120,
+}
+
+FIRST_REAL_TIME_PRIORITY :: thread_priority.REAL_TIME_PRIORITY
+
+// time base for snooze_*(), compatible with the clockid_t constants defined in <time.h> 
+SYSTEM_TIMEBASE :: 0
+
+thread_func :: #type proc "c" (rawptr) -> status_t
+
+foreign libroot {
+	spawn_thread          :: proc(thread_func, name: cstring, priority: thread_priority, data: rawptr) -> thread_id ---
+	kill_thread           :: proc(thread: thread_id) -> status_t ---
+	resume_thread         :: proc(thread: thread_id) -> status_t ---
+	suspend_thread        :: proc(thread: thread_id) -> status_t ---
+	rename_thread         :: proc(thread: thread_id, newName: cstring) -> status_t ---
+	set_thread_priority   :: proc(thread: thread_id, newPriority: thread_priority) -> status_t ---
+	exit_thread           :: proc(status: status_t) ---
+	wait_for_thread       :: proc(thread: thread_id, returnValue: ^status_t) -> status_t ---
+	// FIXME: Find and define those flags.
+	wait_for_thread_etc   :: proc(id: thread_id, flags: u32, timeout: bigtime_t, _returnCode: ^status_t) -> status_t ---
+	on_exit_thread        :: proc(callback: proc "c" (rawptr), data: rawptr) -> status_t ---
+	find_thread           :: proc(name: cstring) -> thread_id ---
+	send_data             :: proc(thread: thread_id, code: i32, buffer: rawptr, bufferSize: c.size_t) -> status_t ---
+	receive_data          :: proc(sender: ^thread_id, buffer: rawptr, bufferSize: c.size_t) -> i32 ---
+	has_data              :: proc(thread: thread_id) -> bool ---
+	snooze                :: proc(amount: bigtime_t) -> status_t ---
+	// FIXME: Find and define those flags.
+	snooze_etc            :: proc(amount: bigtime_t, timeBase: c.int, flags: u32) -> status_t ---
+	snooze_until          :: proc(time: bigtime_t, timeBase: c.int) -> status_t ---
+	_get_thread_info      :: proc(id: thread_id, info: ^thread_info, size: c.size_t) -> status_t ---
+	_get_next_thread_info :: proc(team: team_id, cookie: ^i32, info: ^thread_info, size: c.size_t) -> status_t ---
+	// bridge to the pthread API
+	get_pthread_thread_id :: proc(thread: pthread_t) -> thread_id ---
+}
+
+// Time
+
+foreign libroot {
+	real_time_clock       :: proc() -> c.ulong ---
+	set_real_time_clock   :: proc(secsSinceJan1st1970: c.ulong) ---
+	real_time_clock_usecs :: proc() -> bigtime_t ---
+	// time since booting in microseconds
+	system_time           :: proc() -> bigtime_t ---
+	// time since booting in nanoseconds
+	system_time_nsecs     :: proc() -> nanotime_t ---
+}
+
+// Alarm
+
+alarm_mode :: enum u32 {
+	ONE_SHOT_ABSOLUTE_ALARM	= 1,
+	ONE_SHOT_RELATIVE_ALARM,
+	PERIODIC_ALARM, // "when" specifies the period
+}
+
+foreign libroot {
+	set_alarm :: proc(_when: bigtime_t, mode: alarm_mode) -> bigtime_t ---
+}
+
+// Debugger
+
+foreign libroot {
+	debugger :: proc(message: cstring) ---
+	/*
+		calling this function with a non-zero value will cause your thread
+		to receive signals for any exceptional conditions that occur (i.e.
+		you'll get SIGSEGV for data access exceptions, SIGFPE for floating
+		point errors, SIGILL for illegal instructions, etc).
+
+		to re-enable the default debugger pass a zero.
+	*/
+	disable_debugger :: proc(state: c.int) -> c.int ---
+}
+
+// System information
+
+cpu_info :: struct {
+	active_time:       bigtime_t,
+	enabled:           bool,
+	current_frequency: u64,
+}
+
+system_info :: struct {
+	boot_time:         bigtime_t, // time of boot (usecs since 1/1/1970)
+
+	cpu_count:         u32,       // number of cpus
+
+	max_pages:         u64,       // total # of accessible pages
+	used_pages:        u64,       // # of accessible pages in use
+	cached_pages:      u64,
+	block_cache_pages: u64,
+	ignored_pages:     u64,	      // # of ignored/inaccessible pages
+
+	needed_memory:     u64,
+	free_memory:       u64,
+
+	max_swap_pages:    u64,
+	free_swap_pages:   u64,
+
+	page_faults:       u32,	      // # of page faults
+
+	max_sems:          u32,
+	used_sems:         u32,
+
+	max_ports:         u32,
+	used_ports:        u32,
+
+	max_threads:       u32,
+	used_threads:      u32,
+
+	max_teams:         u32,
+	used_teams:        u32,
+
+	kernel_name:       [FILE_NAME_LENGTH]c.char,
+	kernel_build_date: [OS_NAME_LENGTH]c.char,
+	kernel_build_time: [OS_NAME_LENGTH]c.char,
+
+	kernel_version:    i64,
+	abi:               u32,       // the system API
+}
+
+topology_level_type :: enum c.int {
+	UNKNOWN,
+	ROOT,
+	SMT,
+	CORE,
+	PACKAGE,
+}
+
+cpu_platform :: enum c.int {
+	UNKNOWN,
+	x86,
+	x86_64,
+	PPC,
+	PPC_64,
+	M68K,
+	ARM,
+	ARM_64,
+	ALPHA,
+	MIPS,
+	SH,
+	SPARC,
+	RISC_V,
+}
+
+cpu_vendor :: enum c.int {
+	UNKNOWN,
+	AMD,
+	CYRIX,
+	IDT,
+	INTEL,
+	NATIONAL_SEMICONDUCTOR,
+	RISE,
+	TRANSMETA,
+	VIA,
+	IBM,
+	MOTOROLA,
+	NEC,
+	HYGON,
+	SUN,
+	FUJITSU,
+}
+
+cpu_topology_node_info :: struct {
+	id:            u32,
+	type:          topology_level_type,
+	level:         u32,
+
+	data: struct #raw_union {
+		_root: struct {
+			platform: cpu_platform,
+		},
+		_package: struct {
+			vendor:          cpu_vendor,
+			cache_line_size: u32
+		},
+		_core: struct {
+			model:             u32,
+			default_frequency: u64,
+		},
+	},
+}
+
+// FIXME: Add cpuid_info when bit fields are ready.
+
+foreign libroot {
+	get_system_info       :: proc(info: ^system_info) -> status_t ---
+	_get_cpu_info_etc     :: proc(firstCPU: u32, cpuCount: u32, info: ^cpu_info, size: c.size_t) -> status_t ---
+	get_cpu_topology_info :: proc(topologyInfos: [^]cpu_topology_node_info, topologyInfoCount: ^u32) -> status_t ---
+
+	is_computer_on        :: proc() -> i32 ---
+	is_computer_on_fire   :: proc() -> f64 ---
+}
+
+// Signal.h
+
+SIG_BLOCK   :: 1
+SIG_UNBLOCK :: 2
+SIG_SETMASK :: 3
+
+/*
+ * The list of all defined signals:
+ *
+ * The numbering of signals for Haiku attempts to maintain
+ * some consistency with UN*X conventions so that things
+ * like "kill -9" do what you expect.
+ */
+
+SIGHUP     :: 1  // hangup -- tty is gone!
+SIGINT     :: 2  // interrupt
+SIGQUIT    :: 3  // `quit' special character typed in tty
+SIGILL     :: 4  // illegal instruction
+SIGCHLD    :: 5  // child process exited
+SIGABRT    :: 6  // abort() called, dont' catch
+SIGPIPE    :: 7  // write to a pipe w/no readers
+SIGFPE     :: 8  // floating point exception
+SIGKILL    :: 9  // kill a team (not catchable)
+SIGSTOP    :: 10 // suspend a thread (not catchable)
+SIGSEGV    :: 11 // segmentation violation (read: invalid pointer)
+SIGCONT    :: 12 // continue execution if suspended
+SIGTSTP    :: 13 // `stop' special character typed in tty
+SIGALRM    :: 14 // an alarm has gone off (see alarm())
+SIGTERM    :: 15 // termination requested
+SIGTTIN    :: 16 // read of tty from bg process
+SIGTTOU    :: 17 // write to tty from bg process
+SIGUSR1    :: 18 // app defined signal 1
+SIGUSR2    :: 19 // app defined signal 2
+SIGWINCH   :: 20 // tty window size changed
+SIGKILLTHR :: 21 // be specific: kill just the thread, not team
+SIGTRAP    :: 22 // Trace/breakpoint trap
+SIGPOLL    :: 23 // Pollable event
+SIGPROF    :: 24 // Profiling timer expired
+SIGSYS     :: 25 // Bad system call
+SIGURG     :: 26 // High bandwidth data is available at socket
+SIGVTALRM  :: 27 // Virtual timer expired
+SIGXCPU    :: 28 // CPU time limit exceeded
+SIGXFSZ    :: 29 // File size limit exceeded
+SIGBUS     :: 30 // access to undefined portion of a memory object
+
+sigval :: struct #raw_union {
+	sival_int: c.int,
+	sival_ptr: rawptr,
+}
+
+siginfo_t :: struct {
+	si_signo:  c.int,  // signal number
+	si_code:   c.int,  // signal code
+	si_errno:  c.int,  // if non zero, an error number associated with this signal
+
+	si_pid:    pid_t,  // sending process ID
+	si_uid:    uid_t,  // real user ID of sending process
+	si_addr:   rawptr, // address of faulting instruction
+	si_status: c.int,  // exit value or signal
+	si_band:   c.long, // band event for SIGPOLL
+	si_value:  sigval, // signal value
+}
+
+foreign libroot {
+	// signal set (sigset_t) manipulation
+	sigemptyset  :: proc(set: ^sigset_t) -> c.int ---
+	sigfillset   :: proc(set: ^sigset_t) -> c.int ---
+	sigaddset    :: proc(set: ^sigset_t, _signal: c.int) -> c.int ---
+	sigdelset    :: proc(set: ^sigset_t, _signal: c.int) -> c.int ---
+	sigismember  :: proc(set: ^sigset_t, _signal: c.int) -> c.int ---
+	// querying and waiting for signals
+	sigpending   :: proc(set: ^sigset_t) -> c.int ---
+	sigsuspend   :: proc(mask: ^sigset_t) -> c.int ---
+	sigpause     :: proc(_signal: c.int) -> c.int ---
+	sigwait      :: proc(set: ^sigset_t, _signal: ^c.int) -> c.int ---
+	sigwaitinfo  :: proc(set: ^sigset_t, info: ^siginfo_t) -> c.int ---
+	sigtimedwait :: proc(set: ^sigset_t, info: ^siginfo_t, timeout: ^unix.timespec) -> c.int ---
+
+	send_signal      :: proc(threadID: thread_id, signal: c.uint) -> c.int ---
+	set_signal_stack :: proc(base: rawptr, size: c.size_t) ---
+}

+ 54 - 0
core/sys/haiku/types.odin

@@ -0,0 +1,54 @@
+//+build haiku
+package sys_haiku
+
+import "core:c"
+
+status_t       :: i32
+bigtime_t      :: i64
+nanotime_t     :: i64
+type_code      :: u32
+perform_code   :: u32
+
+phys_addr_t    :: uintptr
+phys_size_t    :: phys_addr_t
+generic_addr_t :: uintptr
+generic_size_t :: generic_addr_t
+
+area_id        :: i32
+port_id        :: i32
+sem_id         :: i32
+team_id        :: i32
+thread_id      :: i32
+
+blkcnt_t       :: i64
+blksize_t      :: i32
+fsblkcnt_t     :: i64
+fsfilcnt_t     :: i64
+off_t          :: i64
+ino_t          :: i64
+cnt_t          :: i32
+dev_t          :: i32
+pid_t          :: i32
+id_t           :: i32
+
+uid_t          :: u32
+gid_t          :: u32
+mode_t         :: u32
+umode_t        :: u32
+nlink_t        :: i32
+
+caddr_t        :: ^c.char
+
+addr_t         :: phys_addr_t
+key_t          :: i32
+
+clockid_t      :: i32
+
+time_t         :: i64 when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 else i32
+
+sig_atomic_t   :: c.int
+sigset_t       :: u64
+
+image_id       :: i32
+
+pthread_t      :: rawptr

+ 71 - 0
core/sys/unix/pthread_haiku.odin

@@ -0,0 +1,71 @@
+package unix
+
+import "core:c"
+
+pthread_t             :: distinct rawptr
+pthread_attr_t        :: distinct rawptr
+pthread_mutex_t       :: distinct rawptr
+pthread_mutexattr_t   :: distinct rawptr
+pthread_cond_t        :: distinct rawptr
+pthread_condattr_t    :: distinct rawptr
+pthread_rwlock_t      :: distinct rawptr
+pthread_rwlockattr_t  :: distinct rawptr
+pthread_barrier_t     :: distinct rawptr
+pthread_barrierattr_t :: distinct rawptr
+pthread_spinlock_t    :: distinct rawptr
+
+pthread_key_t  :: distinct c.int
+pthread_once_t :: struct {
+	state: c.int,
+	mutex: pthread_mutex_t,
+}
+
+PTHREAD_MUTEX_DEFAULT    :: 0
+PTHREAD_MUTEX_NORMAL     :: 1
+PTHREAD_MUTEX_ERRORCHECK :: 2
+PTHREAD_MUTEX_RECURSIVE  :: 3
+
+PTHREAD_DETACHED      :: 0x1
+PTHREAD_SCOPE_SYSTEM  :: 0x2
+PTHREAD_INHERIT_SCHED :: 0x4
+PTHREAD_NOFLOAT       :: 0x8
+
+PTHREAD_CREATE_DETACHED :: PTHREAD_DETACHED
+PTHREAD_CREATE_JOINABLE :: 0
+PTHREAD_SCOPE_PROCESS   :: 0
+PTHREAD_EXPLICIT_SCHED  :: 0
+
+SCHED_FIFO     :: 1
+SCHED_RR       :: 2
+SCHED_SPORADIC :: 3
+SCHED_OTHER    :: 4
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+sem_t :: distinct rawptr
+
+PTHREAD_CANCEL_ENABLE       :: 0
+PTHREAD_CANCEL_DISABLE      :: 1
+PTHREAD_CANCEL_DEFERRED     :: 0
+PTHREAD_CANCEL_ASYNCHRONOUS :: 2
+
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+	sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t ---
+
+	sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int ---
+	sem_destroy :: proc(sem: ^sem_t) -> c.int ---
+	sem_post :: proc(sem: ^sem_t) -> c.int ---
+	sem_wait :: proc(sem: ^sem_t) -> c.int ---
+	sem_trywait :: proc(sem: ^sem_t) -> c.int ---
+	
+	pthread_yield :: proc() ---
+
+	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
+	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
+	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
+}

+ 16 - 8
core/sys/unix/pthread_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 package unix
 
 foreign import "system:pthread"
@@ -16,6 +16,8 @@ foreign pthread {
 	// retval is a pointer to a location to put the return value of the thread proc.
 	pthread_join :: proc(t: pthread_t, retval: ^rawptr) -> c.int ---
 
+	pthread_kill :: proc(t: pthread_t, sig: c.int) -> c.int ---
+
 	pthread_self :: proc() -> pthread_t ---
 
 	pthread_equal :: proc(a, b: pthread_t) -> b32 ---
@@ -31,15 +33,9 @@ foreign pthread {
 	pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---
 	pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---
 
-	pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int ---
-	pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int ---
-
 	// states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE
 	pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int ---
-
-	// scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED
-	pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int ---
-
+	
 	// NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements.
 	// For maximum usefulness, use the OS's page size.
 	// ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems
@@ -52,8 +48,20 @@ foreign pthread {
 	pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int ---
 	pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int ---
 
+	pthread_sigmask :: proc(how: c.int, set: rawptr, oldset: rawptr) -> c.int ---
+
 	sched_yield :: proc() -> c.int ---
+}
+
+// NOTE: Unimplemented in Haiku.
+when ODIN_OS != .Haiku {
+	foreign pthread {
+		// scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED
+		pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int ---
 
+		pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int ---
+		pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int ---
+	}
 }
 
 @(default_calling_convention="c")

+ 1 - 1
core/sys/unix/time_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 package unix
 
 when ODIN_OS == .Darwin {

+ 9 - 4
core/thread/thread_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd, openbsd
+// +build linux, darwin, freebsd, openbsd, haiku
 // +private
 package thread
 
@@ -78,7 +78,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 	// NOTE(tetra, 2019-11-01): These only fail if their argument is invalid.
 	assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0)
-	assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0)
+	when ODIN_OS != .Haiku {
+		assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0)
+	}
 
 	thread := new(Thread)
 	if thread == nil {
@@ -88,8 +90,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
 
 	// Set thread priority.
 	policy: i32
-	res := unix.pthread_attr_getschedpolicy(&attrs, &policy)
-	assert(res == 0)
+	res: i32
+	when ODIN_OS != .Haiku {
+		res = unix.pthread_attr_getschedpolicy(&attrs, &policy)
+		assert(res == 0)
+	}
 	params: unix.sched_param
 	res = unix.pthread_attr_getschedparam(&attrs, &params)
 	assert(res == 0)

+ 1 - 1
core/time/time_unix.odin

@@ -1,5 +1,5 @@
 //+private
-//+build linux, darwin, freebsd, openbsd
+//+build linux, darwin, freebsd, openbsd, haiku
 package time
 
 import "core:sys/unix"

+ 68 - 3
src/build_settings.cpp

@@ -18,6 +18,7 @@ enum TargetOsKind : u16 {
 	TargetOs_essence,
 	TargetOs_freebsd,
 	TargetOs_openbsd,
+	TargetOs_haiku,
 	
 	TargetOs_wasi,
 	TargetOs_js,
@@ -78,6 +79,7 @@ gb_global String target_os_names[TargetOs_COUNT] = {
 	str_lit("essence"),
 	str_lit("freebsd"),
 	str_lit("openbsd"),
+	str_lit("haiku"),
 	
 	str_lit("wasi"),
 	str_lit("js"),
@@ -542,6 +544,13 @@ gb_global TargetMetrics target_openbsd_amd64 = {
 	str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
+gb_global TargetMetrics target_haiku_amd64 = {
+	TargetOs_haiku,
+	TargetArch_amd64,
+	8, 8, 8, 16,
+	str_lit("x86_64-unknown-haiku"),
+};
+
 gb_global TargetMetrics target_essence_amd64 = {
 	TargetOs_essence,
 	TargetArch_amd64,
@@ -641,6 +650,7 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freebsd_amd64"),       &target_freebsd_amd64  },
 
 	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
+	{ str_lit("haiku_amd64"),         &target_haiku_amd64    },
 
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
@@ -872,6 +882,58 @@ gb_internal String internal_odin_root_dir(void) {
 	return path;
 }
 
+#elif defined(GB_SYSTEM_HAIKU)
+
+#include <FindDirectory.h>
+
+gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_);
+
+gb_internal String internal_odin_root_dir(void) {
+	String path = global_module_path;
+	isize len, i;
+	u8 *text;
+
+	if (global_module_path_set) {
+		return global_module_path;
+	}
+
+	auto path_buf = array_make<char>(heap_allocator(), 300);
+	defer (array_free(&path_buf));
+
+	len = 0;
+	for (;;) {
+		u32 sz = path_buf.count;
+		int res = find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, nullptr, &path_buf[0], sz);
+		if(res == B_OK) {
+			len = sz;
+			break;
+		} else {
+			array_resize(&path_buf, sz + 1);
+		}
+	}
+
+	mutex_lock(&string_buffer_mutex);
+	defer (mutex_unlock(&string_buffer_mutex));
+
+	text = gb_alloc_array(permanent_allocator(), u8, len + 1);
+	gb_memmove(text, &path_buf[0], len);
+
+	path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr);
+
+	for (i = path.len-1; i >= 0; i--) {
+		u8 c = path[i];
+		if (c == '/' || c == '\\') {
+			break;
+		}
+		path.len--;
+	}
+
+	global_module_path = path;
+	global_module_path_set = true;
+
+	return path;
+}
+
 #elif defined(GB_SYSTEM_OSX)
 
 #include <mach-o/dyld.h>
@@ -888,6 +950,7 @@ gb_internal String internal_odin_root_dir(void) {
 	}
 
 	auto path_buf = array_make<char>(heap_allocator(), 300);
+	defer (array_free(&path_buf));
 
 	len = 0;
 	for (;;) {
@@ -920,9 +983,6 @@ gb_internal String internal_odin_root_dir(void) {
 	global_module_path = path;
 	global_module_path_set = true;
 
-
-	// array_free(&path_buf);
-
 	return path;
 }
 #else
@@ -1301,6 +1361,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 			metrics = &target_freebsd_amd64;
 		#elif defined(GB_SYSTEM_OPENBSD)
 			metrics = &target_openbsd_amd64;
+		#elif defined(GB_SYSTEM_HAIKU)
+			metrics = &target_haiku_amd64;
 		#elif defined(GB_CPU_ARM)
 			metrics = &target_linux_arm64;
 		#else
@@ -1405,6 +1467,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 		case TargetOs_openbsd:
 			bc->link_flags = str_lit("-arch x86-64 ");
 			break;
+		case TargetOs_haiku:
+			bc->link_flags = str_lit("-arch x86-64 ");
+			break;
 		}
 	} else if (bc->metrics.arch == TargetArch_i386) {
 		switch (bc->metrics.os) {

+ 1 - 0
src/check_builtin.cpp

@@ -4928,6 +4928,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
 			case TargetOs_essence:
 			case TargetOs_freebsd:
 			case TargetOs_openbsd:
+			case TargetOs_haiku:
 				switch (build_context.metrics.arch) {
 				case TargetArch_i386:
 				case TargetArch_amd64:

+ 1 - 0
src/checker.cpp

@@ -1010,6 +1010,7 @@ gb_internal void init_universal(void) {
 			{"Linux",        TargetOs_linux},
 			{"Essence",      TargetOs_essence},
 			{"FreeBSD",      TargetOs_freebsd},
+			{"Haiku",        TargetOs_haiku},
 			{"OpenBSD",      TargetOs_openbsd},
 			{"WASI",         TargetOs_wasi},
 			{"JS",           TargetOs_js},

+ 47 - 2
src/gb/gb.h

@@ -83,6 +83,10 @@ extern "C" {
 		#ifndef GB_SYSTEM_OPENBSD
 		#define GB_SYSTEM_OPENBSD 1
 		#endif
+	#elif defined(__HAIKU__) || defined(__haiku__)
+		#ifndef GB_SYSTEM_HAIKU
+		#define GB_SYSTEM_HAIKU 1
+		#endif
 	#else
 		#error This UNIX operating system is not supported
 	#endif
@@ -206,7 +210,7 @@ extern "C" {
 	#endif
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <sys/mman.h>
-	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
+	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__)
 		#include <sys/sendfile.h>
 	#endif
 	#include <sys/stat.h>
@@ -247,6 +251,13 @@ extern "C" {
 	#include <pthread_np.h>
 	#define lseek64 lseek
 #endif
+
+#if defined(GB_SYSTEM_HAIKU)
+	#include <stdio.h>
+	#include <pthread.h>
+	#include <kernel/OS.h>
+	#define lseek64 lseek
+#endif
     
 #if defined(GB_SYSTEM_UNIX)
 	#include <semaphore.h>
@@ -801,6 +812,13 @@ typedef struct gbAffinity {
 	isize thread_count;
 	isize threads_per_core;
 } gbAffinity;
+#elif defined(GB_SYSTEM_HAIKU)
+typedef struct gbAffinity {
+	b32   is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
 #else
 #error TODO(bill): Unknown system
 #endif
@@ -2984,6 +3002,8 @@ gb_inline u32 gb_thread_current_id(void) {
 	__asm__("mov %%fs:0x10,%0" : "=r"(thread_id));
 #elif defined(GB_SYSTEM_LINUX)
 	thread_id = gettid();
+#elif defined(GB_SYSTEM_HAIKU)
+	thread_id = find_thread(NULL);
 #else
 	#error Unsupported architecture for gb_thread_current_id()
 #endif
@@ -3184,7 +3204,9 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
 	//info.affinity_tag = cast(integer_t)index;
 	//result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT);
 
+#if !defined(GB_SYSTEM_HAIKU)
 	result = pthread_setaffinity_np(thread, sizeof(cpuset_t), &mn);
+#endif
 	return result == 0;
 }
 
@@ -3236,6 +3258,29 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
 	return true;
 }
 
+isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
+	GB_ASSERT(0 <= core && core < a->core_count);
+	return a->threads_per_core;
+}
+#elif defined(GB_SYSTEM_HAIKU)
+#include <unistd.h>
+
+void gb_affinity_init(gbAffinity *a) {
+	a->core_count       = sysconf(_SC_NPROCESSORS_ONLN);
+	a->threads_per_core = 1;
+	a->is_accurate      = a->core_count > 0;
+	a->core_count       = a->is_accurate ? a->core_count : 1;
+	a->thread_count     = a->core_count;
+}
+
+void gb_affinity_destroy(gbAffinity *a) {
+	gb_unused(a);
+}
+
+b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) {
+	return true;
+}
+
 isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) {
 	GB_ASSERT(0 <= core && core < a->core_count);
 	return a->threads_per_core;
@@ -5457,7 +5502,7 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena
 		}
 	}
 	
-	gb_free(buf);
+	gb_mfree(buf);
 	close(new_fd);
 	close(existing_fd);
 

+ 2 - 2
src/linker.cpp

@@ -474,8 +474,8 @@ gb_internal i32 linker_stage(LinkerData *gen) {
 					link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' ");
 				}
 
-			} else if (build_context.metrics.os != TargetOs_openbsd) {
-				// OpenBSD defaults to PIE executable. do not pass -no-pie for it.
+			} else if (build_context.metrics.os != TargetOs_openbsd && build_context.metrics.os != TargetOs_haiku) {
+				// OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it.
 				link_settings = gb_string_appendc(link_settings, "-no-pie ");
 			}
 

+ 2 - 2
src/llvm_backend.cpp

@@ -2564,8 +2564,8 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
 
 	switch (build_context.reloc_mode) {
 	case RelocMode_Default:
-		if (build_context.metrics.os == TargetOs_openbsd) {
-			// Always use PIC for OpenBSD: it defaults to PIE
+		if (build_context.metrics.os == TargetOs_openbsd || build_context.metrics.os == TargetOs_haiku) {
+			// Always use PIC for OpenBSD and Haiku: they default to PIE
 			reloc_mode = LLVMRelocPIC;
 		}
 		break;

+ 461 - 461
src/path.cpp

@@ -1,461 +1,461 @@
-/*
-	Path handling utilities.
-*/
-#if !defined(GB_SYSTEM_WINDOWS)
-#include <unistd.h>
-#endif
-
-gb_internal String remove_extension_from_path(String const &s) {
-	if (s.len != 0 && s.text[s.len-1] == '.') {
-		return s;
-	}
-	for (isize i = s.len-1; i >= 0; i--) {
-		if (s[i] == '.') {
-			return substring(s, 0, i);
-		}
-	}
-	return s;
-}
-
-gb_internal String remove_directory_from_path(String const &s) {
-	isize len = 0;
-	for (isize i = s.len-1; i >= 0; i--) {
-		if (s[i] == '/' ||
-		    s[i] == '\\') {
-			break;
-		}
-		len += 1;
-	}
-	return substring(s, s.len-len, s.len);
-}
-
-
-// NOTE(Mark Naughton): getcwd as String
-#if !defined(GB_SYSTEM_WINDOWS)
-gb_internal String get_current_directory(void) {
-	char cwd[256];
-	getcwd(cwd, 256);
-
-	return make_string_c(cwd);
-}
-
-#else
-gb_internal String get_current_directory(void) {
-	gbAllocator a = heap_allocator();
-
-	wchar_t cwd[256];
-	GetCurrentDirectoryW(256, cwd);
-
-	String16 wstr = make_string16_c(cwd);
-
-	return string16_to_string(a, wstr);
-}
-#endif
-
-gb_internal bool path_is_directory(String path);
-
-gb_internal String directory_from_path(String const &s) {
-	if (path_is_directory(s)) {
-		return s;
-	}
-
-	isize i = s.len-1;
-	for (; i >= 0; i--) {
-		if (s[i] == '/' ||
-		    s[i] == '\\') {
-			break;
-		}
-	}
-	if (i >= 0) {
-		return substring(s, 0, i);	
-	}
-	return substring(s, 0, 0);
-}
-
-#if defined(GB_SYSTEM_WINDOWS)
-	gb_internal bool path_is_directory(String path) {
-		gbAllocator a = heap_allocator();
-		String16 wstr = string_to_string16(a, path);
-		defer (gb_free(a, wstr.text));
-
-		i32 attribs = GetFileAttributesW(wstr.text);
-		if (attribs < 0) return false;
-
-		return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
-	}
-
-#else
-	gb_internal bool path_is_directory(String path) {
-		gbAllocator a = heap_allocator();
-		char *copy = cast(char *)copy_string(a, path).text;
-		defer (gb_free(a, copy));
-
-		struct stat s;
-		if (stat(copy, &s) == 0) {
-			return (s.st_mode & S_IFDIR) != 0;
-		}
-		return false;
-	}
-#endif
-
-
-gb_internal String path_to_full_path(gbAllocator a, String path) {
-	gbAllocator ha = heap_allocator();
-	char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
-	defer (gb_free(ha, path_c));
-
-	char *fullpath = gb_path_get_full_name(a, path_c);
-	String res = string_trim_whitespace(make_string_c(fullpath));
-#if defined(GB_SYSTEM_WINDOWS)
-	for (isize i = 0; i < res.len; i++) {
-		if (res.text[i] == '\\') {
-			res.text[i] = '/';
-		}
-	}
-#endif
-	return copy_string(a, res);
-}
-
-struct Path {
-	String basename;
-	String name;
-	String ext;
-};
-
-// NOTE(Jeroen): Naively turns a Path into a string.
-gb_internal String path_to_string(gbAllocator a, Path path) {
-	if (path.basename.len + path.name.len + path.ext.len == 0) {
-		return make_string(nullptr, 0);
-	}
-
-	isize len = path.basename.len + 1 + path.name.len + 1;
-	if (path.ext.len > 0) {
-		 len += path.ext.len + 1;
-	}
-
-	u8 *str = gb_alloc_array(a, u8, len);
-
-	isize i = 0;
-	gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
-	
-	gb_memmove(str+i, "/", 1);                                i += 1;
-	
-	gb_memmove(str+i, path.name.text,     path.name.len);     i += path.name.len;
-	if (path.ext.len > 0) {
-		gb_memmove(str+i, ".", 1);                            i += 1;
-		gb_memmove(str+i, path.ext.text,  path.ext.len);      i += path.ext.len;
-	}
-	str[i] = 0;
-
-	String res = make_string(str, i);
-	res        = string_trim_whitespace(res);
-	return res;
-}
-
-// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
-gb_internal String path_to_full_path(gbAllocator a, Path path) {
-	String temp = path_to_string(heap_allocator(), path);
-	defer (gb_free(heap_allocator(), temp.text));
-
-	return path_to_full_path(a, temp);
-}
-
-// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
-// and then breaks it into its components to make a Path.
-gb_internal Path path_from_string(gbAllocator a, String const &path) {
-	Path res = {};
-
-	if (path.len == 0) return res;
-
-	String fullpath = path_to_full_path(a, path);
-	defer (gb_free(heap_allocator(), fullpath.text));
-
-	res.basename = directory_from_path(fullpath);	
-	res.basename = copy_string(a, res.basename);
-
-	if (path_is_directory(fullpath)) {
-		// It's a directory. We don't need to tinker with the name and extension.
-		// It could have a superfluous trailing `/`. Remove it if so.
-		if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
-			res.basename.len--;
-		}
-		return res;
-	}
-
-	// Note(Dragos): Is the copy_string required if it's a substring?
-	isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
-	res.name         = substring(fullpath, name_start, fullpath.len);
-	res.name         = remove_extension_from_path(res.name);
-	res.name         = copy_string(a, res.name);
-
-	res.ext          = path_extension(fullpath, false); // false says not to include the dot.
-	res.ext          = copy_string(a, res.ext);
-	return res;
-}
-
-// NOTE(Jeroen): Takes a path String and returns the last path element.
-gb_internal String last_path_element(String const &path) {
-	isize count = 0;
-	u8 * start = (u8 *)(&path.text[path.len - 1]);
-	for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
-		count++;
-		start--;
-	}
-	if (count > 0) {
-		start++; // Advance past the `/` and return the substring.
-		String res = make_string(start, count);
-		return res;
-	}
-	// Must be a root path like `/` or `C:/`, return empty String.
-	return STR_LIT("");
-}
-
-gb_internal bool path_is_directory(Path path) {
-	String path_string = path_to_full_path(heap_allocator(), path);
-	defer (gb_free(heap_allocator(), path_string.text));
-
-	return path_is_directory(path_string);
-}
-
-struct FileInfo {
-	String name;
-	String fullpath;
-	i64    size;
-	bool   is_dir;
-};
-
-enum ReadDirectoryError {
-	ReadDirectory_None,
-
-	ReadDirectory_InvalidPath,
-	ReadDirectory_NotExists,
-	ReadDirectory_Permission,
-	ReadDirectory_NotDir,
-	ReadDirectory_Empty,
-	ReadDirectory_Unknown,
-
-	ReadDirectory_COUNT,
-};
-
-gb_internal i64 get_file_size(String path) {
-	char *c_str = alloc_cstring(heap_allocator(), path);
-	defer (gb_free(heap_allocator(), c_str));
-
-	gbFile f = {};
-	gbFileError err = gb_file_open(&f, c_str);
-	defer (gb_file_close(&f));
-	if (err != gbFileError_None) {
-		return -1;
-	}
-	return gb_file_size(&f);
-}
-
-
-#if defined(GB_SYSTEM_WINDOWS)
-gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
-	GB_ASSERT(fi != nullptr);
-
-
-	while (path.len > 0) {
-		Rune end = path[path.len-1];
-		if (end == '/') {
-			path.len -= 1;
-		} else if (end == '\\') {
-			path.len -= 1;
-		} else {
-			break;
-		}
-	}
-
-	if (path.len == 0) {
-		return ReadDirectory_InvalidPath;
-	}
-	{
-		char *c_str = alloc_cstring(temporary_allocator(), path);
-		gbFile f = {};
-		gbFileError file_err = gb_file_open(&f, c_str);
-		defer (gb_file_close(&f));
-
-		switch (file_err) {
-		case gbFileError_Invalid:    return ReadDirectory_InvalidPath;
-		case gbFileError_NotExists:  return ReadDirectory_NotExists;
-		// case gbFileError_Permission: return ReadDirectory_Permission;
-		}
-	}
-
-	if (!path_is_directory(path)) {
-		return ReadDirectory_NotDir;
-	}
-
-
-	gbAllocator a = heap_allocator();
-	char *new_path = gb_alloc_array(a, char, path.len+3);
-	defer (gb_free(a, new_path));
-
-	gb_memmove(new_path, path.text, path.len);
-	gb_memmove(new_path+path.len, "/*", 2);
-	new_path[path.len+2] = 0;
-
-	String np = make_string(cast(u8 *)new_path, path.len+2);
-	String16 wstr = string_to_string16(a, np);
-	defer (gb_free(a, wstr.text));
-
-	WIN32_FIND_DATAW file_data = {};
-	HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
-	if (find_file == INVALID_HANDLE_VALUE) {
-		return ReadDirectory_Unknown;
-	}
-	defer (FindClose(find_file));
-
-	array_init(fi, a, 0, 100);
-
-	do {
-		wchar_t *filename_w = file_data.cFileName;
-		u64 size = cast(u64)file_data.nFileSizeLow;
-		size |= (cast(u64)file_data.nFileSizeHigh) << 32;
-		String name = string16_to_string(a, make_string16_c(filename_w));
-		if (name == "." || name == "..") {
-			gb_free(a, name.text);
-			continue;
-		}
-
-		String filepath = {};
-		filepath.len = path.len+1+name.len;
-		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
-		defer (gb_free(a, filepath.text));
-		gb_memmove(filepath.text, path.text, path.len);
-		gb_memmove(filepath.text+path.len, "/", 1);
-		gb_memmove(filepath.text+path.len+1, name.text, name.len);
-
-		FileInfo info = {};
-		info.name = name;
-		info.fullpath = path_to_full_path(a, filepath);
-		info.size = cast(i64)size;
-		info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
-		array_add(fi, info);
-	} while (FindNextFileW(find_file, &file_data));
-
-	if (fi->count == 0) {
-		return ReadDirectory_Empty;
-	}
-
-	return ReadDirectory_None;
-}
-#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
-
-#include <dirent.h>
-
-gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
-	GB_ASSERT(fi != nullptr);
-
-	gbAllocator a = heap_allocator();
-
-	char *c_path = alloc_cstring(a, path);
-	defer (gb_free(a, c_path));
-
-	DIR *dir = opendir(c_path);
-	if (!dir) {
-		switch (errno) {
-		case ENOENT:
-			return ReadDirectory_NotExists;
-		case EACCES:
-			return ReadDirectory_Permission;
-		case ENOTDIR:
-			return ReadDirectory_NotDir;
-		default:
-			// ENOMEM: out of memory
-			// EMFILE: per-process limit on open fds reached
-			// ENFILE: system-wide limit on total open files reached
-			return ReadDirectory_Unknown;
-		}
-		GB_PANIC("unreachable");
-	}
-
-	array_init(fi, a, 0, 100);
-
-	for (;;) {
-		struct dirent *entry = readdir(dir);
-		if (entry == nullptr) {
-			break;
-		}
-
-		String name = make_string_c(entry->d_name);
-		if (name == "." || name == "..") {
-			continue;
-		}
-
-		String filepath = {};
-		filepath.len = path.len+1+name.len;
-		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
-		defer (gb_free(a, filepath.text));
-		gb_memmove(filepath.text, path.text, path.len);
-		gb_memmove(filepath.text+path.len, "/", 1);
-		gb_memmove(filepath.text+path.len+1, name.text, name.len);
-		filepath.text[filepath.len] = 0;
-
-
-		struct stat dir_stat = {};
-
-		if (stat((char *)filepath.text, &dir_stat)) {
-			continue;
-		}
-
-		if (S_ISDIR(dir_stat.st_mode)) {
-			continue;
-		}
-
-		i64 size = dir_stat.st_size;
-
-		FileInfo info = {};
-		info.name = name;
-		info.fullpath = path_to_full_path(a, filepath);
-		info.size = size;
-		array_add(fi, info);
-	}
-
-	if (fi->count == 0) {
-		return ReadDirectory_Empty;
-	}
-
-	return ReadDirectory_None;
-}
-
-
-#else
-#error Implement read_directory
-#endif
-
-#if !defined(GB_SYSTEM_WINDOWS)
-gb_internal bool write_directory(String path) {
-	char const *pathname = (char *) path.text;
-
-	if (access(pathname, W_OK) < 0) {
-		return false;
-	}
-
-	return true;
-}
-#else
-gb_internal bool write_directory(String path) {
-	String16 wstr = string_to_string16(heap_allocator(), path);
-	LPCWSTR wdirectory_name = wstr.text;
-
-	HANDLE directory = CreateFileW(wdirectory_name,
-			GENERIC_WRITE,
-			0,
-			NULL,
-			OPEN_EXISTING,
-			FILE_FLAG_BACKUP_SEMANTICS,
-			NULL);
-
-	if (directory == INVALID_HANDLE_VALUE) {
-		DWORD error_code = GetLastError();
-		if (error_code == ERROR_ACCESS_DENIED) {
-			return false;
-		}
-	}
-
-	CloseHandle(directory);
-	return true;
-}
-#endif
+/*
+	Path handling utilities.
+*/
+#if !defined(GB_SYSTEM_WINDOWS)
+#include <unistd.h>
+#endif
+
+gb_internal String remove_extension_from_path(String const &s) {
+	if (s.len != 0 && s.text[s.len-1] == '.') {
+		return s;
+	}
+	for (isize i = s.len-1; i >= 0; i--) {
+		if (s[i] == '.') {
+			return substring(s, 0, i);
+		}
+	}
+	return s;
+}
+
+gb_internal String remove_directory_from_path(String const &s) {
+	isize len = 0;
+	for (isize i = s.len-1; i >= 0; i--) {
+		if (s[i] == '/' ||
+		    s[i] == '\\') {
+			break;
+		}
+		len += 1;
+	}
+	return substring(s, s.len-len, s.len);
+}
+
+
+// NOTE(Mark Naughton): getcwd as String
+#if !defined(GB_SYSTEM_WINDOWS)
+gb_internal String get_current_directory(void) {
+	char cwd[256];
+	getcwd(cwd, 256);
+
+	return make_string_c(cwd);
+}
+
+#else
+gb_internal String get_current_directory(void) {
+	gbAllocator a = heap_allocator();
+
+	wchar_t cwd[256];
+	GetCurrentDirectoryW(256, cwd);
+
+	String16 wstr = make_string16_c(cwd);
+
+	return string16_to_string(a, wstr);
+}
+#endif
+
+gb_internal bool path_is_directory(String path);
+
+gb_internal String directory_from_path(String const &s) {
+	if (path_is_directory(s)) {
+		return s;
+	}
+
+	isize i = s.len-1;
+	for (; i >= 0; i--) {
+		if (s[i] == '/' ||
+		    s[i] == '\\') {
+			break;
+		}
+	}
+	if (i >= 0) {
+		return substring(s, 0, i);	
+	}
+	return substring(s, 0, 0);
+}
+
+#if defined(GB_SYSTEM_WINDOWS)
+	gb_internal bool path_is_directory(String path) {
+		gbAllocator a = heap_allocator();
+		String16 wstr = string_to_string16(a, path);
+		defer (gb_free(a, wstr.text));
+
+		i32 attribs = GetFileAttributesW(wstr.text);
+		if (attribs < 0) return false;
+
+		return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
+	}
+
+#else
+	gb_internal bool path_is_directory(String path) {
+		gbAllocator a = heap_allocator();
+		char *copy = cast(char *)copy_string(a, path).text;
+		defer (gb_free(a, copy));
+
+		struct stat s;
+		if (stat(copy, &s) == 0) {
+			return (s.st_mode & S_IFDIR) != 0;
+		}
+		return false;
+	}
+#endif
+
+
+gb_internal String path_to_full_path(gbAllocator a, String path) {
+	gbAllocator ha = heap_allocator();
+	char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
+	defer (gb_free(ha, path_c));
+
+	char *fullpath = gb_path_get_full_name(a, path_c);
+	String res = string_trim_whitespace(make_string_c(fullpath));
+#if defined(GB_SYSTEM_WINDOWS)
+	for (isize i = 0; i < res.len; i++) {
+		if (res.text[i] == '\\') {
+			res.text[i] = '/';
+		}
+	}
+#endif
+	return copy_string(a, res);
+}
+
+struct Path {
+	String basename;
+	String name;
+	String ext;
+};
+
+// NOTE(Jeroen): Naively turns a Path into a string.
+gb_internal String path_to_string(gbAllocator a, Path path) {
+	if (path.basename.len + path.name.len + path.ext.len == 0) {
+		return make_string(nullptr, 0);
+	}
+
+	isize len = path.basename.len + 1 + path.name.len + 1;
+	if (path.ext.len > 0) {
+		 len += path.ext.len + 1;
+	}
+
+	u8 *str = gb_alloc_array(a, u8, len);
+
+	isize i = 0;
+	gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
+	
+	gb_memmove(str+i, "/", 1);                                i += 1;
+	
+	gb_memmove(str+i, path.name.text,     path.name.len);     i += path.name.len;
+	if (path.ext.len > 0) {
+		gb_memmove(str+i, ".", 1);                            i += 1;
+		gb_memmove(str+i, path.ext.text,  path.ext.len);      i += path.ext.len;
+	}
+	str[i] = 0;
+
+	String res = make_string(str, i);
+	res        = string_trim_whitespace(res);
+	return res;
+}
+
+// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
+gb_internal String path_to_full_path(gbAllocator a, Path path) {
+	String temp = path_to_string(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), temp.text));
+
+	return path_to_full_path(a, temp);
+}
+
+// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
+// and then breaks it into its components to make a Path.
+gb_internal Path path_from_string(gbAllocator a, String const &path) {
+	Path res = {};
+
+	if (path.len == 0) return res;
+
+	String fullpath = path_to_full_path(a, path);
+	defer (gb_free(heap_allocator(), fullpath.text));
+
+	res.basename = directory_from_path(fullpath);	
+	res.basename = copy_string(a, res.basename);
+
+	if (path_is_directory(fullpath)) {
+		// It's a directory. We don't need to tinker with the name and extension.
+		// It could have a superfluous trailing `/`. Remove it if so.
+		if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
+			res.basename.len--;
+		}
+		return res;
+	}
+
+	// Note(Dragos): Is the copy_string required if it's a substring?
+	isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
+	res.name         = substring(fullpath, name_start, fullpath.len);
+	res.name         = remove_extension_from_path(res.name);
+	res.name         = copy_string(a, res.name);
+
+	res.ext          = path_extension(fullpath, false); // false says not to include the dot.
+	res.ext          = copy_string(a, res.ext);
+	return res;
+}
+
+// NOTE(Jeroen): Takes a path String and returns the last path element.
+gb_internal String last_path_element(String const &path) {
+	isize count = 0;
+	u8 * start = (u8 *)(&path.text[path.len - 1]);
+	for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
+		count++;
+		start--;
+	}
+	if (count > 0) {
+		start++; // Advance past the `/` and return the substring.
+		String res = make_string(start, count);
+		return res;
+	}
+	// Must be a root path like `/` or `C:/`, return empty String.
+	return STR_LIT("");
+}
+
+gb_internal bool path_is_directory(Path path) {
+	String path_string = path_to_full_path(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), path_string.text));
+
+	return path_is_directory(path_string);
+}
+
+struct FileInfo {
+	String name;
+	String fullpath;
+	i64    size;
+	bool   is_dir;
+};
+
+enum ReadDirectoryError {
+	ReadDirectory_None,
+
+	ReadDirectory_InvalidPath,
+	ReadDirectory_NotExists,
+	ReadDirectory_Permission,
+	ReadDirectory_NotDir,
+	ReadDirectory_Empty,
+	ReadDirectory_Unknown,
+
+	ReadDirectory_COUNT,
+};
+
+gb_internal i64 get_file_size(String path) {
+	char *c_str = alloc_cstring(heap_allocator(), path);
+	defer (gb_free(heap_allocator(), c_str));
+
+	gbFile f = {};
+	gbFileError err = gb_file_open(&f, c_str);
+	defer (gb_file_close(&f));
+	if (err != gbFileError_None) {
+		return -1;
+	}
+	return gb_file_size(&f);
+}
+
+
+#if defined(GB_SYSTEM_WINDOWS)
+gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+	GB_ASSERT(fi != nullptr);
+
+
+	while (path.len > 0) {
+		Rune end = path[path.len-1];
+		if (end == '/') {
+			path.len -= 1;
+		} else if (end == '\\') {
+			path.len -= 1;
+		} else {
+			break;
+		}
+	}
+
+	if (path.len == 0) {
+		return ReadDirectory_InvalidPath;
+	}
+	{
+		char *c_str = alloc_cstring(temporary_allocator(), path);
+		gbFile f = {};
+		gbFileError file_err = gb_file_open(&f, c_str);
+		defer (gb_file_close(&f));
+
+		switch (file_err) {
+		case gbFileError_Invalid:    return ReadDirectory_InvalidPath;
+		case gbFileError_NotExists:  return ReadDirectory_NotExists;
+		// case gbFileError_Permission: return ReadDirectory_Permission;
+		}
+	}
+
+	if (!path_is_directory(path)) {
+		return ReadDirectory_NotDir;
+	}
+
+
+	gbAllocator a = heap_allocator();
+	char *new_path = gb_alloc_array(a, char, path.len+3);
+	defer (gb_free(a, new_path));
+
+	gb_memmove(new_path, path.text, path.len);
+	gb_memmove(new_path+path.len, "/*", 2);
+	new_path[path.len+2] = 0;
+
+	String np = make_string(cast(u8 *)new_path, path.len+2);
+	String16 wstr = string_to_string16(a, np);
+	defer (gb_free(a, wstr.text));
+
+	WIN32_FIND_DATAW file_data = {};
+	HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
+	if (find_file == INVALID_HANDLE_VALUE) {
+		return ReadDirectory_Unknown;
+	}
+	defer (FindClose(find_file));
+
+	array_init(fi, a, 0, 100);
+
+	do {
+		wchar_t *filename_w = file_data.cFileName;
+		u64 size = cast(u64)file_data.nFileSizeLow;
+		size |= (cast(u64)file_data.nFileSizeHigh) << 32;
+		String name = string16_to_string(a, make_string16_c(filename_w));
+		if (name == "." || name == "..") {
+			gb_free(a, name.text);
+			continue;
+		}
+
+		String filepath = {};
+		filepath.len = path.len+1+name.len;
+		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+		defer (gb_free(a, filepath.text));
+		gb_memmove(filepath.text, path.text, path.len);
+		gb_memmove(filepath.text+path.len, "/", 1);
+		gb_memmove(filepath.text+path.len+1, name.text, name.len);
+
+		FileInfo info = {};
+		info.name = name;
+		info.fullpath = path_to_full_path(a, filepath);
+		info.size = cast(i64)size;
+		info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+		array_add(fi, info);
+	} while (FindNextFileW(find_file, &file_data));
+
+	if (fi->count == 0) {
+		return ReadDirectory_Empty;
+	}
+
+	return ReadDirectory_None;
+}
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU)
+
+#include <dirent.h>
+
+gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
+	GB_ASSERT(fi != nullptr);
+
+	gbAllocator a = heap_allocator();
+
+	char *c_path = alloc_cstring(a, path);
+	defer (gb_free(a, c_path));
+
+	DIR *dir = opendir(c_path);
+	if (!dir) {
+		switch (errno) {
+		case ENOENT:
+			return ReadDirectory_NotExists;
+		case EACCES:
+			return ReadDirectory_Permission;
+		case ENOTDIR:
+			return ReadDirectory_NotDir;
+		default:
+			// ENOMEM: out of memory
+			// EMFILE: per-process limit on open fds reached
+			// ENFILE: system-wide limit on total open files reached
+			return ReadDirectory_Unknown;
+		}
+		GB_PANIC("unreachable");
+	}
+
+	array_init(fi, a, 0, 100);
+
+	for (;;) {
+		struct dirent *entry = readdir(dir);
+		if (entry == nullptr) {
+			break;
+		}
+
+		String name = make_string_c(entry->d_name);
+		if (name == "." || name == "..") {
+			continue;
+		}
+
+		String filepath = {};
+		filepath.len = path.len+1+name.len;
+		filepath.text = gb_alloc_array(a, u8, filepath.len+1);
+		defer (gb_free(a, filepath.text));
+		gb_memmove(filepath.text, path.text, path.len);
+		gb_memmove(filepath.text+path.len, "/", 1);
+		gb_memmove(filepath.text+path.len+1, name.text, name.len);
+		filepath.text[filepath.len] = 0;
+
+
+		struct stat dir_stat = {};
+
+		if (stat((char *)filepath.text, &dir_stat)) {
+			continue;
+		}
+
+		if (S_ISDIR(dir_stat.st_mode)) {
+			continue;
+		}
+
+		i64 size = dir_stat.st_size;
+
+		FileInfo info = {};
+		info.name = name;
+		info.fullpath = path_to_full_path(a, filepath);
+		info.size = size;
+		array_add(fi, info);
+	}
+
+	if (fi->count == 0) {
+		return ReadDirectory_Empty;
+	}
+
+	return ReadDirectory_None;
+}
+
+
+#else
+#error Implement read_directory
+#endif
+
+#if !defined(GB_SYSTEM_WINDOWS)
+gb_internal bool write_directory(String path) {
+	char const *pathname = (char *) path.text;
+
+	if (access(pathname, W_OK) < 0) {
+		return false;
+	}
+
+	return true;
+}
+#else
+gb_internal bool write_directory(String path) {
+	String16 wstr = string_to_string16(heap_allocator(), path);
+	LPCWSTR wdirectory_name = wstr.text;
+
+	HANDLE directory = CreateFileW(wdirectory_name,
+			GENERIC_WRITE,
+			0,
+			NULL,
+			OPEN_EXISTING,
+			FILE_FLAG_BACKUP_SEMANTICS,
+			NULL);
+
+	if (directory == INVALID_HANDLE_VALUE) {
+		DWORD error_code = GetLastError();
+		if (error_code == ERROR_ACCESS_DENIED) {
+			return false;
+		}
+	}
+
+	CloseHandle(directory);
+	return true;
+}
+#endif

+ 173 - 1
src/threading.cpp

@@ -492,6 +492,8 @@ gb_internal u32 thread_current_id(void) {
 	__asm__("mov %%fs:0x10,%0" : "=r"(thread_id));
 #elif defined(GB_SYSTEM_LINUX)
 	thread_id = gettid();
+#elif defined(GB_SYSTEM_HAIKU)
+	thread_id = find_thread(NULL);
 #else
 	#error Unsupported architecture for thread_current_id()
 #endif
@@ -831,8 +833,178 @@ gb_internal void futex_wait(Futex *f, Footex val) {
 		WaitOnAddress(f, (void *)&val, sizeof(val), INFINITE);
 	} while (f->load() == val);
 }
+
+#elif defined(GB_SYSTEM_HAIKU)
+
+// Futex implementation taken from https://tavianator.com/2023/futex.html
+
+#include <pthread.h>
+#include <atomic>
+
+struct _Spinlock {
+	std::atomic_flag state;
+
+	void init() {
+		state.clear();
+	}
+
+	void lock() {
+		while (state.test_and_set(std::memory_order_acquire)) {
+			#if defined(GB_CPU_X86)
+			_mm_pause();
+			#else
+			(void)0; // spin...
+			#endif
+		}
+	}
+
+	void unlock() {
+		state.clear(std::memory_order_release);
+	}
+};
+
+struct Futex_Waitq;
+ 
+struct Futex_Waiter {
+	_Spinlock lock;
+	pthread_t thread;
+	Futex *futex;
+	Futex_Waitq *waitq;
+	Futex_Waiter *prev, *next;	
+};
+ 
+struct Futex_Waitq {
+	_Spinlock lock;
+	Futex_Waiter list;
+ 
+	void init() {
+		auto head = &list;
+		head->prev = head->next = head;
+	}
+};
+
+// FIXME: This approach may scale badly in the future,
+// possible solution - hash map (leads to deadlocks now).
+ 
+Futex_Waitq g_waitq = {
+	.lock = ATOMIC_FLAG_INIT,
+	.list = {
+		.prev = &g_waitq.list,
+		.next = &g_waitq.list,
+	},
+};
+ 
+Futex_Waitq *get_waitq(Futex *f) {
+	// Future hash map method...
+	return &g_waitq;
+}
+ 
+void futex_signal(Futex *f) {
+	auto waitq = get_waitq(f);
+ 
+	waitq->lock.lock();
+ 
+	auto head = &waitq->list;
+	for (auto waiter = head->next; waiter != head; waiter = waiter->next) {
+		if (waiter->futex != f) {
+			continue;
+		}
+		waitq->lock.unlock();
+		pthread_kill(waiter->thread, SIGCONT);
+		return;
+	}
+ 
+	waitq->lock.unlock();
+}
+ 
+void futex_broadcast(Futex *f) {
+	auto waitq = get_waitq(f);
+ 
+	waitq->lock.lock();
+ 
+	auto head = &waitq->list;
+	for (auto waiter = head->next; waiter != head; waiter = waiter->next) {
+		if (waiter->futex != f) {
+			continue;
+		}
+		if (waiter->next == head) {
+			waitq->lock.unlock();
+			pthread_kill(waiter->thread, SIGCONT);
+			return;
+		} else {
+			pthread_kill(waiter->thread, SIGCONT);
+		}
+	}
+ 
+	waitq->lock.unlock();
+}
+ 
+void futex_wait(Futex *f, Footex val) {
+	Futex_Waiter waiter;
+	waiter.thread = pthread_self();
+	waiter.futex = f;
+
+	auto waitq = get_waitq(f);
+	while (waitq->lock.state.test_and_set(std::memory_order_acquire)) {
+		if (f->load(std::memory_order_relaxed) != val) {
+			return;
+		}
+		#if defined(GB_CPU_X86)
+		_mm_pause();
+		#else
+		(void)0; // spin...
+		#endif
+	}
+
+	waiter.waitq = waitq;
+	waiter.lock.init();
+	waiter.lock.lock();
+ 
+	auto head = &waitq->list;
+	waiter.prev = head->prev;
+	waiter.next = head;
+	waiter.prev->next = &waiter;
+	waiter.next->prev = &waiter;
+ 
+	waiter.prev->next = &waiter;
+	waiter.next->prev = &waiter;
+ 
+	sigset_t old_mask, mask;
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGCONT);
+	pthread_sigmask(SIG_BLOCK, &mask, &old_mask);
+
+	if (f->load(std::memory_order_relaxed) == val) {
+			waiter.lock.unlock();
+			waitq->lock.unlock();
+
+			int sig;
+			sigwait(&mask, &sig);
+
+			waitq->lock.lock();
+			waiter.lock.lock();
+
+			while (waitq != waiter.waitq) {
+				auto req = waiter.waitq;
+				waiter.lock.unlock();
+				waitq->lock.unlock();
+				waitq = req;
+				waitq->lock.lock();
+				waiter.lock.lock();
+			}
+	}
+ 
+	waiter.prev->next = waiter.next;
+	waiter.next->prev = waiter.prev;
+ 
+	pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
+ 
+	waiter.lock.unlock();
+	waitq->lock.unlock();
+}
+
 #endif
 
 #if defined(GB_SYSTEM_WINDOWS)
 	#pragma warning(pop)
-#endif
+#endif

+ 1 - 0
src/tilde.cpp

@@ -825,6 +825,7 @@ gb_internal bool cg_generate_code(Checker *c, LinkerData *linker_data) {
 		case TargetOs_essence:
 		case TargetOs_freebsd:
 		case TargetOs_openbsd:
+		case TargetOs_haiku:
 			debug_format = TB_DEBUGFMT_DWARF;
 			break;
 		}