~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
61 6

[PATCH hare 00/32] OpenBSD support for the stdlib and tools

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-1-me@xha.li>
DKIM signature
pass
Download raw message
the order of the patches are as follows: first comes the patches
that change the behavior of other platforms and are "preparations"
for the OpenBSD support. after that, the OpenBSD patches only
introduce new stuff and don't change existing functionality.

before i'll explain all of this, i want you to keep in mind that OpenBSD
is a complete os, not just a kernel. therefore, unlike Linux, we can
make asumptions about userspace, for example, that there will ever only
be one kind of libc (unlink Linux where you have glibc, musl, etc.).

i'll get into more detail in the patches themselfes, but the differences
for OpenBSD are as follows:

1. we always link with libc:
        first of all, OpenBSD syscalls are not supposed to be called
        directly but rather through the libc wrappers. the background
        here beeing that we can call msyscall(2), which will only
        allow system calls from libc. there are other reasons too,
        but that is basicly what matters for us.

        you could, and this has been already implemented by the
        harec rt, still use syscalls, this, however, would prevent
        dyanmic linking, since it is the dynamic linker itself that
        *always* calls msyscall(2) when it finds libc.

        i concluded that the best option here is just to always
        link with libc (and therefore always use cc instead of ld).

2. we don't use linker scripts for regular builds:
        after doing this port, i now hate linker scripts. the are
        horrible.  i could rant here, but i'll just quote theo
        instead:
	```
                You are creating a new class of binary, with different layouts
                and issues, and since you are doing it on your own, you'll never
                know what you are missing until later.

                The linker script stuff is fragile, poorly undocumented stuff
                which changes constantly between releases, has big variance
                between clang and gnu toolchains, and if you don't have to do it
                you shouldn't.

                We only use it for kernels, bootblocks, and ld.so.  And in all
                those cases, we need a linker script *PER ARCHITECTURE*.

                I think you'll never get it perfect for 1 architecture, and
                certainly not for 4 architectures.
        ```[1]

        i was able to make everything work without linker scripts
        except tests.  for this reason, a small linker script is
        included. see the rt patch for more details.

3. PIE is enabled
        since we don't use linker scripts, this is doing a full
        randomization of the hare code and ASLR is working perfectly.
        this was a pain to get working and i am happy that it does
        now. no changes to qbe where needed.

4. IBT/BTI is enforced by default and needs to be disabled
        first of all, an explaination of how indirect branch tracking
        (IBT) works: basicly, you just insert endbr64 instructions
        everywhere you will jump. now, when you enforce IBT, jumps
        need to always point to endbr64 instructions. if not, your
        program will get killed.  this prevents ROP attacks. this
        is, however, x86_64-only and only supported on intel
        processers 11th generation and newer.

        branch target identification (BTI) is basicly a similar
        thing but for aarch64 processors.

        this is a problem because QBE doesn't support this and
        support for this will likely never be added (because of
        their policies, but idk).  you can disable this feature by
        adding a custom segment to the binary: ld.lld(1) can do
        this for us using "-z nobtcfi".

5. use gas instead of as
        the reason behind this is that the "as" in the base system
        (think: Linux distro without any packages installed) is
        older than me (lol) and cannot handle qbe asssembly correctly.

        gas is the binary that is in the 'binutils' port(/package).

the zoneinfo in the base system was also updated[2] to include
leap-seconds.list, however, this was only a few days ago and since
only security fixes are backported, the time::chrono tests will
only run on OpenBSD 7.4-current and when 7.5 is released in
~4 months. everything else should be working in 7.4-release.

OpenBSD 7.3 is *NOT* supported! this is also the reason that the CI doesn't
work for now, see the CI patch for more details.

[1]: https://marc.info/?l=openbsd-misc&m=169704644103398
[2]: https://cvsweb.openbsd.org/src/share/zoneinfo/Makefile?rev=1.16&content-type=text/x-cvsweb-markup

Lorenz (xha) (32):
  cmd::hare arch add comment about values beeing overwritten
  rt: make linker scripts platform-specific
  rt: make start* and initfini platform-specific
  rt: make the signal test platform specific
  os::exec: make cmdfile platform-specific
  unix::tty: make openpty() platform-specific
  Makefile: remove stdlib_deps_*
  remove config.example.mk and add config.example.<platform>.mk
  cmd::hare: changes for OpenBSD
  OpenBSD: add rt
  OpenBSD: add io
  OpenBSD: add path
  OpenBSD: add time
  OpenBSD: add time::chrono
  OpenBSD: add os
  OpenBSD: add os::exec
  OpenBSD: add unix
  OpenBSD: add unix::signal
  OpenBSD: add unix::tty
  OpenBSD: add unix::poll
  OpenBSD: add unix::hosts
  OpenBSD: add unix::resolveconf
  OpenBSD: add format::elf
  OpenBSD: add crypto::random
  OpenBSD: add net
  OpenBSD: add net::ip
  OpenBSD: add net::tcp
  OpenBSD: add net::udp
  OpenBSD: add net::unix
  OpenBSD: add genbootstrap
  MAINTAINERS: add Lorenz (xha) <me@xha.li> to OpenBSD
  CI: add openbsd.yml

 .builds/openbsd.yml                           |   34 +
 MAINTAINERS                                   |    4 +
 Makefile                                      |    4 +-
 cmd/genbootstrap/main.ha                      |   12 +-
 cmd/hare/arch.ha                              |    2 +
 cmd/hare/build.ha                             |    8 +-
 cmd/hare/build/types.ha                       |    3 +
 cmd/hare/build/util.ha                        |   19 +-
 cmd/hare/error.ha                             |    3 +
 cmd/hare/main.ha                              |    2 +
 cmd/hare/platform.ha                          |   32 +
 config.example.freebsd.mk                     |   42 +
 config.example.mk => config.example.linux.mk  |    2 +-
 config.example.openbsd.mk                     |   43 +
 crypto/random/+openbsd.ha                     |   18 +
 format/elf/platform+openbsd.ha                |    5 +
 io/+openbsd/dup.ha                            |   44 +
 io/+openbsd/mmap.ha                           |   53 +
 io/+openbsd/platform_file.ha                  |   82 ++
 io/+openbsd/vector.ha                         |   49 +
 io/file.ha                                    |    4 +-
 makefiles/freebsd.aarch64.mk                  |   10 +-
 makefiles/freebsd.riscv64.mk                  |   10 +-
 makefiles/freebsd.x86_64.mk                   |   10 +-
 makefiles/linux.aarch64.mk                    |   10 +-
 makefiles/linux.riscv64.mk                    |   10 +-
 makefiles/linux.x86_64.mk                     |   10 +-
 makefiles/openbsd.aarch64.mk                  |  256 ++++
 makefiles/openbsd.riscv64.mk                  |  256 ++++
 makefiles/openbsd.x86_64.mk                   |  256 ++++
 net/+openbsd.ha                               |   98 ++
 net/ip/+openbsd.ha                            |   61 +
 net/tcp/+openbsd.ha                           |  159 +++
 net/udp/+openbsd.ha                           |  198 +++
 net/unix/+openbsd.ha                          |  117 ++
 net/unix/socketpair.ha                        |    2 +-
 os/+openbsd/dirfdfs.ha                        |  446 ++++++
 os/+openbsd/exit+test.ha                      |    7 +
 os/+openbsd/exit.ha                           |   10 +
 os/+openbsd/fs.ha                             |   75 +
 os/+openbsd/platform_environ.ha               |  104 ++
 os/+openbsd/status.ha                         |    9 +
 os/+openbsd/stdfd.ha                          |   56 +
 os/exec/{exec+freebsd.ha => +freebsd/exec.ha} |    2 -
 os/exec/+freebsd/platform_cmd.ha              |   23 +
 .../process.ha}                               |    0
 os/exec/{exec+linux.ha => +linux/exec.ha}     |    2 -
 os/exec/+linux/platform_cmd.ha                |   23 +
 .../{process+linux.ha => +linux/process.ha}   |    0
 os/exec/+openbsd/exec.ha                      |  190 +++
 os/exec/+openbsd/platform_cmd.ha              |    6 +
 os/exec/+openbsd/process.ha                   |  223 +++
 os/exec/cmd.ha                                |    9 +-
 path/+openbsd.ha                              |   12 +
 rt/{ => +freebsd}/hare+libc.sc                |    0
 rt/{ => +freebsd}/hare.sc                     |    0
 rt/{ => +freebsd}/initfini.ha                 |    0
 rt/+freebsd/signal.ha                         |   42 +
 rt/{ => +freebsd}/start+libc.ha               |    0
 rt/{ => +freebsd}/start+test+libc.ha          |    0
 rt/{ => +freebsd}/start+test.ha               |    0
 rt/{ => +freebsd}/start.ha                    |    0
 rt/+linux/hare+libc.sc                        |   44 +
 rt/+linux/hare.sc                             |   41 +
 rt/+linux/initfini.ha                         |   20 +
 rt/+linux/signal.ha                           |   42 +
 rt/+linux/start+libc.ha                       |   24 +
 rt/+linux/start+test+libc.ha                  |   23 +
 rt/+linux/start+test.ha                       |   16 +
 rt/+linux/start.ha                            |   16 +
 rt/+openbsd/env.ha                            |    6 +
 rt/+openbsd/errno.ha                          |  501 +++++++
 rt/+openbsd/hare+test.sc                      |    7 +
 rt/+openbsd/hare.sc                           |    1 +
 rt/+openbsd/libc.ha                           |   20 +
 rt/+openbsd/platform_abort.ha                 |   24 +
 .../signal_test.ha => +openbsd/signal.ha}     |   33 +-
 rt/+openbsd/socket.ha                         |  150 ++
 rt/+openbsd/start+test.ha                     |   28 +
 rt/+openbsd/start.ha                          |   26 +
 rt/+openbsd/start.s                           |    4 +
 rt/+openbsd/syscalls.ha                       | 1223 +++++++++++++++++
 rt/+openbsd/types.ha                          |  737 ++++++++++
 scripts/genbootstrap                          |    9 +-
 time/+openbsd/functions.ha                    |   91 ++
 time/chrono/+openbsd.ha                       |    9 +
 unix/+openbsd/getuid.ha                       |   16 +
 unix/+openbsd/groups.ha                       |   17 +
 unix/+openbsd/nice.ha                         |   11 +
 unix/+openbsd/pipe.ha                         |   30 +
 unix/+openbsd/setuid.ha                       |   44 +
 unix/+openbsd/umask.ha                        |    9 +
 unix/hosts/+openbsd.ha                        |    4 +
 unix/poll/+openbsd.ha                         |   51 +
 unix/resolvconf/+openbsd.ha                   |    4 +
 unix/signal/+openbsd.ha                       |  371 +++++
 unix/tty/+freebsd/pty.ha                      |   14 +
 unix/tty/+linux/pty.ha                        |   14 +
 unix/tty/+openbsd/isatty.ha                   |   17 +
 unix/tty/+openbsd/open.ha                     |   18 +
 unix/tty/+openbsd/pty.ha                      |   78 ++
 unix/tty/+openbsd/termios.ha                  |   71 +
 unix/tty/+openbsd/winsize.ha                  |   28 +
 unix/tty/{pty_common.ha => pty_test.ha}       |   15 -
 104 files changed, 6997 insertions(+), 77 deletions(-)
 create mode 100644 .builds/openbsd.yml
 create mode 100644 cmd/hare/platform.ha
 create mode 100644 config.example.freebsd.mk
 rename config.example.mk => config.example.linux.mk (94%)
 create mode 100644 config.example.openbsd.mk
 create mode 100644 crypto/random/+openbsd.ha
 create mode 100644 format/elf/platform+openbsd.ha
 create mode 100644 io/+openbsd/dup.ha
 create mode 100644 io/+openbsd/mmap.ha
 create mode 100644 io/+openbsd/platform_file.ha
 create mode 100644 io/+openbsd/vector.ha
 create mode 100644 makefiles/openbsd.aarch64.mk
 create mode 100644 makefiles/openbsd.riscv64.mk
 create mode 100644 makefiles/openbsd.x86_64.mk
 create mode 100644 net/+openbsd.ha
 create mode 100644 net/ip/+openbsd.ha
 create mode 100644 net/tcp/+openbsd.ha
 create mode 100644 net/udp/+openbsd.ha
 create mode 100644 net/unix/+openbsd.ha
 create mode 100644 os/+openbsd/dirfdfs.ha
 create mode 100644 os/+openbsd/exit+test.ha
 create mode 100644 os/+openbsd/exit.ha
 create mode 100644 os/+openbsd/fs.ha
 create mode 100644 os/+openbsd/platform_environ.ha
 create mode 100644 os/+openbsd/status.ha
 create mode 100644 os/+openbsd/stdfd.ha
 rename os/exec/{exec+freebsd.ha => +freebsd/exec.ha} (99%)
 create mode 100644 os/exec/+freebsd/platform_cmd.ha
 rename os/exec/{process+freebsd.ha => +freebsd/process.ha} (100%)
 rename os/exec/{exec+linux.ha => +linux/exec.ha} (99%)
 create mode 100644 os/exec/+linux/platform_cmd.ha
 rename os/exec/{process+linux.ha => +linux/process.ha} (100%)
 create mode 100644 os/exec/+openbsd/exec.ha
 create mode 100644 os/exec/+openbsd/platform_cmd.ha
 create mode 100644 os/exec/+openbsd/process.ha
 create mode 100644 path/+openbsd.ha
 rename rt/{ => +freebsd}/hare+libc.sc (100%)
 rename rt/{ => +freebsd}/hare.sc (100%)
 rename rt/{ => +freebsd}/initfini.ha (100%)
 rename rt/{ => +freebsd}/start+libc.ha (100%)
 rename rt/{ => +freebsd}/start+test+libc.ha (100%)
 rename rt/{ => +freebsd}/start+test.ha (100%)
 rename rt/{ => +freebsd}/start.ha (100%)
 create mode 100644 rt/+linux/hare+libc.sc
 create mode 100644 rt/+linux/hare.sc
 create mode 100644 rt/+linux/initfini.ha
 create mode 100644 rt/+linux/start+libc.ha
 create mode 100644 rt/+linux/start+test+libc.ha
 create mode 100644 rt/+linux/start+test.ha
 create mode 100644 rt/+linux/start.ha
 create mode 100644 rt/+openbsd/env.ha
 create mode 100644 rt/+openbsd/errno.ha
 create mode 100644 rt/+openbsd/hare+test.sc
 create mode 100644 rt/+openbsd/hare.sc
 create mode 100644 rt/+openbsd/libc.ha
 create mode 100644 rt/+openbsd/platform_abort.ha
 rename rt/{+test/signal_test.ha => +openbsd/signal.ha} (70%)
 create mode 100644 rt/+openbsd/socket.ha
 create mode 100644 rt/+openbsd/start+test.ha
 create mode 100644 rt/+openbsd/start.ha
 create mode 100644 rt/+openbsd/start.s
 create mode 100644 rt/+openbsd/syscalls.ha
 create mode 100644 rt/+openbsd/types.ha
 create mode 100644 time/+openbsd/functions.ha
 create mode 100644 time/chrono/+openbsd.ha
 create mode 100644 unix/+openbsd/getuid.ha
 create mode 100644 unix/+openbsd/groups.ha
 create mode 100644 unix/+openbsd/nice.ha
 create mode 100644 unix/+openbsd/pipe.ha
 create mode 100644 unix/+openbsd/setuid.ha
 create mode 100644 unix/+openbsd/umask.ha
 create mode 100644 unix/hosts/+openbsd.ha
 create mode 100644 unix/poll/+openbsd.ha
 create mode 100644 unix/resolvconf/+openbsd.ha
 create mode 100644 unix/signal/+openbsd.ha
 create mode 100644 unix/tty/+openbsd/isatty.ha
 create mode 100644 unix/tty/+openbsd/open.ha
 create mode 100644 unix/tty/+openbsd/pty.ha
 create mode 100644 unix/tty/+openbsd/termios.ha
 create mode 100644 unix/tty/+openbsd/winsize.ha
 rename unix/tty/{pty_common.ha => pty_test.ha} (64%)

-- 
2.42.0

[PATCH hare 01/32] cmd::hare arch add comment about values beeing overwritten

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-2-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
i was confused why this is not working when i modified them. turns
out they are getting overwritten while compiling. hopefully this
helps the next person that is trying to modify these.

 cmd/hare/arch.ha | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/cmd/hare/arch.ha b/cmd/hare/arch.ha
index 46247f16..8852b22f 100644
--- a/cmd/hare/arch.ha
+++ b/cmd/hare/arch.ha
@@ -5,6 +5,8 @@ use hare::module;
use os;
use strings;

// When building the bootstrap toolchain, these values will get overwritten to
// equal the values in config.mk
def AARCH64_AS = "as";
def AARCH64_CC = "cc";
def AARCH64_LD = "ld";
-- 
2.42.0

[PATCH hare 02/32] rt: make linker scripts platform-specific

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-3-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +85 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
we are using a different linker scripts for openbsd. i think that future
ports are probably going to do the same too so they should probably be
platform-specific anyways.

 rt/{ => +freebsd}/hare+libc.sc |  0
 rt/{ => +freebsd}/hare.sc      |  0
 rt/+linux/hare+libc.sc         | 44 ++++++++++++++++++++++++++++++++++
 rt/+linux/hare.sc              | 41 +++++++++++++++++++++++++++++++
 4 files changed, 85 insertions(+)
 rename rt/{ => +freebsd}/hare+libc.sc (100%)
 rename rt/{ => +freebsd}/hare.sc (100%)
 create mode 100644 rt/+linux/hare+libc.sc
 create mode 100644 rt/+linux/hare.sc

diff --git a/rt/hare+libc.sc b/rt/+freebsd/hare+libc.sc
similarity index 100%
rename from rt/hare+libc.sc
rename to rt/+freebsd/hare+libc.sc
diff --git a/rt/hare.sc b/rt/+freebsd/hare.sc
similarity index 100%
rename from rt/hare.sc
rename to rt/+freebsd/hare.sc
diff --git a/rt/+linux/hare+libc.sc b/rt/+linux/hare+libc.sc
new file mode 100644
index 00000000..b38bb703
--- /dev/null
+++ b/rt/+linux/hare+libc.sc
@@ -0,0 +1,44 @@
SECTIONS {
	. = 0x8000000;
	.text : {
		KEEP (*(.text))
		*(.text.*)
	}
	. = 0x80000000;
	.data : {
		KEEP (*(.data))
		*(.data.*)
	}

	.rela.plt : {
		*(.rela.plt)
	}

	.init_array : {
		PROVIDE_HIDDEN (__init_array_start = .);
		PROVIDE_HIDDEN (__init_array_end = .);
	}

	.libc_init_array : {
		PROVIDE_HIDDEN (__libc_init_array_start = .);
		KEEP (*(.init_array))
		PROVIDE_HIDDEN (__libc_init_array_end = .);
	}

	.fini_array : {
		PROVIDE_HIDDEN (__fini_array_start = .);
		KEEP (*(.fini_array))
		PROVIDE_HIDDEN (__fini_array_end = .);
	}

	.test_array : {
		PROVIDE_HIDDEN (__test_array_start = .);
		KEEP (*(.test_array))
		PROVIDE_HIDDEN (__test_array_end = .);
	}

	.bss : {
		KEEP (*(.bss))
		*(.bss.*)
	}
}
diff --git a/rt/+linux/hare.sc b/rt/+linux/hare.sc
new file mode 100644
index 00000000..f93ed9de
--- /dev/null
+++ b/rt/+linux/hare.sc
@@ -0,0 +1,41 @@
PHDRS {
	headers PT_PHDR PHDRS;
	text PT_LOAD FILEHDR PHDRS;
	data PT_LOAD;
}
ENTRY(_start);
SECTIONS {
	. = 0x8000000;
	.text : {
		KEEP (*(.text))
		*(.text.*)
	} :text
	. = 0x80000000;
	.data : {
		KEEP (*(.data))
		*(.data.*)
	} :data

	.init_array : {
		PROVIDE_HIDDEN (__init_array_start = .);
		KEEP (*(.init_array))
		PROVIDE_HIDDEN (__init_array_end = .);
	} :data

	.fini_array : {
		PROVIDE_HIDDEN (__fini_array_start = .);
		KEEP (*(.fini_array))
		PROVIDE_HIDDEN (__fini_array_end = .);
	} :data

	.test_array : {
		PROVIDE_HIDDEN (__test_array_start = .);
		KEEP (*(.test_array))
		PROVIDE_HIDDEN (__test_array_end = .);
	} :data

	.bss : {
		KEEP (*(.bss))
		*(.bss.*)
	} :data
}
-- 
2.42.0

[PATCH hare 03/32] rt: make start* and initfini platform-specific

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-4-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +99 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
i think they should be platform specific. for the openbsd port,
they need to be anyways so this is a minimal change that still
works.

once this is in tree, i'll send a patch to combine both platformstart
and start. i don't think it does make sense to seperate them anymore.

(i don't do it now because i don't want to change too much stuff in
other platforms in this series)

 rt/{ => +freebsd}/initfini.ha        |  0
 rt/{ => +freebsd}/start+libc.ha      |  0
 rt/{ => +freebsd}/start+test+libc.ha |  0
 rt/{ => +freebsd}/start+test.ha      |  0
 rt/{ => +freebsd}/start.ha           |  0
 rt/+linux/initfini.ha                | 20 ++++++++++++++++++++
 rt/+linux/start+libc.ha              | 24 ++++++++++++++++++++++++
 rt/+linux/start+test+libc.ha         | 23 +++++++++++++++++++++++
 rt/+linux/start+test.ha              | 16 ++++++++++++++++
 rt/+linux/start.ha                   | 16 ++++++++++++++++
 10 files changed, 99 insertions(+)
 rename rt/{ => +freebsd}/initfini.ha (100%)
 rename rt/{ => +freebsd}/start+libc.ha (100%)
 rename rt/{ => +freebsd}/start+test+libc.ha (100%)
 rename rt/{ => +freebsd}/start+test.ha (100%)
 rename rt/{ => +freebsd}/start.ha (100%)
 create mode 100644 rt/+linux/initfini.ha
 create mode 100644 rt/+linux/start+libc.ha
 create mode 100644 rt/+linux/start+test+libc.ha
 create mode 100644 rt/+linux/start+test.ha
 create mode 100644 rt/+linux/start.ha

