commit 2b9c815ae133ec12c5c510d4b3afd6bf5696caf7
parent 86af24a87d2d211120cc65e35c029cf01734de87
Author: Drew DeVault <sir@cmpwn.com>
Date: Sun, 7 Feb 2021 14:59:27 -0500
os::exec: new module
Diffstat:
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 = {