hare

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

commit 9acf605c7d172a4017cf5022b6a5d7f9232e2896
parent 04655ea7214c48b7e57ac03d96b44edc1ea192b7
Author: Eyal Sawady <ecs@d2evs.net>
Date:   Wed,  3 Nov 2021 16:55:48 +0000

os::exec: implement FD manipulation

Signed-off-by: Eyal Sawady <ecs@d2evs.net>

Diffstat:
Mos/exec/cmd.ha | 23+++++++++++++++++++++++
Mos/exec/exec+freebsd.ha | 20+++++++++++++++++++-
Mos/exec/exec+linux.ha | 20+++++++++++++++++++-
Mos/exec/types.ha | 2++
Mrt/+freebsd/syscalls.ha | 4++++
Mrt/+linux/syscalls.ha | 4++++
Mrt/+linux/types.ha | 1+
7 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -1,5 +1,6 @@ use ascii; use errors; +use io; use os; use strings; @@ -37,6 +38,7 @@ export fn cmd(name: str, args: str...) (command | error) = { }, argv = alloc([], len(args) + 1z), env = alloc([], len(env)), + files = [], ... }; append(cmd.argv, name, args...); @@ -114,6 +116,27 @@ export fn setenv(cmd: *command, key: str, value: str) void = { append(cmd.env, strings::concat(fullkey, value)); }; +// Maps an [[io::file]] into the command. All mappings are performed +// atomically, such that the following: +// +// exec::setfd(&cmd, os::stdout_file, os::stderr); +// exec::setfd(&cmd, os::stderr, os::stdout_file); +// +// will swap stdout and stderr, rather than duplicating stdout onto stderr. +// If the same [[io::file]] is mapped to multiple times, only the last mapping +// will take effect. +export fn addfile(cmd: *command, old: io::file, new: io::file) void = { + // TODO: Can we make old be an io::handle? + append(cmd.files, (old, new)); +}; + +// Mapp [[os::stdin]], [[os::stdout]], and [[os::stderr]] into the command. +export fn addstd(cmd: *command) void = { + addfile(cmd, os::stdin_file, os::stdin_file); + addfile(cmd, os::stdout_file, os::stdout_file); + addfile(cmd, os::stderr, os::stderr); +}; + fn lookup(name: str) (platform_cmd | void) = { const path = match (os::getenv("PATH")) { case void => diff --git a/os/exec/exec+freebsd.ha b/os/exec/exec+freebsd.ha @@ -1,9 +1,10 @@ use errors; +use io; use rt; use strings; use os; -export type platform_cmd = int; +export type platform_cmd = io::file; // Forks the current process, returning the pid of the child (to the parent) and // void (to the child), or an error. @@ -55,6 +56,23 @@ fn platform_exec(cmd: *command) error = { envp = env: *[*]nullable *const char; }; + for (let i = 0z; i < len(cmd.files); i += 1) { + cmd.files[i].0 = match (rt::fcntl(cmd.files[i].0, rt::F_DUPFD_CLOEXEC, 0)) { + case fd: int => + yield fd; + case err: rt::errno => + return errors::errno(err); + }; + }; + + for (let i = 0z; i < len(cmd.files); i += 1) { + match (rt::dup2(cmd.files[i].0, cmd.files[i].1)) { + case int => void; + case e: rt::errno => + return errors::errno(e); + }; + }; + return errors::errno(rt::fexecve(cmd.platform, argv: *[*]nullable *const char, envp)); }; diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha @@ -2,8 +2,9 @@ use errors; use rt; use strings; use os; +use io; -export type platform_cmd = int; +export type platform_cmd = io::file; // Forks the current process, returning the pid of the child (to the parent) and // void (to the child), or an error. @@ -56,6 +57,23 @@ fn platform_exec(cmd: *command) error = { envp = env: *[*]nullable *const char; }; + for (let i = 0z; i < len(cmd.files); i += 1) { + cmd.files[i].0 = match (rt::fcntl(cmd.files[i].0, rt::F_DUPFD_CLOEXEC, 0)) { + case fd: int => + yield fd; + case err: rt::errno => + return errors::errno(err); + }; + }; + + for (let i = 0z; i < len(cmd.files); i += 1) { + match (rt::dup2(cmd.files[i].0, cmd.files[i].1)) { + case int => void; + case e: rt::errno => + return errors::errno(e); + }; + }; + return errors::errno(rt::execveat(cmd.platform, strings::c_empty, argv: *[*]nullable *const char, envp, rt::AT_EMPTY_PATH)); }; diff --git a/os/exec/types.ha b/os/exec/types.ha @@ -1,10 +1,12 @@ use errors; +use io; // An executable command. export type command = struct { platform: platform_cmd, argv: []str, env: []str, + files: [](io::file, io::file), }; // Returned when path resolution fails to find a command by its name. diff --git a/rt/+freebsd/syscalls.ha b/rt/+freebsd/syscalls.ha @@ -470,3 +470,7 @@ export fn sysctlbyname(name: str, oldp: nullable *void, oldlenp: nullable *size, newp: uintptr: u64, newlen: u64))?; return; }; + +export fn dup2(oldfd: int, newfd: int) (int | errno) = { + return wrap_return(syscall2(SYS_dup2, oldfd: u64, newfd: u64))?: int; +}; diff --git a/rt/+linux/syscalls.ha b/rt/+linux/syscalls.ha @@ -159,6 +159,10 @@ export fn dup(fd: int) (int | errno) = { return wrap_return(syscall1(SYS_dup, fd: u64))?: int; }; +export fn dup2(oldfd: int, newfd: int) (int | errno) = { + return wrap_return(syscall2(SYS_dup2, oldfd: u64, newfd: u64))?: int; +}; + export fn close(fd: int) (void | errno) = { wrap_return(syscall1(SYS_close, fd: u64))?; return; diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha @@ -215,6 +215,7 @@ export def W_OK: int = 2; export def X_OK: int = 1; export def F_DUPFD: int = 0; +export def F_DUPFD_CLOEXEC: int = 1030; export def F_GETFD: int = 1; export def F_SETFD: int = 2; export def F_GETFL: int = 3;