hare

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

exec.ha (4820B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use errors;
      5 use fs;
      6 use io;
      7 use os;
      8 use rt;
      9 use types::c;
     10 use unix;
     11 use path;
     12 
     13 // Forks the current process, returning the [[process]] of the child (to the
     14 // parent) and void (to the child), or an error.
     15 export fn fork() (process | void | error) = {
     16 	match (rt::fork()) {
     17 	case let err: rt::errno  =>
     18 		return errors::errno(err);
     19 	case let i: int =>
     20 		return i: process;
     21 	case void =>
     22 		return void;
     23 	};
     24 };
     25 
     26 // Creates an anonymous pipe for use with [[addfile]]. Any data written to the
     27 // second file may be read from the first file. The caller should close one or
     28 // both of the file descriptors after they have transferred them to another
     29 // process, and after they have finished using them themselves, if applicable.
     30 //
     31 // This function will abort the process if the system is unable to allocate the
     32 // resources for a pipe. If you need to handle this error gracefully, you may
     33 // call [[unix::pipe]] yourself, but this may reduce the portability of your
     34 // software.
     35 //
     36 // To capture the standard output of a process:
     37 //
     38 // 	const (read, write) = exec::pipe();
     39 // 	exec::addfile(&cmd, os::stdout_file, write);
     40 // 	let proc = exec::start(&cmd)!;
     41 // 	io::close(write)!;
     42 //
     43 // 	let data = io::drain(read)!;
     44 // 	io::close(read)!;
     45 // 	exec::wait(&proc)!;
     46 //
     47 // To write to the standard input of a process:
     48 //
     49 // 	const (read, write) = exec::pipe();
     50 // 	exec::addfile(&cmd, os::stdin_file, read);
     51 // 	let proc = exec::start(&cmd)!;
     52 // 	io::close(read)!;
     53 //
     54 // 	io::writeall(write, data)!;
     55 // 	io::close(write)!;
     56 // 	exec::wait(&proc)!;
     57 export fn pipe() (io::file, io::file) = {
     58 	return unix::pipe()!;
     59 };
     60 
     61 fn open(path: str) (platform_cmd | error) = {
     62 	if (os::access(path, os::amode::X_OK)?) {
     63 		// Length was already checked by access()
     64 		return path::init(path)!;
     65 	};
     66 	return errors::noaccess;
     67 };
     68 
     69 fn platform_finish(cmd: *command) void = void;
     70 
     71 fn platform_exec(cmd: *command) error = {
     72 	// We don't worry about freeing the return values from c::fromstr
     73 	// because once we exec(2) our heap is fried anyway
     74 	let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z)!;
     75 	for (let arg .. cmd.argv) {
     76 		append(argv, c::fromstr(arg))!;
     77 	};
     78 	append(argv, null)!;
     79 
     80 	let envp: nullable *[*]nullable *const c::char = null;
     81 	if (len(cmd.env) != 0) {
     82 		let env: []nullable *const c::char = alloc([], len(cmd.env) + 1)!;
     83 		for (let e .. cmd.env) {
     84 			append(env, c::fromstr(e))!;
     85 		};
     86 		append(env, null)!;
     87 		envp = env: *[*]nullable *const c::char;
     88 	};
     89 
     90 	let need_devnull = false;
     91 	for (let file &.. cmd.files) {
     92 		const from = match (file.0) {
     93 		case let file: io::file =>
     94 			yield file;
     95 		case nullfd =>
     96 			need_devnull = true;
     97 			continue;
     98 		case closefd =>
     99 			continue;
    100 		};
    101 
    102 		file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) {
    103 		case let fd: int =>
    104 			yield fd;
    105 		case let err: rt::errno =>
    106 			return errors::errno(err);
    107 		};
    108 	};
    109 
    110 	const devnull: io::file = if (need_devnull) {
    111 		yield os::open("/dev/null", fs::flag::RDWR)!;
    112 	} else -1;
    113 
    114 	for (let file .. cmd.files) {
    115 		const from = match (file.0) {
    116 		case let file: io::file =>
    117 			yield file;
    118 		case nullfd =>
    119 			yield devnull;
    120 		case closefd =>
    121 			io::close(file.1)?;
    122 			continue;
    123 		};
    124 
    125 		if (file.1 == from) {
    126 			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
    127 			case let flags: int =>
    128 				yield flags;
    129 			case let e: rt::errno =>
    130 				return errors::errno(e);
    131 			};
    132 			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
    133 		} else {
    134 			match (rt::dup2(from, file.1)) {
    135 			case int => void;
    136 			case let e: rt::errno =>
    137 				return errors::errno(e);
    138 			};
    139 		};
    140 	};
    141 
    142 	if (cmd.dir != "") {
    143 		os::chdir(cmd.dir)?;
    144 	};
    145 
    146 	return errors::errno(rt::execve(path::string(&cmd.platform),
    147 		argv: *[*]nullable *const u8,
    148 		envp: *[*]nullable *const u8));
    149 };
    150 
    151 fn platform_start(cmd: *command) (process | errors::error) = {
    152 	// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
    153 	let pipe: [2]int = [0...];
    154 	match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
    155 	case let err: rt::errno =>
    156 		return errors::errno(err);
    157 	case void => void;
    158 	};
    159 
    160 	match (rt::fork()) {
    161 	case let err: rt::errno =>
    162 		return errors::errno(err);
    163 	case let pid: int =>
    164 		rt::close(pipe[1])!;
    165 		defer rt::close(pipe[0])!;
    166 		let errno: int = 0;
    167 		match (rt::read(pipe[0], &errno, size(int))) {
    168 		case let err: rt::errno =>
    169 			return errors::errno(err);
    170 		case let n: size =>
    171 			switch (n) {
    172 			case size(int) =>
    173 				return errors::errno(errno);
    174 			case 0 =>
    175 				return pid;
    176 			case =>
    177 				abort("Unexpected rt::read result");
    178 			};
    179 		};
    180 	case void =>
    181 		rt::close(pipe[0])!;
    182 		let err = platform_exec(cmd);
    183 		if (!(err is errors::opaque_)) {
    184 			rt::exit(1);
    185 		};
    186 		let err = err as errors::opaque_;
    187 		let err = &err.data: *rt::errno;
    188 		rt::write(pipe[1], err, size(int))!;
    189 		rt::exit(1);
    190 	};
    191 };