hare

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

commit 27d0f5dedfe576c06c97cca13227a076461ca913
parent 898c11b700c66a7af54e09d33fea1d82a058f49e
Author: Lorenz (xha) <me@xha.li>
Date:   Sat, 25 Nov 2023 15:18:00 +0100

os::exec: make cmdfile platform-specific

Signed-off-by: Lorenz (xha) <me@xha.li>

Diffstat:
Aos/exec/+freebsd/exec.ha | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/exec/+freebsd/platform_cmd.ha | 22++++++++++++++++++++++
Ros/exec/process+freebsd.ha -> os/exec/+freebsd/process.ha | 0
Aos/exec/+linux/exec.ha | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aos/exec/+linux/platform_cmd.ha | 22++++++++++++++++++++++
Ros/exec/process+linux.ha -> os/exec/+linux/process.ha | 0
Mos/exec/cmd.ha | 9++-------
Dos/exec/exec+freebsd.ha | 217-------------------------------------------------------------------------------
Dos/exec/exec+linux.ha | 218-------------------------------------------------------------------------------
9 files changed, 477 insertions(+), 442 deletions(-)

diff --git a/os/exec/+freebsd/exec.ha b/os/exec/+freebsd/exec.ha @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> + +use errors; +use io; +use os; +use rt; +use types::c; +use unix; + +// Forks the current process, returning the [[process]] of the child (to the +// parent) and void (to the child), or an error. +export fn fork() (process | void | error) = { + match (rt::fork()) { + case let err: rt::errno => + return errors::errno(err); + case let i: int => + return i: process; + case void => + return void; + }; +}; + +// Creates an anonymous pipe for use with [[addfile]]. Any data written to the +// second file may be read from the first file. The caller should close one or +// both of the file descriptors after they have transferred them to another +// process, and after they have finished using them themselves, if applicable. +// +// This function will abort the process if the system is unable to allocate the +// resources for a pipe. If you need to handle this error gracefully, you may +// call [[unix::pipe]] yourself, but this may reduce the portability of your +// software. +// +// To capture the standard output of a process: +// +// let pipe = exec::pipe(); +// exec::addfile(&cmd, pipe.1, os::stdout_file); +// let proc = exec::start(&cmd)!; +// io::close(pipe.1)!; +// +// let data = io::drain(pipe.0)!; +// io::close(pipe.0)!; +// exec::wait(&proc)!; +// +// To write to the standard input of a process: +// +// let pipe = exec::pipe(); +// exec::addfile(&cmd, os::stdin_file, pipe.0); +// let proc = exec::start(&cmd)!; +// +// io::writeall(data)!; +// io::close(pipe.1)!; +// io::close(pipe.0)!; +// exec::wait(&proc)!; +export fn pipe() (io::file, io::file) = { + return unix::pipe()!; +}; + +fn open(path: str) (platform_cmd | error) = { + let fd = match (rt::open(path, rt::O_RDONLY, 0u)) { + case let fd: int => + yield fd; + case let err: rt::errno => + return errors::errno(err); + }; + let success = false; + defer if (!success) rt::close(fd)!; + match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { + case let err: rt::errno => + return errors::errno(err); + case let b: bool => + if (!b) { + return errors::noaccess; + }; + }; + // Make sure we are not trying to execute anything weird. fstat() + // already dereferences symlinks, so if this is anything other than a + // regular file it cannot be executed. + let s = rt::st { ... }; + match (rt::fstat(fd, &s)) { + case let err: rt::errno => + return errors::errno(err); + case void => + if (s.mode & rt::S_IFREG == 0) { + return errors::noaccess; + }; + }; + success = true; + return fd; + +}; + +fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; + +fn platform_exec(cmd: *command) error = { + // We don't worry about freeing the return values from c::fromstr + // because once we exec(2) our heap is fried anyway + let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z); + for (let i = 0z; i < len(cmd.argv); i += 1z) { + append(argv, c::fromstr(cmd.argv[i])); + }; + append(argv, null); + + let envp: nullable *[*]nullable *const c::char = null; + if (len(cmd.env) != 0) { + let env: []nullable *const c::char = alloc([], len(cmd.env) + 1); + for (let i = 0z; i < len(cmd.env); i += 1) { + append(env, c::fromstr(cmd.env[i])); + }; + append(env, null); + envp = env: *[*]nullable *const c::char; + }; + + let need_devnull = false; + for (let i = 0z; i < len(cmd.files); i += 1) { + const from = match (cmd.files[i].0) { + case let file: io::file => + yield file; + case nullfd => + need_devnull = true; + continue; + case closefd => + continue; + }; + + cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { + case let fd: int => + yield fd; + case let err: rt::errno => + return errors::errno(err); + }; + }; + + const devnull: io::file = if (need_devnull) { + yield os::open("/dev/null")!; + } else -1; + + for (let i = 0z; i < len(cmd.files); i += 1) { + const from = match (cmd.files[i].0) { + case let file: io::file => + yield file; + case nullfd => + yield devnull; + case closefd => + io::close(cmd.files[i].1)?; + continue; + }; + + if (cmd.files[i].1 == from) { + let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { + case let flags: int => + yield flags; + case let e: rt::errno => + return errors::errno(e); + }; + rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; + } else { + match (rt::dup2(from, cmd.files[i].1)) { + case int => void; + case let e: rt::errno => + return errors::errno(e); + }; + }; + }; + + if (cmd.dir != "") { + os::chdir(cmd.dir)?; + }; + + return errors::errno(rt::fexecve(cmd.platform, + argv: *[*]nullable *const u8, + envp: *[*]nullable *const u8)); +}; + +fn platform_start(cmd: *command) (process | errors::error) = { + // 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)) { + case let err: rt::errno => + return errors::errno(err); + case void => void; + }; + + match (rt::fork()) { + case let err: rt::errno => + return errors::errno(err); + case let pid: int => + rt::close(pipe[1])!; + defer rt::close(pipe[0])!; + let errno: int = 0; + match (rt::read(pipe[0], &errno, size(int))) { + case let err: rt::errno => + return errors::errno(err); + case let n: size => + switch (n) { + case size(int) => + return errors::errno(errno); + case 0 => + return pid; + case => + abort("Unexpected rt::read result"); + }; + }; + case void => + rt::close(pipe[0])!; + let err = platform_exec(cmd); + if (!(err is errors::opaque_)) { + rt::exit(1); + }; + let err = err as errors::opaque_; + let err = &err.data: *rt::errno; + rt::write(pipe[1], err, size(int))!; + rt::exit(1); + }; +}; diff --git a/os/exec/+freebsd/platform_cmd.ha b/os/exec/+freebsd/platform_cmd.ha @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> +use io; +use os; +use strings; + +export type platform_cmd = io::file; + +// Same as [[cmd]] except that executable file is determined by [[io::file]]. +// This function is not portable. +export fn cmdfile(file: io::file, name: str, args: str...) command = { + let cmd = command { + platform = file, + argv = alloc([], len(args) + 1), + env = strings::dupall(os::getenvs()), + files = [], + dir = "", + }; + append(cmd.argv, name); + append(cmd.argv, args...); + return cmd; +}; diff --git a/os/exec/process+freebsd.ha b/os/exec/+freebsd/process.ha diff --git a/os/exec/+linux/exec.ha b/os/exec/+linux/exec.ha @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> + +use errors; +use io; +use os; +use rt; +use types::c; +use unix; + +// Forks the current process, returning the [[process]] of the child (to the +// parent) and void (to the child), or an error. +export fn fork() (process | void | error) = { + match (rt::fork()) { + case let err: rt::errno => + return errors::errno(err); + case let i: int => + return i: process; + case void => + return void; + }; +}; + +// Creates an anonymous pipe for use with [[addfile]]. Any data written to the +// second file may be read from the first file. The caller should close one or +// both of the file descriptors after they have transferred them to another +// process, and after they have finished using them themselves, if applicable. +// +// This function will abort the process if the system is unable to allocate the +// resources for a pipe. If you need to handle this error gracefully, you may +// call [[unix::pipe]] yourself, but this may reduce the portability of your +// software. +// +// To capture the standard output of a process: +// +// let pipe = exec::pipe(); +// exec::addfile(&cmd, os::stdout_file, pipe.1); +// let proc = exec::start(&cmd)!; +// io::close(pipe.1)!; +// +// let data = io::drain(pipe.0)!; +// io::close(pipe.0)!; +// exec::wait(&proc)!; +// +// To write to the standard input of a process: +// +// let pipe = exec::pipe(); +// exec::addfile(&cmd, os::stdin_file, pipe.0); +// let proc = exec::start(&cmd)!; +// +// io::writeall(data)!; +// io::close(pipe.1)!; +// io::close(pipe.0)!; +// exec::wait(&proc)!; +export fn pipe() (io::file, io::file) = { + return unix::pipe()!; +}; + +fn open(path: str) (platform_cmd | error) = { + // O_PATH is used because it allows us to use an executable for which we + // have execute permissions, but not read permissions. + let fd = match (rt::open(path, rt::O_PATH, 0u)) { + case let fd: int => + yield fd; + case let err: rt::errno => + return errors::errno(err); + }; + let success = false; + defer if (!success) rt::close(fd)!; + match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { + case let err: rt::errno => + return errors::errno(err); + case let b: bool => + if (!b) { + return errors::noaccess; + }; + }; + // Make sure we are not trying to execute anything weird. fstat() + // already dereferences symlinks, so if this is anything other than a + // regular file it cannot be executed. + let s = rt::st { ... }; + match (rt::fstat(fd, &s)) { + case let err: rt::errno => + return errors::errno(err); + case void => + if (s.mode & rt::S_IFREG == 0) { + return errors::noaccess; + }; + }; + success = true; + return fd; +}; + +fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; + +fn platform_exec(cmd: *command) error = { + // We don't worry about freeing the return values from c::fromstr + // because once we exec(2) our heap is fried anyway + let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z); + for (let i = 0z; i < len(cmd.argv); i += 1z) { + append(argv, c::fromstr(cmd.argv[i])); + }; + append(argv, null); + + let envp: nullable *[*]nullable *const c::char = null; + if (len(cmd.env) != 0) { + let env: []nullable *const c::char = alloc([], len(cmd.env) + 1); + for (let i = 0z; i < len(cmd.env); i += 1) { + append(env, c::fromstr(cmd.env[i])); + }; + append(env, null); + envp = env: *[*]nullable *const c::char; + }; + + let need_devnull = false; + for (let i = 0z; i < len(cmd.files); i += 1) { + const from = match (cmd.files[i].0) { + case let file: io::file => + yield file; + case nullfd => + need_devnull = true; + continue; + case closefd => + continue; + }; + + cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { + case let fd: int => + yield fd; + case let err: rt::errno => + return errors::errno(err); + }; + }; + + const devnull: io::file = if (need_devnull) { + yield os::open("/dev/null")!; + } else -1; + + for (let i = 0z; i < len(cmd.files); i += 1) { + const from = match (cmd.files[i].0) { + case let file: io::file => + yield file; + case nullfd => + yield devnull; + case closefd => + io::close(cmd.files[i].1)?; + continue; + }; + + if (cmd.files[i].1 == from) { + let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { + case let flags: int => + yield flags; + case let e: rt::errno => + return errors::errno(e); + }; + rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; + } else { + match (rt::dup2(from, cmd.files[i].1)) { + case int => void; + case let e: rt::errno => + return errors::errno(e); + }; + }; + }; + + if (cmd.dir != "") { + os::chdir(cmd.dir)?; + }; + + return errors::errno(rt::execveat(cmd.platform, + "\0", argv: *[*]nullable *const u8, + envp: *[*]nullable *const u8, rt::AT_EMPTY_PATH)); +}; + +fn platform_start(cmd: *command) (process | errors::error) = { + // 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)) { + case let err: rt::errno => + return errors::errno(err); + case void => void; + }; + + match (rt::clone(null, rt::SIGCHLD, null, null, 0)) { + case let err: rt::errno => + return errors::errno(err); + case let pid: int => + rt::close(pipe[1])!; + defer rt::close(pipe[0])!; + let errno: int = 0; + match (rt::read(pipe[0], &errno, size(int))) { + case let err: rt::errno => + return errors::errno(err); + case let n: size => + switch (n) { + case size(int) => + return errors::errno(errno); + case 0 => + return pid; + case => + abort("Unexpected rt::read result"); + }; + }; + case void => + rt::close(pipe[0])!; + let err = platform_exec(cmd); + if (!(err is errors::opaque_)) { + rt::exit(1); + }; + let err = err as errors::opaque_; + let err = &err.data: *rt::errno; + rt::write(pipe[1], err, size(int))!; + rt::exit(1); + }; +}; diff --git a/os/exec/+linux/platform_cmd.ha b/os/exec/+linux/platform_cmd.ha @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 +// (c) Hare authors <https://harelang.org> +use io; +use os; +use strings; + +export type platform_cmd = io::file; + +// Same as [[cmd]] except that executable file is determined by [[io::file]]. +// This function is not portable. +export fn cmdfile(file: io::file, name: str, args: str...) command = { + let cmd = command { + platform = file, + argv = alloc([], len(args) + 1), + env = strings::dupall(os::getenvs()), + files = [], + dir = "", + }; + append(cmd.argv, name); + append(cmd.argv, args...); + return cmd; +}; diff --git a/os/exec/process+linux.ha b/os/exec/+linux/process.ha diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -21,7 +21,7 @@ use strings; // // By default, the new command will inherit the current process's environment. export fn cmd(name: str, args: str...) (command | error) = { - let file = if (strings::contains(name, '/')) { + let platcmd = if (strings::contains(name, '/')) { yield match (open(name)) { case let p: platform_cmd => yield p; @@ -36,13 +36,8 @@ export fn cmd(name: str, args: str...) (command | error) = { yield p; }; }; - return cmdfile(file, name, args...); -}; - -// Same as [[cmd]] except that executable file is determined by [[io::file]]. -export fn cmdfile(file: io::file, name: str, args: str...) command = { let cmd = command { - platform = file, + platform = platcmd, argv = alloc([], len(args) + 1), env = strings::dupall(os::getenvs()), files = [], diff --git a/os/exec/exec+freebsd.ha b/os/exec/exec+freebsd.ha @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// (c) Hare authors <https://harelang.org> - -use errors; -use io; -use os; -use rt; -use types::c; -use unix; - -export type platform_cmd = io::file; - -// Forks the current process, returning the [[process]] of the child (to the -// parent) and void (to the child), or an error. -export fn fork() (process | void | error) = { - match (rt::fork()) { - case let err: rt::errno => - return errors::errno(err); - case let i: int => - return i: process; - case void => - return void; - }; -}; - -// Creates an anonymous pipe for use with [[addfile]]. Any data written to the -// second file may be read from the first file. The caller should close one or -// both of the file descriptors after they have transferred them to another -// process, and after they have finished using them themselves, if applicable. -// -// This function will abort the process if the system is unable to allocate the -// resources for a pipe. If you need to handle this error gracefully, you may -// call [[unix::pipe]] yourself, but this may reduce the portability of your -// software. -// -// To capture the standard output of a process: -// -// let pipe = exec::pipe(); -// exec::addfile(&cmd, pipe.1, os::stdout_file); -// let proc = exec::start(&cmd)!; -// io::close(pipe.1)!; -// -// let data = io::drain(pipe.0)!; -// io::close(pipe.0)!; -// exec::wait(&proc)!; -// -// To write to the standard input of a process: -// -// let pipe = exec::pipe(); -// exec::addfile(&cmd, os::stdin_file, pipe.0); -// let proc = exec::start(&cmd)!; -// -// io::writeall(data)!; -// io::close(pipe.1)!; -// io::close(pipe.0)!; -// exec::wait(&proc)!; -export fn pipe() (io::file, io::file) = { - return unix::pipe()!; -}; - -fn open(path: str) (platform_cmd | error) = { - let fd = match (rt::open(path, rt::O_RDONLY, 0u)) { - case let fd: int => - yield fd; - case let err: rt::errno => - return errors::errno(err); - }; - let success = false; - defer if (!success) rt::close(fd)!; - match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { - case let err: rt::errno => - return errors::errno(err); - case let b: bool => - if (!b) { - return errors::noaccess; - }; - }; - // Make sure we are not trying to execute anything weird. fstat() - // already dereferences symlinks, so if this is anything other than a - // regular file it cannot be executed. - let s = rt::st { ... }; - match (rt::fstat(fd, &s)) { - case let err: rt::errno => - return errors::errno(err); - case void => - if (s.mode & rt::S_IFREG == 0) { - return errors::noaccess; - }; - }; - success = true; - return fd; - -}; - -fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; - -fn platform_exec(cmd: *command) error = { - // We don't worry about freeing the return values from c::fromstr - // because once we exec(2) our heap is fried anyway - let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z); - for (let i = 0z; i < len(cmd.argv); i += 1z) { - append(argv, c::fromstr(cmd.argv[i])); - }; - append(argv, null); - - let envp: nullable *[*]nullable *const c::char = null; - if (len(cmd.env) != 0) { - let env: []nullable *const c::char = alloc([], len(cmd.env) + 1); - for (let i = 0z; i < len(cmd.env); i += 1) { - append(env, c::fromstr(cmd.env[i])); - }; - append(env, null); - envp = env: *[*]nullable *const c::char; - }; - - let need_devnull = false; - for (let i = 0z; i < len(cmd.files); i += 1) { - const from = match (cmd.files[i].0) { - case let file: io::file => - yield file; - case nullfd => - need_devnull = true; - continue; - case closefd => - continue; - }; - - cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { - case let fd: int => - yield fd; - case let err: rt::errno => - return errors::errno(err); - }; - }; - - const devnull: io::file = if (need_devnull) { - yield os::open("/dev/null")!; - } else -1; - - for (let i = 0z; i < len(cmd.files); i += 1) { - const from = match (cmd.files[i].0) { - case let file: io::file => - yield file; - case nullfd => - yield devnull; - case closefd => - io::close(cmd.files[i].1)?; - continue; - }; - - if (cmd.files[i].1 == from) { - let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { - case let flags: int => - yield flags; - case let e: rt::errno => - return errors::errno(e); - }; - rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; - } else { - match (rt::dup2(from, cmd.files[i].1)) { - case int => void; - case let e: rt::errno => - return errors::errno(e); - }; - }; - }; - - if (cmd.dir != "") { - os::chdir(cmd.dir)?; - }; - - return errors::errno(rt::fexecve(cmd.platform, - argv: *[*]nullable *const u8, - envp: *[*]nullable *const u8)); -}; - -fn platform_start(cmd: *command) (process | errors::error) = { - // 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)) { - case let err: rt::errno => - return errors::errno(err); - case void => void; - }; - - match (rt::fork()) { - case let err: rt::errno => - return errors::errno(err); - case let pid: int => - rt::close(pipe[1])!; - defer rt::close(pipe[0])!; - let errno: int = 0; - match (rt::read(pipe[0], &errno, size(int))) { - case let err: rt::errno => - return errors::errno(err); - case let n: size => - switch (n) { - case size(int) => - return errors::errno(errno); - case 0 => - return pid; - case => - abort("Unexpected rt::read result"); - }; - }; - case void => - rt::close(pipe[0])!; - let err = platform_exec(cmd); - if (!(err is errors::opaque_)) { - rt::exit(1); - }; - let err = err as errors::opaque_; - let err = &err.data: *rt::errno; - rt::write(pipe[1], err, size(int))!; - rt::exit(1); - }; -}; diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// (c) Hare authors <https://harelang.org> - -use errors; -use io; -use os; -use rt; -use types::c; -use unix; - -export type platform_cmd = io::file; - -// Forks the current process, returning the [[process]] of the child (to the -// parent) and void (to the child), or an error. -export fn fork() (process | void | error) = { - match (rt::fork()) { - case let err: rt::errno => - return errors::errno(err); - case let i: int => - return i: process; - case void => - return void; - }; -}; - -// Creates an anonymous pipe for use with [[addfile]]. Any data written to the -// second file may be read from the first file. The caller should close one or -// both of the file descriptors after they have transferred them to another -// process, and after they have finished using them themselves, if applicable. -// -// This function will abort the process if the system is unable to allocate the -// resources for a pipe. If you need to handle this error gracefully, you may -// call [[unix::pipe]] yourself, but this may reduce the portability of your -// software. -// -// To capture the standard output of a process: -// -// let pipe = exec::pipe(); -// exec::addfile(&cmd, os::stdout_file, pipe.1); -// let proc = exec::start(&cmd)!; -// io::close(pipe.1)!; -// -// let data = io::drain(pipe.0)!; -// io::close(pipe.0)!; -// exec::wait(&proc)!; -// -// To write to the standard input of a process: -// -// let pipe = exec::pipe(); -// exec::addfile(&cmd, os::stdin_file, pipe.0); -// let proc = exec::start(&cmd)!; -// -// io::writeall(data)!; -// io::close(pipe.1)!; -// io::close(pipe.0)!; -// exec::wait(&proc)!; -export fn pipe() (io::file, io::file) = { - return unix::pipe()!; -}; - -fn open(path: str) (platform_cmd | error) = { - // O_PATH is used because it allows us to use an executable for which we - // have execute permissions, but not read permissions. - let fd = match (rt::open(path, rt::O_PATH, 0u)) { - case let fd: int => - yield fd; - case let err: rt::errno => - return errors::errno(err); - }; - let success = false; - defer if (!success) rt::close(fd)!; - match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) { - case let err: rt::errno => - return errors::errno(err); - case let b: bool => - if (!b) { - return errors::noaccess; - }; - }; - // Make sure we are not trying to execute anything weird. fstat() - // already dereferences symlinks, so if this is anything other than a - // regular file it cannot be executed. - let s = rt::st { ... }; - match (rt::fstat(fd, &s)) { - case let err: rt::errno => - return errors::errno(err); - case void => - if (s.mode & rt::S_IFREG == 0) { - return errors::noaccess; - }; - }; - success = true; - return fd; -}; - -fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!; - -fn platform_exec(cmd: *command) error = { - // We don't worry about freeing the return values from c::fromstr - // because once we exec(2) our heap is fried anyway - let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z); - for (let i = 0z; i < len(cmd.argv); i += 1z) { - append(argv, c::fromstr(cmd.argv[i])); - }; - append(argv, null); - - let envp: nullable *[*]nullable *const c::char = null; - if (len(cmd.env) != 0) { - let env: []nullable *const c::char = alloc([], len(cmd.env) + 1); - for (let i = 0z; i < len(cmd.env); i += 1) { - append(env, c::fromstr(cmd.env[i])); - }; - append(env, null); - envp = env: *[*]nullable *const c::char; - }; - - let need_devnull = false; - for (let i = 0z; i < len(cmd.files); i += 1) { - const from = match (cmd.files[i].0) { - case let file: io::file => - yield file; - case nullfd => - need_devnull = true; - continue; - case closefd => - continue; - }; - - cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) { - case let fd: int => - yield fd; - case let err: rt::errno => - return errors::errno(err); - }; - }; - - const devnull: io::file = if (need_devnull) { - yield os::open("/dev/null")!; - } else -1; - - for (let i = 0z; i < len(cmd.files); i += 1) { - const from = match (cmd.files[i].0) { - case let file: io::file => - yield file; - case nullfd => - yield devnull; - case closefd => - io::close(cmd.files[i].1)?; - continue; - }; - - if (cmd.files[i].1 == from) { - let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) { - case let flags: int => - yield flags; - case let e: rt::errno => - return errors::errno(e); - }; - rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!; - } else { - match (rt::dup2(from, cmd.files[i].1)) { - case int => void; - case let e: rt::errno => - return errors::errno(e); - }; - }; - }; - - if (cmd.dir != "") { - os::chdir(cmd.dir)?; - }; - - return errors::errno(rt::execveat(cmd.platform, - "\0", argv: *[*]nullable *const u8, - envp: *[*]nullable *const u8, rt::AT_EMPTY_PATH)); -}; - -fn platform_start(cmd: *command) (process | errors::error) = { - // 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)) { - case let err: rt::errno => - return errors::errno(err); - case void => void; - }; - - match (rt::clone(null, rt::SIGCHLD, null, null, 0)) { - case let err: rt::errno => - return errors::errno(err); - case let pid: int => - rt::close(pipe[1])!; - defer rt::close(pipe[0])!; - let errno: int = 0; - match (rt::read(pipe[0], &errno, size(int))) { - case let err: rt::errno => - return errors::errno(err); - case let n: size => - switch (n) { - case size(int) => - return errors::errno(errno); - case 0 => - return pid; - case => - abort("Unexpected rt::read result"); - }; - }; - case void => - rt::close(pipe[0])!; - let err = platform_exec(cmd); - if (!(err is errors::opaque_)) { - rt::exit(1); - }; - let err = err as errors::opaque_; - let err = &err.data: *rt::errno; - rt::write(pipe[1], err, size(int))!; - rt::exit(1); - }; -};