diff --git a/ChildMonitor.h b/ChildMonitor.h new file mode 100644 index 0000000..fa4192c --- /dev/null +++ b/ChildMonitor.h @@ -0,0 +1,20 @@ +// +// ChildMonitor.h +// TwoTerm +// +// Created by Kelvin Sherlock on 1/31/2018. +// + +#import + +@class TermWindowController; +@interface ChildMonitor : NSObject { + +} + ++(id)monitor; + +-(void)removeController: (TermWindowController *)controller; +-(void)addController: (TermWindowController *)controller pid: (pid_t)pid fd: (int)fd; + +@end diff --git a/ChildMonitor.mm b/ChildMonitor.mm new file mode 100644 index 0000000..f18b3b9 --- /dev/null +++ b/ChildMonitor.mm @@ -0,0 +1,239 @@ +// +// 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)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 diff --git a/TermWindowController.h b/TermWindowController.h index 5cd554d..c2990e0 100644 --- a/TermWindowController.h +++ b/TermWindowController.h @@ -25,10 +25,6 @@ NSObject *_emulator; - NSThread * _thread; - int _fd; - std::atomic _pid; - } @property (nonatomic, retain) NSDictionary *parameters; @@ -39,5 +35,7 @@ @property (nonatomic, retain) NSObject *emulator; -(void)initPTY; +-(void)childFinished: (int)status; +-(void)processData: (const void *)buffer size: (size_t)size; @end diff --git a/TermWindowController.mm b/TermWindowController.mm index 70fe1af..e97eded 100644 --- a/TermWindowController.mm +++ b/TermWindowController.mm @@ -31,6 +31,8 @@ #include #include +#include "ChildMonitor.h" + @implementation TermWindowController @synthesize emulator = _emulator; @@ -46,12 +48,13 @@ -(void)dealloc { + [[ChildMonitor monitor] removeController: self]; + [_emulator release]; [_emulatorView release]; [_colorView release]; [_parameters release]; - [_thread release]; [super dealloc]; } @@ -66,6 +69,9 @@ -(void)initPTY { static std::string username; + + pid_t pid; + int fd; struct termios term; struct winsize ws = [_emulator defaultSize]; @@ -94,16 +100,16 @@ username = [NSUserName() UTF8String]; } //NSLog(@"%@ %s %s", NSUserName(), getlogin(), getpwent()->pw_name); - _pid = forkpty(&_fd, NULL, &term, &ws); + pid = forkpty(&fd, NULL, &term, &ws); - if (_pid < 0) + if (pid < 0) { fprintf(stderr, "forkpty failed\n"); fflush(stderr); return; } - if (_pid == 0) + if (pid == 0) { std::vector environ; @@ -175,122 +181,18 @@ [window setMinSize: [window frame].size]; - [_emulatorView setFd: _fd]; - [self monitor]; + [_emulatorView setFd: fd]; + + [[ChildMonitor monitor] addController: self pid: pid fd: fd]; } --(BOOL)read: (int)fd { - BOOL rv = NO; - - for(;;) { - - uint8_t buffer[1024]; - ssize_t size = read(fd, buffer, sizeof(buffer)); - if (size < 0 && errno == EINTR) continue; - - if (size <= 0) break; - [_emulatorView processData: buffer size: size]; - rv = YES; - } - - return rv; +-(void)childFinished: (int)status { + [_emulatorView childFinished: status]; } --(int)wait: (pid_t)pid { - - std::atomic_exchange(&_pid, -1); - - 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; - } - return status; -} - --(void)monitor { - - - int fd = _fd; - pid_t pid = _pid; - - int q = kqueue(); - - struct kevent events[2] = {}; - - EV_SET(&events[0], pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT | NOTE_EXITSTATUS, 0, NULL); - EV_SET(&events[1], fd, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL); - - int flags; - // non-blocking io. - if (fcntl(_fd, F_GETFL, &flags) < 0) flags = 0; - fcntl(_fd, F_SETFL, flags | O_NONBLOCK); - - kevent(q, events, 2, NULL, 0, NULL); - - [_emulatorView childBegan]; - - _thread = [[NSThread alloc] initWithBlock: ^(){ - - struct kevent events[2] = {}; - - bool stop = false; - int status = 0; - - while (!stop) { - - int n = kevent(q, NULL, 0, events, 2, NULL); - if (n <= 0) { - NSLog(@"kevent"); - break; - } - - for (unsigned i = 0; i < n; ++i) { - const auto &e = events[i]; - unsigned flags = e.flags; - if (e.filter == EVFILT_READ) { - int fd = (int)e.ident; - if (flags & EV_EOF) { - NSLog(@"EV_EOF"); - } - - if (flags & EV_ERROR) { - NSLog(@"EV_ERROR"); - } - - [self read: fd]; - continue; - } - - if (e.filter == EVFILT_PROC) { - - pid_t pid = (pid_t)e.ident; - NSLog(@"Child finished"); - status = [self wait: pid]; - stop = true; - } - } - } - - if (![_thread isCancelled]) { - - // read any lingering io... - [self read: fd]; - - [_emulatorView childFinished: status]; - } - close(q); - close(fd); - - _fd = -1; - //NSLog(@"Closing fd"); - }]; - - [_thread start]; +-(void)processData: (const void *)buffer size: (size_t)size { + [_emulatorView processData: (uint8_t *)buffer size: size]; } #pragma mark - @@ -359,14 +261,7 @@ -(void)windowWillClose:(NSNotification *)notification { - - pid_t pid = std::atomic_exchange(&_pid, -1); - [_thread cancel]; - - if (pid > 0) { - kill(pid, 9); - } - + [[ChildMonitor monitor] removeController: self]; [self autorelease]; }