Skip to content
Snippets Groups Projects
Commit b70ccc4f authored by George Nachman's avatar George Nachman
Browse files

Make debug logging thread-safe. Add appendBytes:length:excludingCharacter: to...

Make debug logging thread-safe. Add appendBytes:length:excludingCharacter: to NSMutableData. Do low-level tmux parsing in new VT100TmuxParser class to simplify PTYSession’s threadedReadTask: method. Fix race condition where threadedReadTask: could run after dealloc was begun. Remove the not-so-useful kTmuxGatewayCommandHasEndGuardBug flag. Fix analyzer issues and some leaks.
parent fb8cdaea
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -12,8 +12,9 @@
 
#include <sys/time.h>
 
NSMutableString* gDebugLogStr = nil;
NSMutableString* gDebugLogStr2 = nil;
static NSMutableString* gDebugLogStr = nil;
static NSMutableString* gDebugLogStr2 = nil;
static NSRecursiveLock *gDebugLogLock = nil;
BOOL gDebugLogging = NO;
int gDebugLogFile = -1;
 
Loading
Loading
@@ -63,17 +64,21 @@ static void WriteDebugLogFooter() {
}
 
static void SwapDebugLog() {
[gDebugLogLock lock];
NSMutableString* temp;
temp = gDebugLogStr;
gDebugLogStr = gDebugLogStr2;
gDebugLogStr2 = temp;
[gDebugLogLock unlock];
}
 
static void FlushDebugLog() {
[gDebugLogLock lock];
NSData* data = [gDebugLogStr dataUsingEncoding:NSUTF8StringEncoding];
size_t written = write(gDebugLogFile, [data bytes], [data length]);
assert(written == [data length]);
[gDebugLogStr setString:@""];
[gDebugLogLock unlock];
}
 
