Browse Source

Merge pull request #1557 from semarie/openbsd-support

initial OpenBSD support
gingerBill 3 years ago
parent
commit
dd9843aa21

+ 3 - 0
.github/workflows/ci.yml

@@ -38,6 +38,9 @@ jobs:
           cd tests/vendor
           make
         timeout-minutes: 10
+      - name: Odin check examples/all for OpenBSD amd64
+        run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
+        timeout-minutes: 10
   build_macOS:
     runs-on: macos-latest
     steps:

+ 11 - 3
Makefile

@@ -1,6 +1,6 @@
 GIT_SHA=$(shell git rev-parse --short HEAD)
 DISABLED_WARNINGS=-Wno-switch -Wno-macro-redefined -Wno-unused-value
-LDFLAGS=-pthread -ldl -lm -lstdc++
+LDFLAGS=-pthread -lm -lstdc++
 CFLAGS=-std=c++14 -DGIT_SHA=\"$(GIT_SHA)\"
 CFLAGS:=$(CFLAGS) -DODIN_VERSION_RAW=\"dev-$(shell date +"%Y-%m")\"
 CC=clang
@@ -8,7 +8,7 @@ CC=clang
 OS=$(shell uname)
 
 ifeq ($(OS), Darwin)
-    
+
     ARCH=$(shell uname -m)
     LLVM_CONFIG=llvm-config
 
@@ -35,7 +35,7 @@ ifeq ($(OS), Darwin)
         endif 
     endif 
 
-    LDFLAGS:=$(LDFLAGS) -liconv
+    LDFLAGS:=$(LDFLAGS) -liconv -ldl
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
     LDFLAGS:=$(LDFLAGS) -lLLVM-C
 endif
@@ -51,6 +51,14 @@ ifeq ($(OS), Linux)
         endif
     endif
 
+    LDFLAGS:=$(LDFLAGS) -ldl
+    CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
+    LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
+endif
+ifeq ($(OS), OpenBSD)
+    LLVM_CONFIG=/usr/local/bin/llvm-config
+
+    LDFLAGS:=$(LDFLAGS) -liconv
     CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags)
     LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs)
 endif

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

@@ -27,6 +27,19 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
 	ERANGE :: 34
 }
 
+when ODIN_OS == .OpenBSD {
+	@(private="file")
+	@(default_calling_convention="c")
+	foreign libc {
+		@(link_name="__errno")
+		_get_errno :: proc() -> ^int ---
+	}
+
+	EDOM   :: 33
+	EILSEQ :: 84
+	ERANGE :: 34
+}
+
 when ODIN_OS == .Windows {
 	@(private="file")
 	@(default_calling_convention="c")

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

@@ -78,6 +78,31 @@ when ODIN_OS == .Linux {
 	}
 }
 
+when ODIN_OS == .OpenBSD {
+	fpos_t :: i64
+
+	_IOFBF :: 0
+	_IOLBF :: 1
+	_IONBF :: 1
+
+	BUFSIZ :: 1024
+
+	EOF :: int(-1)
+
+	FOPEN_MAX	:: 20
+	FILENAME_MAX	:: 1024
+
+	SEEK_SET :: 0
+	SEEK_CUR :: 1
+	SEEK_END :: 2
+
+	foreign libc {
+		stderr: ^FILE
+		stdin:  ^FILE
+		stdout: ^FILE
+	}
+}
+
 when ODIN_OS == .Darwin {
 	fpos_t :: distinct i64
 	

+ 7 - 2
core/c/libc/time.odin

@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
 	}
 }
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
 	@(default_calling_convention="c")
 	foreign libc {
 		// 7.27.2 Time manipulation functions
@@ -63,7 +63,12 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin {
 		strftime     :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t ---
 	}
 
-	CLOCKS_PER_SEC :: 1000000
+	when ODIN_OS == .OpenBSD {
+		CLOCKS_PER_SEC :: 100
+	} else {
+		CLOCKS_PER_SEC :: 1000000
+	}
+
 	TIME_UTC       :: 1
 
 	time_t         :: distinct i64

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

@@ -25,6 +25,11 @@ when ODIN_OS == .Darwin {
 	wctype_t  :: distinct u32
 }
 
+when ODIN_OS == .OpenBSD {
+	wctrans_t :: distinct rawptr
+	wctype_t  :: distinct rawptr
+}
+
 @(default_calling_convention="c")
 foreign libc {
 	// 7.30.2.1 Wide character classification functions

+ 1 - 1
core/crypto/rand_generic.odin

@@ -1,6 +1,6 @@
 package crypto
 
-when ODIN_OS != .Linux {
+when ODIN_OS != .Linux && ODIN_OS != .OpenBSD {
 	_rand_bytes :: proc (dst: []byte) {
 		unimplemented("crypto: rand_bytes not supported on this OS")
 	}

+ 12 - 0
core/crypto/rand_openbsd.odin

@@ -0,0 +1,12 @@
+package crypto
+
+import "core:c"
+
+foreign import libc "system:c"
+foreign libc {
+	arc4random_buf :: proc "c" (buf: rawptr, nbytes: c.size_t) ---
+}
+
+_rand_bytes :: proc (dst: []byte) {
+	arc4random_buf(raw_data(dst), len(dst))
+}

+ 1 - 1
core/dynlib/lib_unix.odin

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

+ 71 - 0
core/os/dir_openbsd.odin

@@ -0,0 +1,71 @@
+package os
+
+import "core:strings"
+import "core:mem"
+
+read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
+	dirp: Dir
+	dirp, err = _fdopendir(fd)
+	if err != ERROR_NONE {
+		return
+	}
+
+	defer _closedir(dirp)
+
+	// XXX OpenBSD
+	dirpath: string
+	dirpath, err = absolute_path_from_handle(fd)
+
+	if err != ERROR_NONE {
+		return
+	}
+
+	defer delete(dirpath)
+
+	n := n
+	size := n
+	if n <= 0 {
+		n = -1
+		size = 100
+	}
+
+	dfi := make([dynamic]File_Info, 0, size, allocator)
+
+	for {
+		entry: Dirent
+		end_of_stream: bool
+		entry, err, end_of_stream = _readdir(dirp)
+		if err != ERROR_NONE {
+			for fi_ in dfi {
+				file_info_delete(fi_, allocator)
+			}
+			delete(dfi)
+			return
+		} else if end_of_stream {
+			break
+		}
+
+		fi_: File_Info
+		filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] })
+
+		if filename == "." || filename == ".." {
+			continue
+		}
+
+		fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
+		defer delete(fullpath, context.temp_allocator)
+
+		fi_, err = stat(fullpath, allocator)
+		if err != ERROR_NONE {
+			for fi__ in dfi {
+				file_info_delete(fi__, allocator)
+			}
+			delete(dfi)
+			return
+		}
+
+		append(&dfi, fi_)
+	}
+
+	return dfi[:], ERROR_NONE
+}

