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:
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,
+};