diff --git a/rt/initfini.ha b/rt/+freebsd/initfini.ha
similarity index 100%
rename from rt/initfini.ha
rename to rt/+freebsd/initfini.ha
diff --git a/rt/start+libc.ha b/rt/+freebsd/start+libc.ha
similarity index 100%
rename from rt/start+libc.ha
rename to rt/+freebsd/start+libc.ha
diff --git a/rt/start+test+libc.ha b/rt/+freebsd/start+test+libc.ha
similarity index 100%
rename from rt/start+test+libc.ha
rename to rt/+freebsd/start+test+libc.ha
diff --git a/rt/start+test.ha b/rt/+freebsd/start+test.ha
similarity index 100%
rename from rt/start+test.ha
rename to rt/+freebsd/start+test.ha
diff --git a/rt/start.ha b/rt/+freebsd/start.ha
similarity index 100%
rename from rt/start.ha
rename to rt/+freebsd/start.ha
diff --git a/rt/+linux/initfini.ha b/rt/+linux/initfini.ha
new file mode 100644
index 00000000..1a72e53f
--- /dev/null
+++ b/rt/+linux/initfini.ha
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Run all global initialization functions.
export fn init() void = {
	const ninit = (&init_end: uintptr - &init_start: uintptr): size
		/ size(*fn() void);
	for (let i = 0z; i < ninit; i += 1) {
		init_start[i]();
	};
};

// Run all global finalization functions.
export fn fini() void = {
	const nfini = (&fini_end: uintptr - &fini_start: uintptr): size
		/ size(*fn() void);
	for (let i = nfini; i > 0; i -= 1) {
		fini_start[i - 1]();
	};
};
diff --git a/rt/+linux/start+libc.ha b/rt/+linux/start+libc.ha
new file mode 100644
index 00000000..d5e5030b
--- /dev/null
+++ b/rt/+linux/start+libc.ha
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

@symbol(".main") fn main() void;
@symbol("exit") fn c_exit(status: int) never;

const @symbol("__libc_init_array_start") init_start: [*]*fn() void;
const @symbol("__libc_init_array_end") init_end: [*]*fn() void;
const @symbol("__fini_array_start") fini_start: [*]*fn() void;
const @symbol("__fini_array_end") fini_end: [*]*fn() void;

export @symbol("main") fn start_ha(c_argc: int, c_argv: *[*]*u8) never = {
	argc = c_argc: size;
	argv = c_argv;
	envp = c_envp;
	// we deliberately prevent libc from running @init for us, in order to
	// be able to initialize argc/argv/envp beforehand. we can still get
	// away with just using libc for @fini though
	init();
	main();
	c_exit(0);
};

let @symbol("environ") c_envp: *[*]nullable *u8;
diff --git a/rt/+linux/start+test+libc.ha b/rt/+linux/start+test+libc.ha
new file mode 100644
index 00000000..9db1ad46
--- /dev/null
+++ b/rt/+linux/start+test+libc.ha
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

@symbol("__test_main") fn test_main() size;

const @symbol("__libc_init_array_start") init_start: [*]*fn() void;
const @symbol("__libc_init_array_end") init_end: [*]*fn() void;
const @symbol("__fini_array_start") fini_start: [*]*fn() void;
const @symbol("__fini_array_end") fini_end: [*]*fn() void;

export @symbol("main") fn start_ha(c_argc: int, c_argv: *[*]*u8) int = {
	argc = c_argc: size;
	argv = c_argv;
	envp = c_envp;
	// we deliberately prevent libc from running @init for us, in order to
	// be able to initialize argc/argv/envp beforehand. we can still get
	// away with just using libc for @fini though
	init();
	const nfail = test_main();
	return if (nfail > 0) 1 else 0;
};

let @symbol("environ") c_envp: *[*]nullable *u8;
diff --git a/rt/+linux/start+test.ha b/rt/+linux/start+test.ha
new file mode 100644
index 00000000..2ceaf209
--- /dev/null
+++ b/rt/+linux/start+test.ha
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

@symbol("__test_main") fn test_main() size;

const @symbol("__init_array_start") init_start: [*]*fn() void;
const @symbol("__init_array_end") init_end: [*]*fn() void;
const @symbol("__fini_array_start") fini_start: [*]*fn() void;
const @symbol("__fini_array_end") fini_end: [*]*fn() void;

export fn start_ha() never = {
	init();
	const nfail = test_main();
	fini();
	exit(if (nfail > 0) 1 else 0);
};
diff --git a/rt/+linux/start.ha b/rt/+linux/start.ha
new file mode 100644
index 00000000..473202bb
--- /dev/null
+++ b/rt/+linux/start.ha
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

@symbol("main") fn main() void;

const @symbol("__init_array_start") init_start: [*]*fn() void;
const @symbol("__init_array_end") init_end: [*]*fn() void;
const @symbol("__fini_array_start") fini_start: [*]*fn() void;
const @symbol("__fini_array_end") fini_end: [*]*fn() void;

export fn start_ha() never = {
	init();
	main();
	fini();
	exit(0);
};
-- 
2.42.0

[PATCH hare 04/32] rt: make the signal test platform specific

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-5-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +84 -44
Signed-off-by: Lorenz (xha) <me@xha.li>
---
sigset is a uint instead of a struct on openbsd and therefore cannot
be initilized using { ... }, like on the other platforms.

this might change with default values in the future but i don't think
that there is a way to do this now.

 rt/+freebsd/signal.ha   | 42 +++++++++++++++++++++++++++++++++++++++
 rt/+linux/signal.ha     | 42 +++++++++++++++++++++++++++++++++++++++
 rt/+test/signal_test.ha | 44 -----------------------------------------
 3 files changed, 84 insertions(+), 44 deletions(-)
 delete mode 100644 rt/+test/signal_test.ha

diff --git a/rt/+freebsd/signal.ha b/rt/+freebsd/signal.ha
index decb7e95..f8078fcc 100644
--- a/rt/+freebsd/signal.ha
+++ b/rt/+freebsd/signal.ha
@@ -38,3 +38,45 @@ export fn sigfillset(set: *sigset) (void | errno) = {
		set.__bits[i] = ~0u32;
	};
};

// Test sigset operations do not fail for valid signal numbers.
@test fn sigset_valid_signum() void = {
	let set: sigset = sigset { ... };
	sigemptyset(&set);

	assert(!(sigismember(&set, 1) is errno), "Unexpected error");
	assert(!(sigismember(&set, 15) is errno), "Unexpected error");
	assert(!(sigismember(&set, NSIG) is errno), "Unexpected error");

	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");
	assert(!(sigaddset(&set, 15) is errno), "Unexpected error");
	assert(!(sigaddset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to add a signal that is already present in the set.
	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");

	assert(!(sigdelset(&set, 1) is errno), "Unexpected error");
	assert(!(sigdelset(&set, 15) is errno), "Unexpected error");
	assert(!(sigdelset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to delete a signal that is not present in the set.
	assert(!(sigdelset(&set, 10) is errno), "Unexpected error");
};

// Test sigset operations fail for invalid signal numbers.
@test fn sigset_invalid_signum() void = {
	let set: sigset = sigset { ... };
	sigemptyset(&set);

	assert(sigismember(&set, -1) is errno, "Expected error");
	assert(sigismember(&set, 0) is errno, "Expected error");
	assert(sigismember(&set, NSIG + 1) is errno, "Expected error");

	assert(sigaddset(&set, -1) is errno, "Expected error");
	assert(sigaddset(&set, 0) is errno, "Expected error");
	assert(sigaddset(&set, NSIG + 1) is errno, "Expected error");

	assert(sigdelset(&set, -1) is errno, "Expected error");
	assert(sigdelset(&set, 0) is errno, "Expected error");
	assert(sigdelset(&set, NSIG + 1) is errno, "Expected error");
};
diff --git a/rt/+linux/signal.ha b/rt/+linux/signal.ha
index ea80edc4..43be3b8f 100644
--- a/rt/+linux/signal.ha
+++ b/rt/+linux/signal.ha
@@ -34,3 +34,45 @@ export fn sigismember(set: *sigset, signum: int) (bool | errno) = {
export fn sigfillset(set: *sigset) void = {
	set.__val[0] = ~0u64;
};

// Test sigset operations do not fail for valid signal numbers.
@test fn sigset_valid_signum() void = {
	let set: sigset = sigset { ... };
	sigemptyset(&set);

	assert(!(sigismember(&set, 1) is errno), "Unexpected error");
	assert(!(sigismember(&set, 15) is errno), "Unexpected error");
	assert(!(sigismember(&set, NSIG) is errno), "Unexpected error");

	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");
	assert(!(sigaddset(&set, 15) is errno), "Unexpected error");
	assert(!(sigaddset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to add a signal that is already present in the set.
	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");

	assert(!(sigdelset(&set, 1) is errno), "Unexpected error");
	assert(!(sigdelset(&set, 15) is errno), "Unexpected error");
	assert(!(sigdelset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to delete a signal that is not present in the set.
	assert(!(sigdelset(&set, 10) is errno), "Unexpected error");
};

// Test sigset operations fail for invalid signal numbers.
@test fn sigset_invalid_signum() void = {
	let set: sigset = sigset { ... };
	sigemptyset(&set);

	assert(sigismember(&set, -1) is errno, "Expected error");
	assert(sigismember(&set, 0) is errno, "Expected error");
	assert(sigismember(&set, NSIG + 1) is errno, "Expected error");

	assert(sigaddset(&set, -1) is errno, "Expected error");
	assert(sigaddset(&set, 0) is errno, "Expected error");
	assert(sigaddset(&set, NSIG + 1) is errno, "Expected error");

	assert(sigdelset(&set, -1) is errno, "Expected error");
	assert(sigdelset(&set, 0) is errno, "Expected error");
	assert(sigdelset(&set, NSIG + 1) is errno, "Expected error");
};
diff --git a/rt/+test/signal_test.ha b/rt/+test/signal_test.ha
deleted file mode 100644
index bd694312..00000000
--- a/rt/+test/signal_test.ha
@@ -1,44 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Test sigset operations do not fail for valid signal numbers.
@test fn sigset_valid_signum() void = {
	let set = sigset { ... };
	sigemptyset(&set);

	assert(!(sigismember(&set, 1) is errno), "Unexpected error");
	assert(!(sigismember(&set, 15) is errno), "Unexpected error");
	assert(!(sigismember(&set, NSIG) is errno), "Unexpected error");

	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");
	assert(!(sigaddset(&set, 15) is errno), "Unexpected error");
	assert(!(sigaddset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to add a signal that is already present in the set.
	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");

	assert(!(sigdelset(&set, 1) is errno), "Unexpected error");
	assert(!(sigdelset(&set, 15) is errno), "Unexpected error");
	assert(!(sigdelset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to delete a signal that is not present in the set.
	assert(!(sigdelset(&set, 10) is errno), "Unexpected error");
};

// Test sigset operations fail for invalid signal numbers.
@test fn sigset_invalid_signum() void = {
	let set = sigset { ... };
	sigemptyset(&set);

	assert(sigismember(&set, -1) is errno, "Expected error");
	assert(sigismember(&set, 0) is errno, "Expected error");
	assert(sigismember(&set, NSIG + 1) is errno, "Expected error");

	assert(sigaddset(&set, -1) is errno, "Expected error");
	assert(sigaddset(&set, 0) is errno, "Expected error");
	assert(sigaddset(&set, NSIG + 1) is errno, "Expected error");

	assert(sigdelset(&set, -1) is errno, "Expected error");
	assert(sigdelset(&set, 0) is errno, "Expected error");
	assert(sigdelset(&set, NSIG + 1) is errno, "Expected error");
};
-- 
2.42.0

[PATCH hare 05/32] os::exec: make cmdfile platform-specific

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-6-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +46 -11
Signed-off-by: Lorenz (xha) <me@xha.li>
---
by design, there is no way to exec a file descriptor on openbsd.

discussed with ddevault in #hare-dev, he wants to keep the function
so let's make it platform-specific.

 os/exec/{exec+freebsd.ha => +freebsd/exec.ha} |  2 --
 os/exec/+freebsd/platform_cmd.ha              | 22 +++++++++++++++++++
 .../process.ha}                               |  0
 os/exec/{exec+linux.ha => +linux/exec.ha}     |  2 --
 os/exec/+linux/platform_cmd.ha                | 22 +++++++++++++++++++
 .../{process+linux.ha => +linux/process.ha}   |  0
 os/exec/cmd.ha                                |  9 ++------
 7 files changed, 46 insertions(+), 11 deletions(-)
 rename os/exec/{exec+freebsd.ha => +freebsd/exec.ha} (99%)
 create mode 100644 os/exec/+freebsd/platform_cmd.ha
 rename os/exec/{process+freebsd.ha => +freebsd/process.ha} (100%)
 rename os/exec/{exec+linux.ha => +linux/exec.ha} (99%)
 create mode 100644 os/exec/+linux/platform_cmd.ha
 rename os/exec/{process+linux.ha => +linux/process.ha} (100%)

diff --git a/os/exec/exec+freebsd.ha b/os/exec/+freebsd/exec.ha
similarity index 99%
rename from os/exec/exec+freebsd.ha
rename to os/exec/+freebsd/exec.ha
index 81f5479e..2400737f 100644
--- a/os/exec/exec+freebsd.ha
+++ b/os/exec/+freebsd/exec.ha
@@ -8,8 +8,6 @@ use rt;
use types::c;
use unix;

export type platform_cmd = io::file;

// Forks the current process, returning the [[process]] of the child (to the
// parent) and void (to the child), or an error.
export fn fork() (process | void | error) = {
diff --git a/os/exec/+freebsd/platform_cmd.ha b/os/exec/+freebsd/platform_cmd.ha
new file mode 100644
index 00000000..0aeb3536
--- /dev/null
+++ b/os/exec/+freebsd/platform_cmd.ha
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>
use io;
use os;
use strings;

export type platform_cmd = io::file;

// Same as [[cmd]] except that executable file is determined by [[io::file]].
// This function is not portable.
export fn cmdfile(file: io::file, name: str, args: str...) command = {
	let cmd = command {
		platform = file,
		argv = alloc([], len(args) + 1),
		env = strings::dupall(os::getenvs()),
		files = [],
		dir = "",
	};
	append(cmd.argv, name);
	append(cmd.argv, args...);
	return cmd;
};
diff --git a/os/exec/process+freebsd.ha b/os/exec/+freebsd/process.ha
similarity index 100%
rename from os/exec/process+freebsd.ha
rename to os/exec/+freebsd/process.ha
diff --git a/os/exec/exec+linux.ha b/os/exec/+linux/exec.ha
similarity index 99%
rename from os/exec/exec+linux.ha
rename to os/exec/+linux/exec.ha
index f0f280ca..41a7f074 100644
--- a/os/exec/exec+linux.ha
+++ b/os/exec/+linux/exec.ha
@@ -8,8 +8,6 @@ use rt;
use types::c;
use unix;

export type platform_cmd = io::file;

// Forks the current process, returning the [[process]] of the child (to the
// parent) and void (to the child), or an error.
export fn fork() (process | void | error) = {
diff --git a/os/exec/+linux/platform_cmd.ha b/os/exec/+linux/platform_cmd.ha
new file mode 100644
index 00000000..0aeb3536
--- /dev/null
+++ b/os/exec/+linux/platform_cmd.ha
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>
use io;
use os;
use strings;

export type platform_cmd = io::file;

// Same as [[cmd]] except that executable file is determined by [[io::file]].
// This function is not portable.
export fn cmdfile(file: io::file, name: str, args: str...) command = {
	let cmd = command {
		platform = file,
		argv = alloc([], len(args) + 1),
		env = strings::dupall(os::getenvs()),
		files = [],
		dir = "",
	};
	append(cmd.argv, name);
	append(cmd.argv, args...);
	return cmd;
};
diff --git a/os/exec/process+linux.ha b/os/exec/+linux/process.ha
similarity index 100%
rename from os/exec/process+linux.ha
rename to os/exec/+linux/process.ha
diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha
index 4b74bfbf..bb792619 100644
--- a/os/exec/cmd.ha
+++ b/os/exec/cmd.ha
@@ -21,7 +21,7 @@ use strings;
//
// By default, the new command will inherit the current process's environment.
export fn cmd(name: str, args: str...) (command | error) = {
	let file = if (strings::contains(name, '/')) {
	let platcmd = if (strings::contains(name, '/')) {
		yield match (open(name)) {
		case let p: platform_cmd =>
			yield p;
@@ -36,13 +36,8 @@ export fn cmd(name: str, args: str...) (command | error) = {
			yield p;
		};
	};
	return cmdfile(file, name, args...);
};

// Same as [[cmd]] except that executable file is determined by [[io::file]].
export fn cmdfile(file: io::file, name: str, args: str...) command = {
	let cmd = command {
		platform = file,
		platform = platcmd,
		argv = alloc([], len(args) + 1),
		env = strings::dupall(os::getenvs()),
		files = [],
-- 
2.42.0

[PATCH hare 06/32] unix::tty: make openpty() platform-specific

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-7-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +28 -15
Signed-off-by: Lorenz (xha) <me@xha.li>
---
the reason for this change is that you cannot get the slave from a
master but you do an ioctl on the master to get a pair on OpenBSD.

 unix/tty/+freebsd/pty.ha                | 14 ++++++++++++++
 unix/tty/+linux/pty.ha                  | 14 ++++++++++++++
 unix/tty/{pty_common.ha => pty_test.ha} | 15 ---------------
 3 files changed, 28 insertions(+), 15 deletions(-)
 rename unix/tty/{pty_common.ha => pty_test.ha} (64%)

diff --git a/unix/tty/+freebsd/pty.ha b/unix/tty/+freebsd/pty.ha
index 3a059162..aaa3a89c 100644
--- a/unix/tty/+freebsd/pty.ha
+++ b/unix/tty/+freebsd/pty.ha
@@ -9,6 +9,20 @@ use os;
use rt;
use types::c;

// Opens an available pseudoterminal and returns the file descriptors of the
// master and slave.
export fn openpty() ((io::file, io::file) | fs::error) = {
	let master = open_master()?;
	let slave = match (get_slave(master)) {
	case let e: fs::error =>
		io::close(master)!;
		return e;
	case let s: io::file =>
		yield s;
	};
	return (master, slave);
};

// Opens an available pseudoterminal master.
fn open_master() (io::file | fs::error) = {
	match (rt::posix_openpt(rt::O_RDWR | rt::O_NOCTTY)) {
diff --git a/unix/tty/+linux/pty.ha b/unix/tty/+linux/pty.ha
index 2a4fb11e..3e3146fc 100644
--- a/unix/tty/+linux/pty.ha
+++ b/unix/tty/+linux/pty.ha
@@ -8,6 +8,20 @@ use io;
use os;
use rt;

// Opens an available pseudoterminal and returns the file descriptors of the
// master and slave.
export fn openpty() ((io::file, io::file) | fs::error) = {
	let master = open_master()?;
	let slave = match (get_slave(master)) {
	case let e: fs::error =>
		io::close(master)!;
		return e;
	case let s: io::file =>
		yield s;
	};
	return (master, slave);
};

// Opens an available pseudoterminal master.
fn open_master() (io::file | fs::error) = {
	return os::open("/dev/ptmx", fs::flag::RDWR);
diff --git a/unix/tty/pty_common.ha b/unix/tty/pty_test.ha
similarity index 64%
rename from unix/tty/pty_common.ha
rename to unix/tty/pty_test.ha
index d26084d0..b62d572a 100644
--- a/unix/tty/pty_common.ha
+++ b/unix/tty/pty_test.ha
@@ -8,21 +8,6 @@ use io;
use os;
use strings;

// Opens an available pseudoterminal and returns the file descriptors of the
// master and slave.
export fn openpty() ((io::file, io::file) | fs::error) = {
	let master = open_master()?;
	let slave = match (get_slave(master)) {
	case let e: fs::error =>
		io::close(master)!;
		return e;
	case let s: io::file =>
		yield s;
	};

	return (master, slave);
};

@test fn pty() void = {
	let pty = openpty()!;
	defer io::close(pty.1)!;
-- 
2.42.0

[PATCH hare 07/32] Makefile: remove stdlib_deps_*

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-8-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +2 -2
this also removes the flags because they will be specified in
LDLINKFLAGS from the file config.mk

Signed-off-by: Lorenz (xha) <me@xha.li>
---
this has two reasons:
1. --gc-sections is not supported with cc
2. we need to add -z nobtcfi as explained in the cover letter

see the next patch.

 Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 19a8cbfe..8a86a65e 100644
--- a/Makefile
+++ b/Makefile
@@ -42,14 +42,14 @@ HARE_DEFINES = \
$(BINOUT)/hare: $(OBJS)
	@mkdir -p -- "$(BINOUT)"
	@printf 'LD\t%s\n' "$@"
	@$(LD) $(LDLINKFLAGS) --gc-sections -z noexecstack -T $(RTSCRIPT) -o $@ $(OBJS)
	@$(LD) $(LDLINKFLAGS) -T $(RTSCRIPT) -o $@ $(OBJS)

$(BINOUT)/harec2: $(BINOUT)/hare
	@printf 'HARE\t%s\n' "$@"
	@env HAREPATH=. HAREC=$(HAREC) QBE=$(QBE) AS=$(AS) LD=$(LD) \
		HAREFLAGS=$(HAREFLAGS) HARECFLAGS=$(HARECFLAGS) \
		QBEFLAGS=$(QBEFLAGS) ASFLAGS=$(ASFLAGS) \
		LDLINKFLAGS=$(LDLINKFLAGS) \
		LDLINKFLAGS="$(LDLINKFLAGS)" \
		$(BINOUT)/hare build $(HARE_DEFINES) -o $(BINOUT)/harec2 cmd/harec

$(BINOUT)/haredoc: $(BINOUT)/hare
-- 
2.42.0

[PATCH hare 08/32] remove config.example.mk and add config.example.<platform>.mk

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-9-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +86 -1
there are so many differences with openbsd that it is a pain to
configure them every time. having one for each platform is much easier
for everyone i think.

Signed-off-by: Lorenz (xha) <me@xha.li>
---
the main reason for this is that every platform can now specify their
own flags.

also you just have to do a cp for your platform and it will work most
of the time.

 config.example.freebsd.mk                    | 42 +++++++++++++++++++
 config.example.mk => config.example.linux.mk |  2 +-
 config.example.openbsd.mk                    | 43 ++++++++++++++++++++
 3 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 config.example.freebsd.mk
 rename config.example.mk => config.example.linux.mk (94%)
 create mode 100644 config.example.openbsd.mk

diff --git a/config.example.freebsd.mk b/config.example.freebsd.mk
new file mode 100644
index 00000000..a91708a8
--- /dev/null
+++ b/config.example.freebsd.mk
@@ -0,0 +1,42 @@
# install locations
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
SRCDIR = $(PREFIX)/src
STDLIB = $(SRCDIR)/hare/stdlib

# variables used during build
PLATFORM = freebsd
ARCH = x86_64
HAREFLAGS =
HARECFLAGS =
QBEFLAGS =
ASFLAGS =
LDLINKFLAGS = --gc-sections -z noexecstack

# commands used by the build script
HAREC = harec
QBE = qbe
AS = as
LD = ld
SCDOC = scdoc

# build locations
HARECACHE = .cache
BINOUT = .bin

# variables that will be embedded in the binary with -D definitions
HAREPATH = $(SRCDIR)/hare/stdlib:$(SRCDIR)/hare/third-party
VERSION=$$(./scripts/version)

AARCH64_AS=aarch64-as
AARCH64_CC=aarch64-cc
AARCH64_LD=aarch64-ld

RISCV64_AS=riscv64-as
RISCV64_CC=riscv64-cc
RISCV64_LD=riscv64-ld

X86_64_AS=as
X86_64_CC=cc
X86_64_LD=ld
diff --git a/config.example.mk b/config.example.linux.mk
similarity index 94%
rename from config.example.mk
rename to config.example.linux.mk
index 1424afaf..15dd9993 100644
--- a/config.example.mk
+++ b/config.example.linux.mk
@@ -12,7 +12,7 @@ HAREFLAGS =
HARECFLAGS =
QBEFLAGS =
ASFLAGS =
LDLINKFLAGS =
LDLINKFLAGS = --gc-sections -z noexecstack

# commands used by the build script
HAREC = harec
diff --git a/config.example.openbsd.mk b/config.example.openbsd.mk
new file mode 100644
index 00000000..dc29ea21
--- /dev/null
+++ b/config.example.openbsd.mk
@@ -0,0 +1,43 @@
# install locations
PREFIX = /home/lorenz/bin
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
SRCDIR = $(PREFIX)/src
STDLIB = $(SRCDIR)/hare/stdlib

# variables used during build
PLATFORM = openbsd
ARCH = x86_64
HAREFLAGS =
HARECFLAGS =
QBEFLAGS =
ASFLAGS =
LDLINKFLAGS = -z nobtcfi

# commands used by the build script
HAREC = harec
QBE = qbe
# OpenBSD: gas is in the binutils package. as from the base system is too old.
AS = gas
LD = cc
SCDOC = scdoc

# build locations
HARECACHE = .cache
BINOUT = .bin

# variables that will be embedded in the binary with -D definitions
HAREPATH = $(SRCDIR)/hare/stdlib:$(SRCDIR)/hare/third-party
VERSION=$$(./scripts/version)

AARCH64_AS=aarch64-gas
AARCH64_CC=aarch64-cc
AARCH64_LD=aarch64-cc

RISCV64_AS=riscv64-gas
RISCV64_CC=riscv64-cc
RISCV64_LD=riscv64-cc

X86_64_AS=gas
X86_64_CC=cc
X86_64_LD=cc
-- 
2.42.0

[PATCH hare 09/32] cmd::hare: changes for OpenBSD

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-10-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +58 -9
Signed-off-by: Lorenz (xha) <me@xha.li>
---
well, i couldn't come up better commit message. i broke my head
trying to think how i could implement this but i think this is the
best solution that i could come up with for now.

i am also not a fan of doing if platform == "<>", but for now i
think this is a good thing to avoid extra complexity, like having
platforms have their own flags, although that will probably be
implemented with future ports anyways, idk

suggestions on alternatives how to implement this are very welcome :)

 cmd/hare/build.ha       |  8 ++++++--
 cmd/hare/build/types.ha |  3 +++
 cmd/hare/build/util.ha  | 19 ++++++++++++-------
 cmd/hare/error.ha       |  3 +++
 cmd/hare/main.ha        |  2 ++
 cmd/hare/platform.ha    | 32 ++++++++++++++++++++++++++++++++
 6 files changed, 58 insertions(+), 9 deletions(-)
 create mode 100644 cmd/hare/platform.ha

diff --git a/cmd/hare/build.ha b/cmd/hare/build.ha
index fbd0da73..49e7138b 100644
--- a/cmd/hare/build.ha
+++ b/cmd/hare/build.ha
@@ -22,6 +22,7 @@ use unix::tty;

fn build(name: str, cmd: *getopt::command) (void | error) = {
	let arch = get_arch(os::machine())?;
	let platform = get_platform(os::sysname())?;
	let output = "";
	let ctx = build::context {
		ctx = module::context {
@@ -37,6 +38,8 @@ fn build(name: str, cmd: *getopt::command) (void | error) = {
			yield ncpu;
		},
		version = build::get_version(os::tryenv("HAREC", "harec"))?,
		arch = arch.qbe_name,
		platform = platform.name,
		...
	};
	defer build::ctx_finish(&ctx);
@@ -133,7 +136,6 @@ fn build(name: str, cmd: *getopt::command) (void | error) = {
		os::exit(os::status::FAILURE);
	};

	ctx.arch = arch.qbe_name;
	ctx.cmds = ["",
		os::tryenv("HAREC", "harec"),
		os::tryenv("QBE", "qbe"),
@@ -141,7 +143,9 @@ fn build(name: str, cmd: *getopt::command) (void | error) = {
		os::tryenv("LD", arch.ld_cmd),
	];
	set_arch_tags(&ctx.ctx.tags, arch);
	if (len(ctx.libs) > 0) {

	if (len(ctx.libs) > 0 || platform.need_libc) {
		ctx.libc = true;
		merge_tags(&ctx.ctx.tags, "+libc")?;
		ctx.cmds[build::stage::BIN] = os::tryenv("CC", arch.cc_cmd);
	};
diff --git a/cmd/hare/build/types.ha b/cmd/hare/build/types.ha
index fe28af58..e42534d5 100644
--- a/cmd/hare/build/types.ha
+++ b/cmd/hare/build/types.ha
@@ -54,6 +54,7 @@ export type output = enum {
export type context = struct {
	ctx: module::context,
	arch: str,
	platform: str,
	goal: stage,
	defines: []ast::decl_const,
	libdirs: []str,
@@ -70,6 +71,8 @@ export type context = struct {
	submods: bool,
	// if true, the main function won't be checked by harec
	freestanding: bool,
	// if true, we are linking with libc (using cc instead of ld)
	libc: bool,

	cmds: [NSTAGES]str,

diff --git a/cmd/hare/build/util.ha b/cmd/hare/build/util.ha
index d6fd4fda..3844c5e5 100644
--- a/cmd/hare/build/util.ha
+++ b/cmd/hare/build/util.ha
@@ -84,7 +84,7 @@ fn get_flags(ctx: *context, t: *task) ([]str | error) = {
	case stage::O =>
		yield "ASFLAGS";
	case stage::BIN =>
		yield if (len(ctx.libs) > 0) "LDFLAGS" else "LDLINKFLAGS";
		yield if (ctx.libc) "LDFLAGS" else "LDLINKFLAGS";
	};
	let flags: []str = match (shlex::split(os::tryenv(flags, ""))) {
	case let s: []str =>
@@ -109,12 +109,17 @@ fn get_flags(ctx: *context, t: *task) ([]str | error) = {
			append(flags, strings::dup("-L"));
			append(flags, strings::dup(ctx.libdirs[i]));
		};
		if (len(ctx.libs) == 0) {
		if (ctx.libc) {
			append(flags, strings::dup("-Wl,--gc-sections"));
		} else {
			append(flags, strings::dup("--gc-sections"));
			append(flags, strings::dup("-z"));
			append(flags, strings::dup("noexecstack"));
		} else {
			append(flags, strings::dup("-Wl,--gc-sections"));
		};
		// IBT/BTI is enforced by default but not implemented by QBE
		if (ctx.platform == "OpenBSD") {
			append(flags, strings::dup("-z"));
			append(flags, strings::dup("nobtcfi"));
		};
		return flags;
	};
@@ -123,14 +128,14 @@ fn get_flags(ctx: *context, t: *task) ([]str | error) = {
	if (len(ctx.ns) != 0 && t.idx == ctx.top) {
		append(flags, strings::dup("-N"));
		append(flags, unparse::identstr(ctx.ns));
	} else if (len(mod.ns) != 0 || len(ctx.libs) != 0) {
	} else if (len(mod.ns) != 0 || ctx.libc) {
		append(flags, strings::dup("-N"));
		append(flags, unparse::identstr(mod.ns));
	};
	if (ctx.freestanding) {
		append(flags, strings::dup("-m"));
		append(flags, "");
	} else if (len(ctx.libs) != 0) {
	} else if (ctx.libc) {
		append(flags, strings::dup("-m.main"));
	};
	append(flags, strings::dup("-M"));
@@ -257,7 +262,7 @@ fn get_args(ctx: *context, tmp: str, flags: []str, t: *task) []str = {
				append(args, strings::dup(srcs.o[i]));
			};
		};
		if (len(ctx.libs) > 0) {
		if (ctx.libc) {
			append(args, strings::dup("-Wl,--no-gc-sections"));
		};
		for (let i = 0z; i < len(ctx.libs); i += 1) {
diff --git a/cmd/hare/error.ha b/cmd/hare/error.ha
index 280330aa..2fe5d8ec 100644
--- a/cmd/hare/error.ha
+++ b/cmd/hare/error.ha
@@ -18,6 +18,7 @@ type error = !(
	parse::error |
	strconv::error |
	unknown_arch |
	unknown_platform |
	unknown_output |
	unknown_type |
	output_failed |
@@ -26,6 +27,8 @@ type error = !(

type unknown_arch = !str;

type unknown_platform = !str;

type unknown_output = !void;

type unknown_type = !str;
diff --git a/cmd/hare/main.ha b/cmd/hare/main.ha
index bb06e3c9..e368064b 100644
--- a/cmd/hare/main.ha
+++ b/cmd/hare/main.ha
@@ -115,6 +115,8 @@ export fn main() void = {
			fmt::fatal("Error:", strconv::strerror(e));
		case let e: unknown_arch =>
			fmt::fatalf("Error: Unknown arch: {}", e);
		case let e: unknown_platform =>
			fmt::fatalf("Error: Unknown platform: {}", e);
		case unknown_output =>
			fmt::fatal("Error: Can't guess output in root directory");
		case let e: unknown_type =>
diff --git a/cmd/hare/platform.ha b/cmd/hare/platform.ha
new file mode 100644
index 00000000..8e661ca6
--- /dev/null
+++ b/cmd/hare/platform.ha
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-only
// (c) Hare authors <https://harelang.org>

type platform = struct {
	name: str,
	// Do we always need to link with libc? (and use cc instead of ld)
	need_libc: bool,
};

const platforms: [_]platform = [
	platform {
		name = "Linux",
		need_libc = false,
	},
	platform {
		name = "FreeBSD",
		need_libc = false,
	},
	platform {
		name = "OpenBSD",
		need_libc = true,
	},
];

fn get_platform(name: str) (*platform | unknown_platform) = {
	for (let i = 0z; i < len(platforms); i += 1) {
		if (platforms[i].name == name) {
			return &platforms[i];
		};
	};
	return name: unknown_platform;
};
-- 
2.42.0

[PATCH hare 11/32] OpenBSD: add io

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-12-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +230 -2
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 io/+openbsd/dup.ha           | 44 +++++++++++++++++++
 io/+openbsd/mmap.ha          | 53 +++++++++++++++++++++++
 io/+openbsd/platform_file.ha | 82 ++++++++++++++++++++++++++++++++++++
 io/+openbsd/vector.ha        | 49 +++++++++++++++++++++
 io/file.ha                   |  4 +-
 5 files changed, 230 insertions(+), 2 deletions(-)
 create mode 100644 io/+openbsd/dup.ha
 create mode 100644 io/+openbsd/mmap.ha
 create mode 100644 io/+openbsd/platform_file.ha
 create mode 100644 io/+openbsd/vector.ha

diff --git a/io/+openbsd/dup.ha b/io/+openbsd/dup.ha
new file mode 100644
index 00000000..8c1f4f15
--- /dev/null
+++ b/io/+openbsd/dup.ha
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use rt;

// Flags for [[dup]] and [[dup2]] operations.
export type dupflag = enum {
       NONE = 0,

       // Causes [[dup]] and [[dup2]] not to set the CLOEXEC flag on the
       // duplicated file descriptor. By default, CLOEXEC is set.
       NOCLOEXEC = rt::FD_CLOEXEC,
};

// Duplicates a file descriptor.
export fn dup(old: file, flags: dupflag) (file | error) = {
       flags ^= dupflag::NOCLOEXEC; // Invert CLOEXEC

       match (rt::dup2(old, -1)) {
       case let fd: int =>
               const fl = rt::fcntl(fd, rt::F_GETFD, 0)!;
               rt::fcntl(fd, rt::F_SETFD, fl | rt::FD_CLOEXEC)!;
               return fd;
       case let e: rt::errno =>
               return errors::errno(e);
       };
};

// Duplicates a file descriptor and stores the new file at a specific file
// descriptor number. If the file indicated by "new" already refers to an open
// file, this file will be closed before the file descriptor is reused.
export fn dup2(old: file, new: file, flags: dupflag) (file | error) = {
       flags ^= dupflag::NOCLOEXEC; // Invert CLOEXEC

       match (rt::dup2(old, new)) {
       case let fd: int =>
               const fl = rt::fcntl(fd, rt::F_GETFD, 0)!;
               rt::fcntl(fd, rt::F_SETFD, fl | flags)!;
               return fd;
       case let e: rt::errno =>
               return errors::errno(e);
       };
};
\ No newline at end of file
diff --git a/io/+openbsd/mmap.ha b/io/+openbsd/mmap.ha
new file mode 100644
index 00000000..6d84f7e5
--- /dev/null
+++ b/io/+openbsd/mmap.ha
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use rt;

// Values for the [[mmap]] prot parameter. Only the EXEC, READ, WRITE, and NONE
// values are portable.
export type prot = enum int {
	NONE = rt::PROT_NONE,
	READ = rt::PROT_READ,
	WRITE = rt::PROT_WRITE,
	EXEC = rt::PROT_EXEC,
};

// Values for the [[mmap]] flags parameter. Only the SHARED, PRIVATE, and FIXED
// values are portable.
export type mflag = enum int {
	SHARED = rt::MAP_SHARED,
	PRIVATE = rt::MAP_PRIVATE,
	FIXED = rt::MAP_FIXED,
	ANON = rt::MAP_ANON,
	STACK = rt::MAP_STACK,
	CONCEAL = rt::MAP_CONCEAL
};

// Performs the mmap syscall. Consult your system for documentation on this
// function.
export fn mmap(
	addr: nullable *opaque,
	length: size,
	prot: prot,
	flags: mflag,
	fd: file,
	offs: size
) (*opaque | errors::error) = {
	match (rt::mmap(addr, length, prot, flags, fd, offs: i64)) {
	case let ptr: *opaque =>
		return ptr;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};

// Unmaps memory previously mapped with [[mmap]].
export fn munmap(addr: *opaque, length: size) (void | errors::error) = {
	match (rt::munmap(addr, length)) {
	case void =>
		return;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};
diff --git a/io/+openbsd/platform_file.ha b/io/+openbsd/platform_file.ha
new file mode 100644
index 00000000..e1a8b7a6
--- /dev/null
+++ b/io/+openbsd/platform_file.ha
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>
use errors;
use rt;

// This is an opaque type which encloses an OS-level file handle resource. It
// can be used as a [[handle]] in most situations, but there are some APIs which
// require a [[file]] with some OS-level handle backing it - this type is used
// for such APIs.
//
// On OpenBSD, [[file]] is a file descriptor.
export type file = int;

// Opens a Unix file descriptor as a file. This is a low-level interface, to
// open files most programs will use something like [[os::open]]. This function
// is not portable.
export fn fdopen(fd: int) file = fd;

fn fd_read(fd: file, buf: []u8) (size | EOF | error) = {
	match (rt::read(fd, buf: *[*]u8, len(buf))) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let n: size =>
		switch (n) {
		case 0 =>
			return EOF;
		case =>
			return n;
		};
	};
};

fn fd_write(fd: file, buf: const []u8) (size | error) = {
	match (rt::write(fd, buf: *const [*]u8, len(buf))) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let n: size =>
		return n;
	};
};

fn fd_close(fd: file) (void | error) = {
	match (rt::close(fd)) {
	case void => void;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};

fn fd_seek(
	fd: file,
	offs: off,
	whence: whence,
) (off | error) = {
	match (rt::lseek(fd, offs: i64, whence: int)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let n: i64 =>
		return n: off;
	};
};

fn fd_copy(to: file, from: file) (size | error) = errors::unsupported;

fn fd_lock(fd: file, flags: int) (bool | error) = {
	match (rt::flock(fd: int, flags)) {
	case void => return true;
	case let e: rt::errno =>
		if (e == rt::EWOULDBLOCK: rt::errno) {
			return false;
		} else {
			return errors::errno(e);
		};
	};
};

fn fd_trunc(fd: file, ln: size) (void | error) = {
	match (rt::ftruncate(fd: int, ln: rt::off_t)) {
	case void => void;
	case let e: rt::errno => return errors::errno(e);
	};
};
diff --git a/io/+openbsd/vector.ha b/io/+openbsd/vector.ha
new file mode 100644
index 00000000..3f20c0c4
--- /dev/null
+++ b/io/+openbsd/vector.ha
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use rt;
use types;

export type vector = rt::iovec;

// Creates a vector for use with [[writev]] and [[readv]].
export fn mkvector(buf: []u8) vector = vector {
	iov_base = buf: *[*]u8,
	iov_len = len(buf),
};

// Performs a vectored read on the given file. A read is performed on each of
// the vectors, prepared with [[mkvector]], in order, and the total number of
// bytes read is returned.
export fn readv(fd: file, vectors: vector...) (size | EOF | error) = {
	if (len(vectors) > types::INT_MAX: size) {
		return errors::invalid;
	};
	match (rt::readv(fd, vectors: *[*]rt::iovec, len(vectors): int)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let n: size =>
		switch (n) {
		case 0 =>
			return EOF;
		case =>
			return n;
		};
	};
};

// Performs a vectored write on the given file. Each of the vectors, prepared
// with [[mkvector]], are written to the file in order, and the total number of
// bytes written is returned.
export fn writev(fd: file, vectors: vector...) (size | error) = {
	if (len(vectors) > types::INT_MAX: size) {
		return errors::invalid;
	};
	match (rt::writev(fd, vectors: *[*]rt::iovec, len(vectors): int)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let n: size =>
		return n;
	};
};
diff --git a/io/file.ha b/io/file.ha
index 2843552d..2efcedcb 100644
--- a/io/file.ha
+++ b/io/file.ha
@@ -13,8 +13,8 @@ export type lockop = enum int {
	UNLOCK    = rt::LOCK_UN,
};

// Apply or remove an advisory lock on an open file. If block is true, the request will block while waiting
// for the lock.
// Apply or remove an advisory lock on an open file. If block is true, the
// request will block while waiting for the lock.
export fn lock(fd: file, block: bool, op: lockop) (bool | error) = {
	let flags = op: int;
	if (!block) flags |= rt::LOCK_NB;
-- 
2.42.0

[PATCH hare 12/32] OpenBSD: add path

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-13-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +12 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 path/+openbsd.ha | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 path/+openbsd.ha

diff --git a/path/+openbsd.ha b/path/+openbsd.ha
new file mode 100644
index 00000000..8ca8e430
--- /dev/null
+++ b/path/+openbsd.ha
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Platform-specific path separator byte.
export def SEP: u8 = '/';

const sepstr: str = "/";

// Maximum length of a file path for this platform.
export def MAX: size = rt::PATH_MAX - 1;
-- 
2.42.0

[PATCH hare 13/32] OpenBSD: add time

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-14-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +91 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 time/+openbsd/functions.ha | 91 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 time/+openbsd/functions.ha

diff --git a/time/+openbsd/functions.ha b/time/+openbsd/functions.ha
new file mode 100644
index 00000000..0af46fed
--- /dev/null
+++ b/time/+openbsd/functions.ha
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Converts a [[duration]] to an [[rt::timespec]]. This function is
// non-portable.
export fn duration_to_timespec(n: duration, ts: *rt::timespec) void = {
	ts.tv_sec = n / SECOND;
	ts.tv_nsec = n % SECOND;
};

// Converts an [[instant]] to an [[rt::timespec]]. This function is
// non-portable.
export fn instant_to_timespec(t: instant, ts: *rt::timespec) void = {
	ts.tv_sec = t.sec;
	ts.tv_nsec = t.nsec;
};

// Converts a [[rt::timespec]] to an [[instant]]. This function is
// non-portable.
export fn timespec_to_instant(ts: rt::timespec) instant = instant {
	sec = ts.tv_sec,
	nsec = ts.tv_nsec,
};

// Yields the process to the kernel and returns after the requested duration.
export fn sleep(n: duration) void = {
	let in = rt::timespec { ... };
	duration_to_timespec(n, &in);
	let req = &in;

	for (true) {
		let res = rt::timespec { ... };
		match (rt::nanosleep(req, &res)) {
		case void =>
			return;
		case let err: rt::errno =>
			switch (err) {
			case rt::EINTR =>
				req = &res;
			case =>
				abort("Unexpected error from nanosleep");
			};
		};
	};
};

// An enumeration of clocks available on this system. Different clocks represent
// times from different epochs, and have different characteristics with regards
// to leap seconds, NTP adjustments, and so on. All systems provide the REALTIME
// and MONOTONIC clocks at least; use of other clocks is not guaranteed to be
// portable.
export type clock = enum {
	// The current wall-clock time. This may jump forwards or backwards in
	// time to account for leap seconds, NTP adjustments, etc.
	REALTIME = rt::CLOCK_REALTIME,

	// The current monotonic time. This clock measures from some undefined
	// epoch and is not affected by leap seconds, NTP adjustments, and
	// changes to the system time: it always increases by one second per
	// second.
	MONOTONIC = rt::CLOCK_MONOTONIC,

	// The uptime clock. It is the time that has elapsed since the system
	// booted. Begins at zero.
	BOOT = rt::CLOCK_BOOTTIME,

	// The runtime clock. It only advances while the system is not suspended
	// and begins when the system is booted. Begins at zero.
	UPTIME = rt::CLOCK_UPTIME,

	// The process CPU clock. It begins at zero and is advanced while the
	// calling process is running in user or kernel mode.
	PROCESS_CPU = rt::CLOCK_PROCESS_CPUTIME_ID,

	// The thread CPU clock. It begins at zero and is advanced while the
	// calling thread is running in user or kernel mode.
	THREAD_CPU = rt::CLOCK_THREAD_CPUTIME_ID,
};

// Returns the current time for a given clock.
export fn now(clock: clock) instant = {
	let ts = rt::timespec { ... };
	match (rt::clock_gettime(clock, &ts)) {
	case void =>
		return timespec_to_instant(ts);
	case let err: rt::errno =>
		abort("Unexpected error from clock_gettime");
	};
};
-- 
2.42.0

[PATCH hare 14/32] OpenBSD: add time::chrono

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-15-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +9 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 time/chrono/+openbsd.ha | 9 +++++++++
 1 file changed, 9 insertions(+)
 create mode 100644 time/chrono/+openbsd.ha

diff --git a/time/chrono/+openbsd.ha b/time/chrono/+openbsd.ha
new file mode 100644
index 00000000..600f606c
--- /dev/null
+++ b/time/chrono/+openbsd.ha
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

def LOCALTIME_PATH: str = "/etc/localtime";
def ZONEINFO_PREFIX: str = "/usr/share/zoneinfo/";

// The filepath of the system's "leap-seconds.list" file, which contains UTC/TAI
// leap second data.
export def UTC_LEAPSECS_FILE: str = "/usr/share/zoneinfo/leap-seconds.list";
-- 
2.42.0

[PATCH hare 16/32] OpenBSD: add os::exec

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-17-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +421 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
i just see that now that i added a space to both freebsd and linux...

i hope it's fine, i couldn't resist :D

 os/exec/+freebsd/platform_cmd.ha |   1 +
 os/exec/+linux/platform_cmd.ha   |   1 +
 os/exec/+openbsd/exec.ha         | 190 ++++++++++++++++++++++++++
 os/exec/+openbsd/platform_cmd.ha |   6 +
 os/exec/+openbsd/process.ha      | 223 +++++++++++++++++++++++++++++++
 5 files changed, 421 insertions(+)
 create mode 100644 os/exec/+openbsd/exec.ha
 create mode 100644 os/exec/+openbsd/platform_cmd.ha
 create mode 100644 os/exec/+openbsd/process.ha

diff --git a/os/exec/+freebsd/platform_cmd.ha b/os/exec/+freebsd/platform_cmd.ha
index 0aeb3536..c0259ae8 100644
--- a/os/exec/+freebsd/platform_cmd.ha
+++ b/os/exec/+freebsd/platform_cmd.ha
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use io;
use os;
use strings;
diff --git a/os/exec/+linux/platform_cmd.ha b/os/exec/+linux/platform_cmd.ha
index 0aeb3536..c0259ae8 100644
--- a/os/exec/+linux/platform_cmd.ha
+++ b/os/exec/+linux/platform_cmd.ha
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use io;
use os;
use strings;
diff --git a/os/exec/+openbsd/exec.ha b/os/exec/+openbsd/exec.ha
new file mode 100644
index 00000000..b3b37632
--- /dev/null
+++ b/os/exec/+openbsd/exec.ha
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use io;
use os;
use rt;
use types::c;
use unix;
use path;

// Forks the current process, returning the [[process]] of the child (to the
// parent) and void (to the child), or an error.
export fn fork() (process | void | error) = {
	match (rt::fork()) {
	case let err: rt::errno  =>
		return errors::errno(err);
	case let i: int =>
		return i: process;
	case void =>
		return void;
	};
};

// Creates an anonymous pipe for use with [[addfile]]. Any data written to the
// second file may be read from the first file. The caller should close one or
// both of the file descriptors after they have transferred them to another
// process, and after they have finished using them themselves, if applicable.
//
// This function will abort the process if the system is unable to allocate the
// resources for a pipe. If you need to handle this error gracefully, you may
// call [[unix::pipe]] yourself, but this may reduce the portability of your
// software.
//
// To capture the standard output of a process:
//
// 	let pipe = exec::pipe();
// 	exec::addfile(&cmd, pipe.1, os::stdout_file);
// 	let proc = exec::start(&cmd)!;
// 	io::close(pipe.1)!;
//
// 	let data = io::drain(pipe.0)!;
// 	io::close(pipe.0)!;
// 	exec::wait(&proc)!;
//
// To write to the standard input of a process:
//
// 	let pipe = exec::pipe();
// 	exec::addfile(&cmd, os::stdin_file, pipe.0);
// 	let proc = exec::start(&cmd)!;
//
// 	io::writeall(data)!;
// 	io::close(pipe.1)!;
// 	io::close(pipe.0)!;
// 	exec::wait(&proc)!;
export fn pipe() (io::file, io::file) = {
	return unix::pipe()!;
};

fn open(path: str) (platform_cmd | error) = {
	if (os::access(path, os::amode::X_OK)?) {
		// Length was already checked by access()
		return path::init(path)!;
	};
	return errors::noaccess;
};

fn platform_finish(cmd: *command) void = void;

fn platform_exec(cmd: *command) error = {
	// We don't worry about freeing the return values from c::fromstr
	// because once we exec(2) our heap is fried anyway
	let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z);
	for (let i = 0z; i < len(cmd.argv); i += 1z) {
		append(argv, c::fromstr(cmd.argv[i]));
	};
	append(argv, null);

	let envp: nullable *[*]nullable *const c::char = null;
	if (len(cmd.env) != 0) {
		let env: []nullable *const c::char = alloc([], len(cmd.env) + 1);
		for (let i = 0z; i < len(cmd.env); i += 1) {
			append(env, c::fromstr(cmd.env[i]));
		};
		append(env, null);
		envp = env: *[*]nullable *const c::char;
	};

	let need_devnull = false;
	for (let i = 0z; i < len(cmd.files); i += 1) {
		const from = match (cmd.files[i].0) {
		case let file: io::file =>
			yield file;
		case nullfd =>
			need_devnull = true;
			continue;
		case closefd =>
			continue;
		};

		cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) {
		case let fd: int =>
			yield fd;
		case let err: rt::errno =>
			return errors::errno(err);
		};
	};

	const devnull: io::file = if (need_devnull) {
		yield os::open("/dev/null")!;
	} else -1;

	for (let i = 0z; i < len(cmd.files); i += 1) {
		const from = match (cmd.files[i].0) {
		case let file: io::file =>
			yield file;
		case nullfd =>
			yield devnull;
		case closefd =>
			io::close(cmd.files[i].1)?;
			continue;
		};

		if (cmd.files[i].1 == from) {
			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
			case let flags: int =>
				yield flags;
			case let e: rt::errno =>
				return errors::errno(e);
			};
			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
		} else {
			match (rt::dup2(from, cmd.files[i].1)) {
			case int => void;
			case let e: rt::errno =>
				return errors::errno(e);
			};
		};
	};

	if (cmd.dir != "") {
		os::chdir(cmd.dir)?;
	};

	return errors::errno(rt::execve(path::string(&cmd.platform),
		argv: *[*]nullable *const u8,
		envp: *[*]nullable *const u8));
};

fn platform_start(cmd: *command) (process | errors::error) = {
	// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
	let pipe: [2]int = [0...];
	match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case void => void;
	};

	match (rt::fork()) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		rt::close(pipe[1])!;
		defer rt::close(pipe[0])!;
		let errno: int = 0;
		match (rt::read(pipe[0], &errno, size(int))) {
		case let err: rt::errno =>
			return errors::errno(err);
		case let n: size =>
			switch (n) {
			case size(int) =>
				return errors::errno(errno);
			case 0 =>
				return pid;
			case =>
				abort("Unexpected rt::read result");
			};
		};
	case void =>
		rt::close(pipe[0])!;
		let err = platform_exec(cmd);
		if (!(err is errors::opaque_)) {
			rt::exit(1);
		};
		let err = err as errors::opaque_;
		let err = &err.data: *rt::errno;
		rt::write(pipe[1], err, size(int))!;
		rt::exit(1);
	};
};
diff --git a/os/exec/+openbsd/platform_cmd.ha b/os/exec/+openbsd/platform_cmd.ha
new file mode 100644
index 00000000..3c5fa4ba
--- /dev/null
+++ b/os/exec/+openbsd/platform_cmd.ha
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use path;

export type platform_cmd = path::buffer;
diff --git a/os/exec/+openbsd/process.ha b/os/exec/+openbsd/process.ha
new file mode 100644
index 00000000..7d381799
--- /dev/null
+++ b/os/exec/+openbsd/process.ha
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use fmt;
use rt;
use time;
use unix::signal;

// Stores information about a child process.
export type process = int;

// Returns the currently running [[process]].
export fn self() process = {
	return rt::getpid();
};

// Stores information about an exited process.
export type status = struct {
	status: int,
	// Not all of these members are supported on all operating systems.
	// Only utime and stime are guaranteed to be available.
	rusage: struct {
		utime: time::instant,
		stime: time::instant,
		maxrss: i64,
		ixrss: i64,
		idrss: i64,
		isrss: i64,
		minflt: i64,
		majflt: i64,
		nswap: i64,
		inblock: i64,
		oublock: i64,
		msgsnd: i64,
		msgrcv: i64,
		nsignals: i64,
		nvcsw: i64,
		nivcsw: i64,
	},
};

fn rusage(st: *status, ru: *rt::rusage) void = {
	st.rusage.utime = time::instant {
		sec = ru.ru_utime.tv_sec,
		nsec = ru.ru_utime.tv_usec * time::MICROSECOND: i64,
	};
	st.rusage.stime = time::instant {
		sec = ru.ru_stime.tv_sec,
		nsec = ru.ru_stime.tv_usec * time::MICROSECOND: i64,
	};
	st.rusage.maxrss = ru.ru_maxrss;
	st.rusage.ixrss = ru.ru_ixrss;
	st.rusage.idrss = ru.ru_idrss;
	st.rusage.isrss = ru.ru_isrss;
	st.rusage.minflt = ru.ru_minflt;
	st.rusage.majflt = ru.ru_majflt;
	st.rusage.nswap = ru.ru_nswap;
	st.rusage.inblock = ru.ru_inblock;
	st.rusage.oublock = ru.ru_oublock;
	st.rusage.msgsnd = ru.ru_msgsnd;
	st.rusage.msgrcv = ru.ru_msgrcv;
	st.rusage.nsignals = ru.ru_nsignals;
	st.rusage.nvcsw = ru.ru_nvcsw;
	st.rusage.nivcsw = ru.ru_nivcsw;
};

// Waits for a process to complete, then returns its status information.
export fn wait(proc: *process) (status | error) = {
	let ru = rt::rusage { ... };
	let st = status { ... };
	match (rt::wait4(*proc, &st.status, 0, &ru)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		// TODO: Handle invalid pid?
		assert(pid == *proc);
	};
	rusage(&st, &ru);
	return st;
};

// Waits for the first child process to complete, then returns its process info
// and status
export fn waitany() ((process, status) | error) = {
	let ru = rt::rusage { ... };
	let st = status { ... };
	match (rt::wait4(rt::WAIT_ANY, &st.status, 0, &ru)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		rusage(&st, &ru);
		return (pid, st);
	};
};

// Waits for all children to terminate succesfully. If a child process exits
// with a nonzero status, returns its process info and exit status immediately,
// not waiting for the remaining children.
export fn waitall() (uint | error | !(process, exit_status)) = {
	let st = status { ... };
	let ru = rt::rusage { ... };
	for (let i = 0u; true; i += 1) {
		match (rt::wait4(rt::WAIT_ANY, &st.status, 0, &ru)) {
		case let err: rt::errno =>
			if (err == rt::ECHILD) {
				return i;
			} else {
				return errors::errno(err);
			};
		case let pid: int =>
			match (check(&st)) {
			case void => void;
			case let es: !exit_status =>
				return (pid, es);
			};
		};
	};
	abort("unreachable");
};

// Checks for process completion, returning its status information on
// completion, or void if it is still running.
export fn peek(proc: *process) (status | void | error) = {
	let ru = rt::rusage { ... };
	let st = status { ... };
	match (rt::wait4(*proc, &st.status, rt::WNOHANG, &ru)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		switch (pid) {
		case 0 =>
			return;
		case =>
			assert(pid == *proc);
		};
	};
	rusage(&st, &ru);
	return st;
};

// Checks if any child process has completed, returning its process info and
// status if so.
export fn peekany() ((process, status) | void | error) = {
	let ru = rt::rusage { ... };
	let st = status { ... };
	match (rt::wait4(rt::WAIT_ANY, &st.status, rt::WNOHANG, &ru)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		switch (pid) {
		case 0 =>
			return;
		case =>
			return (pid, st);
		};
	};
};

// The exit status code of a process.
export type exited = int;

// The signal number which caused a process to terminate.
export type signaled = signal::sig;

// The exit status of a process.
export type exit_status = (exited | signaled);

// Returns a human friendly string describing the exit status. The string is
// statically allocated; use [[strings::dup]] to extend its lifetime.
export fn exitstr(status: exit_status) const str = {
	static let buf: [64]u8 = [0...];
	match (status) {
	case let i: exited =>
		switch (i) {
		case 0 =>
			return "exited normally";
		case =>
			return fmt::bsprintf(buf, "exited with status {}",
				i: int);
		};
	case let s: signaled =>
		return fmt::bsprintf(buf, "exited with signal {}",
			signal::signame(s));
	};
};

// Returns the exit status of a completed process.
export fn exit(stat: *status) exit_status = {
	if (rt::wifexited(stat.status)) {
		return rt::wexitstatus(stat.status): exited;
	};
	if (rt::wifsignaled(stat.status)) {
		return rt::wtermsig(stat.status): signaled;
	};
	abort("Unexpected exit status");
};

// Checks the exit status of a completed process, returning void if successful,
// or its status code as an error type if not.
export fn check(stat: *status) (void | !exit_status) = {
	if (rt::wifexited(stat.status) && rt::wexitstatus(stat.status) == 0) {
		return;
	};
	return exit(stat);
};

// Terminates a process. On OpenBSD, this sends [[unix::signal::sig::TERM]] to
// the process.
export fn kill(proc: process) (void | errors::error) = {
	return sig(proc, signal::sig::TERM);
};

// Sends a signal to a child process. This function is only supported on
// Unix-like systems.
export fn sig(proc: process, sig: signal::sig) (void | errors::error) = {
	match (rt::kill(proc, sig)) {
	case let errno: rt::errno =>
		return errors::errno(errno);
	case void =>
		return;
	};
};
-- 
2.42.0

[PATCH hare 15/32] OpenBSD: add os

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-16-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +707 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 os/+openbsd/dirfdfs.ha          | 446 ++++++++++++++++++++++++++++++++
 os/+openbsd/exit+test.ha        |   7 +
 os/+openbsd/exit.ha             |  10 +
 os/+openbsd/fs.ha               |  75 ++++++
 os/+openbsd/platform_environ.ha | 104 ++++++++
 os/+openbsd/status.ha           |   9 +
 os/+openbsd/stdfd.ha            |  56 ++++
 7 files changed, 707 insertions(+)
 create mode 100644 os/+openbsd/dirfdfs.ha
 create mode 100644 os/+openbsd/exit+test.ha
 create mode 100644 os/+openbsd/exit.ha
 create mode 100644 os/+openbsd/fs.ha
 create mode 100644 os/+openbsd/platform_environ.ha
 create mode 100644 os/+openbsd/status.ha
 create mode 100644 os/+openbsd/stdfd.ha

diff --git a/os/+openbsd/dirfdfs.ha b/os/+openbsd/dirfdfs.ha
new file mode 100644
index 00000000..a82cd4d5
--- /dev/null
+++ b/os/+openbsd/dirfdfs.ha
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// License: MPL-2.0
use errors;
use encoding::utf8;
use fs;
use rt;
use strings;
use io;
use types::c;
use time;
use path;

type os_filesystem = struct {
	fs: fs::fs,
	dirfd: int,
	getdents_bufsz: size,
};

fn fsflags_to_bsd(flags: fs::flag) int = {
	let out = rt::O_CLOEXEC;
	if (flags & fs::flag::RDONLY > 0) {
		out |= rt::O_RDONLY;
	};
	if (flags & fs::flag::WRONLY > 0) {
		out |= rt::O_WRONLY;
	};
	if (flags & fs::flag::RDWR > 0) {
		out |= rt::O_RDWR;
	};
	if (flags & fs::flag::CREATE > 0) {
		out |= rt::O_CREAT;
	};
	if (flags & fs::flag::EXCL > 0) {
		out |= rt::O_EXCL;
	};
	if (flags & fs::flag::TRUNC > 0) {
		out |= rt::O_TRUNC;
	};
	if (flags & fs::flag::APPEND > 0) {
		out |= rt::O_APPEND;
	};
	if (flags & fs::flag::NONBLOCK > 0) {
		out |= rt::O_NONBLOCK;
	};
	if (flags & fs::flag::SYNC > 0
			|| flags & fs::flag::DSYNC > 0
			|| flags & fs::flag::RSYNC > 0) {
		out |= rt::O_SYNC;
	};
	if (flags & fs::flag::DIRECTORY > 0) {
		out |= rt::O_DIRECTORY;
	};
	if (flags & fs::flag::NOFOLLOW > 0) {
		out |= rt::O_NOFOLLOW;
	};
	if (flags & fs::flag::NOCLOEXEC > 0) {
		out &= ~rt::O_CLOEXEC;
	};
	if (flags & fs::flag::PATH > 0) {
		abort("fs::flag::PATH is not supported on OpenBSD");
	};
	if (flags & fs::flag::NOATIME > 0) {
		abort("fs::flag::NOATIME > 0 is not supported on OpenBSD");
	};
	if (flags & fs::flag::TMPFILE > 0) {
		abort("fs::flag::TMPFILE is not supported on OpenBSD");
	};
	if (flags & fs::flag::CTTY > 0) {
		abort("fs::flag::CTTY is not supported on OpenBSD");
	};
	return out;
};

fn _fs_open(
	fs: *fs::fs,
	path: str,
	flags: int,
	mode: uint,
) (io::file | fs::error) = {
	let fs = fs: *os_filesystem;

	let fd = match (rt::openat(fs.dirfd, path, flags, mode)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case let fd: int =>
		yield fd;
	};

	return io::fdopen(fd);
};

fn fs_open_file(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
) (io::file | fs::error) = {
	let oflags = fs::flag::RDONLY;
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i];
	};
	return _fs_open(fs, path, fsflags_to_bsd(oflags), 0);
};

fn fs_open(
	fs: *fs::fs,
	path: str,
	flags: fs::flag...
) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?;

fn fs_readlink(fs: *fs::fs, path: str) (str | fs::error) = {
	let fs = fs: *os_filesystem;
	static let buf: [rt::PATH_MAX]u8 = [0...];
	let sz = match (rt::readlinkat(fs.dirfd, path, buf[..])) {
	case let err: rt::errno =>
		switch (err) {
		case rt::EINVAL =>
			return fs::wrongtype;
		case =>
			return errno_to_fs(err);
		};
	case let sz: size =>
		yield sz;
	};
	return strings::fromutf8(buf[..sz])!;
};

fn fs_create_file(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
) (io::file | fs::error) = {
	let oflags: fs::flag = 0;
	if (len(flags) == 0z) {
		oflags |= fs::flag::WRONLY;
	};
	for (let i = 0z; i < len(flags); i += 1z) {
		oflags |= flags[i];
	};
	oflags |= fs::flag::CREATE;
	return _fs_open(fs, path, fsflags_to_bsd(oflags), mode)?;
};

fn fs_create(
	fs: *fs::fs,
	path: str,
	mode: fs::mode,
	flags: fs::flag...
) (io::handle | fs::error) = fs_create_file(fs, path, mode, flags...)?;

fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::unlinkat(fs.dirfd, path, 0)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

fn fs_rename(fs: *fs::fs, oldpath: str, newpath: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::renameat(fs.dirfd, oldpath, fs.dirfd, newpath)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

type os_iterator = struct {
	iter: fs::iterator,
	fd: int,
	buf_pos: int,
	buf_end: int,
	buf: []u8,
};

fn iter_next(iter: *fs::iterator) (fs::dirent | void) = {
	let iter = iter: *os_iterator;
	if (iter.buf_pos >= iter.buf_end) {
		let n = rt::getdents(iter.fd,
			iter.buf: *[*]u8, len(iter.buf)) as int;
		if (n == 0) {
			return;
		};
		iter.buf_end = n;
		iter.buf_pos = 0;
	};
	let de = &iter.buf[iter.buf_pos]: *rt::dirent;
	iter.buf_pos += de.d_reclen: int;
	let name = c::tostr(&de.d_name: *const c::char)!;
	if (name == "." || name == "..") {
		return iter_next(iter);
	};

	let ftype: fs::mode = switch (de.d_type) {
	case rt::DT_UNKNOWN =>
		yield fs::mode::UNKNOWN;
	case rt::DT_FIFO =>
		yield fs::mode::FIFO;
	case rt::DT_CHR =>
		yield fs::mode::CHR;
	case rt::DT_DIR =>
		yield fs::mode::DIR;
	case rt::DT_BLK =>
		yield fs::mode::BLK;
	case rt::DT_REG =>
		yield fs::mode::REG;
	case rt::DT_LNK =>
		yield fs::mode::LINK;
	case rt::DT_SOCK =>
		yield fs::mode::SOCK;
	case =>
		yield fs::mode::UNKNOWN;
	};
	return fs::dirent {
		name = name,
		ftype = ftype,
	};
};

fn iter_finish(iter: *fs::iterator) void = {
	let iter = iter: *os_iterator;
	rt::close(iter.fd)!;
	free(iter.buf);
	free(iter);
};

fn fs_iter(fs: *fs::fs, path: str) (*fs::iterator | fs::error) = {
	let fs = fs: *os_filesystem;
	let flags = rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY;
	let fd: int = match (rt::openat(fs.dirfd, path, flags, 0)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case let fd: int =>
		yield fd;
	};

	let buf = match (rt::malloc(fs.getdents_bufsz)) {
	case let v: *opaque =>
		yield v: *[*]u8;
	case null =>
		return errors::nomem;
	};
	let iter = alloc(os_iterator {
		iter = fs::iterator {
			next = &iter_next,
			finish = &iter_finish,
		},
		fd = fd,
		buf = buf[..fs.getdents_bufsz],
		...
	});
	return &iter.iter;
};

fn fs_stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {
	let fs = fs: *os_filesystem;
	let stat = rt::stat { ... };
	match (rt::fstatat(fs.dirfd, path, &stat, rt::AT_SYMLINK_NOFOLLOW)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
	return fs::filestat {
		mask = fs::stat_mask::UID
			| fs::stat_mask::GID
			| fs::stat_mask::SIZE
			| fs::stat_mask::INODE
			| fs::stat_mask::ATIME
			| fs::stat_mask::MTIME
			| fs::stat_mask::CTIME,
		mode = stat.st_mode: fs::mode,
		uid = stat.st_uid,
		gid = stat.st_gid,
		sz = stat.st_size: size,
		inode = stat.st_ino,
		atime = time::instant {
			sec = stat.st_atim.tv_sec,
			nsec = stat.st_atim.tv_nsec,
		},
		mtime = time::instant {
			sec = stat.st_mtim.tv_sec,
			nsec = stat.st_mtim.tv_nsec,
		},
		ctime = time::instant {
			sec = stat.st_ctim.tv_sec,
			nsec = stat.st_ctim.tv_nsec,
		},
	};
};

fn fs_mkdir(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::mkdirat(fs.dirfd, path, mode: uint)) {
	case let err: rt::errno =>
		switch (err) {
		case rt::EISDIR =>
			return errors::exists;
		case =>
			return errno_to_fs(err);
		};
	case void => void;
	};
};

fn fs_rmdir(fs: *fs::fs, path: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::unlinkat(fs.dirfd, path, rt::AT_REMOVEDIR)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};

};

fn fs_chmod(fs: *fs::fs, path: str, mode: fs::mode) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::fchmodat(fs.dirfd, path, mode: uint, 0)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

fn fs_chown(fs: *fs::fs, path: str, uid: uint, gid: uint) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::fchownat(fs.dirfd, path, uid, gid, 0)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

// TODO: cannot handle errors, i.e. path too long or cannot resolve.
fn fs_resolve(fs: *fs::fs, path: str) str = {
	let fs = fs: *os_filesystem;
	static let buf = path::buffer { ... };

	if (path::abs(path)) {
		return path;
	};

	if (fs.dirfd == rt::AT_FDCWD) {
		path::set(&buf, getcwd(), path)!;
	} else {
		// XXX: this is the best we can for now. we should probably
		// return an error
		path::set(&buf, "<unknown>", path)!;
	};

	return path::string(&buf);
};

fn fs_link(fs: *fs::fs, old: str, new: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::linkat(fs.dirfd, old, fs.dirfd, new, 0)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

fn fs_symlink(fs: *fs::fs, target: str, path: str) (void | fs::error) = {
	let fs = fs: *os_filesystem;
	match (rt::symlinkat(target, fs.dirfd, path)) {
	case let err: rt::errno =>
		return errno_to_fs(err);
	case void => void;
	};
};

fn fs_close(fs: *fs::fs) void = {
	let fs = fs: *os_filesystem;
	rt::close(fs.dirfd)!;
};

// Opens a file descriptor as an [[fs::fs]]. This file descriptor must be a
// directory file. The file will be closed when the fs is closed.
export fn dirfdopen(fd: io::file) *fs::fs = {
	let ofs = alloc(os_filesystem { ... });
	let fs = static_dirfdopen(fd, ofs);
	fs.close = &fs_close;
	return fs;
};

fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = {
        *filesystem = os_filesystem {
                fs = fs::fs {
                        open = &fs_open,
                        openfile = &fs_open_file,
                        create = &fs_create,
                        createfile = &fs_create_file,
                        remove = &fs_remove,
                        rename = &fs_rename,
                        iter = &fs_iter,
                        stat = &fs_stat,
                        readlink = &fs_readlink,
                        mkdir = &fs_mkdir,
                        rmdir = &fs_rmdir,
                        chmod = &fs_chmod,
                        chown = &fs_chown,
                        resolve = &fs_resolve,
                        link = &fs_link,
                        symlink = &fs_symlink,
                        ...
                },
                dirfd = fd,
                getdents_bufsz = 32768, // 32 KiB
                ...
        };
        return &filesystem.fs;
};

// Sets the buffer size to use with the getdents(2) system call, for use with
// [[fs::iter]]. A larger buffer requires a larger runtime allocation, but can
// scan large directories faster. The default buffer size is 32 KiB.
//
// This function is not portable.
export fn dirfdfs_set_getdents_bufsz(fs: *fs::fs, sz: size) void = {
	assert(fs.open == &fs_open);
	let fs = fs: *os_filesystem;
	fs.getdents_bufsz = sz;
};

fn errno_to_fs(err: rt::errno) fs::error = {
	switch (err) {
	case rt::ENOENT =>
		return errors::noentry;
	case rt::EEXIST =>
		return errors::exists;
	case rt::EACCES =>
		return errors::noaccess;
	case rt::EBUSY =>
		return errors::busy;
	case rt::ENOTDIR =>
		return fs::wrongtype;
	case rt::EOPNOTSUPP, rt::ENOSYS =>
		return errors::unsupported;
	case rt::EXDEV =>
		return fs::cannotrename;
	case =>
		return errors::errno(err);
	};
};
diff --git a/os/+openbsd/exit+test.ha b/os/+openbsd/exit+test.ha
new file mode 100644
index 00000000..20802471
--- /dev/null
+++ b/os/+openbsd/exit+test.ha
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Exit the program with the provided status code.
export fn exit(status: int) never = {
	abort("os::exit disabled in +test");
};
diff --git a/os/+openbsd/exit.ha b/os/+openbsd/exit.ha
new file mode 100644
index 00000000..327f227d
--- /dev/null
+++ b/os/+openbsd/exit.ha
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Exit the program with the provided status code.
export fn exit(status: int) never = {
	// The @fini functions will be run by libc.
	rt::exit(status);
};
diff --git a/os/+openbsd/fs.ha b/os/+openbsd/fs.ha
new file mode 100644
index 00000000..ac94df34
--- /dev/null
+++ b/os/+openbsd/fs.ha
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use fs;
use path;
use rt;
use types::c;

@init fn init_cwd() void = {
	static let cwd_fs = os_filesystem { ... };
	cwd = static_dirfdopen(rt::AT_FDCWD, &cwd_fs);
};

// Returns the current working directory. The return value is statically
// allocated and must be duplicated (see [[strings::dup]]) before calling getcwd
// again.
export fn getcwd() str = c::tostr(rt::getcwd() as *const u8: *const c::char)!;

// Change the current working directory.
export fn chdir(target: (*fs::fs | str)) (void | fs::error) = {
	const path: str = match (target) {
	case let fs: *fs::fs =>
		assert(fs.open == &fs_open);
		let fs = fs: *os_filesystem;
		match (rt::fchdir(fs.dirfd)) {
		case let err: rt::errno =>
			return errors::errno(err);
		case void =>
			return;
		};
	case let s: str =>
		yield s;
	};
	match (rt::chdir(path)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case void => void;
	};
};

// Changes the root directory of the process. Generally requires the caller to
// have root or otherwise elevated permissions.
//
// This function is not appropriate for sandboxing.
export fn chroot(target: str) (void | fs::error) = {
	match (rt::chroot(target)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case void => void;
	};
};

// Access modes for [[access]].
export type amode = enum int {
	F_OK = rt::F_OK,
	R_OK = rt::R_OK,
	W_OK = rt::W_OK,
	X_OK = rt::X_OK,
};

// Returns true if the given mode of access is permissible. The use of this
// function is discouraged as it can allow for a race condition to occur betwen
// testing for the desired access mode and actually using the file should the
// permissions of the file change between these operations. It is recommended
// instead to attempt to use the file directly and to handle any errors that
// should occur at that time.
export fn access(path: str, mode: amode) (bool | fs::error) = {
	match (rt::access(path, mode)) {
	case let b: bool =>
		return b;
	case let err: rt::errno =>
		return errno_to_fs(err);
	};
};
diff --git a/os/+openbsd/platform_environ.ha b/os/+openbsd/platform_environ.ha
new file mode 100644
index 00000000..97ac841f
--- /dev/null
+++ b/os/+openbsd/platform_environ.ha
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use math;
use rt;
use strings;
use types::c;

// The command line arguments provided to the program. By convention, the first
// member is usually the name of the program.
export let args: []str = [];

// Statically allocate arg strings if there are few enough arguments, saves a
// syscall if we don't need it.
let args_static: [32]str = [""...];

@init fn args() void = {
	if (rt::argc < len(args_static)) {
		args = args_static[..rt::argc];
		for (let i = 0z; i < rt::argc; i += 1) {
			args[i] = c::tostr(rt::argv[i]: *const c::char)!;
		};
	} else {
		args = alloc([], rt::argc);
		for (let i = 0z; i < rt::argc; i += 1) {
			append(args, c::tostr(rt::argv[i]: *const c::char)!);
		};
	};
};

@fini fn args() void = {
	if (rt::argc >= len(args_static)) {
		free(args);
	};
};

// Returns a slice of the environment strings in form KEY=VALUE.
export fn getenvs() []str = {
	if (len(envp) > 0) {
		return envp;
	};
	for (let i = 0z; rt::envp[i] != null; i += 1) {
		let s = c::tostr(rt::envp[i]: *const c::char)!;
		append(envp, strings::dup(s));
	};
	return envp;
};

// Returns the host kernel name
export fn sysname() const str = {
	let name: [2]int = [rt::CTL_KERN, rt::KERN_OSTYPE];

	static let buf: [32]u8 = [0...];
	let buf_sz = len(buf);

	rt::sysctl(name, len(name): uint, &buf, &buf_sz, null, 0)!;
	return strings::fromutf8(buf[..(buf_sz -1)])!;
};

// Returns the host operating system version
export fn version() const str = {
	let name: [2]int = [rt::CTL_KERN, rt::KERN_OSRELEASE];

	static let buf: [32]u8 = [0...];
	let buf_sz = len(buf);

	rt::sysctl(name, len(name): uint, &buf, &buf_sz, null, 0)!;
	return strings::fromutf8(buf[..(buf_sz -1)])!;
};

// Returns the host CPU architecture
export fn machine() const str = {
	let name: [2]int = [rt::CTL_HW, rt::HW_MACHINE];

	static let buf: [32]u8 = [0...];
	let buf_sz = len(buf);

	rt::sysctl(name, len(name): uint, &buf, &buf_sz, null, 0)!;

	const mach = strings::fromutf8(buf[..(buf_sz - 1)])!;
	// Translate to Hare names
	switch (mach) {
	case "amd64" =>
		return "x86_64";
	case =>
		return mach;
	};
};

// Returns the number of usable CPUs.
export fn cpucount() (size | errors::error) = {
	let name: [2]int = [rt::CTL_HW, rt::HW_NCPU];

	let ncpu: int = 0;
	let ncpu_sz = size(int);

	match (rt::sysctl(name, len(name): uint, &ncpu, &ncpu_sz, null, 0)) {
	case void =>
		return ncpu: size;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};
diff --git a/os/+openbsd/status.ha b/os/+openbsd/status.ha
new file mode 100644
index 00000000..030dbbd0
--- /dev/null
+++ b/os/+openbsd/status.ha
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Values that may be passed to [[exit]] to indicate successful or unsuccessful
// termination, respectively.
export type status = enum {
	SUCCESS = 0,
	FAILURE = 1,
};
diff --git a/os/+openbsd/stdfd.ha b/os/+openbsd/stdfd.ha
new file mode 100644
index 00000000..dd757ee1
--- /dev/null
+++ b/os/+openbsd/stdfd.ha
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use bufio;
use io;
use rt;

let stdin_bufio: bufio::stream = bufio::stream {
	// Will be overwritten, but must be initialized
	stream = null: io::stream,
	source = 0,
	...
};

let stdout_bufio: bufio::stream = bufio::stream {
	// Will be overwritten, but must be initialized
	stream = null: io::stream,
	source = 1,
	...
};

// The standard input. This handle is buffered.
export let stdin: io::handle = rt::STDIN_FILENO; // initialized by init_stdfd

// The standard input, as an [[io::file]]. This handle is unbuffered.
export let stdin_file: io::file = rt::STDIN_FILENO;

// The standard output. This handle is buffered.
export let stdout: io::handle = rt::STDOUT_FILENO; // initialized by init_stdfd

// The standard output, as an [[io::file]]. This handle is unbuffered.
export let stdout_file: io::file = rt::STDOUT_FILENO;

// The standard error. This handle is unbuffered.
export let stderr: io::handle = rt::STDERR_FILENO;

// The standard error, as an [[io::file]]. This handle is unbuffered.
export let stderr_file: io::file = rt::STDERR_FILENO;

// The recommended buffer size for reading from disk.
export def BUFSZ: size = 4096; // 4 KiB

@init fn init_stdfd() void = {
	static let stdinbuf: [BUFSZ]u8 = [0...];
	stdin_bufio = bufio::init(stdin_file, stdinbuf, []);
	stdin = &stdin_bufio;

	static let stdoutbuf: [BUFSZ]u8 = [0...];
	stdout_bufio = bufio::init(stdout_file, [], stdoutbuf);
	stdout = &stdout_bufio;
};

@fini fn fini_stdfd() void = {
	// Flush any pending writes
	io::close(stdout): void;
};
-- 
2.42.0

[PATCH hare 17/32] OpenBSD: add unix

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-18-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +127 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
if you are wondering about the TODOs, there is some weirdness going on
that i haven't yet figured out so i didn't implement them.

going to implement them in tree if that is ok

 unix/+openbsd/getuid.ha | 16 +++++++++++++++
 unix/+openbsd/groups.ha | 17 ++++++++++++++++
 unix/+openbsd/nice.ha   | 11 +++++++++++
 unix/+openbsd/pipe.ha   | 30 ++++++++++++++++++++++++++++
 unix/+openbsd/setuid.ha | 44 +++++++++++++++++++++++++++++++++++++++++
 unix/+openbsd/umask.ha  |  9 +++++++++
 6 files changed, 127 insertions(+)
 create mode 100644 unix/+openbsd/getuid.ha
 create mode 100644 unix/+openbsd/groups.ha
 create mode 100644 unix/+openbsd/nice.ha
 create mode 100644 unix/+openbsd/pipe.ha
 create mode 100644 unix/+openbsd/setuid.ha
 create mode 100644 unix/+openbsd/umask.ha

diff --git a/unix/+openbsd/getuid.ha b/unix/+openbsd/getuid.ha
new file mode 100644
index 00000000..db3923c9
--- /dev/null
+++ b/unix/+openbsd/getuid.ha
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Returns the current process user ID.
export fn getuid() uint = rt::getuid(): uint;

// Returns the current process effective user ID.
export fn geteuid() uint = rt::geteuid(): uint;

// Returns the current process group ID.
export fn getgid() uint = rt::getgid(): uint;

// Returns the current process effective group ID.
export fn getegid() uint = rt::getegid(): uint;
\ No newline at end of file
diff --git a/unix/+openbsd/groups.ha b/unix/+openbsd/groups.ha
new file mode 100644
index 00000000..e0b72d32
--- /dev/null
+++ b/unix/+openbsd/groups.ha
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Returns a list of supplementary group IDs for the current process.
export fn getgroups() []uint = abort(); // TODO

// Sets the list of supplementary group IDs which apply to the current process.
// This generally requires elevated permissions.
//
// If the system returns an error, this function will abort the program. Failing
// to handle errors from setgroups is a grave security issue in your program,
// and therefore we require this function to succeed. If you need to handle the
// error case gracefully, call the appropriate syscall wrapper in [[rt::]]
// yourself, and take extreme care to handle errors correctly.
export fn setgroups(gids: []uint) void = abort(); // TODO
diff --git a/unix/+openbsd/nice.ha b/unix/+openbsd/nice.ha
new file mode 100644
index 00000000..a678b3d2
--- /dev/null
+++ b/unix/+openbsd/nice.ha
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use rt;

// Adds the argument to the niceness of the current process. The input should be
// between -20 and 19 (inclusive); lower numbers represent a higher priority.
// Generally, you must have elevated permissions to reduce your niceness, but
// not to increase it.
export fn nice(inc: int) (void | errors::error) = abort(); // TODO
\ No newline at end of file
diff --git a/unix/+openbsd/pipe.ha b/unix/+openbsd/pipe.ha
new file mode 100644
index 00000000..4f3780c7
--- /dev/null
+++ b/unix/+openbsd/pipe.ha
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use io;
use rt;

// Flags to use for the [[io::file]]s returned by [[pipe]].
// Only NOCLOEXEC and NONBLOCK are guaranteed to be available.
export type pipe_flag = enum {
	NOCLOEXEC = rt::O_CLOEXEC,
	NONBLOCK = rt::O_NONBLOCK,
};

// Create a pair of two linked [[io::file]]s, such that any data written to the
// second [[io::file]] may be read from the first.
export fn pipe(flags: pipe_flag...) ((io::file, io::file) | errors::error) = {
	let fds: [2]int = [0...];
	let flag: pipe_flag = 0;
	for (let i = 0z; i < len(flags); i += 1) {
		flag |= flags[i];
	};
	flag ^= pipe_flag::NOCLOEXEC; // invert CLOEXEC
	match (rt::pipe2(&fds, flag)) {
	case void => void;
	case let e: rt::errno =>
		return errors::errno(e);
	};
	return (fds[0], fds[1]);
};
diff --git a/unix/+openbsd/setuid.ha b/unix/+openbsd/setuid.ha
new file mode 100644
index 00000000..ba28436a
--- /dev/null
+++ b/unix/+openbsd/setuid.ha
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;

// Sets the caller's user ID to the specified value. This generally requires
// elevated permissions from the calling process.
//
// If the system returns an error, this function will abort the program. Failing
// to handle errors from setuid is a grave security issue in your program, and
// therefore we require this function to succeed. If you need to handle the
// error case gracefully, call the appropriate syscall wrapper in [[rt::]] yourself,
// and take extreme care to handle errors correctly.
export fn setuid(uid: uint) void = rt::setuid(uid: rt::uid_t)!;

// Sets the caller's effective user ID to the specified value. This generally
// requires elevated permissions from the calling process.
//
// If the system returns an error, this function will abort the program. Failing
// to handle errors from seteuid is a grave security issue in your program, and
// therefore we require this function to succeed. If you need to handle the
// error case gracefully, call the appropriate syscall wrapper in [[rt::]] yourself,
// and take extreme care to handle errors correctly.
export fn seteuid(uid: uint) void = rt::seteuid(uid: rt::uid_t)!;

// Sets the caller's group ID to the specified value. This generally requires
// elevated permissions from the calling process.
//
// If the system returns an error, this function will abort the program. Failing
// to handle errors from setuid is a grave security issue in your program, and
// therefore we require this function to succeed. If you need to handle the
// error case gracefully, call the appropriate syscall wrapper in [[rt::]] yourself,
// and take extreme care to handle errors correctly.
export fn setgid(gid: uint) void = rt::setgid(gid: rt::gid_t)!;

// Sets the caller's effective group ID to the specified value. This generally
// requires elevated permissions from the calling process.
//
// If the system returns an error, this function will abort the program. Failing
// to handle errors from setegid is a grave security issue in your program, and
// therefore we require this function to succeed. If you need to handle the
// error case gracefully, call the appropriate syscall wrapper in [[rt::]] yourself,
// and take extreme care to handle errors correctly.
export fn setegid(gid: uint) void = rt::setegid(gid: rt::gid_t)!;
diff --git a/unix/+openbsd/umask.ha b/unix/+openbsd/umask.ha
new file mode 100644
index 00000000..a54c7951
--- /dev/null
+++ b/unix/+openbsd/umask.ha
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use fs;
use rt;

// Sets the file mode creation mask for the current process and return the
// previous value of the mask.
export fn umask(mode: fs::mode) fs::mode = rt::umask(mode: rt::mode_t)!: fs::mode;
-- 
2.42.0

[PATCH hare 18/32] OpenBSD: add unix::signal

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-19-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +371 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 unix/signal/+openbsd.ha | 371 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 371 insertions(+)
 create mode 100644 unix/signal/+openbsd.ha

diff --git a/unix/signal/+openbsd.ha b/unix/signal/+openbsd.ha
new file mode 100644
index 00000000..b8997bc5
--- /dev/null
+++ b/unix/signal/+openbsd.ha
@@ -0,0 +1,371 @@
use io;
use rt;

// Configures a new signal handler, returning the old details (which can be
// passed to [[restore]] to restore its behavior).
//
// The variadic parameters specify either [[flag]]s to enable or a signal mask
// to use via [[sigset]]; if the latter is provided no more than one may be
// used.
export fn handle(
	signum: sig,
	handler: *handler,
	opt: (flag | sigset)...
) sigaction = {
	let sa_mask = newsigset();

	let sa_flags = rt::SA_SIGINFO: int, nmask = 0;
	for (let i = 0z; i < len(opt); i += 1) {
		match (opt[i]) {
		case let flag: flag =>
			sa_flags |= flag: int;
		case let mask: sigset =>
			assert(nmask == 0, "Multiple signal masks provided to signal::handle");
			nmask += 1;
			sa_mask = mask;
		};
	};

	let new = rt::sigact {
		sa_sigaction = handler: *fn(int, *rt::siginfo, *opaque) void,
		sa_mask = sa_mask,
		sa_flags = sa_flags,
	};
	let old = rt::sigact {
		sa_sigaction = null: *fn(int, *rt::siginfo, *opaque) void,
		...
	};
	match (rt::sigaction(signum, &new, &old)) {
	case void =>
		yield;
	case let err: rt::errno =>
		abort("sigaction failed (invalid signal?)");
	};
	return old;

};

// Restores previous signal behavior following [[handle]].
export fn restore(signum: sig, action: *sigaction) void = {
	match (rt::sigaction(signum, action: *rt::sigact, null)) {
	case void =>
		yield;
	case rt::errno =>
		abort("sigaction failed (invalid signal?)");
	};

};

// Unregisters signal handlers for the specified signal.
export fn reset(signum: sig) void = {
	handle(signum, rt::SIG_DFL: *handler);
};

// Unregisters all signal handlers.
export fn resetall() void = {
	// sig::KILL and sig::STOP deliberately omitted; see sigaction(2)
	reset(sig::HUP);
	reset(sig::INT);
	reset(sig::QUIT);
	reset(sig::ILL);
	reset(sig::TRAP);
	reset(sig::ABRT);
	reset(sig::EMT);
	reset(sig::FPE);
	reset(sig::BUS);
	reset(sig::SEGV);
	reset(sig::SYS);
	reset(sig::PIPE);
	reset(sig::ALRM);
	reset(sig::TERM);
	reset(sig::URG);
	reset(sig::TSTP);
	reset(sig::CONT);
	reset(sig::CHLD);
	reset(sig::TTIN);
	reset(sig::TTOU);
	reset(sig::IO);
	reset(sig::XCPU);
	reset(sig::XFSZ);
	reset(sig::VTALRM);
	reset(sig::PROF);
	reset(sig::WINCH);
	reset(sig::INFO);
	reset(sig::USR1);
	reset(sig::USR2);
};

// Prevents given signal from arriving to the current process.
// One common use case is to ignore SIGCHLD to avoid zombie child processes.
export fn ignore(signum: sig) void = {
	handle(signum, rt::SIG_IGN: *handler);
};

// Adds the given list of signals to the process's current signal mask,
// returning the old signal mask. This is a convenience function around
// [[setprocmask]].
export fn block(signals: sig...) sigset = {
	let new = newsigset(signals...);
	return setprocmask(how::BLOCK, &new);
};

// Removes the given list of signals from the process's current signal mask,
// returning the old signal mask. This is a convenience function around
// [[setprocmask]].
export fn unblock(signals: sig...) sigset = {
	let new = newsigset(signals...);
	return setprocmask(how::UNBLOCK, &new);
};

// Sets the process's signal mask, returning the previous mask.
export fn setprocmask(how: how, mask: *sigset) sigset = {
	let old: sigset = 0;
	rt::sigprocmask(how, mask: *rt::sigset, &old)!;
	return old;
};

// Gets the current process's signal mask.
export fn getprocmask() sigset = {
	let old: sigset = 0;
	rt::sigprocmask(how::SETMASK, null, &old)!;
	return old;
};

// Defines the modes of operation for [[setprocmask]].
export type how = enum int {
	// Adds the given set of signals to the current mask.
	BLOCK = rt::SIG_BLOCK,
	// Removes the given set of signals from the current mask.
	UNBLOCK = rt::SIG_UNBLOCK,
	// Sets the process mask to the given set.
	SETMASK = rt::SIG_SETMASK,
};

export type sigaction = rt::sigact;
export type sigset = rt::sigset;

// Creates a new signal set filled in with the provided signals (or empty if
// none are provided).
export fn newsigset(items: sig...) sigset = {
	let set: sigset = 0;
	rt::sigemptyset(&set);
	sigset_add(&set, items...);
	return set;
};

// Sets a [[sigset]] to empty.
export fn sigset_empty(set: *sigset) void = {
	rt::sigemptyset(set: *rt::sigset);
};

// Adds signals to a [[sigset]].
export fn sigset_add(set: *sigset, items: sig...) void = {
	for (let i = 0z; i < len(items); i += 1) {
		rt::sigaddset(set: *rt::sigset, items[i])!;
	};
};

// Removes signals from a [[sigset]].
export fn sigset_del(set: *sigset, items: sig...) void = {
	for (let i = 0z; i < len(items); i += 1) {
		rt::sigdelset(set: *rt::sigset, items[i])!;
	};
};

// Returns true if the given signal is a member of this [[sigset]].
export fn sigset_member(set: *sigset, item: sig) bool = {
	return rt::sigismember(set: *rt::sigset, item)!;
};

// Provides additional information about signal deliveries. Only the members
// defined by POSIX are available here; cast to [[rt::siginfo]] to access
// non-portable members.
export type siginfo = struct {
	// The signal number being delivered.
	signo: sig,
	// The signal code, if any.
	code: code,
	// The errno, if any, associated with this signal. See
	// [[errors::errno]] to convert to a Hare-native error.
	errno: rt::errno,
	union {
		struct {
			// Process ID of the sender.
			pid: int,
			// Real user ID of the sending process.
			uid: uint,
		},
		// Pads the structure out to the length used by the kernel; do not use.
		_si_pad: [29]int,
	},
};

export type code = enum int {
	NOINFO = rt::SI_NOINFO, // no signal information
	USER = rt::SI_USER, // user generated signal via kill()
	LWP = rt::SI_LWP, // user generated signal via lwp_kill()
	QUEUE = rt::SI_QUEUE, // user generated signal via sigqueue()
	TIMER = rt::SI_TIMER, // from timer expiration

	ILLOPC = rt::ILL_ILLOPC, // sig::ILL: illegal opcode
	ILLOPN = rt::ILL_ILLOPN, // sig::ILL: illegal operand
	ILLADR = rt::ILL_ILLADR, // sig::ILL: illegal addressing mode
	ILLTRP = rt::ILL_ILLTRP, // sig::ILL: illegal trap
	PRVOPC = rt::ILL_PRVOPC, // sig::ILL: privileged opcode
	PRVREG = rt::ILL_PRVREG, // sig::ILL: privileged register
	COPROC = rt::ILL_COPROC, // sig::ILL: co-processor
	BADSTK = rt::ILL_BADSTK, // sig::ILL: bad stack

	INTDIV = rt::FPE_INTDIV, // sig::FPE: integer divide by zero
	INTOVF = rt::FPE_INTOVF, // sig::FPE: integer overflow
	FLTDIV = rt::FPE_FLTDIV, // sig::FPE: floating point divide by zero
	FLTOVF = rt::FPE_FLTOVF, // sig::FPE: floating point overflow
	FLTUND = rt::FPE_FLTUND, // sig::FPE: floating point underflow
	FLTRES = rt::FPE_FLTRES, // sig::FPE: floating point inexact result
	FLTINV = rt::FPE_FLTINV, // sig::FPE: invalid floating point operation
	FLTSUB = rt::FPE_FLTSUB, // sig::FPE: subscript out of range

	MAPERR = rt::SEGV_MAPERR, // sig::SEGV: address not mapped to object
	ACCERR = rt::SEGV_ACCERR, // sig::SEGV: invalid permissions

	ADRALN = rt::BUS_ADRALN, // sig::BUS: invalid address alignment
	ADRERR = rt::BUS_ADRERR, // sig::BUS: non-existent physical address
	OBJERR = rt::BUS_OBJERR, // sig::BUS: object specific hardware error

	BRKPT = rt::TRAP_BRKPT, // sig::TRAP: breakpoint trap
	TRACE = rt::TRAP_TRACE, // sig::TRAP: trace trap

	EXITED = rt::CLD_EXITED, // sig::CHLD: child has exited
	KILLED = rt::CLD_KILLED, // sig::CHLD: child was killed
	DUMPED = rt::CLD_DUMPED, // sig::CHLD: child has coredumped
	TRAPPED = rt::CLD_TRAPPED, // sig::CHLD: traced child has stopped
	STOPPED = rt::CLD_STOPPED, // sig::CHLD: child has stopped on signal
	CONTINUED = rt::CLD_CONTINUED, // sig::CHLD: stopped child has continued
};

export type flag = enum int {
	// For use with sig::CHLD. Prevents notifications when child processes
	// stop (e.g. via sig::STOP) or resume (i.e. sig::CONT).
	NOCLDSTOP = rt::SA_NOCLDSTOP: int,
	// For use with sig::CHLD. Do not transform children into zombies when
	// they terminate. Note that POSIX leaves the delivery of sig::CHLD
	// unspecified when this flag is present; some systems will still
	// deliver a signal and others may not.
	NOCLDWAIT = rt::SA_NOCLDWAIT: int,
	// Uses an alternate stack when handling this signal. See
	// [[setaltstack]] and [[getaltstack]] for details.
	ONSTACK = rt::SA_ONSTACK: int,
	// Do not add the signal to the signal mask while executing the signal
	// handler. This can cause the same signal to be delivered again during
	// the execution of the signal handler.
	NODEFER = rt::SA_NODEFER: int,
	// Restore the signal handler to the default behavior upon entering the
	// signal handler.
	RESETHAND = rt::SA_RESETHAND: int,
	// Makes certain system calls restartable across signals. See signal(7)
	// or similar documentation for your local system for details.
	RESTART = rt::SA_RESTART: int,
};

// All possible signals.
export type sig = enum int {
	HUP = rt::SIGHUP, // Hangup.
	INT = rt::SIGINT, // Terminal interrupt.
	QUIT = rt::SIGQUIT, // Terminal quit.
	ILL = rt::SIGILL, // Illegal instruction.
	TRAP = rt::SIGTRAP, // Trace/breakpoint trap.
	ABRT = rt::SIGABRT, // Process abort.
	EMT = rt::SIGEMT, // Emulate instruction executed.
	FPE = rt::SIGFPE, // Erroneous arithmetic operation.
	KILL = rt::SIGKILL, // Kill (cannot be caught or ignored).
	BUS = rt::SIGBUS, // Access to an undefined portion of a memory object.
	SEGV = rt::SIGSEGV, // Invalid memory reference.
	SYS = rt::SIGSYS, // Bad system call.
	PIPE = rt::SIGPIPE, // Write on a pipe with no one to read it.
	ALRM = rt::SIGALRM, // Alarm clock.
	TERM = rt::SIGTERM, // Termination.
	URG = rt::SIGURG, // High bandwidth data is available at a socket.
	STOP = rt::SIGSTOP, // Stop executing (cannot be caught or ignored).
	TSTP = rt::SIGTSTP, // Terminal stop.
	CONT = rt::SIGCONT, // Continue executing, if stopped.
	CHLD = rt::SIGCHLD, // Child process terminated, stopped, or continued.
	TTIN = rt::SIGTTIN, // Background process attempting read.
	TTOU = rt::SIGTTOU, // Background process attempting write.
	IO = rt::SIGIO, // I/O now possible.
	XCPU = rt::SIGXCPU, // CPU time limit exceeded.
	XFSZ = rt::SIGXFSZ, // File size limit exceeded.
	VTALRM = rt::SIGVTALRM, // Virtual timer expired.
	PROF = rt::SIGPROF, // Profiling timer expired.
	WINCH = rt::SIGWINCH, // Window resize.
	INFO = rt::SIGINFO, // Status request from keyboard.
	USR1 = rt::SIGUSR1, // User-defined signal 1.
	USR2 = rt::SIGUSR2, // User-defined signal 2.
};

// Returns the human friendly name of a given signal.
export fn signame(sig: sig) const str = {
	switch (sig) {
	case sig::HUP =>
		return "SIGHUP";
	case sig::INT =>
		return "SIGINT";
	case sig::QUIT =>
		return "SIGQUIT";
	case sig::ILL =>
		return "SIGILL";
	case sig::TRAP =>
		return "SIGTRAP";
	case sig::ABRT =>
		return "SIGABRT";
	case sig::EMT =>
		return "SIGEMT";
	case sig::FPE =>
		return "SIGFPE";
	case sig::KILL =>
		return "SIGKILL";
	case sig::BUS =>
		return "SIGBUS";
	case sig::SEGV =>
		return "SIGSEGV";
	case sig::SYS =>
		return "SIGSYS";
	case sig::PIPE =>
		return "SIGPIPE";
	case sig::ALRM =>
		return "SIGALRM";
	case sig::TERM =>
		return "SIGTERM";
	case sig::URG =>
		return "SIGURG";
	case sig::STOP =>
		return "SIGSTOP";
	case sig::TSTP =>
		return "SIGTSTP";
	case sig::CONT =>
		return "SIGCONT";
	case sig::CHLD =>
		return "SIGCHLD";
	case sig::TTIN =>
		return "SIGTTIN";
	case sig::TTOU =>
		return "SIGTTOU";
	case sig::IO =>
		return "SIGIO";
	case sig::XCPU =>
		return "SIGXCPU";
	case sig::XFSZ =>
		return "SIGXFSZ";
	case sig::VTALRM =>
		return "SIGVTALRM";
	case sig::PROF =>
		return "SIGPROF";
	case sig::WINCH =>
		return "SIGWINCH";
	case sig::INFO =>
		return "SIGINFO";
	case sig::USR1 =>
		return "SIGUSR1";
	case sig::USR2 =>
		return "SIGUSR2";
	};
};
-- 
2.42.0

[PATCH hare 19/32] OpenBSD: add unix::tty

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-20-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +212 -0
Signed-off-by: Lorenz (xha) <me@xha.li>
---
 unix/tty/+openbsd/isatty.ha  | 17 ++++++++
 unix/tty/+openbsd/open.ha    | 18 +++++++++
 unix/tty/+openbsd/pty.ha     | 78 ++++++++++++++++++++++++++++++++++++
 unix/tty/+openbsd/termios.ha | 71 ++++++++++++++++++++++++++++++++
 unix/tty/+openbsd/winsize.ha | 28 +++++++++++++
 5 files changed, 212 insertions(+)
 create mode 100644 unix/tty/+openbsd/isatty.ha
 create mode 100644 unix/tty/+openbsd/open.ha
 create mode 100644 unix/tty/+openbsd/pty.ha
 create mode 100644 unix/tty/+openbsd/termios.ha
 create mode 100644 unix/tty/+openbsd/winsize.ha

diff --git a/unix/tty/+openbsd/isatty.ha b/unix/tty/+openbsd/isatty.ha
new file mode 100644
index 00000000..82cbbeca
--- /dev/null
+++ b/unix/tty/+openbsd/isatty.ha
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use rt;
use io;
use os;

// Returns whether the given stream is connected to a terminal.
export fn isatty(fd: io::file) bool = {
	let wsz = rt::winsize { ... };
	match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *opaque)) {
	case let e: rt::errno =>
		return false;
	case let r: int =>
		return r == 0;
	};
};
diff --git a/unix/tty/+openbsd/open.ha b/unix/tty/+openbsd/open.ha
new file mode 100644
index 00000000..8c93627e
--- /dev/null
+++ b/unix/tty/+openbsd/open.ha
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use fs;
use io;
use os;

// Returns a stream connected to the TTY of the current process. The caller must
// close it using [[io::close]].
export fn open() (io::file | error) = {
	match (os::open("/dev/tty", fs::flag::RDWR)) {
	case let f: io::file =>
		return f;
	case fs::error =>
		return errors::noentry;
	};
};
diff --git a/unix/tty/+openbsd/pty.ha b/unix/tty/+openbsd/pty.ha
new file mode 100644
index 00000000..a9c67c58
--- /dev/null
+++ b/unix/tty/+openbsd/pty.ha
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use fmt;
use fs;
use io;
use os;
use rt;
use types::c;

// Opens an available pseudoterminal and returns the file descriptors of the
// master and slave.
export fn openpty() ((io::file, io::file) | fs::error) = {
	let master = open_master()?;
	defer io::close(master)!;

	let ptm = rt::ptmget { ... };
	match (rt::ioctl(master, rt::PTMGET, &ptm)) {
	case let e: rt::errno =>
		return errors::errno(e);
	case =>	void;
	};

	return (ptm.cfd, ptm.sfd);
};

// Opens an available pseudoterminal master.
fn open_master() (io::file | fs::error) = {
	match (rt::open(rt::PATH_PTMDEV, rt::O_RDWR, 0)) {
	case let e: rt::errno =>
		return errors::errno(e);
	case let i: int =>
		return io::fdopen(i);
	};
};

// Returns the filename of the pseudoterminal slave.
export fn ptsname(master: io::file) (str | error) = {
	static let path_buf: [rt::PATH_MAX]u8 = [0...];

	let name = match (rt::ptsname(master)) {
	case let name: *u8 =>
		yield name: *[*]u8;
	case let err: rt::errno =>
		switch (err) {
		// master is not a pseudo-terminal device
		case rt::EINVAL =>
			return errors::unsupported;
		// master is not an open valid file descriptor
		case rt::EBADF =>
			return errors::invalid;
		case =>
			abort("Unexpected error from ptsname");
		};
	};
	let namelen = c::strlen(name: *const c::char);
	path_buf[..namelen] = name[..namelen];

	return c::tostrn(&path_buf: *const c::char, namelen)!;
};

// Sets the dimensions of the underlying pseudoterminal for an [[io::file]].
export fn set_winsize(pty: io::file, sz: ttysize) (void | error) = {
	let wsz = rt::winsize { ws_row = sz.rows, ws_col = sz.columns, ... };
	match (rt::ioctl(pty, rt::TIOCSWINSZ, &wsz)) {
	case let e: rt::errno =>
		switch (e) {
		case rt::EBADF, rt::EINVAL =>
			return errors::invalid;
		case rt::ENOTTY =>
			return errors::unsupported;
		case =>
			abort("Unexpected error from ioctl");
		};
	case => void;
	};
};
diff --git a/unix/tty/+openbsd/termios.ha b/unix/tty/+openbsd/termios.ha
new file mode 100644
index 00000000..cd62a765
--- /dev/null
+++ b/unix/tty/+openbsd/termios.ha
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// TODO: Not in love with this interface
use io;
use rt;
use errors;

export type termios = struct {
	file: io::file,
	saved: rt::termios,
	current: rt::termios,
};

// Retrieves serial port settings of the given terminal.
export fn termios_query(file: io::file) (termios | errors::error) = {
	let settings = rt::termios { ... };

	match (rt::ioctl(file, rt::TIOCGETA, &settings)) {
	case int => void;
	case let err: rt::errno =>
		return errors::errno(err);
	};

	return termios {
		file = file,
		saved = settings,
		current = settings,
	};
};

// Restores original serial port settings.
export fn termios_restore(termios: *const termios) void = {
	rt::ioctl(termios.file, rt::TIOCSETA, &termios.saved): void;
};

// Sets serial port settings.
export fn termios_set(termios: *const termios) (void | errors::error) = {
	match (rt::ioctl(termios.file, rt::TIOCSETA, &termios.current)) {
	case int => void;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};

// Enables "raw" mode for this terminal, disabling echoing, line
// editing, and signal handling. Users should call [[termios_query]]
// prior to this to save the previous terminal settings, and
// [[termios_restore]] to restore them before exiting.
export fn makeraw(termios: *termios) (void | errors::error) = {
	// Disable break signal and CR<->LF processing
	termios.current.c_iflag &= ~(rt::tcflag::IGNBRK | rt::tcflag::BRKINT
		| rt::tcflag::INLCR | rt::tcflag::IGNCR
		| rt::tcflag::ICRNL);
	// Disable output post-processing
	termios.current.c_oflag &= ~rt::tcflag::OPOST;
	// Disable character echo, canonical mode, implementation defined
	// extensions and INTR/QUIT/SUSP characters
	termios.current.c_lflag &= ~(rt::tcflag::ECHO | rt::tcflag::ECHONL
		| rt::tcflag::ICANON | rt::tcflag::IEXTEN
		| rt::tcflag::ISIG);

	termios_set(termios)?;
};

// Disables "echo" on this terminal. Users should call [[termios_restore]] to
// restore settings.
export fn noecho(termios: *termios) (void | errors::error) = {
	termios.current.c_lflag &= ~rt::tcflag::ECHO;
	termios_set(termios)?;
};
diff --git a/unix/tty/+openbsd/winsize.ha b/unix/tty/+openbsd/winsize.ha
new file mode 100644
index 00000000..270260d8
--- /dev/null
+++ b/unix/tty/+openbsd/winsize.ha
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use io;
use os;
use rt;

// Returns the dimensions of underlying terminal for an [[io::file]].
export fn winsize(fd: io::file) (ttysize | error) = {
	let wsz = rt::winsize { ... };
	match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *opaque)) {
	case let e: rt::errno =>
		switch (e) {
		case rt::EBADF =>
			return errors::invalid;
		case rt::ENOTTY =>
			return errors::unsupported;
		case =>
			abort("Unexpected error from ioctl");
		};
	case int =>
		return ttysize {
			rows = wsz.ws_row,
			columns = wsz.ws_col,
		};
	};
};
-- 
2.42.0

[PATCH hare 10/32] OpenBSD: add rt

Lorenz (xha) <me@xha.li>
Details
Message ID
<20231119193255.21032-11-me@xha.li>
In-Reply-To
<20231119193255.21032-1-me@xha.li> (view parent)
DKIM signature
pass
Download raw message
Patch: +2800 -0
Co-authored-by: Lennart Jablonka <humm@ljabl.com>
Signed-off-by: Lorenz (xha) <me@xha.li>
---
we are using a simple linker script for tests and none for normal
builds, the reasons where already discussed in the cover letter.

if you look at the code of /usr/src/lib/csu/crt0.c:
	static void
	___start(MD_START_ARGS)
	{
	[...]
                size = __preinit_array_end - __preinit_array_start;
                for (i = 0; i < size; i++)
                        __preinit_array_start[i](argc, argv, envp, NULL);
                RCRT0_RELRO();
                size = __init_array_end - __init_array_start;
                for (i = 0; i < size; i++)
                        __init_array_start[i](argc, argv, envp, NULL);
                __csu_do_fini_array = 1;
        [...]
	}

there are preinit functions that are ran before the normal .init
functions! the coolest thing here is that __preinit_array_end and
__preinit_array_start are generated by clang compiler rules so we
don't need to do anything else than put a pointer of that function
into the .preinit section! it even includes the envp and argc/argv :D

so yeah we are using the preinit function to setup the environment
for init functions. we tests are ran using a simple linker script.

 rt/+openbsd/env.ha            |    6 +
 rt/+openbsd/errno.ha          |  501 ++++++++++++++
 rt/+openbsd/hare+test.sc      |    7 +
 rt/+openbsd/hare.sc           |    1 +
 rt/+openbsd/libc.ha           |   20 +
 rt/+openbsd/platform_abort.ha |   24 +
 rt/+openbsd/signal.ha         |   73 ++
 rt/+openbsd/socket.ha         |  150 ++++
 rt/+openbsd/start+test.ha     |   28 +
 rt/+openbsd/start.ha          |   26 +
 rt/+openbsd/start.s           |    4 +
 rt/+openbsd/syscalls.ha       | 1223 +++++++++++++++++++++++++++++++++
 rt/+openbsd/types.ha          |  737 ++++++++++++++++++++
 13 files changed, 2800 insertions(+)
 create mode 100644 rt/+openbsd/env.ha
 create mode 100644 rt/+openbsd/errno.ha
 create mode 100644 rt/+openbsd/hare+test.sc
 create mode 100644 rt/+openbsd/hare.sc
 create mode 100644 rt/+openbsd/libc.ha
 create mode 100644 rt/+openbsd/platform_abort.ha
 create mode 100644 rt/+openbsd/signal.ha
 create mode 100644 rt/+openbsd/socket.ha
 create mode 100644 rt/+openbsd/start+test.ha
 create mode 100644 rt/+openbsd/start.ha
 create mode 100644 rt/+openbsd/start.s
 create mode 100644 rt/+openbsd/syscalls.ha
 create mode 100644 rt/+openbsd/types.ha

diff --git a/rt/+openbsd/env.ha b/rt/+openbsd/env.ha
new file mode 100644
index 00000000..d164d912
--- /dev/null
+++ b/rt/+openbsd/env.ha
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

export let argc: size = 0;
export let argv: *[*]*u8 = null: *[*]*u8;
export let envp: *[*]nullable *u8 = null: *[*]nullable *u8;
diff --git a/rt/+openbsd/errno.ha b/rt/+openbsd/errno.ha
new file mode 100644
index 00000000..2615b32d
--- /dev/null
+++ b/rt/+openbsd/errno.ha
@@ -0,0 +1,501 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

//	$OpenBSD: errno.h,v 1.25 2017/09/05 03:06:26 jsg Exp $

// Represents an error returned from the OpenBSD kernel.
export type errno = !int;

// Obtains a human-friendly reading of an [[errno]] (e.g. "Operation not
// permitted").
export fn strerror(err: errno) str = {
	switch (err) {
	case EPERM =>
		return "Operation not permitted";
	case ENOENT =>
		return "No such file or directory";
	case ESRCH =>
		return "No such process";
	case EINTR =>
		return "Interrupted system call";
	case EIO =>
		return "Input/output error";
	case ENXIO =>
		return "Device not configured";
	case E2BIG =>
		return "Argument list too long";
	case ENOEXEC =>
		return "Exec format error";
	case EBADF =>
		return "Bad file descriptor";
	case ECHILD =>
		return "No child processes";
	case EDEADLK =>
		return "Resource deadlock avoided";
	case ENOMEM =>
		return "Cannot allocate memory";
	case EACCES =>
		return "Permission denied";
	case EFAULT =>
		return "Bad address";
	case ENOTBLK =>
		return "Block device required";
	case EBUSY =>
		return "Device busy";
	case EEXIST =>
		return "File exists";
	case EXDEV =>
		return "Cross-device link";
	case ENODEV =>
		return "Operation not supported by device";
	case ENOTDIR =>
		return "Not a directory";
	case EISDIR =>
		return "Is a directory";
	case EINVAL =>
		return "Invalid argument";
	case ENFILE =>
		return "Too many open files in system";
	case EMFILE =>
		return "Too many open files";
	case ENOTTY =>
		return "Inappropriate ioctl for device";
	case ETXTBSY =>
		return "Text file busy";
	case EFBIG =>
		return "File too large";
	case ENOSPC =>
		return "No space left on device";
	case ESPIPE =>
		return "Illegal seek";
	case EROFS =>
		return "Read-only file system";
	case EMLINK =>
		return "Too many links";
	case EPIPE =>
		return "Broken pipe";
	case EDOM =>
		return "Numerical argument out of domain";
	case ERANGE =>
		return "Result too large";
	case EAGAIN =>
		return "Resource temporarily unavailable";
	case EINPROGRESS =>
		return "Operation now in progress";
	case EALREADY =>
		return "Operation already in progress";
	case ENOTSOCK =>
		return "Socket operation on non-socket";
	case EDESTADDRREQ =>
		return "Destination address required";
	case EMSGSIZE =>
		return "Message too long";
	case EPROTOTYPE =>
		return "Protocol wrong type for socket";
	case ENOPROTOOPT =>
		return "Protocol not available";
	case EPROTONOSUPPORT =>
		return "Protocol not supported";
	case ESOCKTNOSUPPORT =>
		return "Socket type not supported";
	case EOPNOTSUPP =>
		return "Operation not supported";
	case EPFNOSUPPORT =>
		return "Protocol family not supported";
	case EAFNOSUPPORT =>
		return "Address family not supported by protocol family";
	case EADDRINUSE =>
		return "Address already in use";
	case EADDRNOTAVAIL =>
		return "Can't assign requested address";
	case ENETDOWN =>
		return "Network is down";
	case ENETUNREACH =>
		return "Network is unreachable";
	case ENETRESET =>
		return "Network dropped connection on reset";
	case ECONNABORTED =>
		return "Software caused connection abort";
	case ECONNRESET =>
		return "Connection reset by peer";
	case ENOBUFS =>
		return "No buffer space available";
	case EISCONN =>
		return "Socket is already connected";
	case ENOTCONN =>
		return "Socket is not connected";
	case ESHUTDOWN =>
		return "Can't send after socket shutdown";
	case ETOOMANYREFS =>
		return "Too many references: can't splice";
	case ETIMEDOUT =>
		return "Operation timed out";
	case ECONNREFUSED =>
		return "Connection refused";
	case ELOOP =>
		return "Too many levels of symbolic links";
	case ENAMETOOLONG =>
		return "File name too long";
	case EHOSTDOWN =>
		return "Host is down";
	case EHOSTUNREACH =>
		return "No route to host";
	case ENOTEMPTY =>
		return "Directory not empty";
	case EPROCLIM =>
		return "Too many processes";
	case EUSERS =>
		return "Too many users";
	case EDQUOT =>
		return "Disk quota exceeded";
	case ESTALE =>
		return "Stale NFS file handle";
	case EREMOTE =>
		return "Too many levels of remote in path";
	case EBADRPC =>
		return "RPC struct is bad";
	case ERPCMISMATCH =>
		return "RPC version wrong";
	case EPROGUNAVAIL =>
		return "RPC program not available";
	case EPROGMISMATCH =>
		return "Program version wrong";
	case EPROCUNAVAIL =>
		return "Bad procedure for program";
	case ENOLCK =>
		return "No locks available";
	case ENOSYS =>
		return "Function not implemented";
	case EFTYPE =>
		return "Inappropriate file type or format";
	case EAUTH =>
		return "Authentication error";
	case ENEEDAUTH =>
		return "Need authenticator";
	case EIPSEC =>
		return "IPsec processing failure";
	case ENOATTR =>
		return "Attribute not found";
	case EILSEQ =>
		return "Illegal byte sequence";
	case ENOMEDIUM =>
		return "No medium found";
	case EMEDIUMTYPE =>
		return "Wrong medium type";
	case EOVERFLOW =>
		return "Value too large to be stored in data type";
	case ECANCELED =>
		return "Operation canceled";
	case EIDRM =>
		return "Identifier removed";
	case ENOMSG =>
		return "No message of desired type";
	case ENOTSUP =>
		return "Not supported";
	case EBADMSG =>
		return "Bad message";
	case ENOTRECOVERABLE =>
		return "State not recoverable";
	case EOWNERDEAD =>
		return "Previous owner died";
	case EPROTO =>
		return "Protocol error";
	case =>
		return unknown_errno(err);
	};
};

// Gets the programmer-friendly name for an [[errno]] (e.g. EPERM).
export fn errname(err: errno) str = {
	switch (err) {
	case EPERM =>
		return "EPERM";
	case ENOENT =>
		return "ENOENT";
	case ESRCH =>
		return "ESRCH";
	case EINTR =>
		return "EINTR";
	case EIO =>
		return "EIO";
	case ENXIO =>
		return "ENXIO";
	case E2BIG =>
		return "E2BIG";
	case ENOEXEC =>
		return "ENOEXEC";
	case EBADF =>
		return "EBADF";
	case ECHILD =>
		return "ECHILD";
	case EDEADLK =>
		return "EDEADLK";
	case ENOMEM =>
		return "ENOMEM";
	case EACCES =>
		return "EACCES";
	case EFAULT =>
		return "EFAULT";
	case ENOTBLK =>
		return "ENOTBLK";
	case EBUSY =>
		return "EBUSY";
	case EEXIST =>
		return "EEXIST";
	case EXDEV =>
		return "EXDEV";
	case ENODEV =>
		return "ENODEV";
	case ENOTDIR =>
		return "ENOTDIR";
	case EISDIR =>
		return "EISDIR";
	case EINVAL =>
		return "EINVAL";
	case ENFILE =>
		return "ENFILE";
	case EMFILE =>
		return "EMFILE";
	case ENOTTY =>
		return "ENOTTY";
	case ETXTBSY =>
		return "ETXTBSY";
	case EFBIG =>
		return "EFBIG";
	case ENOSPC =>
		return "ENOSPC";
	case ESPIPE =>
		return "ESPIPE";
	case EROFS =>
		return "EROFS";
	case EMLINK =>
		return "EMLINK";
	case EPIPE =>
		return "EPIPE";
	case EDOM =>
		return "EDOM";
	case ERANGE =>
		return "ERANGE";
	case EAGAIN =>
		return "EAGAIN";
	case EINPROGRESS =>
		return "EINPROGRESS";
	case EALREADY =>
		return "EALREADY";
	case ENOTSOCK =>
		return "ENOTSOCK";
	case EDESTADDRREQ =>
		return "EDESTADDRREQ";
	case EMSGSIZE =>
		return "EMSGSIZE";
	case EPROTOTYPE =>
		return "EPROTOTYPE";
	case ENOPROTOOPT =>
		return "ENOPROTOOPT";
	case EPROTONOSUPPORT =>
		return "EPROTONOSUPPORT";
	case ESOCKTNOSUPPORT =>
		return "ESOCKTNOSUPPORT";
	case EOPNOTSUPP =>
		return "EOPNOTSUPP";
	case EPFNOSUPPORT =>
		return "EPFNOSUPPORT";
	case EAFNOSUPPORT =>
		return "EAFNOSUPPORT";
	case EADDRINUSE =>
		return "EADDRINUSE";
	case EADDRNOTAVAIL =>
		return "EADDRNOTAVAIL";
	case ENETDOWN =>
		return "ENETDOWN";
	case ENETUNREACH =>
		return "ENETUNREACH";
	case ENETRESET =>
		return "ENETRESET";
	case ECONNABORTED =>
		return "ECONNABORTED";
	case ECONNRESET =>
		return "ECONNRESET";
	case ENOBUFS =>
		return "ENOBUFS";
	case EISCONN =>
		return "EISCONN";
	case ENOTCONN =>
		return "ENOTCONN";
	case ESHUTDOWN =>
		return "ESHUTDOWN";
	case ETOOMANYREFS =>
		return "ETOOMANYREFS";
	case ETIMEDOUT =>
		return "ETIMEDOUT";
	case ECONNREFUSED =>
		return "ECONNREFUSED";
	case ELOOP =>
		return "ELOOP";
	case ENAMETOOLONG =>
		return "ENAMETOOLONG";
	case EHOSTDOWN =>
		return "EHOSTDOWN";
	case EHOSTUNREACH =>
		return "EHOSTUNREACH";
	case ENOTEMPTY =>
		return "ENOTEMPTY";
	case EPROCLIM =>
		return "EPROCLIM";
	case EUSERS =>
		return "EUSERS";
	case EDQUOT =>
		return "EDQUOT";
	case ESTALE =>
		return "ESTALE";
	case EREMOTE =>
		return "EREMOTE";
	case EBADRPC =>
		return "EBADRPC";
	case ERPCMISMATCH =>
		return "ERPCMISMATCH";
	case EPROGUNAVAIL =>
		return "EPROGUNAVAIL";
	case EPROGMISMATCH =>
		return "EPROGMISMATCH";
	case EPROCUNAVAIL =>
		return "EPROCUNAVAIL";
	case ENOLCK =>
		return "ENOLCK";
	case ENOSYS =>
		return "ENOSYS";
	case EFTYPE =>
		return "EFTYPE";
	case EAUTH =>
		return "EAUTH";
	case ENEEDAUTH =>
		return "ENEEDAUTH";
	case EIPSEC =>
		return "EIPSEC";
	case ENOATTR =>
		return "ENOATTR";
	case EILSEQ =>
		return "EILSEQ";
	case ENOMEDIUM =>
		return "ENOMEDIUM";
	case EMEDIUMTYPE =>
		return "EMEDIUMTYPE";
	case EOVERFLOW =>
		return "EOVERFLOW";
	case ECANCELED =>
		return "ECANCELED";
	case EIDRM =>
		return "EIDRM";
	case ENOMSG =>
		return "ENOMSG";
	case ENOTSUP =>
		return "ENOTSUP";
	case EBADMSG =>
		return "EBADMSG";
	case ENOTRECOVERABLE =>
		return "ENOTRECOVERABLE";
	case EOWNERDEAD =>
		return "EOWNERDEAD";
	case EPROTO =>
		return "EPROTO";
	case =>
		return unknown_errno(err);
	};
};

export def EPERM: errno = 1;
export def ENOENT: errno = 2;
export def ESRCH: errno = 3;
export def EINTR: errno = 4;
export def EIO: errno = 5;
export def ENXIO: errno = 6;
export def E2BIG: errno = 7;
export def ENOEXEC: errno = 8;
export def EBADF: errno = 9;
export def ECHILD: errno = 10;
export def EDEADLK: errno = 11;
export def ENOMEM: errno = 12;
export def EACCES: errno = 13;
export def EFAULT: errno = 14;
export def ENOTBLK: errno = 15;
export def EBUSY: errno = 16;
export def EEXIST: errno = 17;
export def EXDEV: errno = 18;
export def ENODEV: errno = 19;
export def ENOTDIR: errno = 20;
export def EISDIR: errno = 21;
export def EINVAL: errno = 22;
export def ENFILE: errno = 23;
export def EMFILE: errno = 24;
export def ENOTTY: errno = 25;
export def ETXTBSY: errno = 26;
export def EFBIG: errno = 27;
export def ENOSPC: errno = 28;
export def ESPIPE: errno = 29;
export def EROFS: errno = 30;
export def EMLINK: errno = 31;
export def EPIPE: errno = 32;
export def EDOM: errno = 33;
export def ERANGE: errno = 34;
export def EAGAIN: errno = 35;
export def EWOULDBLOCK: errno = EAGAIN;
export def EINPROGRESS: errno = 36;
export def EALREADY: errno = 37;
export def ENOTSOCK: errno = 38;
export def EDESTADDRREQ: errno = 39;
export def EMSGSIZE: errno = 40;
export def EPROTOTYPE: errno = 41;
export def ENOPROTOOPT: errno = 42;
export def EPROTONOSUPPORT: errno = 43;
export def ESOCKTNOSUPPORT: errno = 44;
export def EOPNOTSUPP: errno = 45;
export def EPFNOSUPPORT: errno = 46;
export def EAFNOSUPPORT: errno = 47;
export def EADDRINUSE: errno = 48;
export def EADDRNOTAVAIL: errno = 49;
export def ENETDOWN: errno = 50;
export def ENETUNREACH: errno = 51;
export def ENETRESET: errno = 52;
export def ECONNABORTED: errno = 53;
export def ECONNRESET: errno = 54;
export def ENOBUFS: errno = 55;
export def EISCONN: errno = 56;
export def ENOTCONN: errno = 57;
export def ESHUTDOWN: errno = 58;
export def ETOOMANYREFS: errno = 59;
export def ETIMEDOUT: errno = 60;
export def ECONNREFUSED: errno = 61;
export def ELOOP: errno = 62;
export def ENAMETOOLONG: errno = 63;
export def EHOSTDOWN: errno = 64;
export def EHOSTUNREACH: errno = 65;
export def ENOTEMPTY: errno = 66;
export def EPROCLIM: errno = 67;
export def EUSERS: errno = 68;
export def EDQUOT: errno = 69;
export def ESTALE: errno = 70;
export def EREMOTE: errno = 71;
export def EBADRPC: errno = 72;
export def ERPCMISMATCH: errno = 73;
export def EPROGUNAVAIL: errno = 74;
export def EPROGMISMATCH: errno = 75;
export def EPROCUNAVAIL: errno = 76;
export def ENOLCK: errno = 77;
export def ENOSYS: errno = 78;
export def EFTYPE: errno = 79;
export def EAUTH: errno = 80;
export def ENEEDAUTH: errno = 81;
export def EIPSEC: errno = 82;
export def ENOATTR: errno = 83;
export def EILSEQ: errno = 84;
export def ENOMEDIUM: errno = 85;
export def EMEDIUMTYPE: errno = 86;
export def EOVERFLOW: errno = 87;
export def ECANCELED: errno = 88;
export def EIDRM: errno = 89;
export def ENOMSG: errno = 90;
export def ENOTSUP: errno = 91;
export def EBADMSG: errno = 92;
export def ENOTRECOVERABLE: errno = 93;
export def EOWNERDEAD: errno = 94;
export def EPROTO: errno = 95;
diff --git a/rt/+openbsd/hare+test.sc b/rt/+openbsd/hare+test.sc
new file mode 100644
index 00000000..24624939
--- /dev/null
+++ b/rt/+openbsd/hare+test.sc
@@ -0,0 +1,7 @@
SECTIONS {
	.test_array : {
		PROVIDE(__test_array_start	= .);
		KEEP(*(.test_array*))
		PROVIDE(__test_array_end	= .);
	}
} INSERT AFTER .bss; /* .bss was choosen arbitrarily. */
diff --git a/rt/+openbsd/hare.sc b/rt/+openbsd/hare.sc
new file mode 100644
index 00000000..c2f465d5
--- /dev/null
+++ b/rt/+openbsd/hare.sc
@@ -0,0 +1 @@
/* empty linker script; not needed for OpenBSD */
\ No newline at end of file
diff --git a/rt/+openbsd/libc.ha b/rt/+openbsd/libc.ha
new file mode 100644
index 00000000..7e564c4b
--- /dev/null
+++ b/rt/+openbsd/libc.ha
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Miscellaneous libc functions

export @symbol("arc4random_buf") fn arc4random_buf(
	buf: *opaque,
	nbytes: size
) void;

@symbol("ptsname") fn libc_ptsname(flides: int) *u8;

export fn ptsname(flides: int) (*u8 | errno) = {
	let res = libc_ptsname(flides);

	if (res == null) {
		return *__errno(): errno;
	};
	return res;
};
diff --git a/rt/+openbsd/platform_abort.ha b/rt/+openbsd/platform_abort.ha
new file mode 100644
index 00000000..8e30e5ae
--- /dev/null
+++ b/rt/+openbsd/platform_abort.ha
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>


fn platform_abort(path: *str, line: u64, col: u64, msg: str) void = {
	const prefix = "Abort: ";
	const sep = ":";
	const sepspace = ": ";
	const linefeed = "\n";
	write(STDERR_FILENO, *(&prefix: **opaque): *const u8, len(prefix)): void;
	write(STDERR_FILENO, *(path: **opaque): *const u8, len(path)): void;
	write(STDERR_FILENO, *(&sep: **opaque): *const u8, len(sep)): void;
	let (line, z) = u64tos(line);
	write(STDERR_FILENO, line, z): void;
	write(STDERR_FILENO, *(&sep: **opaque): *const u8, len(sep)): void;
	let (col, z) = u64tos(col);
	write(STDERR_FILENO, col, z): void;
	write(STDERR_FILENO, *(&sepspace: **opaque): *const u8,
		len(sepspace)): void;
	write(STDERR_FILENO, *(&msg: **opaque): *const u8, len(msg)): void;
	write(STDERR_FILENO, *(&linefeed: **opaque): *const u8, 1): void;
	kill(getpid(), SIGABRT): void;
	for (true) void;
};
diff --git a/rt/+openbsd/signal.ha b/rt/+openbsd/signal.ha
new file mode 100644
index 00000000..29f0740b
--- /dev/null
+++ b/rt/+openbsd/signal.ha
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

export fn sigemptyset(set: *sigset) void = {
	*set = 0;
};

export fn sigaddset(set: *sigset, signum: int) (void | errno) = {
	if (signum < 1 || signum > NSIG) {
		return *__errno(): errno;
	};
	*set |= 1u << (signum: uint - 1);
};

export fn sigdelset(set: *sigset, signum: int) (void | errno) = {
	if (signum < 1 || signum > NSIG) {
		return *__errno(): errno;
	};
	*set &= ~(1u << (signum: uint - 1));
};

export fn sigismember(set: *sigset, signum: int) (bool | errno) = {
	if (signum < 1 || signum > NSIG) {
		return *__errno(): errno;
	};
	return (*set & (1u << (signum: uint - 1))) != 0;
};

export fn sigfillset(set: *sigset) (void | errno) = {
	*set = ~0u;
};

// Test sigset operations do not fail for valid signal numbers.
@test fn sigset_valid_signum() void = {
	let set: sigset = 0;
	sigemptyset(&set);

	assert(!(sigismember(&set, 1) is errno), "Unexpected error");
	assert(!(sigismember(&set, 15) is errno), "Unexpected error");
	assert(!(sigismember(&set, NSIG) is errno), "Unexpected error");

	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");
	assert(!(sigaddset(&set, 15) is errno), "Unexpected error");
	assert(!(sigaddset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to add a signal that is already present in the set.
	assert(!(sigaddset(&set, 1) is errno), "Unexpected error");

	assert(!(sigdelset(&set, 1) is errno), "Unexpected error");
	assert(!(sigdelset(&set, 15) is errno), "Unexpected error");
	assert(!(sigdelset(&set, NSIG) is errno), "Unexpected error");

	// It's ok to delete a signal that is not present in the set.
	assert(!(sigdelset(&set, 10) is errno), "Unexpected error");
};

// Test sigset operations fail for invalid signal numbers.
@test fn sigset_invalid_signum() void = {
	let set: sigset = 0;
	sigemptyset(&set);

	assert(sigismember(&set, -1) is errno, "Expected error");
	assert(sigismember(&set, 0) is errno, "Expected error");
	assert(sigismember(&set, NSIG + 1) is errno, "Expected error");

	assert(sigaddset(&set, -1) is errno, "Expected error");
	assert(sigaddset(&set, 0) is errno, "Expected error");
	assert(sigaddset(&set, NSIG + 1) is errno, "Expected error");

	assert(sigdelset(&set, -1) is errno, "Expected error");
	assert(sigdelset(&set, 0) is errno, "Expected error");
	assert(sigdelset(&set, NSIG + 1) is errno, "Expected error");
};
diff --git a/rt/+openbsd/socket.ha b/rt/+openbsd/socket.ha
new file mode 100644
index 00000000..e9fae03b
--- /dev/null
+++ b/rt/+openbsd/socket.ha
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

export type socklen_t = u32;
export type sa_family_t = u8;

export type in_addr = struct {
	s_addr: u32
};

export type sockaddr_in = struct {
	sin_len: u8,
	sin_family: sa_family_t,
	sin_port: u16,
	sin_addr: in_addr,
	__pad: [8]u8,
};

export type in6_addr = struct {
	union {
		s6_addr: [16]u8,
		s6_addr16: [8]u16,
		s6_addr32: [4]u32,
	}
};

export type sockaddr_in6 = struct {
	sin6_len: u8,
	sin6_family: sa_family_t,
	sin6_port: u16,
	sin6_flowinfo: u32,
	sin6_addr: in6_addr,
	sin6_scope_id: u32,
};

export def UNIX_PATH_MAX: size = 104;

export type sockaddr_un = struct {
	sun_len: u8,
	sun_family: sa_family_t,
	sun_path: [UNIX_PATH_MAX]u8,
};

export type sockaddr = struct {
	union {
		in: sockaddr_in,
		in6: sockaddr_in6,
		un: sockaddr_un,
	},
};

export def SCM_RIGHTS: int = 0x01;
export def SCM_TIMESTAMP: int = 0x04;

export type msghdr = struct {
	msg_name: nullable *opaque,
	msg_namelen: socklen_t,
	msg_iov: nullable *[*]iovec,
	msg_iovlen: uint,
	msg_control: nullable *opaque,
	msg_controllen: socklen_t,
	msg_flags: int
};

export type cmsghdr = struct {
	cmsg_len: socklen_t,
	cmsg_level: int,
	cmsg_type: int,
};

export type cmsg = struct {
	hdr: cmsghdr,
	cmsg_data: [*]u8,
};

export def SOCK_STREAM: int = 1;
export def SOCK_DGRAM: int = 2;
export def SOCK_RAW: int = 3;
export def SOCK_RDM: int = 4;
export def SOCK_SEQPACKET: int = 5;

export def SOCK_CLOEXEC: int = 0x8000;
export def SOCK_NONBLOCK: int = 0x4000;

export def SOL_SOCKET: int = 0xffff;

export def AF_UNSPEC: sa_family_t = 0;
export def AF_UNIX: sa_family_t = 1;
export def AF_LOCAL: sa_family_t = AF_UNIX;
export def AF_INET: sa_family_t = 2;
export def AF_IMPLINK: sa_family_t = 3;
export def AF_PUP: sa_family_t = 4;
export def AF_CHAOS: sa_family_t = 5;
export def AF_NS: sa_family_t = 6;
export def AF_ISO: sa_family_t = 7;
export def AF_OSI: sa_family_t = AF_ISO;
export def AF_ECMA: sa_family_t = 8;
export def AF_DATAKIT: sa_family_t = 9;
export def AF_CCITT: sa_family_t = 10;
export def AF_SNA: sa_family_t = 11;
export def AF_DECnet: sa_family_t = 12;
export def AF_DLI: sa_family_t = 13;
export def AF_LAT: sa_family_t = 14;
export def AF_HYLINK: sa_family_t = 15;
export def AF_APPLETALK: sa_family_t = 16;
export def AF_ROUTE: sa_family_t = 17;
export def AF_LINK: sa_family_t = 18;
export def pseudo_AF_XTP: sa_family_t = 19;
export def AF_COIP: sa_family_t = 20;
export def AF_CNT: sa_family_t = 21;
export def pseudo_AF_RTIP: sa_family_t = 22;
export def AF_IPX: sa_family_t = 23;
export def AF_INET6: sa_family_t = 24;
export def pseudo_AF_PIP: sa_family_t = 25;
export def AF_ISDN: sa_family_t = 26;
export def AF_E164: sa_family_t = AF_ISDN;
export def AF_NATM: sa_family_t = 27;
export def AF_ENCAP: sa_family_t = 28;
export def AF_SIP: sa_family_t = 29;
export def AF_KEY: sa_family_t = 30;
export def pseudo_AF_HDRCMPLT: sa_family_t = 31;

export def SO_DEBUG: int = 0x0001;
export def SO_ACCEPTCONN: int = 0x0002;
export def SO_REUSEADDR: int = 0x0004;
export def SO_KEEPALIVE: int = 0x0008;
export def SO_DONTROUTE: int = 0x0010;
export def SO_BROADCAST: int = 0x0020;
export def SO_USELOOPBACK: int = 0x0040;
export def SO_LINGER: int = 0x0080;
export def SO_OOBINLINE: int = 0x0100;
export def SO_REUSEPORT: int = 0x0200;
export def SO_TIMESTAMP: int = 0x0800;
export def SO_BINDANY: int = 0x1000;
export def SO_ZEROIZE: int = 0x2000;

export def SO_SNDBUF: int = 0x1001;
export def SO_RCVBUF: int = 0x1002;
export def SO_SNDLOWAT: int = 0x1003;
export def SO_RCVLOWAT: int = 0x1004;
export def SO_SNDTIMEO: int = 0x1005;
export def SO_RCVTIMEO: int = 0x1006;
export def SO_ERROR: int = 0x1007;
export def SO_TYPE: int = 0x1008;
export def SO_NETPROC: int = 0x1020;
export def SO_RTABLE: int = 0x1021;
export def SO_PEERCRED: int = 0x1022;
export def SO_SPLICE: int = 0x1023;
export def SO_DOMAIN: int = 0x1024;
export def SO_PROTOCOL: int = 0x1025;
diff --git a/rt/+openbsd/start+test.ha b/rt/+openbsd/start+test.ha
new file mode 100644
index 00000000..cd5df5b6
--- /dev/null
+++ b/rt/+openbsd/start+test.ha
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// The real main function.
@symbol(".main") fn main() void;

@symbol("__test_main") fn test_main() size;

// The setup of envp and args is done here. This is called by crt0 before
// normal init functions are called.
export @symbol("preinit_hare") fn preinit_hare(
	c_argc: int,
	c_argv: *[*]*u8,
	c_envp: *[*]nullable *u8
) void = {
	argc = c_argc: size;
	argv = c_argv;
	envp = c_envp;
};

// The purpose of this "fake" main function is to make sure we exit with the
// correct exit code in the case that rt::exit() is not called from within the
// program. The intilization and finilization functions are not run from here,
// they are ran by crt0.
export @symbol("main") fn _main() void = {
	const ret = if (test_main() > 0) 1 else 0;
	exit(ret);
};
diff --git a/rt/+openbsd/start.ha b/rt/+openbsd/start.ha
new file mode 100644
index 00000000..c6a22b9e
--- /dev/null
+++ b/rt/+openbsd/start.ha
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// The real main function.
@symbol(".main") fn main() void;

// The setup of envp and args is done here. This is called by crt0 before
// normal init functions are called.
export @symbol("preinit_hare") fn preinit_hare(
	c_argc: int,
	c_argv: *[*]*u8,
	c_envp: *[*]nullable *u8
) void = {
	argc = c_argc: size;
	argv = c_argv;
	envp = c_envp;
};

// The purpose of this "fake" main function is to make sure we exit with the
// correct exit code in the case that rt::exit() is not called from within the
// program. The intilization and finilization functions are not run from here,
// they are ran by crt0.
export @symbol("main") fn _main() void = {
	main();
	exit(0);
};
diff --git a/rt/+openbsd/start.s b/rt/+openbsd/start.s
new file mode 100644
index 00000000..d1d46438
--- /dev/null
+++ b/rt/+openbsd/start.s
@@ -0,0 +1,4 @@
.section ".preinit_array"
.balign 8
.init.initfunc.0:
        .quad preinit_hare+0
diff --git a/rt/+openbsd/syscalls.ha b/rt/+openbsd/syscalls.ha
new file mode 100644
index 00000000..b11c205d
--- /dev/null
+++ b/rt/+openbsd/syscalls.ha
@@ -0,0 +1,1223 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

let pathbuf: [PATH_MAX]u8 = [0...];
export type path = (str | []u8 | *const u8);

fn copy_kpath(path: path, buf: []u8) (*const u8 | errno) = {
	let path = match (path) {
	case let c: *const u8 =>
		return c;
	case let s: str =>
		let ptr = &s: *struct {
			buf: *[*]u8,
			length: size,
			capacity: size,
		};
		yield ptr.buf[..ptr.length];
	case let b: []u8 =>
		yield b;
	};
	if (len(path) + 1 >= len(buf)) {
		return ENAMETOOLONG;
	};
	memcpy(buf: *[*]u8, path: *[*]u8, len(path));
	buf[len(path)] = 0;
	return buf: *[*]u8: *const u8;
};

// NUL terminates a string and stores it in a static buffer of PATH_MAX bytes in
// length.
fn kpath(path: path) (*const u8 | errno) = {
	return copy_kpath(path, pathbuf);
};

@symbol("__errno") fn __errno() *int;

// exit
export @symbol("exit") fn exit(status: int) never;

// fork

@symbol("fork") fn libc_fork() int;

export fn fork() (int | void | errno) = {
	let res = libc_fork();
	if (res == -1) {
		return *__errno(): errno;
	};
	if (res == 0) {
		return;
	};
	return res;
};

// read

@symbol("read") fn libc_read(d: int, buf: *opaque, nbytes: size) size;

export fn read(fd: int, buf: *opaque, count: size) (size | errno) = {
	let res: u64 = libc_read(fd, buf, count);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// write

@symbol("write") fn libc_write(d: int, buf: *const opaque, nbytes: size) size;

export fn write(fd: int, buf: *const opaque, count: size) (size | errno) = {
	let res: u64 = libc_write(fd, buf, count);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// open

@symbol("open") fn libc_open(path: *opaque, flags: int, mode: int) int;

export fn open(path: path, flags: int, mode: int) (int | errno) = {
	let res = libc_open(kpath(path)?, flags, mode);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// posix_openpt (libc function not a syscall)

@symbol("posix_openpt") fn libc_openpt(oflag: int) int;

export fn posix_openpt(flags: int) (int | errno) = {
	let res = libc_openpt(flags);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// close

@symbol("close") fn libc_close(d: int) int;

export fn close(fd: int) (void | errno) = {
	let res = libc_close(fd);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// getentropy
// __tfork
// link
// unlink
// wait4

@symbol("wait4") fn libc_wait4(
	wpid: int,
	status: nullable *int,
	options: int,
	rusage: nullable *rusage
) int;

export fn wait4(
	pid: int,
	wstatus: nullable *int,
	options: int,
	rusage: nullable *rusage,
) (int | errno) = {
	let res = libc_wait4(pid, wstatus, options, rusage);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// chdir

@symbol("chdir") fn libc_chdir(path: *const u8) int;

export fn chdir(path: path) (void | errno) = {
	let res = libc_chdir(kpath(path)?);

	if (res == -1)  {
		return *__errno(): errno;
	};
};

// fchdir

@symbol("fchdir") fn libc_fchdir(fd: int) int;

export fn fchdir(fd: int) (void | errno) = {
	let res = libc_fchdir(fd);

	if (res == -1)  {
		return *__errno(): errno;
	};
};

// mknod
// chmod
// chown
// obreak
// getdtablecount
// getrusage
// getpid

export @symbol("getpid") fn getpid() int;

// mount
// unmount
// setuid

@symbol("setuid") fn libc_setuid(uid: uid_t) int;

export fn setuid(uid: uid_t) (void | errno) = {
	let res = libc_setuid(uid);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// getuid

export @symbol("getuid") fn getuid() uid_t;

// geteuid

export @symbol("geteuid") fn geteuid() uid_t;

// ptrace
// recvmsg

@symbol("recvmsg") fn libc_recvmsg(s: int, msg: *const msghdr, flags: int) i64;

export fn recvmsg(fd: int, msg: *const msghdr, flags: int) (int | errno) = {
	let res = libc_recvmsg(fd, msg, flags);
	if (res == -1) {
		return *__errno(): errno;
	};
	// TODO: could overflow
	return res: int;
};

// sendmsg

@symbol("sendmsg") fn libc_sendmsg(s: int, msg: *const msghdr, flags: int) i64;

export fn sendmsg(fd: int, msg: *const msghdr, flags: int) (int | errno) = {
	let res = libc_sendmsg(fd, msg, flags);
	if (res == -1) {
		return *__errno(): errno;
	};
	// TODO: could overflow
	return res: int;
};

// recvfrom

@symbol("recvfrom") fn libc_recvfrom(
	s: int,
	buf: *opaque,
	length: size,
	flags: int,
	from: nullable *sockaddr,
	fromlen: nullable *u32,
) i64;

export fn recvfrom(
	sockfd: int,
	buf: *opaque,
	length: size,
	flags: int,
	from: nullable *sockaddr,
	fromlen: nullable *u32
) (size | errno) = {
	let res = libc_recvfrom(sockfd, buf, length, flags, from, fromlen);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res: size;
};

// accept
// getpeername

@symbol("getpeername") fn libc_getpeername(
	s: int,
	name: *sockaddr,
	namelen: *u32
) int;

export fn getpeername(
	sockfd: int,
	addr: *sockaddr,
	addrlen: *u32
) (void | errno) = {
	let res = libc_getpeername(sockfd, addr, addrlen);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// getsockname

@symbol("getsockname") fn libc_getsockname(
	sockfd: int,
	addr: nullable *sockaddr,
	addrlen: nullable *u32
) int;

export fn getsockname(
	sockfd: int,
	addr: nullable *sockaddr,
	addrlen: nullable *u32
) (void | errno) = {
	let res = libc_getsockname(sockfd, addr, addrlen);
	if (res == -1) {
		return *__errno(): errno;
	};

};
// access

@symbol("access") fn libc_access(path: *const u8, amode: int) int;

export fn access(path: path, amode: int) (bool | errno) = {
	let res = libc_access(kpath(path)?, amode);
	if (res == -1) {
		let err = *__errno(): errno;

		switch (res) {
		case EACCES =>
			return false;
		case =>
			return err;
		};
	};

	return true;
};


// chflags
// fchflags
// sync
// msyscall
// stat
// getppid
// lstat
// dup
// fstatat

@symbol("fstatat") fn libc_fstatat(fd: int, path: *const u8, sb: *stat, flag: int) int;

export fn fstatat(
	dirfd: int,
	path: path,
	stat: *stat,
	flag: int
) (void | errno) = {
	let res = libc_fstatat(dirfd, kpath(path)?, stat, flag);
	if (res == -1) {
		return *__errno(): errno;
	};
};


// getegid

export @symbol("getegid") fn getegid() gid_t;

// profil
// ktrace
// sigaction

export @symbol("sigaction") fn libc_sigaction(
	sig: int,
	act: *const sigact,
	oact: nullable *sigact
) int;

export fn sigaction(
	signum: int,
	act: *const sigact,
	old: nullable *sigact,
) (void | errno) = {
	let res = libc_sigaction(signum, act, old);
	if (res == -1) {
		return *__errno(): errno;
	};
};
// getgid

export @symbol("getgid") fn getgid() gid_t;

// sigprocmask

@symbol("sigprocmask") fn libc_sigprocmask(
	how: int,
	set: nullable *const sigset,
	old: nullable *sigset
) int;

export fn sigprocmask(
	how: int,
	set: nullable *const sigset,
	old: nullable *sigset
) (void | errno) = {
	let res = libc_sigprocmask(how, set, old);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// mmap

@symbol("mmap") fn libc_mmap(
	addr: nullable *opaque,
	len_: size,
	prot: int,
	flags: int,
	fd: int,
	pos: i64
) nullable *opaque;

export fn mmap(
	addr: nullable *opaque,
	len_: size,
	prot: int,
	flags: int,
	fd: int,
	pos: i64
) (*opaque | errno) = {
	let res = libc_mmap(addr, len_, prot, flags, fd, pos);

	if (res == null) {
		return *__errno(): errno;
	};
	return res: *opaque;
};

// setlogin
// acct
// sigpending
// fstat
// ioctl

@symbol("ioctl") fn libc_ioctl(fd: int, req: u64, arg: u64) int;

export type ioctl_arg = (nullable *opaque | u64);

export fn ioctl(fd: int, req: u64, arg: ioctl_arg) (int | errno) = {
	let res = match (arg) {
	case let u: u64 =>
		yield libc_ioctl(fd, req, u);
	case let ptr: nullable *opaque =>
		yield libc_ioctl(fd, req, ptr: uintptr: u64);
	};
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// reboot
// revoke
// symlink
// readlink
// execve

@symbol("execve") fn libc_execve(path: *const u8, argv: *[*]nullable *const u8,
		envp: *[*]nullable *const u8) int;

export fn execve(path: path, argv: *[*]nullable *const u8,
		envp: *[*]nullable *const u8) errno = {
	let res = libc_execve(kpath(path)?, argv, envp);
	return *__errno(): errno;
};

// umask

@symbol("umask") fn libc_umask(numask: mode_t) mode_t;

export fn umask(mode: mode_t) (mode_t | errno) = {
	// Always successful on OpenBSD.
	return libc_umask(mode);
};

// chroot

@symbol("chroot") fn libc_chroot(dirname: *const u8) int;

export fn chroot(path: path) (void | errno) = {
	let res = libc_chroot(kpath(path)?);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// getfsstat
// statfs
// fstatfs
// fhstatfs
// vfork
// gettimeofday
// settimeofday
// setitimer
// getitimer
// select
// kevent
// munmap

@symbol("munmap") fn libc_munmap(addr: *opaque, len_: size) int;

export fn munmap(addr: *opaque, len_: size) (void | errno) = {
	let res = munmap(addr, len_);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// mprotect
// madvise
// utimes
// futimes
// mquery
// getgroups
// setgroups
// getpgrp
// setpgid
// futex
// utimensat
// futimens
// kbind
// clock_gettime

@symbol("clock_gettime") fn libc_clock_gettime(clock: int, now: *timespec) int;

export fn clock_gettime(clock: int, now: *timespec) (void | errno) = {
	let res = libc_clock_gettime(clock, now);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// clock_settime
// clock_getres
// dup2

@symbol("dup2") fn libc_dup2(oldd: int, newd: int) int;

export fn dup2(oldfd: int, newfd: int) (int | errno) = {
	let res = libc_dup2(oldfd, newfd);
	if (res == -1) {
		return *__errno(): errno;
	};
	return res;
};

// nanosleep

@symbol("nanosleep") fn libc_nanosleep(
	timeout: *const timespec,
	remainder: *timespec
) int;

export fn nanosleep(
	timeout: *const timespec,
	remainder: *timespec
) (void | errno) = {
	let res = libc_nanosleep(timeout, remainder);
	if (res == -1) {
		return *__errno(): errno;
	};
};

// fcntl

@symbol("fcntl") fn libc_fcntl(fd: int, cmd: int, arg: u64) int;

export type fcntl_arg = (void | int | *st_flock | *u64);

export fn fcntl(fd: int, cmd: int, arg: fcntl_arg) (int | errno) = {
	let res = match (arg) {
	case void =>
		yield libc_fcntl(fd, cmd, 0);
	case let i: int =>
		yield libc_fcntl(fd, cmd, i: u64);
	case let l: *st_flock =>