+ 706 - 0
core/os/os_openbsd.odin

@@ -0,0 +1,706 @@
+package os
+
+foreign import libc "system:c"
+
+import "core:runtime"
+import "core:strings"
+import "core:c"
+
+Handle    :: distinct i32
+Pid       :: distinct i32
+File_Time :: distinct u64
+Errno     :: distinct i32
+
+INVALID_HANDLE :: ~Handle(0)
+
+ERROR_NONE:	Errno: 0
+
+EPERM:		Errno: 1
+ENOENT:		Errno: 2
+ESRCH:		Errno: 3
+EINTR:		Errno: 4
+EIO:		Errno: 5
+ENXIO:		Errno: 6
+E2BIG:		Errno: 7
+ENOEXEC:	Errno: 8
+EBADF:		Errno: 9
+ECHILD:		Errno: 10
+EDEADLK:	Errno: 11
+ENOMEM:		Errno: 12
+EACCES:		Errno: 13
+EFAULT:		Errno: 14
+ENOTBLK:	Errno: 15
+EBUSY:		Errno: 16
+EEXIST:		Errno: 17
+EXDEV:		Errno: 18
+ENODEV:		Errno: 19
+ENOTDIR:	Errno: 20
+EISDIR:		Errno: 21
+EINVAL:		Errno: 22
+ENFILE:		Errno: 23
+EMFILE:		Errno: 24
+ENOTTY:		Errno: 25
+ETXTBSY:	Errno: 26
+EFBIG:		Errno: 27
+ENOSPC:		Errno: 28
+ESPIPE:		Errno: 29
+EROFS:		Errno: 30
+EMLINK:		Errno: 31
+EPIPE:		Errno: 32
+EDOM:		Errno: 33
+ERANGE:		Errno: 34
+EAGAIN:		Errno: 35
+EWOULDBLOCK:	Errno: EAGAIN
+EINPROGRESS:	Errno: 36
+EALREADY:	Errno: 37
+ENOTSOCK:	Errno: 38
+EDESTADDRREQ:	Errno: 39
+EMSGSIZE:	Errno: 40
+EPROTOTYPE:	Errno: 41
+ENOPROTOOPT:	Errno: 42
+EPROTONOSUPPORT: Errno: 43
+ESOCKTNOSUPPORT: Errno: 44
+EOPNOTSUPP:	Errno: 45
+EPFNOSUPPORT:	Errno: 46
+EAFNOSUPPORT:	Errno: 47
+EADDRINUSE:	Errno: 48
+EADDRNOTAVAIL:	Errno: 49
+ENETDOWN:	Errno: 50
+ENETUNREACH:	Errno: 51
+ENETRESET:	Errno: 52
+ECONNABORTED:	Errno: 53
+ECONNRESET:	Errno: 54
+ENOBUFS:	Errno: 55
+EISCONN:	Errno: 56
+ENOTCONN:	Errno: 57
+ESHUTDOWN:	Errno: 58
+ETOOMANYREFS:	Errno: 59
+ETIMEDOUT:	Errno: 60
+ECONNREFUSED:	Errno: 61
+ELOOP:		Errno: 62
+ENAMETOOLONG:	Errno: 63
+EHOSTDOWN:	Errno: 64
+EHOSTUNREACH:	Errno: 65
+ENOTEMPTY:	Errno: 66
+EPROCLIM:	Errno: 67
+EUSERS:		Errno: 68
+EDQUOT:		Errno: 69
+ESTALE:		Errno: 70
+EREMOTE:	Errno: 71
+EBADRPC:	Errno: 72
+ERPCMISMATCH:	Errno: 73
+EPROGUNAVAIL:	Errno: 74
+EPROGMISMATCH:	Errno: 75
+EPROCUNAVAIL:	Errno: 76
+ENOLCK:		Errno: 77
+ENOSYS:		Errno: 78
+EFTYPE:		Errno: 79
+EAUTH:		Errno: 80
+ENEEDAUTH:	Errno: 81
+EIPSEC:		Errno: 82
+ENOATTR:	Errno: 83
+EILSEQ:		Errno: 84
+ENOMEDIUM:	Errno: 85
+EMEDIUMTYPE:	Errno: 86
+EOVERFLOW:	Errno: 87
+ECANCELED:	Errno: 88
+EIDRM:		Errno: 89
+ENOMSG:		Errno: 90
+ENOTSUP:	Errno: 91
+EBADMSG:	Errno: 92
+ENOTRECOVERABLE: Errno: 93
+EOWNERDEAD:	Errno: 94
+EPROTO:		Errno: 95
+
+O_RDONLY   :: 0x00000
+O_WRONLY   :: 0x00001
+O_RDWR     :: 0x00002
+O_NONBLOCK :: 0x00004
+O_APPEND   :: 0x00008
+O_ASYNC    :: 0x00040
+O_SYNC     :: 0x00080
+O_CREATE   :: 0x00200
+O_TRUNC    :: 0x00400
+O_EXCL     :: 0x00800
+O_NOCTTY   :: 0x08000
+O_CLOEXEC  :: 0x10000
+
+SEEK_SET :: 0
+SEEK_CUR :: 1
+SEEK_END :: 2
+
+RTLD_LAZY     :: 0x001
+RTLD_NOW      :: 0x002
+RTLD_LOCAL    :: 0x000
+RTLD_GLOBAL   :: 0x100
+RTLD_TRACE    :: 0x200
+RTLD_NODELETE :: 0x400
+
+MAX_PATH :: 1024
+
+// "Argv" arguments converted to Odin strings
+args := _alloc_command_line_arguments()
+
+pid_t     :: i32
+time_t    :: i64
+mode_t    :: u32
+dev_t     :: i32
+ino_t     :: u64
+nlink_t   :: u32
+uid_t     :: u32
+gid_t     :: u32
+off_t     :: i64
+blkcnt_t  :: u64
+blksize_t :: i32
+
+Unix_File_Time :: struct {
+	seconds:     time_t,
+	nanoseconds: c.long,
+}
+
+OS_Stat :: struct {
+	mode: mode_t,			// inode protection mode
+	device_id: dev_t,		// inode's device
+	serial: ino_t,			// inode's number
+	nlink: nlink_t,			// number of hard links
+	uid: uid_t,			// user ID of the file's owner
+	gid: gid_t,			// group ID of the file's group
+	rdev: dev_t,			// device type
+
+	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
+
+	size: off_t,			// file size, in bytes
+	blocks: blkcnt_t,		// blocks allocated for file
+	block_size:	blksize_t,	// optimal blocksize for I/O
+
+	flags:		u32,		// user defined flags for file
+	gen:		u32,		// file generation number
+	birthtime:	Unix_File_Time,	// time of file creation
+}
+
+MAXNAMLEN :: 255
+
+// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above
+Dirent :: struct {
+	ino:      ino_t,	// file number of entry
+	off:      off_t,	// offset after this entry
+	reclen:   u16,		// length of this record
+	type:     u8,		// file type
+	namlen:   u8,		// length of string in name
+	_padding: [4]u8,
+	name:     [MAXNAMLEN + 1]byte, // name
+}
+
+Dir :: distinct rawptr // DIR*
+
+// 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 }
+
+F_OK :: 0x00 // Test for file existance
+X_OK :: 0x01 // Test for execute permission
+W_OK :: 0x02 // Test for write permission
+R_OK :: 0x04 // Test for read permission
+
+AT_FDCWD            :: -100
+AT_EACCESS          :: 0x01
+AT_SYMLINK_NOFOLLOW :: 0x02
+AT_SYMLINK_FOLLOW   :: 0x04
+AT_REMOVEDIR        :: 0x08
+
+@(default_calling_convention="c")
+foreign libc {
+	@(link_name="__errno")	__errno		:: proc() -> ^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="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 ---
+}
+
+is_path_separator :: proc(r: rune) -> bool {
+	return r == '/'
+}
+
+get_last_error :: proc() -> int {
+	return __errno()^
+}
+
+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) {
+	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
+}
+
+read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
+	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
+	}
+	bytes_written := _unix_write(fd, &data[0], c.size_t(len(data)))
+	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
+}
+
+rename :: proc(old_path, new_path: string) -> Errno {
+	old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
+	new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
+	res := _unix_rename(old_path_cstr, new_path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove :: proc(path: string) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_unlink(path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_mkdir(path_cstr, mode)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+remove_directory :: proc(path: string) -> Errno {
+	path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_rmdir(path_cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+is_file_handle :: proc(fd: Handle) -> bool {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
+	s: OS_Stat
+	err: Errno
+	if follow_links {
+		s, err = _stat(path)
+	} else {
+		s, err = _lstat(path)
+	}
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISREG(s.mode)
+}
+
+is_dir_handle :: proc(fd: Handle) -> bool {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
+	s: OS_Stat
+	err: Errno
+	if follow_links {
+		s, err = _stat(path)
+	} else {
+		s, err = _lstat(path)
+	}
+	if err != ERROR_NONE {
+		return false
+	}
+	return S_ISDIR(s.mode)
+}
+
+is_file :: proc {is_file_path, is_file_handle}
+is_dir :: proc {is_dir_path, is_dir_handle}
+
+// NOTE(bill): Uses startup to initialize it
+
+stdin:  Handle = 0
+stdout: Handle = 1
+stderr: Handle = 2
+
+/* TODO(zangent): Implement these!                                                                                
+last_write_time :: proc(fd: Handle) -> File_Time {}                                                               
+last_write_time_by_name :: proc(name: string) -> File_Time {}                                                     
+*/
+last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
+	s, err := _fstat(fd)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
+	s, err := _stat(name)
+	if err != ERROR_NONE {
+		return 0, err
+	}
+	modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
+	return File_Time(modified), ERROR_NONE
+}
+
+@private
+_stat :: proc(path: string) -> (OS_Stat, Errno) {
+	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) {
+	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) {
+	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
+		}	
+	}
+	unreachable()
+}
+
+// XXX OpenBSD
+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 = "."
+	}
+
+	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) {
+	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
+}
+
+heap_alloc :: proc(size: int) -> rawptr {
+	assert(size >= 0)
+	return _unix_calloc(1, c.size_t(size))
+}
+
+heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
+	// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
+	// POSIX platforms. Ensure your caller takes this into account.
+	return _unix_realloc(ptr, c.size_t(new_size))
+}
+
+heap_free :: proc(ptr: rawptr) {
+	_unix_free(ptr)
+}
+
+getenv :: proc(name: string) -> (string, bool) {
+	path_str := strings.clone_to_cstring(name, context.temp_allocator)
+	cstr := _unix_getenv(path_str)
+	if cstr == nil {
+		return "", false
+	}
+	return string(cstr), true
+}
+
+get_current_directory :: proc() -> string {
+	buf := make([dynamic]u8, MAX_PATH)
+	for {
+		cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf)))
+		if cwd != nil {
+			return string(cwd)
+		}
+		if Errno(get_last_error()) != ERANGE {
+			return ""
+		}
+		resize(&buf, len(buf) + MAX_PATH)
+	}
+	unreachable()
+}
+
+set_current_directory :: proc(path: string) -> (err: Errno) {
+	cstr := strings.clone_to_cstring(path, context.temp_allocator)
+	res := _unix_chdir(cstr)
+	if res == -1 {
+		return Errno(get_last_error())
+	}
+	return ERROR_NONE
+}
+
+exit :: proc "contextless" (code: int) -> ! {
+	_unix_exit(c.int(code))
+}
+
+current_thread_id :: proc "contextless" () -> int {
+	return _unix_getthrid()
+}
+
+dlopen :: proc(filename: string, flags: int) -> rawptr {
+	cstr := strings.clone_to_cstring(filename, context.temp_allocator)
+	handle := _unix_dlopen(cstr, c.int(flags))
+	return handle
+}
+dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
+	assert(handle != nil)
+	cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
+	proc_handle := _unix_dlsym(handle, cstr)
+	return proc_handle
+}
+dlclose :: proc(handle: rawptr) -> bool {
+	assert(handle != nil)
+	return _unix_dlclose(handle) == 0
+}
+dlerror :: proc() -> string {
+	return string(_unix_dlerror())
+}
+
+get_page_size :: proc() -> int {
+	// NOTE(tetra): The page size never changes, so why do anything complicated
+	// if we don't have to.
+	@static page_size := -1
+	if page_size != -1 {
+		return page_size
+	}
+
+	page_size = int(_unix_getpagesize())
+	return page_size
+}
+
+
+_alloc_command_line_arguments :: proc() -> []string {
+	res := make([]string, len(runtime.args__))
+	for arg, i in runtime.args__ {
+		res[i] = string(arg)
+	}
+	return res
+}

