hare

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

commit 79a87fa3a6785f63402eae40f1518b944e676177
parent a1ac4a051fe597bef7f356a517d1298f3385d7f5
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue,  2 Mar 2021 09:43:06 -0500

os::exec: record process information

Diffstat:
Mos/exec/+linux.ha | 127+++++++++++++++----------------------------------------------------------------
Aos/exec/cmd+linux.ha | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mos/exec/cmd.ha | 36+++++++-----------------------------
Aos/exec/process+linux.ha | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/exec/process.ha | 0
Aos/exec/types.ha | 27+++++++++++++++++++++++++++
Mrt/+linux/syscalls.ha | 11+++++++++++
Mrt/+linux/types.ha | 37+++++++++++++++++++++++++++++++++++++
8 files changed, 287 insertions(+), 133 deletions(-)

diff --git a/os/exec/+linux.ha b/os/exec/+linux.ha @@ -1,105 +1,24 @@ -use rt; -use strings; -use os; - -export type platform = int; - -// An error provided by the operating system. -export type os_error = struct { - string: *fn(data: *void) str, - data: *void, -}; - -// Forks the current process, returning the pid of the child (to the parent) and -// void (to the child), or an error. -export fn fork() (int | void | error) = match (rt::fork()) { - err: rt::errno => errno_to_os(err), - i: (int | void) => i, -}; - -fn errno_errstr(data: *void) str = { - const errno = data: uintptr: int: rt::errno; - return rt::errstr(errno); -}; - -fn errno_to_os(err: rt::errno) os_error = { - return os_error { - string = &errno_errstr, - data = err: uintptr: *void, - }; -}; - -fn open(path: str) (platform | os_error) = { - match (rt::access(path, rt::X_OK)) { - err: rt::errno => errno_to_os(err), - b: bool => if (!b) { - return errno_to_os(rt::EACCES); - }, - }; - // O_PATH is used because it allows us to use an executable for which we - // have execute permissions, but not read permissions. - return match (rt::open(path, rt::O_PATH, 0u)) { - fd: int => fd, - err: rt::errno => errno_to_os(err), - }; -}; - -fn platform_finish(cmd: *command) void = { - rt::close(cmd.data); -}; - -fn platform_exec(cmd: *command) os_error = { - // TODO: These strings need to be NUL terminated - let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z); - for (let i = 0z; i < len(cmd.argv); i += 1z) { - append(argv, cmd.argv[i]: *const char); - }; - append(argv, null); - - let envp: nullable *[*]nullable *const char = null; - if (len(cmd.envp) != 0) { - let env: []nullable *const char = alloc([], len(cmd.envp) + 1); - for (let i = 0z; i < len(cmd.envp); i += 1) { - append(env, cmd.envp[i]: *const char); - }; - append(env, null); - envp = env: *[*]nullable *const char; - }; - - return errno_to_os(rt::execveat(cmd.data, strings::c_empty, - argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH)); -}; - -fn platform_start(cmd: *command) (os_error | void) = { - // 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)) { - err: rt::errno => return errno_to_os(err), - void => void, - }; - - match (rt::clone(null, 0, null, null, 0)) { - err: rt::errno => return errno_to_os(err), - pid: int => { - // TODO: Fill in some kind of process structure - rt::close(pipe[1]); - let errno: int = 0; - match (rt::read(pipe[0], &errno, size(int))) { - err: rt::errno => return errno_to_os(err), - n: size => switch (n) { - size(int) => return errno_to_os(errno), - * => abort("Unexpected rt::read result"), - 0 => void, - }, - }; - return; - }, - void => { - rt::close(pipe[0]); - let err = platform_exec(cmd); - let errno = err.data: uintptr: int; - rt::write(pipe[1], &errno, size(int)); - rt::exit(1); - }, - }; +// TODO: This file doesn't need to exist once we have working forward +// declarations + +export type platform_cmd = int; + +// Stores information about a child process. +export type process = int; + +// 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 { + // TODO: utime, stime + maxrss: u64, + minflt: u64, + majflt: u64, + inblock: u64, + oublock: u64, + nvcsw: u64, + nivcsw: u64, + }, }; diff --git a/os/exec/cmd+linux.ha b/os/exec/cmd+linux.ha @@ -0,0 +1,96 @@ +use rt; +use strings; +use os; + +// Forks the current process, returning the pid of the child (to the parent) and +// void (to the child), or an error. +export fn fork() (int | void | error) = match (rt::fork()) { + err: rt::errno => errno_to_os(err), + i: (int | void) => i, +}; + +fn errno_errstr(data: *void) str = { + const errno = data: uintptr: int: rt::errno; + return rt::errstr(errno); +}; + +fn errno_to_os(err: rt::errno) os_error = { + return os_error { + string = &errno_errstr, + data = err: uintptr: *void, + }; +}; + +fn open(path: str) (platform_cmd | os_error) = { + match (rt::access(path, rt::X_OK)) { + err: rt::errno => errno_to_os(err), + b: bool => if (!b) { + return errno_to_os(rt::EACCES); + }, + }; + // O_PATH is used because it allows us to use an executable for which we + // have execute permissions, but not read permissions. + return match (rt::open(path, rt::O_PATH, 0u)) { + fd: int => fd, + err: rt::errno => errno_to_os(err), + }; +}; + +fn platform_finish(cmd: *command) void = rt::close(cmd.platform); + +fn platform_exec(cmd: *command) os_error = { + let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z); + for (let i = 0z; i < len(cmd.argv); i += 1z) { + append(argv, cmd.argv[i]: *const char); + // TODO: These strings need to be NUL terminated + assert((argv[i]: *[*]u8)[len(cmd.argv[i])] == 0); + }; + append(argv, null); + + let envp: nullable *[*]nullable *const char = null; + if (len(cmd.envp) != 0) { + let env: []nullable *const char = alloc([], len(cmd.envp) + 1); + for (let i = 0z; i < len(cmd.envp); i += 1) { + append(env, cmd.envp[i]: *const char); + // TODO: These strings need to be NUL terminated + assert((env[i]: *[*]u8)[len(cmd.argv[i])] == 0); + }; + append(env, null); + envp = env: *[*]nullable *const char; + }; + + return errno_to_os(rt::execveat(cmd.platform, strings::c_empty, + argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH)); +}; + +fn platform_start(cmd: *command) (os_error | process) = { + // 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)) { + err: rt::errno => return errno_to_os(err), + void => void, + }; + + match (rt::clone(null, 0, null, null, 0)) { + err: rt::errno => return errno_to_os(err), + pid: int => { + rt::close(pipe[1]); + let errno: int = 0; + return match (rt::read(pipe[0], &errno, size(int))) { + err: rt::errno => errno_to_os(err), + n: size => switch (n) { + size(int) => errno_to_os(errno), + * => abort("Unexpected rt::read result"), + 0 => pid, + }, + }; + }, + void => { + rt::close(pipe[0]); + let err = platform_exec(cmd); + let errno = err.data: uintptr: int; + rt::write(pipe[1], &errno, size(int)); + rt::exit(1); + }, + }; +}; diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -1,27 +1,6 @@ use os; use strings; -// An executable command. -export type command = struct { - data: platform, - argv: []str, - envp: []str, -}; - -// Returned when path resolution fails to find a command by its name. -export type nocmd = void; - -// All errors that can be returned from os::exec. -export type error = (nocmd | os_error); - -// Returns a human-readable message for the given error. -export fn errstr(err: error) const str = { - return match (err) { - err: os_error => err.string(err.data), - nocmd => "Command not found", - }; -}; - // Prepares a [command] based on its name and a list of arguments. The argument // list should not start with the command name; it will be added for you. The // argument list is borrowed from the strings you pass into this command. @@ -37,13 +16,13 @@ export fn errstr(err: error) const str = { // By default, the new command will inherit the current process's environment. export fn cmd(name: str, args: str...) (command | error) = { let cmd = command { - data: platform = + platform: platform_cmd = if (strings::contains(name, '/')) match (open(name)) { err: os_error => return nocmd, - p: platform => p, + p: platform_cmd => p, } else match (lookup(name)) { void => return nocmd, - p: platform => p, + p: platform_cmd => p, }, argv = alloc([], len(args) + 1z), ... @@ -68,16 +47,15 @@ export fn exec(cmd: *command) error = { }; // Starts a prepared command in a new process. -export fn start(cmd: *command) (error | void) = { - // TODO: Return a handle which gives information about the new process. +export fn start(cmd: *command) (error | process) = { defer finish(cmd); return match (platform_start(cmd)) { err: os_error => err, - void => void, + proc: process => proc, }; }; -fn lookup(name: str) (platform | void) = { +fn lookup(name: str) (platform_cmd | void) = { const path = match (os::getenv("PATH")) { void => return, s: str => s, @@ -92,7 +70,7 @@ fn lookup(name: str) (platform | void) = { defer free(path); match (open(path)) { err: os_error => continue, - p: platform => return p, + p: platform_cmd => return p, }; }; }; diff --git a/os/exec/process+linux.ha b/os/exec/process+linux.ha @@ -0,0 +1,86 @@ +use rt; +use fmt; +// TODO: Add function to wait on all/any children + +fn rusage(st: *status, ru: *rt::rusage) void = { + st.rusage.maxrss = ru.ru_maxrss; + st.rusage.minflt = ru.ru_minflt; + st.rusage.majflt = ru.ru_majflt; + st.rusage.inblock = ru.ru_inblock; + st.rusage.oublock = ru.ru_oublock; + 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 = rt::rusage { ... }; + let st: status = status { ... }; + match (rt::wait4(*proc, &st.status, 0, &ru)) { + err: rt::errno => errno_to_os(err), + pid: int => assert(pid == *proc), + }; + rusage(&st, &ru); + return st; +}; + +// 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 = rt::rusage { ... }; + let st: status = status { ... }; + match (rt::wait4(*proc, &st.status, 0, &ru)) { + err: rt::errno => errno_to_os(err), + pid: int => switch (pid) { + 0 => return void, + * => assert(pid == *proc), + }, + }; + rusage(&st, &ru); + return st; +}; + +// The exit status code of a process. +export type exited = int; + +// The signal number which caused a process to terminate. +export type signaled = int; + +// The exit status of a process. +export type exit_status = (exited | signaled); + +// Returns a human friendly string describing the exit status. +export fn exitstr(status: exit_status) const str = { + static let buf: [1024]u8 = [0...]; + return match (status) { + i: exited => switch (i) { + 0 => "exited normally", + * => fmt::bsprintf(buf, "exited with status {}", i: int), + }, + // TODO: Add signal name + s: signaled => fmt::bsprintf(buf, "exited with signal {}", s: int), + }; +}; + +// 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)) { + return switch (rt::wexitstatus(stat.status)) { + 0 => void, + * => exit(stat), + }; + }; + return exit(stat); +}; diff --git a/os/exec/process.ha b/os/exec/process.ha diff --git a/os/exec/types.ha b/os/exec/types.ha @@ -0,0 +1,27 @@ +// An executable command. +export type command = struct { + platform: platform_cmd, + argv: []str, + envp: []str, +}; + +// Returned when path resolution fails to find a command by its name. +export type nocmd = void; + +// An error provided by the operating system. +export type os_error = struct { + string: *fn(data: *void) str, + data: *void, +}; + +// All errors that can be returned from os::exec. +export type error = (nocmd | os_error); + +// Returns a human-readable message for the given error. +export fn errstr(err: error) const str = { + return match (err) { + err: os_error => err.string(err.data), + nocmd => "Command not found", + }; +}; + diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha @@ -115,6 +115,17 @@ export fn fork() (int | void | errno) = clone(null, SIGCHLD, null, null, 0); export fn getpid() int = syscall0(SYS_getpid): int; +export fn wait4( + pid: int, + wstatus: *int, + options: int, + rusage: *rusage, +) (int | errno) = { + return wrap_return(syscall4(SYS_wait4, + pid: u64, wstatus: uintptr: u64, + options: u64, rusage: uintptr: u64))?: int; +}; + export fn sendfile( out: int, in: int, diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha @@ -1,3 +1,4 @@ +export type off_t = u64; export type dev_t = u64; export type ino_t = u64; export type nlink_t = u64; @@ -258,3 +259,39 @@ export type dirent64 = struct { d_type: u8, d_name: [*]char, }; + +export def WNOHANG: int = 1; +export def WUNTRACED: int = 2; +export def WSTOPPED: int = 2; +export def WEXITED: int = 4; +export def WCONTINUED: int = 8; +export def WNOWAIT: int = 0x1000000; + +export fn wexitstatus(s: int) int = (s & 0xff00) >> 8; +export fn wtermsig(s: int) int = s & 0x7f; +export fn wstopsig(s: int) int = wexitstatus(s); +export fn wcoredump(s: int) int = s & 0x80; +export fn wifexited(s: int) bool = wtermsig(s) <= 0; +export fn wifstopped(s: int) bool = (((s & 0xFFFF) * 0x10001) >> 8) > 0x7f00; +export fn wifsignaled(s: int) bool = (s & 0xFFFF) - 1 < 0xFF; +export fn wifcontinued(s: int) bool = s == 0xFFFF; + +export type rusage = struct { + ru_utime: timeval, + ru_stime: timeval, + ru_maxrss: u64, + ru_ixrss: u64, + ru_idrss: u64, + ru_isrss: u64, + ru_minflt: u64, + ru_majflt: u64, + ru_nswap: u64, + ru_inblock: u64, + ru_oublock: u64, + ru_msgsnd: u64, + ru_msgrcv: u64, + ru_nsignals: u64, + ru_nvcsw: u64, + ru_nivcsw: u64, + __reserved: [16]u64, +};