int DebugLogImpl(const char *file, int line, const char *function, NSString* value)
Loading
Loading
@@ -82,6 +87,7 @@ int DebugLogImpl(const char *file, int line, const char *function, NSString* val
struct timeval tv;
gettimeofday(&tv, NULL);
 
[gDebugLogLock lock];
[gDebugLogStr appendFormat:@"%lld.%08lld %s:%d (%s): ", (long long)tv.tv_sec, (long long)tv.tv_usec, file, line, function];
[gDebugLogStr appendString:value];
[gDebugLogStr appendString:@"\n"];
Loading
Loading
@@ -89,6 +95,7 @@ int DebugLogImpl(const char *file, int line, const char *function, NSString* val
SwapDebugLog();
[gDebugLogStr2 setString:@""];
}
[gDebugLogLock unlock];
}
return 1;
}
Loading
Loading
@@ -98,12 +105,19 @@ void ToggleDebugLogging() {
NSRunAlertPanel(@"Debug Logging Enabled",
@"Writing to /tmp/debuglog.txt",
@"OK", nil, nil);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gDebugLogLock = [[NSRecursiveLock alloc] init];
});
[gDebugLogLock lock];
gDebugLogFile = open("/tmp/debuglog.txt", O_TRUNC | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
WriteDebugLogHeader();
gDebugLogStr = [[NSMutableString alloc] init];
gDebugLogStr2 = [[NSMutableString alloc] init];
gDebugLogging = !gDebugLogging;
[gDebugLogLock unlock];
} else {
[gDebugLogLock lock];
gDebugLogging = !gDebugLogging;
SwapDebugLog();
FlushDebugLog();
Loading
Loading
@@ -118,5 +132,6 @@ void ToggleDebugLogging() {
@"OK", nil, nil);
[gDebugLogStr release];
[gDebugLogStr2 release];
[gDebugLogLock unlock];
}
}
//
// NSMutableData+iTerm.h
// iTerm
//
// Created by George Nachman on 3/10/14.
//
//
#import <Foundation/Foundation.h>
@interface NSMutableData (iTerm)
- (void)appendBytes:(unsigned char *)bytes length:(int)length excludingCharacter:(char)exclude;
@end
//
// NSMutableData+iTerm.m
// iTerm
//
// Created by George Nachman on 3/10/14.
//
//
#import "NSMutableData+iTerm.h"
@implementation NSMutableData (iTerm)
- (void)appendBytes:(unsigned char *)bytes length:(int)length excludingCharacter:(char)exclude {
int i;
int lastIndex = 0;
for (i = 0; i < length; i++) {
if (bytes[i] == exclude) {
if (i > lastIndex) {
[self appendBytes:bytes + lastIndex length:i - lastIndex];
}
lastIndex = i + 1;
}
}
if (i > lastIndex) {
[self appendBytes:bytes + lastIndex length:i - lastIndex];
}
}
@end
Loading
Loading
@@ -297,11 +297,9 @@ typedef enum {
 
- (BOOL)shouldSendEscPrefixForModifier:(unsigned int)modmask;
 
// PTYTask
// Writing output.
- (void)writeTask:(NSData*)data;
- (void)writeTaskNoBroadcast:(NSData *)data;
- (void)readTask:(const char *)bytes length:(int)length;
- (void)brokenPipe;
 
// PTYTextView
- (BOOL)hasTextSendingKeyMappingForEvent:(NSEvent*)event;
Loading
Loading
Loading
Loading
@@ -192,7 +192,6 @@ typedef enum {
 
TmuxGateway *_tmuxGateway;
int _tmuxPane;
BOOL _tmuxLogging; // log to gateway client
BOOL _tmuxSecureLogging;
 
NSMutableArray *_eventQueue;
Loading
Loading
@@ -267,6 +266,7 @@ typedef enum {
- (void)dealloc
{
[self stopTailFind]; // This frees the substring in the tail find context, if needed.
_shell.delegate = nil;
dispatch_release(_executionSemaphore);
[_colorMap release];
[_triggerLine release];
Loading
Loading
@@ -918,7 +918,9 @@ typedef enum {
if (_exited) {
[self _maybeWarnAboutShortLivedSessions];
}
BOOL isClient = NO;
if (self.tmuxMode == TMUX_CLIENT) {
isClient = YES;
assert([_tab tmuxWindow] >= 0);
[_tmuxController deregisterWindow:[_tab tmuxWindow]
windowPane:_tmuxPane];
Loading
Loading
@@ -935,11 +937,20 @@ typedef enum {
// changed size
[_tmuxController fitLayoutToWindows];
}
// PTYTask will never call taskWasDeregistered since tmux clients are never registered in
// the first place. There can be calls queued in this queue from previous tmuxReadTask:
// calls, so queue up a fake call to taskWasDeregistered that will run after all of them,
// serving the same purpose.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self taskWasDeregistered];
});
} else if (self.tmuxMode == TMUX_GATEWAY) {
[_tmuxController detach];
[_tmuxGateway release];
_tmuxGateway = nil;
[_tmuxGateway release];
_tmuxGateway = nil;
}
_terminal.parser.tmuxParser = nil;
self.tmuxMode = TMUX_NONE;
[_tmuxController release];
_tmuxController = nil;
Loading
Loading
@@ -957,6 +968,7 @@ typedef enum {
 
_exited = YES;
[_shell stop];
[self retain]; // We must live until -taskWasDeregistered is called.
 
// final update of display
[self updateDisplay];
Loading
Loading
@@ -969,7 +981,6 @@ typedef enum {
_colorMap.delegate = nil;
_textview = nil;
 
[_shell setDelegate:nil];
_screen.delegate = nil;
[_screen setTerminal:nil];
_terminal.delegate = nil;
Loading
Loading
@@ -1049,8 +1060,8 @@ typedef enum {
if (unicode == 27) {
[self tmuxDetach];
} else if (unicode == 'L') {
_tmuxLogging = !_tmuxLogging;
[self printTmuxMessage:[NSString stringWithFormat:@"tmux logging %@", (_tmuxLogging ? @"on" : @"off")]];
_tmuxGateway.tmuxLogging = !_tmuxGateway.tmuxLogging;
[self printTmuxMessage:[NSString stringWithFormat:@"tmux logging %@", (_tmuxGateway.tmuxLogging ? @"on" : @"off")]];
} else if (unicode == 'C') {
NSAlert *alert = [NSAlert alertWithMessageText:@"Enter command to send tmux:"
defaultButton:@"Ok"
Loading
Loading
@@ -1099,82 +1110,51 @@ typedef enum {
[self writeTaskImpl:data];
}
 
- (void)taskWasDeregistered {
DLog(@"taskWasDeregistered");
// This is called on the background thread. After this is called, we won't get any more calls
// on the background thread and it is safe for us to be dealloc'ed.
[self release];
}
// This is run in PTYTask's thread. It parses the input here and then queues an async task to run
// in the main thread to execute the parsed tokens.
- (void)threadedReadTask:(char *)buffer length:(int)length {
// A lock isn't needed here.
// 1. If _exited becomes true through a race, it'll be caught later after some wasted parsing.
// 2. No guarantees are made about when mute coprocesses begin muting, though it would be nice
// if it were immediate.
// 3. tmuxMode can only become TMUX_GATEWAY from a dispatch_sync() call in this method. If it
// should become TMUX_NONE in the main thread, it's OK for this test to give the
// wrong result (parsing will happen in the main thread).
if (length == 0 ||
_exited ||
[_shell hasMuteCoprocess] ||
self.tmuxMode == TMUX_GATEWAY) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self readTask:buffer length:length];
});
return;
}
// Pass the input stream to the parser.
STOPWATCH_START(putStreamData);
[_terminal.parser putStreamData:buffer length:length];
STOPWATCH_LAP(putStreamData);
// Parse the input stream into an array of tokens.
STOPWATCH_START(parsing);
CVector vector;
CVectorCreate(&vector, 100);
[_terminal.parser addParsedTokensToVector:&vector];
STOPWATCH_LAP(parsing);
if ([CVectorLastObject(&vector) startsTmuxMode]) {
// Will become a tmux gateway when the last token is parsed. We don't want this method to be
// called again before that happens because future tokens shouldn't be parsed until we're
// no longer a tmux gateway. From now on, the main thread will do parsing.
dispatch_sync(dispatch_get_main_queue(), ^{
[self executeTokens:&vector bytesHandled:length];
// Handle whatever is left in the parser's buffer.
[self readTask:"" length:0];
});
} else {
// This limits the number of outstanding execution blocks to prevent the main thread from
// getting bogged down.
STOPWATCH_START(blocking);
dispatch_semaphore_wait(_executionSemaphore, DISPATCH_TIME_FOREVER);
STOPWATCH_LAP(blocking);
[self retain];
dispatch_retain(_executionSemaphore);
dispatch_async(dispatch_get_main_queue(), ^{
assert(self.tmuxMode != TMUX_GATEWAY);
 
if (![_shell hasMuteCoprocess]) {
[self executeTokens:&vector bytesHandled:length];
assert(self.tmuxMode != TMUX_GATEWAY);
} else {
int n = CVectorCount(&vector);
for (int i = 0; i < n; i++) {
[CVectorGetObject(&vector, i) recycleObject];
}
CVectorDestroy(&vector);
}
// Unblock the background thread; if it's ready, it can send the main thread more tokens
// now.
dispatch_semaphore_signal(_executionSemaphore);
dispatch_release(_executionSemaphore);
[self release];
});
if (CVectorCount(&vector) == 0) {
CVectorDestroy(&vector);
return;
}
// This limits the number of outstanding execution blocks to prevent the main thread from
// getting bogged down.
dispatch_semaphore_wait(_executionSemaphore, DISPATCH_TIME_FOREVER);
[self retain];
dispatch_retain(_executionSemaphore);
dispatch_async(dispatch_get_main_queue(), ^{
[self executeTokens:&vector bytesHandled:length];
// Unblock the background thread; if it's ready, it can send the main thread more tokens
// now.
dispatch_semaphore_signal(_executionSemaphore);
dispatch_release(_executionSemaphore);
[self release];
});
}
 
- (void)executeTokens:(const CVector *)vector bytesHandled:(int)length {
STOPWATCH_START(executing);
int n = CVectorCount(vector);
for (int i = 0; i < n; i++) {
if (_exited || !_terminal || [_shell hasMuteCoprocess] || self.tmuxMode == TMUX_GATEWAY) {
if (_exited || !_terminal || (self.tmuxMode != TMUX_GATEWAY && [_shell hasMuteCoprocess])) {
break;
}
Loading
Loading
@@ -1197,65 +1177,6 @@ typedef enum {
STOPWATCH_LAP(executing);
}
 
- (NSData *)handleTmuxGatewayInput:(NSData *)data {
if (_tmuxLogging) {
[self printTmuxCommandOutputToScreen:[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
}
return [_tmuxGateway readTask:data];
}
// This runs in the main thread and handles tmux gateway input properly.
- (void)readTask:(const char *)buffer length:(int)length
{
do {
if ((length == 0 && _terminal.parser.streamLength == 0) || _exited) {
return;
}
if ([_shell hasMuteCoprocess]) {
return;
}
if (gDebugLogging) {
DebugLog([NSString stringWithFormat:@"readTask called with %d bytes. The last byte is %d", (int)length, (int)buffer[length-1]]);
}
if (self.tmuxMode == TMUX_GATEWAY) {
NSData *data = [NSData dataWithBytes:buffer length:length];
data = [self handleTmuxGatewayInput:data];
if (!data) {
// Still a tmux gateway.
break;
}
assert(self.tmuxMode == TMUX_NONE);
buffer = data.bytes;
length = data.length;
}
[_terminal.parser putStreamData:buffer length:length];
NSMutableArray *tokens = [NSMutableArray arrayWithCapacity:100];
CVector vector;
CVectorCreate(&vector, 100);
[_terminal.parser addParsedTokensToVector:&vector];
int n = CVectorCount(&vector);
for (int i = 0; i < n; i++) {
VT100Token *token = CVectorGetObject(&vector, i);
if (!_exited) {
[_terminal executeToken:token];
}
[token recycleObject];
}
CVectorDestroy(&vector);
if (!_exited && self.tmuxMode == TMUX_GATEWAY) {
// The last token turned us into a tmux gateway. Re-run with the tail of the data.
NSData *gatewayInput = [[_terminal.parser.streamData copy] autorelease];
buffer = gatewayInput.bytes;
length = gatewayInput.length;
[_terminal.parser clearStream];
}
} while (!_exited && self.tmuxMode == TMUX_GATEWAY);
[self finishedHandlingNewOutputOfLength:length];
}
- (void)finishedHandlingNewOutputOfLength:(int)length {
gettimeofday(&_lastOutput, NULL);
_newOutput = YES;
Loading
Loading
@@ -3262,9 +3183,6 @@ static long long timeInTenthsOfSeconds(struct timeval t)
if ([[PreferencePanel sharedInstance] autoHideTmuxClientSession]) {
[self hideSession];
}
[_tmuxGateway readTask:_terminal.parser.streamData];
[_terminal.parser clearStream];
}
 
- (BOOL)isTmuxClient
Loading
Loading
@@ -3500,8 +3418,11 @@ static long long timeInTenthsOfSeconds(struct timeval t)
_tmuxController = nil;
[_screen appendStringAtCursor:@"Detached"];
[_screen crlf];
// There's a not-so-bad race condition here. It's possible that tmux would exit and a new
// session would start right away and we'd wack the wrong tmux parser. However, it would be
// very unusual for that to happen so quickly.
_terminal.parser.tmuxParser = nil;
self.tmuxMode = TMUX_NONE;
_tmuxLogging = NO;
 
if ([[PreferencePanel sharedInstance] autoHideTmuxClientSession] &&
[[[_tab realParentWindow] window] isMiniaturized]) {
Loading
Loading
@@ -3523,17 +3444,27 @@ static long long timeInTenthsOfSeconds(struct timeval t)
} else {
DLog(@"Write to tmux: \"%@\"", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
}
if (_tmuxLogging) {
if (_tmuxGateway.tmuxLogging) {
[self printTmuxMessage:[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]];
}
[self writeTaskImpl:data];
}
 
// This is called on the main thread.
- (void)tmuxReadTask:(NSData *)data
{
if (!_exited) {
[_shell logData:(const char *)[data bytes] length:[data length]];
[self readTask:(const char *)[data bytes] length:[data length]];
// Dispatch this in a background thread to keep threadedReadTask:
// simple. It always asynchronously dispatches to the main thread,
// which would deadlock if it were called on the main thread.
[data retain];
[self retain];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self threadedReadTask:(char *)[data bytes] length:[data length]];
[data release];
[self release];
});
}
}
 
Loading
Loading
@@ -4894,6 +4825,10 @@ static long long timeInTenthsOfSeconds(struct timeval t)
[self startTmuxMode];
}
 
- (void)screenHandleTmuxInput:(VT100Token *)token {
[_tmuxGateway executeToken:token];
}
- (void)screenModifiersDidChangeTo:(NSArray *)modifiers {
[self setSendModifiers:modifiers];
}
Loading
Loading
Loading
Loading
@@ -12,11 +12,13 @@ extern NSString *kCoprocessStatusChangeNotification;
// thread before kicking off a possibly async task in the main thread.
- (void)threadedReadTask:(char *)buffer length:(int)length;
- (void)brokenPipe;
- (void)taskWasDeregistered;
@end
 
@interface PTYTask : NSObject
 
@property(atomic, readonly) BOOL hasMuteCoprocess;
@property(atomic, assign) id<PTYTaskDelegate> delegate;
 
- (id)init;
- (void)dealloc;
Loading
Loading
@@ -31,8 +33,6 @@ extern NSString *kCoprocessStatusChangeNotification;
 
- (NSString*)currentJob:(BOOL)forceRefresh;
 
- (void)setDelegate:(id<PTYTaskDelegate>)object;
- (id<PTYTaskDelegate>)delegate;
- (void)writeTask:(NSData*)data;
 
- (void)sendSignal:(int)signo;
Loading
Loading
Loading
Loading
@@ -81,7 +81,6 @@ setup_tty_param(struct termios* term,
pid_t pid;
int fd;
int status;
id<PTYTaskDelegate> delegate;
NSString* tty;
NSString* path;
BOOL hasOutput;
Loading
Loading
@@ -94,7 +93,10 @@ setup_tty_param(struct termios* term,
 
Coprocess *coprocess_; // synchronized (self)
BOOL brokenPipe_;
NSString *command_; // Command that was run if launchWithPath:arguments:etc was called
NSString *command_; // Command that was run if launchWithPath:arguments:etc was called
// Number of spins of the select loop left before we tell the delegate we were deregistered.
int _spinsNeeded;
}
 
- (id)init
Loading
Loading
@@ -102,15 +104,7 @@ setup_tty_param(struct termios* term,
self = [super init];
if (self) {
pid = (pid_t)-1;
status = 0;
delegate = nil;
fd = -1;
tty = nil;
logPath = nil;
@synchronized(logHandle) {
logHandle = nil;
}
hasOutput = NO;
 
writeBuffer = [[NSMutableData alloc] init];
writeLock = [[NSLock alloc] init];
Loading
Loading
@@ -162,7 +156,7 @@ static void reapchild(int n)
 
- (NSString *)command
{
return command_;
return command_;
}
 
- (void)launchWithPath:(NSString*)progpath
Loading
Loading
@@ -350,16 +344,6 @@ static void reapchild(int n)
return hasOutput;
}
 
- (void)setDelegate:(id)object
{
delegate = object;
}
- (id)delegate
{
return delegate;
}
- (void)logData:(const char *)buffer length:(int)length {
@synchronized(logHandle) {
if ([self logging]) {
Loading
Loading
@@ -376,7 +360,7 @@ static void reapchild(int n)
 
// The delegate is responsible for parsing VT100 tokens here and sending them off to the
// main thread for execution. If its queues get too large, it can block.
[delegate threadedReadTask:buffer length:length];
[self.delegate threadedReadTask:buffer length:length];
 
@synchronized (self) {
if (coprocess_) {
Loading
Loading
@@ -399,12 +383,9 @@ static void reapchild(int n)
{
brokenPipe_ = YES;
[[TaskNotifier sharedInstance] deregisterTask:self];
if ([delegate respondsToSelector:@selector(brokenPipe)]) {
NSObject *delegateObj = delegate;
[delegateObj performSelectorOnMainThread:@selector(brokenPipe)
withObject:nil
waitUntilDone:YES];
}
[(NSObject *)self.delegate performSelectorOnMainThread:@selector(brokenPipe)
withObject:nil
waitUntilDone:YES];
}
 
- (void)sendSignal:(int)signo
Loading
Loading
@@ -448,12 +429,43 @@ static void reapchild(int n)
 
if (fd >= 0) {
close(fd);
[[TaskNotifier sharedInstance] deregisterTask:self];
// Require that it spin twice so we can be completely sure that the task won't get called
// again. If we add the observer just before select() was going to be called, it wouldn't
// mean anything; but after the second call, we know we've been moved into the dead pool.
@synchronized(self) {
_spinsNeeded = 2;
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notifierDidSpin)
name:kTaskNotifierDidSpin
object:nil];
// Force a spin
[[TaskNotifier sharedInstance] unblock];
// This isn't an atomic update, but select() should be resilient to
// being passed a half-broken fd. We must change it because after this
// function returns, a new task may be created with this fd and then
// the select thread wouldn't know which task a fd belongs to.
fd = -1;
}
}
// This runs in TaskNotifier's thread.
- (void)notifierDidSpin
{
BOOL unblock = NO;
@synchronized(self) {
unblock = (--_spinsNeeded) > 0;
}
if (unblock) {
// Force select() to return so we get another spin even if there is no
// activity on the file descriptors.
[[TaskNotifier sharedInstance] unblock];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self.delegate taskWasDeregistered];
}
// This isn't an atomic update, but select() should be resilient to
// being passed a half-broken fd. We must change it because after this
// function returns, a new task may be created with this fd and then
// the select thread wouldn't know which task a fd belongs to.
fd = -1;
}
 
- (int)status
Loading
Loading
Loading
Loading
@@ -8757,7 +8757,8 @@ static double EuclideanDistance(NSPoint p1, NSPoint p2) {
// Copy selection position to detect change in selected chars next call.
[_oldSelection release];
_oldSelection = [_selection copy];
_oldSelection.delegate = nil;
// Redraw lines with dirty characters
int lineStart = [_dataSource numberOfLines] - [_dataSource height];
int lineEnd = [_dataSource numberOfLines];
Loading
Loading
Loading
Loading
@@ -2,6 +2,9 @@
 
#import <Foundation/Foundation.h>
 
// Posted just before select() is called.
extern NSString *const kTaskNotifierDidSpin;
@class PTYTask;
 
@interface TaskNotifier : NSObject
Loading
Loading
Loading
Loading
@@ -13,6 +13,8 @@
 
#define PtyTaskDebugLog(args...)
 
NSString *const kTaskNotifierDidSpin = @"kTaskNotifierDidSpin";
@implementation TaskNotifier
{
NSMutableArray* tasks;
Loading
Loading
@@ -85,7 +87,7 @@
[self unblock];
}
 
- (void)deregisterTask:(PTYTask*)task
- (void)deregisterTask:(PTYTask *)task
{
PtyTaskDebugLog(@"deregisterTask: lock\n");
[tasksLock lock];
Loading
Loading
@@ -157,6 +159,9 @@
// waitpid() on pids that we think are dead or will be dead soon.
NSMutableSet* newDeadpool = [NSMutableSet setWithCapacity:[deadpool count]];
for (NSNumber* pid in deadpool) {
if ([pid intValue] < 0) {
continue;
}
int statLoc;
PtyTaskDebugLog(@"wait on %d", [pid intValue]);
if (waitpid([pid intValue], &statLoc, WNOHANG) < 0) {
Loading
Loading
@@ -228,7 +233,9 @@
}
PtyTaskDebugLog(@"run1: unlock");
[tasksLock unlock];
[[NSNotificationCenter defaultCenter] postNotificationName:kTaskNotifierDidSpin object:nil];
// Poll...
if (select(highfd+1, &rfds, &wfds, &efds, NULL) <= 0) {
switch(errno) {
Loading
Loading
Loading
Loading
@@ -564,14 +564,11 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
 
- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName
{
// see the notes in TmuxGateway.h about kTmuxGatewayCommandHasEndGuardBug.
// I submitted a patch to tmux on 4/6/13, but it's not clear how long the
// workaround should stick around.
[gateway_ sendCommand:[NSString stringWithFormat:@"unlink-window -k -t @%d", windowId]
responseTarget:nil
responseSelector:nil
responseObject:nil
flags:kTmuxGatewayCommandHasEndGuardBug];
responseObject:nil
flags:0];
}
 
- (void)renameWindowWithId:(int)windowId inSession:(NSString *)sessionName toName:(NSString *)newName
Loading
Loading
@@ -587,7 +584,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
responseTarget:nil
responseSelector:nil
responseObject:nil
flags:kTmuxGatewayCommandHasEndGuardBug];
flags:0];
}
 
- (void)breakOutWindowPane:(int)windowPane toPoint:(NSPoint)screenPoint
Loading
Loading
Loading
Loading
@@ -14,11 +14,9 @@ extern const int kTmuxGatewayCommandShouldTolerateErrors;
// Send NSData, not NSString, for output (allowing busted/partial utf-8
// sequences).
extern const int kTmuxGatewayCommandWantsData;
// Bug in tmux 1.8. %end guard not printed, so watch for %error in command
// output.
extern const int kTmuxGatewayCommandHasEndGuardBug;
 
@class TmuxController;
@class VT100Token;
 
extern NSString * const kTmuxGatewayErrorDomain;
 
Loading
Loading
@@ -52,15 +50,8 @@ typedef enum {
CONTROL_COMMAND_NOOP
} ControlCommand;
 
typedef enum {
CONTROL_STATE_READY,
CONTROL_STATE_DETACHED,
} ControlState;
@interface TmuxGateway : NSObject {
NSObject<TmuxGatewayDelegate> *delegate_; // weak
ControlState state_;
NSMutableData *stream_;
 
// Data from parsing an incoming command
ControlCommand command_;
Loading
Loading
@@ -75,10 +66,15 @@ typedef enum {
NSMutableString *strayMessages_;
}
 
// Should all protocol-level input be logged to the gateway's session?
@property(nonatomic, assign) BOOL tmuxLogging;
- (id)initWithDelegate:(NSObject<TmuxGatewayDelegate> *)delegate;
 
// Returns any unconsumed data if tmux mode is exited.
- (NSData *)readTask:(NSData *)data;
// The token must be TMUX_xxx.
- (void)executeToken:(VT100Token *)token;
- (void)sendCommand:(NSString *)command
responseTarget:(id)target
responseSelector:(SEL)selector;
Loading
Loading
Loading
Loading
@@ -10,11 +10,11 @@
#import "TmuxController.h"
#import "iTermApplicationDelegate.h"
#import "NSStringITerm.h"
#import "VT100Token.h"
 
NSString * const kTmuxGatewayErrorDomain = @"kTmuxGatewayErrorDomain";;
const int kTmuxGatewayCommandShouldTolerateErrors = (1 << 0);
const int kTmuxGatewayCommandWantsData = (1 << 1);
const int kTmuxGatewayCommandHasEndGuardBug = (1 << 2);
 
#define NEWLINE @"\r"
 
Loading
Loading
@@ -22,12 +22,7 @@ const int kTmuxGatewayCommandHasEndGuardBug = (1 << 2);
#ifdef TMUX_VERBOSE_LOGGING
#define TmuxLog NSLog
#else
#define TmuxLog(args...) \
do { \
if (gDebugLogging) { \
DebugLog([NSString stringWithFormat:args]); \
} \
} while (0)
#define TmuxLog DLog
#endif
 
static NSString *kCommandTarget = @"target";
Loading
Loading
@@ -40,16 +35,18 @@ static NSString *kCommandId = @"id";
static NSString *kCommandIsInList = @"inList";
static NSString *kCommandIsLastInList = @"lastInList";
 
@implementation TmuxGateway
@implementation TmuxGateway {
// Set to YES when the remote host closed the connection. We won't send commands when this is
// set.
BOOL disconnected_;
}
 
- (id)initWithDelegate:(NSObject<TmuxGatewayDelegate> *)delegate
{
self = [super init];
if (self) {
delegate_ = delegate;
state_ = CONTROL_STATE_READY;
commandQueue_ = [[NSMutableArray alloc] init];
stream_ = [[NSMutableData alloc] init];
strayMessages_ = [[NSMutableString alloc] init];
}
return self;
Loading
Loading
@@ -58,7 +55,6 @@ static NSString *kCommandIsLastInList = @"lastInList";
- (void)dealloc
{
[commandQueue_ release];
[stream_ release];
[currentCommand_ release];
[currentCommandResponse_ release];
[currentCommandData_ release];
Loading
Loading
@@ -83,7 +79,6 @@ static NSString *kCommandIsLastInList = @"lastInList";
informativeTextWithFormat:@"%@", message] runModal];
[self detach];
[delegate_ tmuxHostDisconnected]; // Force the client to quit
[stream_ replaceBytesInRange:NSMakeRange(0, stream_.length) withBytes:"" length:0];
}
 
- (NSData *)decodeEscapedOutput:(const char *)bytes
Loading
Loading
@@ -153,8 +148,7 @@ static NSString *kCommandIsLastInList = @"lastInList";
NSData *decodedData = [self decodeEscapedOutput:space + 1];
 
TmuxLog(@"Run tmux command: \"%%output %%%d %.*s", windowPane, (int)[decodedData length], [decodedData bytes]);
[[[delegate_ tmuxController] sessionForWindowPane:windowPane] tmuxReadTask:decodedData];
state_ = CONTROL_STATE_READY;
[[[delegate_ tmuxController] sessionForWindowPane:windowPane] tmuxReadTask:decodedData];
 
return;
error:
Loading
Loading
@@ -174,7 +168,6 @@ error:
NSString *layout = [components objectAtIndex:2];
[delegate_ tmuxUpdateLayoutForWindow:window
layout:layout];
state_ = CONTROL_STATE_READY;
}
 
- (void)broadcastWindowChange
Loading
Loading
@@ -190,7 +183,6 @@ error:
return;
}
[delegate_ tmuxWindowAddedWithId:[[components objectAtIndex:1] intValue]];
state_ = CONTROL_STATE_READY;
}
 
- (void)parseWindowCloseCommand:(NSString *)command
Loading
Loading
@@ -201,7 +193,6 @@ error:
return;
}
[delegate_ tmuxWindowClosedWithId:[[components objectAtIndex:1] intValue]];
state_ = CONTROL_STATE_READY;
}
 
- (void)parseWindowRenamedCommand:(NSString *)command
Loading
Loading
@@ -213,7 +204,6 @@ error:
}
[delegate_ tmuxWindowRenamedWithId:[[components objectAtIndex:1] intValue]
to:[components objectAtIndex:2]];
state_ = CONTROL_STATE_READY;
}
 
- (void)parseSessionRenamedCommand:(NSString *)command
Loading
Loading
@@ -224,7 +214,6 @@ error:
return;
}
[delegate_ tmuxSession:[[components objectAtIndex:1] intValue] renamed:[components objectAtIndex:2]];
state_ = CONTROL_STATE_READY;
}
 
- (void)parseSessionChangeCommand:(NSString *)command
Loading
Loading
@@ -235,7 +224,6 @@ error:
return;
}
[delegate_ tmuxSessionChanged:[components objectAtIndex:2] sessionId:[[components objectAtIndex:1] intValue]];
state_ = CONTROL_STATE_READY;
}
 
- (void)parseSessionsChangedCommand:(NSString *)command
Loading
Loading
@@ -246,14 +234,13 @@ error:
return;
}
[delegate_ tmuxSessionsChanged];
state_ = CONTROL_STATE_READY;
}
 