+ 1 - 1
core/os/stat_unix.odin

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

+ 6 - 1
core/path/filepath/path_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package filepath
 
 when ODIN_OS == .Darwin {
@@ -59,6 +59,11 @@ when ODIN_OS == .Darwin {
 	foreign libc {
 		@(link_name="__error")          __error :: proc() -> ^i32 ---
 	}
+} else when ODIN_OS == .OpenBSD {
+	@(private)
+	foreign libc {
+		@(link_name="__errno")		__error :: proc() -> ^i32 ---
+	}
 } else {
 	@(private)
 	foreign libc {

+ 1 - 1
core/runtime/entry_unix.odin

@@ -1,5 +1,5 @@
 //+private
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package runtime
 
 import "core:intrinsics"

+ 1 - 1
core/sync/channel_unix.odin

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

+ 78 - 0
core/sync/sync2/futex_openbsd.odin

@@ -0,0 +1,78 @@
+//+private
+//+build openbsd
+package sync2
+
+import "core:c"
+import "core:os"
+import "core:time"
+
+FUTEX_WAIT :: 1
+FUTEX_WAKE :: 2
+
+FUTEX_PRIVATE_FLAG :: 128
+
+FUTEX_WAIT_PRIVATE :: (FUTEX_WAIT | FUTEX_PRIVATE_FLAG)
+FUTEX_WAKE_PRIVATE :: (FUTEX_WAKE | FUTEX_PRIVATE_FLAG)
+
+foreign import libc "system:c"
+
+foreign libc {
+	@(link_name="futex")
+	_unix_futex :: proc "c" (f: ^Futex, op: c.int, val: u32, timeout: rawptr) -> c.int ---
+}
+
+_futex_wait :: proc(f: ^Futex, expected: u32) -> bool {
+	res := _unix_futex(f, FUTEX_WAIT_PRIVATE, expected, nil)
+
+	if res != -1 {
+		return true
+	}
+
+	if os.Errno(os.get_last_error()) == os.ETIMEDOUT {
+		return false
+	}
+
+	panic("futex_wait failure")
+}
+
+_futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool {
+	if duration <= 0 {
+		return false
+	}
+
+	timespec_t :: struct {
+		tv_sec:  c.long,
+		tv_nsec: c.long,
+        }
+
+	res := _unix_futex(f, FUTEX_WAIT_PRIVATE, expected, &timespec_t{
+		tv_sec  = (c.long)(duration/1e9),
+		tv_nsec = (c.long)(duration%1e9),
+	})
+
+	if res != -1 {
+		return true
+	}
+
+	if os.Errno(os.get_last_error()) == os.ETIMEDOUT {
+		return false
+	}
+
+	panic("futex_wait_with_timeout failure")
+}
+
+_futex_signal :: proc(f: ^Futex) {
+	res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil)
+
+	if res == -1 {
+		panic("futex_wake_single failure")
+	}
+}
+
+_futex_broadcast :: proc(f: ^Futex)  {
+	res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil)
+
+	if res == -1 {
+		panic("_futex_wake_all failure")
+	}
+}

+ 9 - 0
core/sync/sync2/primitives_openbsd.odin

@@ -0,0 +1,9 @@
+//+build openbsd
+//+private
+package sync2
+
+import "core:os"
+
+_current_thread_id :: proc "contextless" () -> int {
+	return os.current_thread_id()
+}

+ 1 - 1
core/sync/sync2/primitives_pthreads.odin

@@ -1,4 +1,4 @@
-//+build linux, freebsd
+//+build linux, freebsd, openbsd
 //+private
 package sync2
 

+ 36 - 0
core/sync/sync_openbsd.odin

@@ -0,0 +1,36 @@
+package sync
+
+import "core:sys/unix"
+import "core:os"
+
+current_thread_id :: proc "contextless" () -> int {
+	return os.current_thread_id()
+}
+
+// The Darwin docs say it best:
+// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously.
+// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, 
+// but when there are none left, a thread must wait until another thread returns one.
+Semaphore :: struct #align 16 {
+	handle: unix.sem_t,
+}
+
+semaphore_init :: proc(s: ^Semaphore, initial_count := 0) {
+	assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0)
+}
+
+semaphore_destroy :: proc(s: ^Semaphore) {
+	assert(unix.sem_destroy(&s.handle) == 0)
+	s.handle = {}
+}
+
+semaphore_post :: proc(s: ^Semaphore, count := 1) {
+    // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop.
+    for in 0..<count {
+	    assert(unix.sem_post(&s.handle) == 0)
+    }
+}
+
+semaphore_wait_for :: proc(s: ^Semaphore) {
+	assert(unix.sem_wait(&s.handle) == 0)
+}

