commit 5ff23d5a654c64f10969037b9db01f923b728455
parent 43e3c81853cc80d38fa4f1b6db5ded01f580a172
Author: Lorenz (xha) <me@xha.li>
Date: Sat, 25 Nov 2023 15:18:12 +0100
OpenBSD: add os::exec
Signed-off-by: Lorenz (xha) <me@xha.li>
Diffstat:
5 files changed, 421 insertions(+), 0 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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;
+ };
+};