Lindenii Project Forge
Login

hare-ev

Temporary fork of hare-ev for... reasons

Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.

/ev/+linux/process.ha (raw)

use errors;
use os::exec;
use rt;
use time;
use unix::signal::{sig, code};

// Starts a child process prepared with [[exec::cmd]] and returns a [[file]]
// that can be used to [[wait]] for it to exit.
export fn exec(
	loop: *loop,
	cmd: *exec::command,
) (*file | errors::error | nomem) = {
	let pidfd: int = 0;
	let args = rt::clone_args {
		flags = rt::CLONE_PIDFD,
		pidfd = &pidfd: uintptr: u64,
		exit_signal = sig::CHLD: u64,
		...
	};

	const pid = match (rt::clone3(&args)) {
	case let pid: rt::pid_t =>
		yield pid;
	case let errno: rt::errno =>
		return errors::errno(errno);
	};

	if (pid == 0) {
		exec::exec(cmd);
	};

	return register(loop, pidfd);
};

// A callback for a [[wait]] operation.
export type waitcb = fn(file: *file, result: exec::status) (void | nomem);

// Waits for a process to exit. After the callback is run, the [[file]] is
// closed and unregistered from the event loop and may not be used again.
export fn wait(proc: *file, cb: *waitcb) req = {
	assert(proc.op & op::WAIT == 0);

	// Attempt to waitid now to hit error cases early
	match (do_waitid(proc, cb, rt::WNOHANG)) {
	case let err: rt::errno =>
		assert(err == rt::EAGAIN,
			"ev::wait: unexpected error from waitid (not a pidfd?)");
	case void =>
		// Child has already exited. do_waitid ran the callback, so we
		// can return with no further fanfare
		return req { ... };
	};

	proc.op |= op::WAIT;
	proc.cb = cb;
	file_epoll_ctl(proc);
	return mkreq(&wait_cancel, proc);
};

// Calls [[rt::waitid]] with the given options. If successful, calls the
// callback and cleans up the file (and returns void). If unsuccessful, returns
// the error.
fn do_waitid(proc: *file, cb: *waitcb, options: int = 0) (void | rt::errno | nomem) = {
	def WAIT_OPTIONS = rt::WEXITED | rt::WSTOPPED | rt::WCONTINUED;

	let si = rt::siginfo { ... };
	let ru = rt::rusage { ... };
	match (rt::waitid(rt::idtype::P_PIDFD, proc.fd: rt::id_t, &si,
		WAIT_OPTIONS | options, &ru)) {
	case let err: rt::errno =>
		return err;
	case void =>
		if (si.si_pid == 0) {
			assert(options & rt::WNOHANG != 0);
			return rt::EAGAIN;
		};
	};

	// Convert the siginfo data into a wait(2)-style exit status
	let status = 0i;
	if (si.si_code == code::EXITED) {
		status = si.si_status << 8;
	} else {
		status = si.si_status;
	};

	let st = exec::status {
		status = status,
		...
	};
	rusage(&st, &ru);
	cb(proc, st)?;
	close(proc);
};

fn wait_cancel(req: *req) void = {
	const proc = req.user: *file;
	assert(proc.op & op::WAIT != 0);
	proc.op &= ~op::WAIT;
	file_epoll_ctl(proc);
};

fn wait_ready(proc: *file, ev: *rt::epoll_event) (void | nomem) = {
	assert(proc.op & op::WAIT != 0);
	assert(proc.cb != null);
	const cb = proc.cb: *waitcb;
	proc.op &= ~op::WAIT;
	file_epoll_ctl(proc);

	// waitid should not fail at this point
	for (true) {
		match (do_waitid(proc, cb)) {
		case void =>
			break;
		case let err: rt::errno =>
			// Interrupted by signal handler, go again
			assert(err == rt::EINTR);
		};
	};
};

// Copied from os::exec
fn rusage(st: *exec::status, ru: *rt::rusage) void = {
	st.rusage.utime = time::instant {
		sec = ru.ru_utime.tv_sec,
		nsec = ru.ru_utime.tv_usec * time::MICROSECOND: i64,
	};
	st.rusage.stime = time::instant {
		sec = ru.ru_stime.tv_sec,
		nsec = ru.ru_stime.tv_usec * time::MICROSECOND: i64,
	};
	st.rusage.maxrss = ru.ru_maxrss;
	st.rusage.minflt = ru.ru_minflt;
	st.rusage.majflt = ru.ru_majflt;
	st.rusage.inblock = ru.ru_inblock;
	st.rusage.oublock = ru.ru_oublock;
	st.rusage.nvcsw = ru.ru_nvcsw;
	st.rusage.nivcsw = ru.ru_nivcsw;
};

// Sends a signal to a process started with [[ev::exec]].
export fn kill(child: *file, sig: sig = sig::TERM) (void | errors::error) = {
	match (rt::pidfd_send_signal(child.fd, sig, null, 0)) {
	case void => void;
	case let err: rt::errno =>
		return errors::errno(err);
	};
};