hare

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

exec+linux.ha (5784B)


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