From 1d5a01f530d91f0fd101c9a40e41dda0c14c3db5 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Thu, 27 Feb 2025 16:19:47 +0100 Subject: [PATCH] Add ev::exec, ev::wait, ev::kill Signed-of-by: Drew DeVault --- cmd/child/main.ha | 40 ++++++++++++++++++++++++++++++++++++++++ ev/+linux/file.ha | 3 +++ ev/+linux/loop.ha | 2 ++ ev/+linux/process.ha | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/cmd/child/main.ha b/cmd/child/main.ha new file mode 100644 index 0000000000000000000000000000000000000000..ec9582a20ab449b3835d4e6b69ca2b839c8e5e28 --- /dev/null +++ b/cmd/child/main.ha @@ -0,0 +1,40 @@ +use errors; +use ev; +use fmt; +use os; +use os::exec; +use time; + +export fn main() void = { + const loop = ev::newloop()!; + defer ev::finish(&loop); + + const cmd = exec::cmd(os::args[1], os::args[2..]...)!; + const child = ev::exec(&loop, &cmd)!; + ev::wait(child, &child_exited); + + const timer = ev::newtimer(&loop, &expired, time::clock::MONOTONIC)!; + ev::timer_configure(timer, 5 * time::SECOND, 0); + ev::setuser(timer, child); + + for (ev::dispatch(&loop, -1)!) void; +}; + +fn expired(timer: *ev::file) void = { + fmt::println("Child timed out, sending SIGTERM")!; + const child = ev::getuser(timer): *ev::file; + ev::kill(child)!; +}; + +fn child_exited(child: *ev::file, r: (exec::status | errors::error)) void = { + match (r) { + case let st: exec::status => + const exit = exec::exit(&st); + fmt::printfln("child exited: {}", exec::exitstr(exit))!; + case let err: errors::error => + fmt::printfln("ev::wait: {}", errors::strerror(err))!; + }; + + const loop = ev::getloop(child); + ev::stop(loop); +}; diff --git a/ev/+linux/file.ha b/ev/+linux/file.ha index 084452c06f8099ed038952d42c4063a9ded92636..9e8f83baad4657eb116af0cef7c6954209ab63a1 100644 --- a/ev/+linux/file.ha +++ b/ev/+linux/file.ha @@ -21,6 +21,7 @@ SENDTO = 8 << 16, RECVFROM = 9 << 16, SEND = 10 << 16, RECV = 11 << 16, + WAIT = 12 << 16, }; export type fflags = enum uint { @@ -197,6 +198,8 @@ events |= rt::EPOLLIN; case op::SEND, op::SENDTO => events |= rt::EPOLLOUT; case op::RECV, op::RECVFROM => + events |= rt::EPOLLIN; + case op::WAIT => events |= rt::EPOLLIN; case => yield; diff --git a/ev/+linux/loop.ha b/ev/+linux/loop.ha index 4127ac84b222350aa69d483ca964cc0b299f5b23..732f55c73b32a076fef8b2db139480c176794dff 100644 --- a/ev/+linux/loop.ha +++ b/ev/+linux/loop.ha @@ -196,6 +196,8 @@ case op::SEND => send_ready(file, ev); case op::RECV => recv_ready(file, ev); + case op::WAIT => + wait_ready(file, ev); case => assert(pending & ~(op::READV | op::WRITEV) == 0); }; diff --git a/ev/+linux/process.ha b/ev/+linux/process.ha new file mode 100644 index 0000000000000000000000000000000000000000..2a950d2500ae40c2987cdc8cc8cc638e683e1a95 --- /dev/null +++ b/ev/+linux/process.ha @@ -0,0 +1,115 @@ +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 = 0, + ... + }; + + 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 | errors::error)) void; + +// 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); + proc.op |= op::WAIT; + proc.cb = cb; + file_epoll_ctl(proc); + return mkreq(&wait_cancel, 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 = { + assert(proc.op & op::WAIT != 0); + assert(proc.cb != null); + const cb = proc.cb: *waitcb; + proc.op &= ~op::WAIT; + file_epoll_ctl(proc); + + let si = rt::siginfo { ... }; + let ru = rt::rusage { ... }; + match (rt::waitid(rt::idtype::P_PIDFD, proc.fd: rt::id_t, &si, + rt::WEXITED | rt::WSTOPPED | rt::WCONTINUED, &ru)) { + case let err: rt::errno => + cb(proc, errors::errno(err)); + case void => void; + }; + + // 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); +}; + +// 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); + }; +}; -- 2.48.1