hare

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

exec+linux.ha (4963B)


      1 // License: MPL-2.0
      2 // (c) 2021 Drew DeVault <sir@cmpwn.com>
      3 // (c) 2021 Eyal Sawady <ecs@d2evs.net>
      4 use errors;
      5 use io;
      6 use os;
      7 use rt;
      8 use strings;
      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, pipe.1, os::stdout_file);
     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 export fn pipe() (io::file, io::file) = {
     47 	return unix::pipe()!;
     48 };
     49 
     50 fn open(path: str) (platform_cmd | error) = {
     51 	match (rt::access(path, rt::X_OK)) {
     52 	case let err: rt::errno =>
     53 		return errors::errno(err);
     54 	case let b: bool =>
     55 		if (!b) {
     56 			return errors::noaccess;
     57 		};
     58 	};
     59 	// O_PATH is used because it allows us to use an executable for which we
     60 	// have execute permissions, but not read permissions.
     61 	match (rt::open(path, rt::O_PATH, 0u)) {
     62 	case let fd: int =>
     63 		return fd;
     64 	case let err: rt::errno =>
     65 		return errors::errno(err);
     66 	};
     67 };
     68 
     69 fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!;
     70 
     71 fn platform_exec(cmd: *command) error = {
     72 	// We don't worry about freeing the return values from strings::to_c
     73 	// because once we exec(2) our heap is fried anyway
     74 	let argv: []nullable *const char = alloc([], len(cmd.argv) + 1z);
     75 	for (let i = 0z; i < len(cmd.argv); i += 1z) {
     76 		append(argv, strings::to_c(cmd.argv[i]));
     77 	};
     78 	append(argv, null);
     79 
     80 	let envp: nullable *[*]nullable *const char = null;
     81 	if (len(cmd.env) != 0) {
     82 		let env: []nullable *const char = alloc([], len(cmd.env) + 1);
     83 		for (let i = 0z; i < len(cmd.env); i += 1) {
     84 			append(env, strings::to_c(cmd.env[i]));
     85 		};
     86 		append(env, null);
     87 		envp = env: *[*]nullable *const char;
     88 	};
     89 
     90 	let need_devnull = false;
     91 	for (let i = 0z; i < len(cmd.files); i += 1) {
     92 		const from = match (cmd.files[i].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 		cmd.files[i].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")!;
    112 	} else -1;
    113 
    114 	for (let i = 0z; i < len(cmd.files); i += 1) {
    115 		const from = match (cmd.files[i].0) {
    116 		case let file: io::file =>
    117 			yield file;
    118 		case nullfd =>
    119 			yield devnull;
    120 		case closefd =>
    121 			io::close(cmd.files[i].1)?;
    122 			continue;
    123 		};
    124 
    125 		if (cmd.files[i].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, cmd.files[i].1)) {
    135 			case int => void;
    136 			case let e: rt::errno =>
    137 				return errors::errno(e);
    138 			};
    139 		};
    140 	};
    141 
    142 	return errors::errno(rt::execveat(cmd.platform,
    143 		"\0", argv: *[*]nullable *const char,
    144 		envp: *[*]nullable *const char, rt::AT_EMPTY_PATH));
    145 };
    146 
    147 fn platform_start(cmd: *command) (process | errors::error) = {
    148 	// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
    149 	let pipe: [2]int = [0...];
    150 	match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
    151 	case let err: rt::errno =>
    152 		return errors::errno(err);
    153 	case void => void;
    154 	};
    155 
    156 	match (rt::clone(null, rt::SIGCHLD, null, null, 0)) {
    157 	case let err: rt::errno =>
    158 		return errors::errno(err);
    159 	case let pid: int =>
    160 		rt::close(pipe[1])!;
    161 		let errno: int = 0;
    162 		match (rt::read(pipe[0], &errno, size(int))) {
    163 		case let err: rt::errno =>
    164 			return errors::errno(err);
    165 		case let n: size =>
    166 			switch (n) {
    167 			case size(int) =>
    168 				return errors::errno(errno);
    169 			case 0 =>
    170 				return pid;
    171 			case =>
    172 				abort("Unexpected rt::read result");
    173 			};
    174 		};
    175 	case void =>
    176 		rt::close(pipe[0])!;
    177 		let err = platform_exec(cmd);
    178 		if (!(err is errors::opaque)) {
    179 			rt::exit(1);
    180 		};
    181 		let err = err as errors::opaque;
    182 		let err = &err.data: *rt::errno;
    183 		rt::write(pipe[1], &err, size(int))!;
    184 		rt::exit(1);
    185 	};
    186 };