+ 1 - 1
core/sync/sync_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 package sync
 
 import "core:sys/unix"

+ 65 - 0
core/sys/unix/pthread_openbsd.odin

@@ -0,0 +1,65 @@
+//+build openbsd
+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_ERRORCHECK :: 1
+PTHREAD_MUTEX_RECURSIVE  :: 2
+PTHREAD_MUTEX_NORMAL     :: 3
+PTHREAD_MUTEX_STRICT_NP  :: 4
+
+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_OTHER :: 2
+SCHED_RR    :: 3
+
+sched_param :: struct {
+	sched_priority: c.int,
+}
+
+sem_t :: distinct rawptr
+
+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 ---
+	//sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---
+
+	// NOTE: unclear whether pthread_yield is well-supported on Linux systems,
+	// see https://linux.die.net/man/3/pthread_yield
+	pthread_yield :: proc() ---
+}

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

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package unix
 
 foreign import "system:pthread"

+ 1 - 1
core/thread/thread_unix.odin

@@ -1,4 +1,4 @@
-// +build linux, darwin, freebsd
+// +build linux, darwin, freebsd, openbsd
 // +private
 package thread
 

+ 23 - 11
core/time/time_unix.odin

@@ -1,4 +1,4 @@
-//+build linux, darwin, freebsd
+//+build linux, darwin, freebsd, openbsd
 package time
 
 IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC.
