TwoTerm/ChildMonitor.mm

256 lines
6.6 KiB
Plaintext

//
// ChildMonitor.m
// TwoTerm
//
// Created by Kelvin Sherlock on 1/31/2018.
//
#import "ChildMonitor.h"
#import "TermWindowController.h"
#include "Lock.h"
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <vector>
#include <algorithm>
namespace {
struct entry {
pid_t pid;
int fd;
TermWindowController *controller;
};
typedef std::vector<entry> 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<entry> _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