hare

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

exec.ha (5794B)


      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 
     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: rt::pid_t =>
     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 // 	const (read, write) = exec::pipe();
     38 // 	exec::addfile(&cmd, os::stdout_file, write);
     39 // 	let proc = exec::start(&cmd)!;
     40 // 	io::close(write)!;
     41 //
     42 // 	let data = io::drain(read)!;
     43 // 	io::close(read)!;
     44 // 	exec::wait(&proc)!;
     45 //
     46 // To write to the standard input of a process:
     47 //
     48 // 	const (read, write) = exec::pipe();
     49 // 	exec::addfile(&cmd, os::stdin_file, read);
     50 // 	let proc = exec::start(&cmd)!;
     51 // 	io::close(read)!;
     52 //
     53 // 	io::writeall(write, data)!;
     54 // 	io::close(write)!;
     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 	// O_PATH is used because it allows us to use an executable for which we
     62 	// have execute permissions, but not read permissions.
     63 	let fd = match (rt::open(path, rt::O_PATH, 0u)) {
     64 	case let fd: int =>
     65 		yield fd;
     66 	case let err: rt::errno =>
     67 		return errors::errno(err);
     68 	};
     69 	let success = false;
     70 	defer if (!success) rt::close(fd)!;
     71 	match (rt::faccessat(fd, "", rt::X_OK, rt::AT_EMPTY_PATH)) {
     72 	case let err: rt::errno =>
     73 		// not ideal, but better to do Something on old kernels rather
     74 		// than just breaking entirely
     75 		if (err != rt::ENOSYS) {
     76 			return errors::errno(err);
     77 		};
     78 	case let b: bool =>
     79 		if (!b) {
     80 			return errors::noaccess;
     81 		};
     82 	};
     83 	// Make sure we are not trying to execute anything weird. fstat()
     84 	// already dereferences symlinks, so if this is anything other than a
     85 	// regular file it cannot be executed.
     86 	let s = rt::st { ... };
     87 	match (rt::fstat(fd, &s)) {
     88 	case let err: rt::errno =>
     89 		return errors::errno(err);
     90 	case void =>
     91 		if (s.mode & rt::S_IFREG == 0) {
     92 			return errors::noaccess;
     93 		};
     94 	};
     95 	success = true;
     96 	return fd;
     97 };
     98 
     99 fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!;
    100 
    101 fn platform_exec(cmd: *command) error = {
    102 	// We don't worry about freeing the return values from c::fromstr
    103 	// because once we exec(2) our heap is fried anyway
    104 	let argv: []nullable *const c::char = alloc([], len(cmd.argv) + 1z)!;
    105 	for (let arg .. cmd.argv) {
    106 		append(argv, c::fromstr(arg))!;
    107 	};
    108 	append(argv, null)!;
    109 
    110 	let envp: nullable *[*]nullable *const c::char = null;
    111 	if (len(cmd.env) != 0) {
    112 		let env: []nullable *const c::char = alloc([], len(cmd.env) + 1)!;
    113 		for (let e .. cmd.env) {
    114 			append(env, c::fromstr(e))!;
    115 		};
    116 		append(env, null)!;
    117 		envp = env: *[*]nullable *const c::char;
    118 	};
    119 
    120 	let need_devnull = false;
    121 	for (let file &.. cmd.files) {
    122 		const from = match (file.0) {
    123 		case let file: io::file =>
    124 			yield file;
    125 		case nullfd =>
    126 			need_devnull = true;
    127 			continue;
    128 		case closefd =>
    129 			continue;
    130 		};
    131 
    132 		file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) {
    133 		case let fd: int =>
    134 			yield fd;
    135 		case let err: rt::errno =>
    136 			return errors::errno(err);
    137 		};
    138 	};
    139 
    140 	const devnull: io::file = if (need_devnull) {
    141 		yield os::open("/dev/null", fs::flag::RDWR)!;
    142 	} else -1;
    143 
    144 	for (let file .. cmd.files) {
    145 		const from = match (file.0) {
    146 		case let file: io::file =>
    147 			yield file;
    148 		case nullfd =>
    149 			yield devnull;
    150 		case closefd =>
    151 			io::close(file.1)?;
    152 			continue;
    153 		};
    154 
    155 		if (file.1 == from) {
    156 			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
    157 			case let flags: int =>
    158 				yield flags;
    159 			case let e: rt::errno =>
    160 				return errors::errno(e);
    161 			};
    162 			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
    163 		} else {
    164 			match (rt::dup2(from, file.1)) {
    165 			case int => void;
    166 			case let e: rt::errno =>
    167 				return errors::errno(e);
    168 			};
    169 		};
    170 	};
    171 
    172 	if (cmd.dir != "") {
    173 		os::chdir(cmd.dir)?;
    174 	};
    175 
    176 	return errors::errno(rt::execveat(cmd.platform,
    177 		"\0", argv: *[*]nullable *const u8,
    178 		envp: *[*]nullable *const u8, rt::AT_EMPTY_PATH));
    179 };
    180 
    181 fn platform_start(cmd: *command) (process | errors::error) = {
    182 	// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
    183 	let pipe: [2]int = [0...];
    184 	match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
    185 	case let err: rt::errno =>
    186 		return errors::errno(err);
    187 	case void => void;
    188 	};
    189 
    190 	match (rt::clone(null, rt::SIGCHLD, null, null, 0)) {
    191 	case let err: rt::errno =>
    192 		return errors::errno(err);
    193 	case let pid: int =>
    194 		rt::close(pipe[1])!;
    195 		defer rt::close(pipe[0])!;
    196 		let errno: int = 0;
    197 		match (rt::read(pipe[0], &errno, size(int))) {
    198 		case let err: rt::errno =>
    199 			return errors::errno(err);
    200 		case let n: size =>
    201 			switch (n) {
    202 			case size(int) =>
    203 				return errors::errno(errno);
    204 			case 0 =>
    205 				return pid;
    206 			case =>
    207 				abort("Unexpected rt::read result");
    208 			};
    209 		};
    210 	case void =>
    211 		rt::close(pipe[0])!;
    212 		let err = platform_exec(cmd);
    213 		if (!(err is errors::opaque_)) {
    214 			rt::exit(1);
    215 		};
    216 		let err = err as errors::opaque_;
    217 		let err = &err.data: *rt::errno;
    218 		rt::write(pipe[1], err, size(int))!;
    219 		rt::exit(1);
    220 	};
    221 };