@@ -22,16 +22,28 @@ TimeSpec :: struct {
 	tv_nsec : i64,  /* nanoseconds */
 }
 
-CLOCK_REALTIME           :: 0 // NOTE(tetra): May jump in time, when user changes the system time.
-CLOCK_MONOTONIC          :: 1 // NOTE(tetra): May stand still while system is asleep.
-CLOCK_PROCESS_CPUTIME_ID :: 2
-CLOCK_THREAD_CPUTIME_ID  :: 3
-CLOCK_MONOTONIC_RAW      :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
-CLOCK_REALTIME_COARSE    :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained."
-CLOCK_MONOTONIC_COARSE   :: 6
-CLOCK_BOOTTIME           :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep.
-CLOCK_REALTIME_ALARM     :: 8
-CLOCK_BOOTTIME_ALARM     :: 9
+when ODIN_OS == .OpenBSD {
+	CLOCK_REALTIME           :: 0
+	CLOCK_PROCESS_CPUTIME_ID :: 2
+	CLOCK_MONOTONIC          :: 3
+	CLOCK_THREAD_CPUTIME_ID  :: 4
+	CLOCK_UPTIME             :: 5
+	CLOCK_BOOTTIME           :: 6
+
+	// CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC
+	CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC
+} else {
+	CLOCK_REALTIME           :: 0 // NOTE(tetra): May jump in time, when user changes the system time.
+	CLOCK_MONOTONIC          :: 1 // NOTE(tetra): May stand still while system is asleep.
+	CLOCK_PROCESS_CPUTIME_ID :: 2
+	CLOCK_THREAD_CPUTIME_ID  :: 3
+	CLOCK_MONOTONIC_RAW      :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP.
+	CLOCK_REALTIME_COARSE    :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained."
+	CLOCK_MONOTONIC_COARSE   :: 6
+	CLOCK_BOOTTIME           :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep.
+	CLOCK_REALTIME_ALARM     :: 8
+	CLOCK_BOOTTIME_ALARM     :: 9
+}
 
 // TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants.
 // I do not know if Darwin programmers are used to the existance of these constants or not, so