- (void)hostDisconnected
{
[delegate_ tmuxHostDisconnected];
[commandQueue_ removeAllObjects];
state_ = CONTROL_STATE_DETACHED;
[delegate_ tmuxHostDisconnected];
[commandQueue_ removeAllObjects];
disconnected_ = YES;
}
 
// Accessors for objects in the current-command dictionary.
Loading
Loading
@@ -378,66 +365,23 @@ error:
}
}
 
- (BOOL)parseCommand
{
NSRange newlineRange = NSMakeRange(NSNotFound, 0);
unsigned char *streamBytes = [stream_ mutableBytes];
for (int i = 0; i < stream_.length; i++) {
if (streamBytes[i] == '\n') {
newlineRange.location = i;
newlineRange.length = 1;
break;
}
}
if (newlineRange.location == NSNotFound) {
return NO;
}
NSRange commandRange;
commandRange.location = 0;
commandRange.length = newlineRange.location; // Command range doesn't include the newline.
// Make a temp copy of the data, and remove linefeeds. Line drivers randomly add linefeeds.
NSMutableData *data = [NSMutableData dataWithCapacity:commandRange.length];
const char *bytes = [stream_ bytes] + commandRange.location;
int lastIndex = 0;
int i;
for (i = 0; i < commandRange.length; i++) {
if (bytes[i] == '\r') {
if (i > lastIndex) {
[data appendBytes:bytes + lastIndex length:i - lastIndex];
}
lastIndex = i + 1;
}
}
if (i > lastIndex) {
[data appendBytes:bytes + lastIndex length:i - lastIndex];
}
NSString *command = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
if (!command) {
// The command was not UTF-8. Unfortunately, this can happen. If tmux has a non-UTF-8
// character in a pane, it will just output it in capture-pane.
command = [[[NSString alloc] initWithUTF8DataIgnoringErrors:data] autorelease];
- (void)executeToken:(VT100Token *)token {
NSString *command = token.string;
NSData *data = token.savedData;
if (_tmuxLogging) {
[delegate_ tmuxPrintLine:command];
}
// At least on osx, the terminal driver adds \r at random places, sometimes adding two of them in a row!
// We split on \n, which is safe, and just throw out any \r's that we see.
command = [command stringByReplacingOccurrencesOfString:@"\r" withString:@""];
if (![command hasPrefix:@"%output "] &&
!currentCommand_) {
TmuxLog(@"Read tmux command: \"%@\"", command);
} else if (currentCommand_) {
TmuxLog(@"Read command response: \"%@\"", command);
}
// Advance range to include newline so we can chop it off
commandRange.length += newlineRange.length;
 
// Work around a bug in tmux 1.8: if unlink-window causes the current
// session to be destroyed, no end guard is printed but %exit may be
// received.
int flags = [self currentCommandFlags];
if (currentCommand_ &&
(flags & kTmuxGatewayCommandHasEndGuardBug) &&
([command hasPrefix:@"%exit "] ||
[command isEqualToString:@"%exit"])) {
// Work around the bug by ending the command so the %exit can be
Loading
Loading
@@ -514,35 +458,6 @@ error:
NSLog(@"Unrecognized command \"%@\"", command);
[strayMessages_ appendFormat:@"%@\n", command];
}
// Erase the just-handled command from the stream.
if (stream_.length > 0) { // length could be 0 if abortWtihErrorMessage: was called.
[stream_ replaceBytesInRange:commandRange withBytes:"" length:0];
}
return YES;
}
- (NSData *)readTask:(NSData *)data
{
[stream_ appendData:data];
while ([stream_ length] > 0) {
switch (state_) {
case CONTROL_STATE_READY:
if (![self parseCommand]) {
// Don't have a full command yet, need to read more.
return nil;
}
break;
case CONTROL_STATE_DETACHED:
data = [[stream_ copy] autorelease];
[stream_ setLength:0];
return data;
}
}
return nil;
}
 
- (NSString *)keyEncodedByte:(char)byte
Loading
Loading
@@ -642,7 +557,7 @@ error:
responseObject:(id)obj
flags:(int)flags
{
if (detachSent_ || state_ == CONTROL_STATE_DETACHED) {
if (detachSent_ || disconnected_) {
return;
}
NSString *commandWithNewline = [command stringByAppendingString:NEWLINE];
Loading
Loading
@@ -663,7 +578,7 @@ error:
 
- (void)sendCommandList:(NSArray *)commandDicts initial:(BOOL)initial
{
if (detachSent_ || state_ == CONTROL_STATE_DETACHED) {
if (detachSent_ || disconnected_) {
return;
}
NSMutableString *cmd = [NSMutableString string];
Loading
Loading
Loading
Loading
@@ -294,7 +294,10 @@
screenTop_ = (screenTop_ + 1) % size_.height;
 
// Empty contents of last line on screen.
[self clearLineData:[self lineDataAtLineNumber:(size_.height - 1)]];
NSMutableData *lastLineData = [self lineDataAtLineNumber:(size_.height - 1)];
if (lastLineData) { // This if statement is just to quiet the analyzer.
[self clearLineData:lastLineData];
}
 
if (lineBuffer) {
// Mark new line at bottom of screen dirty.
Loading
Loading
@@ -1881,6 +1884,7 @@ void DumpBuf(screen_char_t* p, int n) {
for (NSObject *line in lines_) {
[theCopy->lines_ addObject:[[line mutableCopy] autorelease]];
}
[theCopy->lineInfos_ release];
theCopy->lineInfos_ = [[NSMutableArray alloc] init];
for (VT100LineInfo *line in lineInfos_) {
[theCopy->lineInfos_ addObject:[[line copy] autorelease]];
Loading
Loading
Loading
Loading
@@ -10,11 +10,14 @@
#import "CVector.h"
#import "VT100Token.h"
 
@class VT100TmuxParser;
@interface VT100Parser : NSObject
 
@property(nonatomic, readonly) NSData *streamData;
@property(atomic, assign) NSStringEncoding encoding;
@property(nonatomic, readonly) int streamLength;
@property(atomic, retain) VT100TmuxParser *tmuxParser;
 
- (void)putStreamData:(const char *)buffer length:(int)length;
- (void)clearStream;
Loading
Loading
Loading
Loading
@@ -10,6 +10,7 @@
#import "DebugLogging.h"
#import "VT100ControlParser.h"
#import "VT100StringParser.h"
#import "VT100TmuxParser.h"
 
#define kDefaultStreamSize 100000
 
Loading
Loading
@@ -32,6 +33,7 @@
 
- (void)dealloc {
free(_stream);
[_tmuxParser release];
[super dealloc];
}
 
Loading
Loading
@@ -61,7 +63,14 @@
}
} else {
int rmlen = 0;
if (isAsciiString(datap)) {
VT100TmuxParser *tmuxParser = [self.tmuxParser retain];
if (tmuxParser) {
[tmuxParser decodeBytes:datap length:datalen bytesUsed:&rmlen token:token];
[tmuxParser release];
if (token->type == TMUX_EXIT) {
self.tmuxParser = nil;
}
} else if (isAsciiString(datap)) {
ParseString(datap, datalen, &rmlen, token, self.encoding);
length = rmlen;
position = datap;
Loading
Loading
@@ -78,6 +87,8 @@
} else if ([token.kvpKey isEqualToString:@"EndCopy"]) {
_saveData = NO;
}
} else if (token->type == DCS_TMUX && !_tmuxParser) {
self.tmuxParser = [[[VT100TmuxParser alloc] init] autorelease];
}
length = rmlen;
position = datap;
Loading
Loading
@@ -117,10 +128,9 @@
while (i < length) {
unsigned char c = datap[i];
[loginfo appendFormat:@"%02x ", (int)c];
[ascii appendFormat:@"%c", (c>=32 && c<128) ? c : '.'];
[ascii appendFormat:@"%c", (c >= 32 && c < 128) ? c : '.'];
if (i == length - 1 || loginfo.length > 60) {
DebugLog([NSString stringWithFormat:@"Bytes %d-%d of %d: %@ (%@)", start, i,
(int)length, loginfo, ascii]);
DLog(@"Bytes %d-%d of %d: %@ (%@)", start, i, (int)length, loginfo, ascii);
[loginfo setString:@""];
[ascii setString:@""];
start = i;
Loading
Loading
@@ -138,10 +148,7 @@
[token setAsciiBytes:(char *)position length:length];
}
CVectorAppend(vector, token);
// We return NO on DCS_TMUX because futher tokens should not be parsed (they are handled by
// the tmux parser, which does not speak VT100).
return token->type != DCS_TMUX;
return YES;
} else {
[token recycleObject];
return NO;
Loading
Loading
Loading
Loading
@@ -110,6 +110,7 @@ static NSString *const kInlineFileBase64String = @"base64 string"; // NSMutable
[dvr_ release];
[terminal_ release];
[findContext_ release];
[savedIntervalTree_ release];
[intervalTree_ release];
[markCache_ release];
[inlineFileInfo_ release];
Loading
Loading
@@ -2618,6 +2619,10 @@ static NSString *const kInlineFileBase64String = @"base64 string"; // NSMutable
[delegate_ screenStartTmuxMode];
}
 
- (void)terminalHandleTmuxInput:(VT100Token *)token {
[delegate_ screenHandleTmuxInput:token];
}
- (int)terminalWidth {
return [self width];
}
Loading
Loading
Loading
Loading
@@ -119,6 +119,9 @@
// Requests that tmux integration mode begin.
- (void)screenStartTmuxMode;
 
// Handle a line of input in tmux mode in the token's string.
- (void)screenHandleTmuxInput:(VT100Token *)token;
// See comment in setSendModifiers:
- (void)screenModifiersDidChangeTo:(NSArray *)modifiers;
 
Loading
Loading
Loading
Loading
@@ -962,7 +962,16 @@ static const int kMaxScreenRows = 4096;
}
 
- (void)executeToken:(VT100Token *)token {
// First, handle sending input to pasteboard/receving files.
// Handle tmux stuff, which completely bypasses all other normal execution steps.
if (token->type == DCS_TMUX) {
[delegate_ terminalStartTmuxMode];
return;
} else if (token->type == TMUX_EXIT || token->type == TMUX_LINE) {
[delegate_ terminalHandleTmuxInput:token];
return;
}
// Handle sending input to pasteboard/receving files.
if (receivingFile_) {
if (token->type == VT100CC_BEL) {
[delegate_ terminalDidFinishReceivingFile];
Loading
Loading
@@ -1487,10 +1496,6 @@ static const int kMaxScreenRows = 4096;
[delegate_ terminalPostGrowlNotification:token.string];
break;
case DCS_TMUX:
[delegate_ terminalStartTmuxMode];
break;
case XTERMCC_SET_KVP:
[self executeXtermSetKvp:token];
break;
Loading
Loading
Loading
Loading
@@ -227,6 +227,9 @@ typedef enum {
// Enters Tmux mode.
- (void)terminalStartTmuxMode;
 
// Handles input during tmux mode. A single line of input will be in the token's string.
- (void)terminalHandleTmuxInput:(VT100Token *)token;
// Returns the size of the terminal in cells.
- (int)terminalWidth;
- (int)terminalHeight;
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment