commit 43e3c81853cc80d38fa4f1b6db5ded01f580a172
parent 12dde04d3b98475d731cb74efc6b287868192aff
Author: Lorenz (xha) <me@xha.li>
Date: Sat, 25 Nov 2023 15:18:11 +0100
OpenBSD: add os
Signed-off-by: Lorenz (xha) <me@xha.li>
Diffstat:
7 files changed, 717 insertions(+), 0 deletions(-)
diff --git a/os/+openbsd/dirfdfs.ha b/os/+openbsd/dirfdfs.ha
@@ -0,0 +1,456 @@
+// 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;
+
+ for (true) {
+ 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;
+ // getdents() may return invalid entries which will have
+ // d_fileno set to 0
+ if (de.d_fileno == 0) {
+ continue;
+ };
+
+ let name = c::tostr(&de.d_name: *const c::char)!;
+ if (name == "." || name == "..") {
+ continue;
+ };
+
+ 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
+};