hare

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

commit 2b9c815ae133ec12c5c510d4b3afd6bf5696caf7
parent 86af24a87d2d211120cc65e35c029cf01734de87
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sun,  7 Feb 2021 14:59:27 -0500

os::exec: new module

Diffstat:
Mos/+linux/open.ha | 6+++---
Aos/exec/+linux.ha | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/exec/cmd.ha | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mrt/+linux/syscalls.ha | 22++++++++++++++++++++++
Mrt/+linux/types.ha | 4++++
Mstrings/cstrings.ha | 9+++++++++
6 files changed, 197 insertions(+), 3 deletions(-)

diff --git a/os/+linux/open.ha b/os/+linux/open.ha @@ -27,9 +27,9 @@ export fn open( flag: flags... ) (*io::stream | io::error) = { // Verify that these line up with the Linux ABI: - static assert(io::mode::RDONLY: int == 0); - static assert(io::mode::WRONLY: int == 1); - static assert(io::mode::RDWR: int == 2); + static assert(io::mode::RDONLY: int == rt::O_RDONLY); + static assert(io::mode::WRONLY: int == rt::O_WRONLY); + static assert(io::mode::RDWR: int == rt::O_RDWR); const p: []u8 = match (path) { s: str => strings::to_utf8(s), diff --git a/os/exec/+linux.ha b/os/exec/+linux.ha @@ -0,0 +1,68 @@ +use rt; +use strings; + +export type platform = int; + +// An error provided by the operating system. +export type os_error = struct { + string: *fn(data: *void) str, + data: *void, +}; + +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) = { + return match (rt::open(path: *const char, rt::O_RDONLY, 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 = { + let argv = alloc([]nullable *const char, [], 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) != 0z) { + let env = alloc([]nullable *const char, [], len(cmd.envp) + 1z); + for (let i = 0z; i < len(cmd.envp); i += 1z) { + 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: Set aside a pipe to fetch errno from child process if exec + // fails + match (rt::fork()) { + err: rt::errno => return errno_to_os(err), + int => { + // TODO: Fill in some kind of process structure + return; + }, + void => void, + }; + platform_exec(cmd); +}; diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -0,0 +1,91 @@ +use fmt; +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. +// +// If 'name' does not contain a '/', the $PATH will be consulted to find the +// correct executable. If path resolution fails, nocmd is returned. +// +// let cmd = exec::cmd("echo", "hello world"); +// let proc = exec::start(&cmd); +// let status = exec::wait(&proc); +// assert(exec::status(status) == 0); +// +// 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 = + if (strings::contains(name, '/')) match (open(name)) { + p: platform => p, + err: os_error => return nocmd, + } else match (lookup(name)) { + p: platform => p, + void => return nocmd, + }, + argv = alloc([]str, [], len(args) + 1z), + ... + }; + append(cmd.argv, name); + for (let i = 0z; i < len(args); i += 1z) { + append(cmd.argv, args[i]); + }; + return cmd; +}; + +// Frees state associated with a command. +export fn finish(cmd: *command) void = { + platform_finish(cmd); + free(cmd.argv); +}; + +// Executes a prepared command in the current address space, overwriting the +// running process with the new command. +export fn exec(cmd: *command) os_error = platform_exec(cmd); + +// Starts a prepared command in a new process. +// +// TODO: Return a handle which gives information about the new process. +export fn start(cmd: *command) (os_error | void) = platform_start(cmd); + +fn lookup(name: str) (platform | void) = { + const path = match (os::getenv("PATH")) { + void => return void, + s: str => s, + }; + let tok = strings::tokenize(path, ":"); + for (true) { + const item = match (strings::next_token(&tok)) { + void => break, + s: str => s, + }; + let path = strings::concat(item, "/", name); + defer free(path); + match (open(path)) { + err: os_error => continue, + p: platform => return p, + }; + }; +}; diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha @@ -37,6 +37,28 @@ export fn close(fd: int) (void | errno) = { }; }; +export fn execveat(dirfd: int, path: *const char, argv: *[*]nullable *const char, + envp: *[*]nullable *const char, flags: int) errno = { + return match (wrap_return(syscall5(SYS_execveat, dirfd: u64, + path: uintptr: u64, argv: uintptr: u64, + envp: uintptr: u64, flags: u64))) { + err: errno => err, + u64 => abort("unreachable"), + }; +}; + +// Returns the new PID to the parent, void to the child, or errno if something +// goes wrong. +export fn fork() (int | void | errno) = { + return match (wrap_return(syscall0(SYS_fork))) { + u: u64 => switch (u) { + 0u64 => void, + * => u: int, + }, + err: errno => err, + }; +}; + export fn getpid() int = syscall0(SYS_getpid): int; export fn sendfile( diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha @@ -30,6 +30,10 @@ export def AT_STATX_FORCE_SYNC: int = 0x2000; export def AT_STATX_DONT_SYNC: int = 0x4000; export def AT_RECURSIVE: int = 0x8000; +export def O_RDONLY: int = 0; +export def O_WRONLY: int = 1; +export def O_RDWR: int = 2; + type statx_timestamp = struct { tv_sec: i64, tv_nsec: u32, diff --git a/strings/cstrings.ha b/strings/cstrings.ha @@ -1,6 +1,15 @@ use encoding::utf8; use types; +// A C-compatible empty string. Empty Hare strings have a null pointer instead +// of containing only '\0', so a special string is needed for this case. +export let c_empty: *const char = null: *const char; + +@init fn init() void = { + static let buf: [1]u8 = [0u8]; + c_empty = &buf: *[*]u8: *const char; +}; + // Computes the length of a NUL-terminated C string, in octets, in O(n). The // computed length does not include the NUL terminator. export fn c_strlen(cstr: *const char) size = {