// // ChildMonitor.m // TwoTerm // // Created by Kelvin Sherlock on 1/31/2018. // #import "ChildMonitor.h" #import "TermWindowController.h" #include "Lock.h" #include #include #include #include #include namespace { struct entry { pid_t pid; int fd; TermWindowController *controller; }; typedef std::vector entry_vector; entry_vector::iterator find_controller(entry_vector &table, TermWindowController *controller) { return std::find_if(table.begin(), table.end(), [=](const entry &e){ return e.controller == controller; }); } entry_vector::iterator find_pid(entry_vector &table, pid_t pid) { return std::find_if(table.begin(), table.end(), [=](const entry &e){ return e.pid == pid; }); } entry_vector::iterator find_fd(entry_vector &table, int fd) { return std::find_if(table.begin(), table.end(), [=](const entry &e){ return e.fd == fd; }); } /* return NO on EOF */ BOOL read(int fd, TermWindowController *controller) { size_t total = 0; for (;;) { uint8_t buffer[2048]; ssize_t ok = ::read(fd, buffer, sizeof(buffer)); if (ok == 0) return total > 0; if (ok < 1) { if (errno == EINTR) continue; if (errno == EAGAIN) return YES; return YES; } [controller processData: buffer size: ok]; if (ok < sizeof(buffer)) return YES; total += ok; } } } @interface ChildMonitor() { std::vector _table; int _kq; Lock _lock; } @end @implementation ChildMonitor +(id)monitor { static ChildMonitor *me = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ me = [ChildMonitor new]; [NSThread detachNewThreadSelector: @selector(run) toTarget: me withObject: nil]; }); return me; } -(id)init { if ((self = [super init])) { _kq = kqueue(); } return self; } -(void)dealloc { _lock.lock(); close(_kq); for (const auto &e : _table) { if (e.fd >= 0) close(e.fd); if (e.pid > 0) kill(e.pid, SIGHUP); if (e.controller) [e.controller release]; } _lock.unlock(); [super dealloc]; } -(void)removeAll { Locker l(_lock); for (auto &e : _table) { if (e.controller) { [e.controller release]; e.controller = nil; } if (e.pid > 0) { kill(e.pid, SIGHUP); e.pid = -1; } } } -(void)removeController: (TermWindowController *)controller { if (!controller) return; Locker l(_lock); auto iter = find_controller(_table, controller); if (iter != _table.end()) { [iter->controller release]; iter->controller = nil; if (iter->pid > 0) kill(iter->pid, SIGHUP); } } -(void)addController: (TermWindowController *)controller pid: (pid_t)pid fd: (int)fd { NSLog(@"Adding pid: %d fd: %d", pid, fd); int flags; // non-blocking io. if (fcntl(fd, F_GETFL, &flags) < 0) flags = 0; fcntl(fd, F_SETFL, flags | O_NONBLOCK); Locker l(_lock); _table.emplace_back(entry{pid, fd, [controller retain]}); struct kevent events[2] = {}; EV_SET(&events[0], fd, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL); EV_SET(&events[1], pid, EVFILT_PROC, EV_ADD | EV_ONESHOT | EV_RECEIPT, NOTE_EXIT | NOTE_EXITSTATUS, 0, NULL); kevent(_kq, events, 2, NULL, 0, NULL); } -(void) run { struct kevent events[16] = {}; for(;;) { @autoreleasepool { int n = kevent(_kq, NULL, 0, events, 2, NULL); if (n < 0) { NSLog(@"kevent: %s", strerror(errno)); continue; } if (n == 0) { continue; } Locker l(_lock); // should process twice, first for reading, second for dead children. std::for_each(events, events + n, [&](const struct kevent &e){ if (e.filter != EVFILT_READ) return; int fd = (int)e.ident; if (e.flags & EV_EOF) { NSLog(@"EV_EOF %d", fd); return; } if (e.flags & EV_ERROR) { NSLog(@"EV_ERROR %d", fd); return; } auto iter = find_fd(_table, fd); if (iter == _table.end() || iter->controller == nil) { NSLog(@"Closing fd %d (not found)", fd); close(fd); // should automatically remove itself from kevent iter->fd = -1; } else { BOOL ok = read(fd, iter->controller); if (!ok) { NSLog(@"Closing fd %d (eof)", fd); close(fd); // should automatically remove itself from kevent iter->fd = -1; } } }); std::for_each(events, events + n, [&](const struct kevent &e){ if (e.filter != EVFILT_PROC) return; pid_t pid = (pid_t)e.ident; int status = 0; for(;;) { int ok = waitpid(pid, &status, WNOHANG); if (ok >= 0) break; if (errno == EINTR) continue; NSLog(@"waitpid(%d): %s", pid, strerror(errno)); break; } auto iter = find_pid(_table, pid); if (iter == _table.end()) { } else { if (iter->fd >= 0) { // check for pending i/o ? if (iter->controller) read(iter->fd, iter->controller); NSLog(@"Closing fd %d (child exited)", iter->fd); close(iter->fd); iter->fd = -1; } [iter->controller childFinished: status]; [iter->controller release]; iter->controller = nil; *iter = std::move(_table.back()); _table.pop_back(); } NSLog(@"Child %d finished", pid); }); } } } @end