hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mos/exec/+freebsd/platform_cmd.ha | 1+
Mos/exec/+linux/platform_cmd.ha | 1+
Aos/exec/+openbsd/exec.ha | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/exec/+openbsd/platform_cmd.ha | 6++++++
Aos/exec/+openbsd/process.ha | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; + }; +};