1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>
use errors;
use fs;
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: rt::pid_t =>
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:
//
// const (read, write) = exec::pipe();
// exec::addfile(&cmd, os::stdout_file, write);
// let proc = exec::start(&cmd)!;
// io::close(write)!;
//
// let data = io::drain(read)!;
// io::close(read)!;
// exec::wait(&proc)!;
//
// To write to the standard input of a process:
//
// const (read, write) = exec::pipe();
// exec::addfile(&cmd, os::stdin_file, read);
// let proc = exec::start(&cmd)!;
// io::close(read)!;
//
// io::writeall(write, data)!;
// io::close(write)!;
// 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 =>
// not ideal, but better to do Something on old kernels rather
// than just breaking entirely
if (err != rt::ENOSYS) {
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_exec(cmd: *command) error = {
let need_devnull = false;
for (let file &.. cmd.files) {
const from = match (file.0) {
case let file: io::file =>
yield file;
case nullfd =>
need_devnull = true;
continue;
case closefd =>
continue;
};
file.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", fs::flag::RDWR)!;
} else -1;
for (let file .. cmd.files) {
const from = match (file.0) {
case let file: io::file =>
yield file;
case nullfd =>
yield devnull;
case closefd =>
io::close(file.1)?;
continue;
};
if (file.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, file.1)) {
case int => void;
case let e: rt::errno =>
return errors::errno(e);
};
};
};
if (cmd.dir != "") {
os::chdir(cmd.dir)?;
};
let envp: nullable *[*]nullable *const c::char = null;
if (len(cmd.env) > 1) {
envp = cmd.env: *[*]nullable *const c::char;
};
return errors::errno(rt::execveat(cmd.platform,
"\0", cmd.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);
};
};
|