hare

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

exec.ha (5435B)


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