+ 22 - 1
src/bug_report.cpp

@@ -17,6 +17,11 @@
 	#include <sys/sysctl.h>
 #endif
 
+#if defined(GB_SYSTEM_OPENBSD)
+	#include <sys/sysctl.h>
+	#include <sys/utsname.h>
+#endif
+
 /*
 	NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`.
 */
@@ -242,6 +247,14 @@ void report_ram_info() {
 		if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
 			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
 		}
+	#elif defined(GB_SYSTEM_OPENBSD)
+		uint64_t ram_amount;
+		size_t   val_size = sizeof(ram_amount);
+
+		int sysctls[] = { CTL_HW, HW_PHYSMEM64 };
+		if (sysctl(sysctls, 2, &ram_amount, &val_size, NULL, 0) != -1) {
+			gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1));
+		}
 	#else
 		gb_printf("Unknown.\n");
 	#endif
@@ -643,6 +656,14 @@ void print_bug_report_help() {
 		} else {
 			gb_printf("macOS: Unknown\n");
 		}			
+	#elif defined(GB_SYSTEM_OPENBSD)
+		struct utsname un;
+		
+		if (uname(&un) != -1) {
+			gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine);
+		} else {
+			gb_printf("OpenBSD: Unknown\n");    
+		}
 	#else
 		gb_printf("Unknown\n");
 
@@ -657,4 +678,4 @@ void print_bug_report_help() {
 		And RAM info.
 	*/
 	report_ram_info();
-}
+}

+ 48 - 3
src/build_settings.cpp

