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:
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);
- };
-};