hare

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

exec.ha (4772B)


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