@@ -1,4 +1,4 @@
-#if defined(GB_SYSTEM_FREEBSD)
+#if defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #endif
@@ -16,6 +16,7 @@ enum TargetOsKind {
 	TargetOs_linux,
 	TargetOs_essence,
 	TargetOs_freebsd,
+	TargetOs_openbsd,
 	
 	TargetOs_wasi,
 	TargetOs_js,
@@ -53,6 +54,7 @@ String target_os_names[TargetOs_COUNT] = {
 	str_lit("linux"),
 	str_lit("essence"),
 	str_lit("freebsd"),
+	str_lit("openbsd"),
 	
 	str_lit("wasi"),
 	str_lit("js"),
@@ -354,6 +356,15 @@ gb_global TargetMetrics target_freebsd_amd64 = {
 	str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"),
 };
 
+gb_global TargetMetrics target_openbsd_amd64 = {
+	TargetOs_openbsd,
+	TargetArch_amd64,
+	8,
+	16,
+	str_lit("x86_64-unknown-openbsd-elf"),
+	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_essence_amd64 = {
 	TargetOs_essence,
 	TargetArch_amd64,
@@ -417,6 +428,7 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("windows_amd64"),       &target_windows_amd64  },
 	{ str_lit("freebsd_386"),         &target_freebsd_386    },
 	{ str_lit("freebsd_amd64"),       &target_freebsd_amd64  },
+	{ str_lit("openbsd_amd64"),       &target_openbsd_amd64  },
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
@@ -722,10 +734,38 @@ String internal_odin_root_dir(void) {
 		len = readlink("/proc/curproc/exe", &path_buf[0], path_buf.count);
 #elif defined(GB_SYSTEM_DRAGONFLYBSD)
 		len = readlink("/proc/curproc/file", &path_buf[0], path_buf.count);
-#else
+#elif defined(GB_SYSTEM_LINUX)
 		len = readlink("/proc/self/exe", &path_buf[0], path_buf.count);
+#elif defined(GB_SYSTEM_OPENBSD)
+		int error;
+		int mib[] = {
+			CTL_KERN,
+			KERN_PROC_ARGS,
+			getpid(),
+			KERN_PROC_ARGV,
+		};
+		// get argv size
+		error = sysctl(mib, 4, NULL, (size_t *) &len, NULL, 0);
+		if (error == -1) {
+			// sysctl error
+			return make_string(nullptr, 0);
+		}
+		// get argv
+		char **argv = (char **)gb_malloc(len);
+		error = sysctl(mib, 4, argv, (size_t *) &len, NULL, 0);
+		if (error == -1) {
+			// sysctl error
+			gb_mfree(argv);
+			return make_string(nullptr, 0);
+		}
+		// copy argv[0] to path_buf
+		len = gb_strlen(argv[0]);
+		if(len < path_buf.count) {
+			gb_memmove(&path_buf[0], argv[0], len);
+		}
+		gb_mfree(argv);
 #endif
-		if(len == 0) {
+		if(len == 0 || len == -1) {
 			return make_string(nullptr, 0);
 		}
 		if (len < path_buf.count) {
@@ -922,6 +962,8 @@ void init_build_context(TargetMetrics *cross_target) {
 			#endif
 		#elif defined(GB_SYSTEM_FREEBSD)
 			metrics = &target_freebsd_amd64;
+		#elif defined(GB_SYSTEM_OPENBSD)
+			metrics = &target_openbsd_amd64;
 		#elif defined(GB_CPU_ARM)
 			metrics = &target_linux_arm64;
 		#else
@@ -980,6 +1022,9 @@ void init_build_context(TargetMetrics *cross_target) {
 		case TargetOs_freebsd:
 			bc->link_flags = str_lit("-arch x86-64 ");
 			break;
+		case TargetOs_openbsd:
+			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

@@ -3506,6 +3506,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			case TargetOs_linux:
 			case TargetOs_essence:
 			case TargetOs_freebsd:
+			case TargetOs_openbsd:
 				switch (build_context.metrics.arch) {
 				case TargetArch_i386:
 				case TargetArch_amd64:

+ 1 - 0
src/checker.cpp

@@ -906,6 +906,7 @@ void init_universal(void) {
 			{"Linux",        TargetOs_linux},
 			{"Essence",      TargetOs_essence},
 			{"FreeBSD",      TargetOs_freebsd},
+			{"OpenBSD",      TargetOs_openbsd},
 			{"WASI",         TargetOs_wasi},
 			{"JS",           TargetOs_js},
 			{"Freestanding", TargetOs_freestanding},

+ 1 - 1
src/common.cpp

@@ -848,7 +848,7 @@ ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
 
 	return ReadDirectory_None;
 }
-#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD)
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 
 #include <dirent.h>
 

+ 86 - 2
src/gb/gb.h

@@ -79,6 +79,10 @@ extern "C" {
 		#ifndef GB_SYSTEM_FREEBSD
 		#define GB_SYSTEM_FREEBSD 1
 		#endif
+	#elif defined(__OpenBSD__)
+		#ifndef GB_SYSTEM_OPENBSD
+		#define GB_SYSTEM_OPENBSD 1
+		#endif
 	#else
 		#error This UNIX operating system is not supported
 	#endif
@@ -199,7 +203,7 @@ extern "C" {
 	#endif
 	#include <stdlib.h> // NOTE(bill): malloc on linux
 	#include <sys/mman.h>
-	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__)
+	#if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
 		#include <sys/sendfile.h>
 	#endif
 	#include <sys/stat.h>
@@ -235,6 +239,12 @@ extern "C" {
 	#define sendfile(out, in, offset, count) sendfile(out, in, offset, count, NULL, NULL, 0)
 #endif
 
+#if defined(GB_SYSTEM_OPENBSD)
+	#include <stdio.h>
+	#include <pthread_np.h>
+	#define lseek64 lseek
+#endif
+    
 #if defined(GB_SYSTEM_UNIX)
 	#include <semaphore.h>
 #endif
@@ -783,6 +793,13 @@ typedef struct gbAffinity {
 	isize thread_count;
 	isize threads_per_core;
 } gbAffinity;
+#elif defined(GB_SYSTEM_OPENBSD)
+typedef struct gbAffinity {
+	b32   is_accurate;
+	isize core_count;
+	isize thread_count;
+	isize threads_per_core;
+} gbAffinity;
 #else
 #error TODO(bill): Unknown system
 #endif
@@ -3678,6 +3695,30 @@ 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_OPENBSD)
+#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;
@@ -6025,7 +6066,7 @@ gbFileTime gb_file_last_write_time(char const *filepath) {
 gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filename, b32 fail_if_exists) {
 #if defined(GB_SYSTEM_OSX)
 	return copyfile(existing_filename, new_filename, NULL, COPYFILE_DATA) == 0;
-#else
+#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_FREEBSD)
 	isize size;
 	int existing_fd = open(existing_filename, O_RDONLY, 0);
 	int new_fd      = open(new_filename, O_WRONLY|O_CREAT, 0666);
@@ -6041,6 +6082,49 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena
 	close(new_fd);
 	close(existing_fd);
 
+	return size == stat_existing.st_size;
+#else
+	int new_flags = O_WRONLY | O_CREAT;
+	if (fail_if_exists) {
+		new_flags |= O_EXCL;
+	}
+	int existing_fd = open(existing_filename, O_RDONLY, 0);
+	int new_fd      = open(new_filename, new_flags, 0666);
+
+	struct stat stat_existing;
+	if (fstat(existing_fd, &stat_existing) == -1) {
+		return 0;
+	}
+
+	size_t bsize = stat_existing.st_blksize > BUFSIZ ? stat_existing.st_blksize : BUFSIZ;
+	char *buf = (char *)malloc(bsize);
+	if (buf == NULL) {
+		close(new_fd);
+		close(existing_fd);
+		return 0;
+	}
+
+	isize size = 0;
+	ssize_t nread, nwrite, offset;
+	while ((nread = read(existing_fd, buf, bsize)) != -1 && nread != 0) {
+		for (offset = 0; nread; nread -= nwrite, offset += nwrite) {
+			if ((nwrite = write(new_fd, buf + offset, nread)) == -1 || nwrite == 0) {
+				free(buf);
+				close(new_fd);
+				close(existing_fd);
+				return 0;
+			}
+			size += nwrite;
+		}
+	}
+	
+	free(buf);
+	close(new_fd);
+	close(existing_fd);
+
+	if (nread == -1) {
+		return 0;
+	}
 	return size == stat_existing.st_size;
 #endif
 }

+ 1 - 1
src/threading.cpp

@@ -486,7 +486,7 @@ void thread_set_name(Thread *t, char const *name) {
 #elif defined(GB_SYSTEM_OSX)
 	// TODO(bill): Test if this works
 	pthread_setname_np(name);
-#elif defined(GB_SYSTEM_FREEBSD)
+#elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
 	pthread_set_name_np(t->posix_handle, name);
 #else
 	// TODO(bill): Test if this works

+ 8 - 1
tests/vendor/Makefile

@@ -1,6 +1,13 @@
 ODIN=../../odin
+ODINFLAGS=
+
+OS=$(shell uname)
+
+ifeq ($(OS), OpenBSD)
+    ODINFLAGS:=$(ODINFLAGS) -extra-linker-flags:-L/usr/local/lib
+endif
 
 all: botan_test
 
 botan_test:
-	$(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check
+	$(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS)

+ 3 - 1
vendor/botan/bindings/botan.odin

@@ -146,6 +146,8 @@ when ODIN_OS == .Windows {
     foreign import botan_lib "system:botan-2"
 } else when ODIN_OS == .Darwin {
     foreign import botan_lib "system:botan-2"
+} else when ODIN_OS == .OpenBSD {
+    foreign import botan_lib "system:botan-2"
 }
 
 @(default_calling_convention="c")
@@ -471,4 +473,4 @@ foreign botan_lib {
     fpe_destroy                         :: proc(fpe: fpe_t) -> c.int ---
     fpe_encrypt                         :: proc(fpe: fpe_t, x: mp_t, tweak: ^c.char, tweak_len: c.size_t) -> c.int ---
     fpe_decrypt                         :: proc(fpe: fpe_t, x: mp_t, tweak: ^c.char, tweak_len: c.size_t) -> c.int ---
-}
+}