diff --git a/ChangeLog b/ChangeLog index 9f3096b6e..506c078bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,18 @@ 2006-08-11 Richard Frith-Macdonald * Source/NSTask.m: Fixup for path validation error on mingw32 + * Headers/Foundation/NSStream.h: improve documentation. + * Source/GSStream.h: + * Source/GSStream.m: + * Source/unix/NSStream.m: + * Source/win32/NSStreamWin32.m: + Add lots of error checking, simplify a little, get local streams on + mingw to close down properly. 2006-08-10 Richard Frith-Macdonald - * Source\GSStream.m: - * Source\win32\NSStreamWin32.m: + * Source/GSStream.m: + * Source/win32/NSStreamWin32.m: Fix error in pipe read offset. Imitial implementation of local 'unix domain sockets' (actually named pipe) stream. @@ -16,10 +23,10 @@ 2006-08-10 Richard Frith-Macdonald - * Source\GSStream.h: - * Source\GSStream.m: - * Source\unix\NSStream.m: - * Source\win32\NSStreamWin32.m: + * Source/GSStream.h: + * Source/GSStream.m: + * Source/unix/NSStream.m: + * Source/win32/NSStreamWin32.m: Ensure all events are posted. Fixup for winsock write signalling. 2006-08-09 Richard Frith-Macdonald @@ -930,13 +937,13 @@ * Source/NSUserDefaults.m: Restructure for lazy creation of defaults database ... only create when we attempt to write. - * Source\NSSocketPort.m: Include GSPrivate.h - * Source\NSRunLoop.m: ditto - * Source\unix\GSRunLoopCtxt.m: ditto - * Source\GSPrivate.h: ditto - * Source\win32\GSFileHandleWin32.m: ditto - * Source\win32\GSRunLoopCtxt.m: ditto - * Headers\Foundation\NSNotificationQueue.h: Move library internal + * Source/NSSocketPort.m: Include GSPrivate.h + * Source/NSRunLoop.m: ditto + * Source/unix/GSRunLoopCtxt.m: ditto + * Source/GSPrivate.h: ditto + * Source/win32/GSFileHandleWin32.m: ditto + * Source/win32/GSRunLoopCtxt.m: ditto + * Headers/Foundation/NSNotificationQueue.h: Move library internal function declarations to GSPrivate.h, thanks to Lloyd Dupont for spotting problem. diff --git a/Headers/Foundation/NSStream.h b/Headers/Foundation/NSStream.h index 029250f24..d24b17dce 100644 --- a/Headers/Foundation/NSStream.h +++ b/Headers/Foundation/NSStream.h @@ -70,7 +70,8 @@ typedef enum { outputStream: (NSOutputStream **)outputStream; /** - * Closes the receiver. + * Closes the receiver.
+ * Repeated calls to this method on the same stream are quietly ignored. */ - (void) close; @@ -80,7 +81,10 @@ typedef enum { - (id) delegate; /** - * Opens the receiving stream. + * Opens the receiving stream.
+ * Upon completion of the open operation, an NSStreamEventOpenCompleted + * event is sent to the recevier's delegate.
+ * Repeated calls to this method on the same stream are quietly ignored. */ - (void) open; @@ -91,12 +95,17 @@ typedef enum { /** * Removes the receiver from the NSRunLoop specified by aRunLoop - * running in the mode. + * running in the mode.
+ * Attempts to remove the receiver from a run loop or a mode in + * which it has not been scheduled are quietly ignored. */ - (void) removeFromRunLoop: (NSRunLoop *)aRunLoop forMode: (NSString *)mode; /** - * Schedules the receiver on aRunLoop using the specified mode. + * Schedules the receiver on aRunLoop using the specified mode.
+ * You must not attempt to add a stream to more than one run loop, + * but you may call this method multiple times to add the receiver + * in different modes for the same run loop. */ - (void) scheduleInRunLoop: (NSRunLoop *)aRunLoop forMode: (NSString *)mode; @@ -262,14 +271,17 @@ typedef enum { * that is a stream that binds to a socket and accepts incoming connections */ @interface GSServerStream : NSStream + /** * Createe a ip (ipv6) server stream */ + (id) serverStreamToAddr: (NSString*)addr port: (int)port; + /** - * Create a local (unix domain) server stream + * Create a local (unix domain or named pipe) server stream */ + (id) serverStreamToAddr: (NSString*)addr; + /** * This is the method that accepts a connection and generates two streams * as the server side inputStream and OutputStream. @@ -283,8 +295,10 @@ typedef enum { * the designated initializer for a ip (ipv6) server stream */ - (id) initToAddr: (NSString*)addr port: (int)port; + /** - * the designated initializer for a local (unix domain) server stream + * the designated initializer for a local (unix domain or named pipe) + * server stream */ - (id) initToAddr: (NSString*)addr; diff --git a/Source/GSStream.h b/Source/GSStream.h index 76d99efa9..34792c99a 100644 --- a/Source/GSStream.h +++ b/Source/GSStream.h @@ -28,7 +28,7 @@ NSStream |-- NSInputStream | `--GSInputStream - | |-- GSMemoryInputStream + | |-- GSDataInputStream | |-- GSFileInputStream | `-- GSSocketInputStream | |-- GSInetInputStream @@ -36,7 +36,8 @@ | `-- GSInet6InputStream |-- NSOutputStream | `--GSOutputStream - | |-- GSMemoryOutputStream + | |-- GSBufferOutputStream + | |-- GSDataOutputStream | |-- GSFileOutputStream | `-- GSSocketOutputStream | |-- GSInetOutputStream @@ -144,7 +145,7 @@ IVARS /** * The concrete subclass of NSInputStream that reads from the memory */ -@interface GSMemoryInputStream : GSInputStream +@interface GSDataInputStream : GSInputStream { @private NSData *_data; @@ -158,14 +159,30 @@ IVARS @end /** - * The concrete subclass of NSOutputStream that writes to memory + * The concrete subclass of NSOutputStream that writes to a buffer */ -@interface GSMemoryOutputStream : GSOutputStream +@interface GSBufferOutputStream : GSOutputStream +{ +@private + uint8_t *_buffer; + unsigned _capacity; + unsigned long _pointer; +} + +/** + * this is the bridge method for asynchronized operation. Do not call. + */ +- (void) _dispatch; +@end + +/** + * The concrete subclass of NSOutputStream that writes to a variable sise buffer + */ +@interface GSDataOutputStream : GSOutputStream { @private NSMutableData *_data; unsigned long _pointer; - BOOL _fixedSize; } /** diff --git a/Source/GSStream.m b/Source/GSStream.m index 89032853c..bf15e4b0a 100644 --- a/Source/GSStream.m +++ b/Source/GSStream.m @@ -31,6 +31,7 @@ #include #include #include +#include #include "GSStream.h" @@ -132,9 +133,14 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (void) close { - NSAssert(_currentStatus != NSStreamStatusNotOpen - && _currentStatus != NSStreamStatusClosed, - @"Attempt to close a stream not yet opened."); + if (_currentStatus == NSStreamStatusNotOpen) + { + NSDebugMLog(@"Attempt to close unopened stream %@", self); + } + if (_currentStatus == NSStreamStatusClosed) + { + NSDebugMLog(@"Attempt to close already closed stream %@", self); + } if (_runloop) { unsigned i = [_modes count]; @@ -186,9 +192,10 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (void) open { - NSAssert(_currentStatus == NSStreamStatusNotOpen - || _currentStatus == NSStreamStatusOpening, - @"Attempt to open a stream already opened."); + if (_currentStatus != NSStreamStatusNotOpen) + { + NSDebugMLog(@"Attempt to re-open stream %@", self); + } [self _setStatus: NSStreamStatusOpen]; if (_runloop) { @@ -364,9 +371,6 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (void) _sendEvent: (NSStreamEvent)event { - NSStreamStatus last = [self streamStatus]; - NSStreamStatus current; - if (event == NSStreamEventNone) { return; @@ -454,21 +458,6 @@ static RunLoopEventType typeForStream(NSStream *aStream) [NSException raise: NSInvalidArgumentException format: @"Unknown event (%d) passed to _sendEvent:", event]; } - - /* If our status changed while the handler was dealing with an - * event, we may need to send it the new event to let it know. - */ - if ((current = [self streamStatus]) != last) - { - if (current == NSStreamStatusAtEnd) - { - [self _sendEvent: NSStreamEventEndEncountered]; - } - else if (current == NSStreamStatusError) - { - [self _sendEvent: NSStreamEventErrorOccurred]; - } - } } - (void) _setLoopID: (void *)ref @@ -493,18 +482,35 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (BOOL) runLoopShouldBlock: (BOOL*)trigger { - if ([self _unhandledData] == YES - || _currentStatus == NSStreamStatusError - || _currentStatus == NSStreamStatusAtEnd) + if (_events + & (NSStreamEventHasBytesAvailable | NSStreamEventHasSpaceAvailable)) { /* If we have an unhandled data event, we should not watch for more - * or trigger until the appropriate rad or write has been done. - * If an error has occurred, we should not watch for any events at all. + * or trigger until the appropriate read or write has been done. */ *trigger = NO; return NO; } - else if (_loopID == (void*)self) + if (_currentStatus == NSStreamStatusError && + (_events & NSStreamEventErrorOccurred) == NSStreamEventErrorOccurred) + { + /* If an error has occurred (and been handled), + * we should not watch for any events at all. + */ + *trigger = NO; + return NO; + } + if (_currentStatus == NSStreamStatusAtEnd && + (_events & NSStreamEventEndEncountered) == NSStreamEventEndEncountered) + { + /* If an error has occurred (and been handled), + * we should not watch for any events at all. + */ + *trigger = NO; + return NO; + } + + if (_loopID == (void*)self) { /* If _loopID is the receiver, the stream is not receiving external * input, so it must trigger an event when the loop runs and must not @@ -522,6 +528,7 @@ static RunLoopEventType typeForStream(NSStream *aStream) @end @implementation GSInputStream + + (void) initialize { if (self == [GSInputStream class]) @@ -529,9 +536,31 @@ static RunLoopEventType typeForStream(NSStream *aStream) GSObjCAddClassBehavior(self, [GSStream class]); } } + +- (BOOL) hasBytesAvailable +{ + if (_currentStatus == NSStreamStatusOpen) + { + return YES; + } + if (_currentStatus == NSStreamStatusAtEnd) + { + if ((_events & NSStreamEventEndEncountered) == 0) + { + /* We have not sent the appropriate event yet, so the + * client must not have issued a read:maxLength: + * (which is the point at which we should send). + */ + return YES; + } + } + return NO; +} + @end @implementation GSOutputStream + + (void) initialize { if (self == [GSOutputStream class]) @@ -539,6 +568,27 @@ static RunLoopEventType typeForStream(NSStream *aStream) GSObjCAddClassBehavior(self, [GSStream class]); } } + +- (BOOL) hasSpaceAvailable +{ + if (_currentStatus == NSStreamStatusOpen) + { + return YES; + } + if (_currentStatus == NSStreamStatusAtEnd) + { + if ((_events & NSStreamEventEndEncountered) == 0) + { + /* We have not sent the appropriate event yet, so the + * client must not have issued a write:maxLength: + * (which is the point at which we should send). + */ + return YES; + } + } + return NO; +} + @end @implementation GSAbstractServerStream + (void) initialize @@ -551,7 +601,7 @@ static RunLoopEventType typeForStream(NSStream *aStream) @end -@implementation GSMemoryInputStream +@implementation GSDataInputStream /** * the designated initializer @@ -576,10 +626,28 @@ static RunLoopEventType typeForStream(NSStream *aStream) - (int) read: (uint8_t *)buffer maxLength: (unsigned int)len { - unsigned long dataSize = [_data length]; + unsigned long dataSize; unsigned long copySize; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte read write requested"]; + } + _events &= ~NSStreamEventHasSpaceAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + + dataSize = [_data length]; NSAssert(dataSize >= _pointer, @"Buffer overflow!"); if (len + _pointer > dataSize) { @@ -597,6 +665,7 @@ static RunLoopEventType typeForStream(NSStream *aStream) else { [self _setStatus: NSStreamStatusAtEnd]; + [self _sendEvent: NSStreamEventEndEncountered]; } return copySize; } @@ -639,71 +708,132 @@ static RunLoopEventType typeForStream(NSStream *aStream) @end -@implementation GSMemoryOutputStream +@implementation GSBufferOutputStream - (id) initToBuffer: (uint8_t *)buffer capacity: (unsigned int)capacity { if ((self = [super init]) != nil) { - if (!buffer) - { - _data = [NSMutableData new]; - _fixedSize = NO; - } - else - { - _data = [[NSMutableData alloc] initWithBytesNoCopy: buffer - length: capacity freeWhenDone: NO]; - _fixedSize = YES; - } + _buffer = buffer; + _capacity = capacity; _pointer = 0; } return self; } -- (void) dealloc -{ - RELEASE(_data); - [super dealloc]; -} - - (int) write: (const uint8_t *)buffer maxLength: (unsigned int)len { - _events &= ~NSStreamEventHasBytesAvailable; - if (_fixedSize) + if (buffer == 0) { - unsigned long dataLen = [_data length]; - uint8_t *origin = (uint8_t *)[_data mutableBytes]; - - if (_pointer+len>dataLen) - len = dataLen - _pointer; - memcpy(origin+_pointer, buffer, len); - _pointer = _pointer + len; + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; } - else - [_data appendBytes: buffer length: len]; - return len; -} -- (BOOL) hasSpaceAvailable -{ - if (_fixedSize) - return [_data length]>_pointer; - else - return YES; + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + + if ((_pointer + len) > _capacity) + { + len = _capacity - _pointer; + if (len == 0) + { + [self _setStatus: NSStreamStatusAtEnd]; + [self _sendEvent: NSStreamEventEndEncountered]; + return 0; + } + } + memcpy((_buffer + _pointer), buffer, len); + _pointer += len; + return len; } - (id) propertyForKey: (NSString *)key { if ([key isEqualToString: NSStreamFileCurrentOffsetKey]) { - if (_fixedSize) - return [NSNumber numberWithLong: _pointer]; - else - return [NSNumber numberWithLong:[_data length]]; + return [NSNumber numberWithLong: _pointer]; + } + return [super propertyForKey: key]; +} + +- (void) _dispatch +{ + BOOL av = [self hasSpaceAvailable]; + NSStreamEvent myEvent = av ? NSStreamEventHasSpaceAvailable : + NSStreamEventEndEncountered; + + [self _sendEvent: myEvent]; +} + +@end + +@implementation GSDataOutputStream + +- (id) init +{ + if ((self = [super init]) != nil) + { + _data = [NSMutableData new]; + _pointer = 0; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_data); + [super dealloc]; +} + +- (int) write: (const uint8_t *)buffer maxLength: (unsigned int)len +{ + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + + [_data appendBytes: buffer length: len]; + _pointer += len; + return len; +} + +- (BOOL) hasSpaceAvailable +{ + return YES; +} + +- (id) propertyForKey: (NSString *)key +{ + if ([key isEqualToString: NSStreamFileCurrentOffsetKey]) + { + return [NSNumber numberWithLong: _pointer]; + } + else if ([key isEqualToString: NSStreamDataWrittenToMemoryStreamKey]) + { + return _data; } - else if ([key isEqualToString: NSStreamDataWrittenToMemoryStreamKey]) - return _data; return [super propertyForKey: key]; } diff --git a/Source/unix/NSStream.m b/Source/unix/NSStream.m index b4dbfea2f..099f52a64 100644 --- a/Source/unix/NSStream.m +++ b/Source/unix/NSStream.m @@ -297,7 +297,24 @@ static void setNonblocking(int fd) { int readLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte read write requested"]; + } + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + readLen = read((intptr_t)_loopID, buffer, len); if (readLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -475,7 +492,24 @@ static void setNonblocking(int fd) { int readLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte read write requested"]; + } + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + readLen = read((intptr_t)_loopID, buffer, len); if (readLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -676,7 +710,24 @@ static void setNonblocking(int fd) { int writeLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + _events &= ~NSStreamEventHasSpaceAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + writeLen = write((intptr_t)_loopID, buffer, len); if (writeLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -790,7 +841,24 @@ static void setNonblocking(int fd) { int writeLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + _events &= ~NSStreamEventHasSpaceAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + writeLen = write((intptr_t)_loopID, buffer, len); if (writeLen < 0 && errno != EAGAIN && errno != EINTR) [self _recordError]; @@ -1186,7 +1254,7 @@ static void setNonblocking(int fd) + (id) inputStreamWithData: (NSData *)data { - return AUTORELEASE([[GSMemoryInputStream alloc] initWithData: data]); + return AUTORELEASE([[GSDataInputStream alloc] initWithData: data]); } + (id) inputStreamWithFileAtPath: (NSString *)path @@ -1194,24 +1262,6 @@ static void setNonblocking(int fd) return AUTORELEASE([[GSFileInputStream alloc] initWithFileAtPath: path]); } -- (id) initWithData: (NSData *)data -{ - RELEASE(self); - return [[GSMemoryInputStream alloc] initWithData: data]; -} - -- (id) initWithFileAtPath: (NSString *)path -{ - RELEASE(self); - return [[GSFileInputStream alloc] initWithFileAtPath: path]; -} - -- (int) read: (uint8_t *)buffer maxLength: (unsigned int)len -{ - [self subclassResponsibility: _cmd]; - return -1; -} - - (BOOL) getBuffer: (uint8_t **)buffer length: (unsigned int *)len { [self subclassResponsibility: _cmd]; @@ -1224,19 +1274,31 @@ static void setNonblocking(int fd) return NO; } +- (id) initWithData: (NSData *)data +{ + RELEASE(self); + return [[GSDataInputStream alloc] initWithData: data]; +} + +- (id) initWithFileAtPath: (NSString *)path +{ + RELEASE(self); + return [[GSFileInputStream alloc] initWithFileAtPath: path]; +} + +- (int) read: (uint8_t *)buffer maxLength: (unsigned int)len +{ + [self subclassResponsibility: _cmd]; + return -1; +} + @end @implementation NSOutputStream -+ (id) outputStreamToMemory -{ - return AUTORELEASE([[GSMemoryOutputStream alloc] - initToBuffer: NULL capacity: 0]); -} - + (id) outputStreamToBuffer: (uint8_t *)buffer capacity: (unsigned int)capacity { - return AUTORELEASE([[GSMemoryOutputStream alloc] + return AUTORELEASE([[GSBufferOutputStream alloc] initToBuffer: buffer capacity: capacity]); } @@ -1246,16 +1308,21 @@ static void setNonblocking(int fd) initToFileAtPath: path append: shouldAppend]); } -- (id) initToMemory ++ (id) outputStreamToMemory { - RELEASE(self); - return [[GSMemoryOutputStream alloc] initToBuffer: NULL capacity: 0]; + return AUTORELEASE([[GSDataOutputStream alloc] init]); +} + +- (BOOL) hasSpaceAvailable +{ + [self subclassResponsibility: _cmd]; + return NO; } - (id) initToBuffer: (uint8_t *)buffer capacity: (unsigned int)capacity { RELEASE(self); - return [[GSMemoryOutputStream alloc] initToBuffer: buffer capacity: capacity]; + return [[GSBufferOutputStream alloc] initToBuffer: buffer capacity: capacity]; } - (id) initToFileAtPath: (NSString *)path append: (BOOL)shouldAppend @@ -1265,18 +1332,18 @@ static void setNonblocking(int fd) append: shouldAppend]; } +- (id) initToMemory +{ + RELEASE(self); + return [[GSDataOutputStream alloc] init]; +} + - (int) write: (const uint8_t *)buffer maxLength: (unsigned int)len { [self subclassResponsibility: _cmd]; return -1; } -- (BOOL) hasSpaceAvailable -{ - [self subclassResponsibility: _cmd]; - return NO; -} - @end @implementation GSServerStream diff --git a/Source/win32/NSStreamWin32.m b/Source/win32/NSStreamWin32.m index 300eec243..10e4a8b37 100644 --- a/Source/win32/NSStreamWin32.m +++ b/Source/win32/NSStreamWin32.m @@ -41,6 +41,7 @@ #include #include #include +#include #include "../GSStream.h" @@ -73,6 +74,7 @@ typedef int socklen_t; unsigned want; // Amount of data we want to read. DWORD size; // Number of bytes returned by read. GSPipeOutputStream *_sibling; + BOOL hadEOF; } - (NSStreamStatus) _check; - (void) _queue; @@ -154,6 +156,8 @@ typedef int socklen_t; unsigned want; DWORD size; GSPipeInputStream *_sibling; + BOOL closing; + BOOL writtenEOF; } - (NSStreamStatus) _check; - (void) _queue; @@ -298,13 +302,6 @@ static void setNonblocking(SOCKET fd) return NO; } -- (BOOL) hasBytesAvailable -{ - if ([self _isOpened] && [self streamStatus] != NSStreamStatusAtEnd) - return YES; - return NO; -} - - (id) initWithFileAtPath: (NSString *)path { if ((self = [super init]) != nil) @@ -351,7 +348,24 @@ static void setNonblocking(SOCKET fd) { DWORD readLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length read requested"]; + } + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + if (ReadFile((HANDLE)_loopID, buffer, len, &readLen, NULL) == 0) { [self _recordError]; @@ -360,6 +374,7 @@ static void setNonblocking(SOCKET fd) else if (readLen == 0) { [self _setStatus: NSStreamStatusAtEnd]; + [self _sendEvent: NSStreamEventEndEncountered]; } return (int)readLen; } @@ -383,26 +398,43 @@ static void setNonblocking(SOCKET fd) - (void) close { - if (want > 0 && handle != INVALID_HANDLE_VALUE) - { - want = 0; - CancelIo(handle); - } + length = offset = 0; if (_loopID != INVALID_HANDLE_VALUE) { CloseHandle((HANDLE)_loopID); } - if (handle != INVALID_HANDLE_VALUE && [_sibling _isOpened] == NO) + if (handle != INVALID_HANDLE_VALUE) { - if (CloseHandle(handle) == 0) + /* If we have an outstanding read in progess, we must cancel it + * before closing the pipe. + */ + if (want > 0) { - [self _recordError]; + want = 0; + CancelIo(handle); } + + /* We can only close the pipe if there is no sibling using it. + */ + if ([_sibling _isOpened] == NO) + { + if (DisconnectNamedPipe(handle) == 0) + { + if ((errno = GetLastError()) != ERROR_PIPE_NOT_CONNECTED) + { + [self _recordError]; + } + } + if (CloseHandle(handle) == 0) + { + [self _recordError]; + } + } + handle = INVALID_HANDLE_VALUE; } - length = offset = 0; [super close]; - handle = INVALID_HANDLE_VALUE; _loopID = (void*)INVALID_HANDLE_VALUE; + } - (void) dealloc @@ -426,13 +458,6 @@ static void setNonblocking(SOCKET fd) return NO; } -- (BOOL) hasBytesAvailable -{ - if ([self streamStatus] == NSStreamStatusOpen) - return YES; - return NO; -} - - (id) init { if ((self = [super init]) != nil) @@ -459,8 +484,8 @@ static void setNonblocking(SOCKET fd) if (GetOverlappedResult(handle, &ov, &size, TRUE) == 0) { - errno = GetLastError(); - if (errno == ERROR_HANDLE_EOF + if ((errno = GetLastError()) == ERROR_HANDLE_EOF + || errno == ERROR_PIPE_NOT_CONNECTED || errno == ERROR_BROKEN_PIPE) { /* @@ -469,6 +494,7 @@ static void setNonblocking(SOCKET fd) */ offset = length = want = 0; [self _setStatus: NSStreamStatusOpen]; + hadEOF = YES; } else if (errno != ERROR_IO_PENDING) { @@ -479,6 +505,12 @@ static void setNonblocking(SOCKET fd) [self _recordError]; } } + else if (size == 0) + { + length = want = 0; + [self _setStatus: NSStreamStatusOpen]; + hadEOF = YES; + } else { /* @@ -492,7 +524,7 @@ static void setNonblocking(SOCKET fd) - (void) _queue { - if ([self streamStatus] == NSStreamStatusOpen) + if (hadEOF == NO && [self streamStatus] == NSStreamStatusOpen) { int rc; @@ -508,14 +540,14 @@ static void setNonblocking(SOCKET fd) length = size; if (length == 0) { - [self _setStatus: NSStreamStatusAtEnd]; + hadEOF = YES; } } else if ((errno = GetLastError()) == ERROR_HANDLE_EOF + || errno == ERROR_PIPE_NOT_CONNECTED || errno == ERROR_BROKEN_PIPE) { - offset = length = 0; - [self _setStatus: NSStreamStatusOpen]; // Read of zero length + hadEOF = YES; } else if (errno != ERROR_IO_PENDING) { @@ -530,24 +562,38 @@ static void setNonblocking(SOCKET fd) - (int) read: (uint8_t *)buffer maxLength: (unsigned int)len { - NSStreamStatus myStatus = [self streamStatus]; + NSStreamStatus myStatus; + + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length read requested"]; + } _events &= ~NSStreamEventHasBytesAvailable; + + myStatus = [self streamStatus]; if (myStatus == NSStreamStatusReading) { myStatus = [self _check]; } - if (myStatus == NSStreamStatusAtEnd) + if (myStatus == NSStreamStatusClosed) { - return 0; // At EOF - } - if (len <= 0 - || (myStatus != NSStreamStatusReading && myStatus != NSStreamStatusOpen)) - { - return -1; // Bad length or status + return 0; } + if (offset == length) { + if (myStatus == NSStreamStatusError) + { + [self _sendEvent: NSStreamEventErrorOccurred]; + return -1; // Waiting for read. + } if (myStatus == NSStreamStatusOpen) { /* @@ -555,10 +601,11 @@ static void setNonblocking(SOCKET fd) * so we must be at EOF. */ [self _setStatus: NSStreamStatusAtEnd]; - return 0; + [self _sendEvent: NSStreamEventEndEncountered]; } - return -1; // Waiting for read. + return 0; } + /* * We already have data buffered ... return some or all of it. */ @@ -775,7 +822,24 @@ static void setNonblocking(SOCKET fd) { int readLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length read requested"]; + } + _events &= ~NSStreamEventHasBytesAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + readLen = recv(_sock, buffer, len, 0); if (readLen == SOCKET_ERROR) { @@ -806,13 +870,6 @@ static void setNonblocking(SOCKET fd) return NO; } -- (BOOL) hasBytesAvailable -{ - if ([self streamStatus] == NSStreamStatusOpen) - return YES; - return NO; -} - - (void) _dispatch { /* @@ -995,13 +1052,6 @@ static void setNonblocking(SOCKET fd) [super dealloc]; } -- (BOOL) hasSpaceAvailable -{ - if ([self streamStatus] == NSStreamStatusOpen) - return YES; - return NO; -} - - (id) initToFileAtPath: (NSString *)path append: (BOOL)shouldAppend { if ((self = [super init]) != nil) @@ -1056,7 +1106,24 @@ static void setNonblocking(SOCKET fd) { DWORD writeLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + _events &= ~NSStreamEventHasSpaceAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + if (_shouldAppend == YES) { SetFilePointer((HANDLE)_loopID, 0, 0, FILE_END); @@ -1084,21 +1151,67 @@ static void setNonblocking(SOCKET fd) - (void) close { + /* If we have a write in progress, we must wait for it to complete, + * so we just set a flag to close as soon as the write finishes. + */ + if ([self streamStatus] == NSStreamStatusWriting) + { + closing = YES; + return; + } + + /* Where we have a sibling, we can't close the pipe handle, so the + * only way to tell the remote end we have finished is to write a + * zero length packet to it. + */ + if ([_sibling _isOpened] == YES && writtenEOF == NO) + { + int rc; + + writtenEOF = YES; + ov.Offset = 0; + ov.OffsetHigh = 0; + ov.hEvent = (HANDLE)_loopID; + size = 0; + rc = WriteFile(handle, "", 0, &size, &ov); + if (rc == 0) + { + if ((errno = GetLastError()) == ERROR_IO_PENDING) + { + [self _setStatus: NSStreamStatusWriting]; + return; // Wait for write to complete + } + [self _recordError]; // Failed to write EOF + } + } + + offset = want = 0; if (_loopID != INVALID_HANDLE_VALUE) { CloseHandle((HANDLE)_loopID); } - if (handle != INVALID_HANDLE_VALUE && [_sibling _isOpened] == NO) + if (handle != INVALID_HANDLE_VALUE) { - if (CloseHandle(handle) == 0) + if ([_sibling _isOpened] == NO) { - [self _recordError]; + if (DisconnectNamedPipe(handle) == 0) + { + if ((errno = GetLastError()) != ERROR_PIPE_NOT_CONNECTED) + { + [self _recordError]; + } + [self _recordError]; + } + if (CloseHandle(handle) == 0) + { + [self _recordError]; + } } + handle = INVALID_HANDLE_VALUE; } - offset = want = 0; + [super close]; _loopID = (void*)INVALID_HANDLE_VALUE; - handle = INVALID_HANDLE_VALUE; } - (void) dealloc @@ -1112,13 +1225,6 @@ static void setNonblocking(SOCKET fd) [super dealloc]; } -- (BOOL) hasSpaceAvailable -{ - if ([self streamStatus] == NSStreamStatusOpen) - return YES; - return NO; -} - - (id) init { if ((self = [super init]) != nil) @@ -1179,19 +1285,33 @@ static void setNonblocking(SOCKET fd) { NSStreamStatus myStatus = [self streamStatus]; - _events &= ~NSStreamEventHasSpaceAvailable; - if (len < 0) + if (buffer == 0) { - return -1; + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + + _events &= ~NSStreamEventHasSpaceAvailable; + if (myStatus == NSStreamStatusWriting) { myStatus = [self _check]; } + if (myStatus == NSStreamStatusClosed) + { + return 0; + } + if ((myStatus != NSStreamStatusOpen && myStatus != NSStreamStatusWriting)) { return -1; } + if (len > (sizeof(data) - offset)) { len = sizeof(data) - offset; @@ -1231,6 +1351,10 @@ static void setNonblocking(SOCKET fd) offset = want = 0; } } + if (closing == YES && [self streamStatus] != NSStreamStatusWriting) + { + [self close]; + } return [self streamStatus]; } @@ -1313,7 +1437,7 @@ static void setNonblocking(SOCKET fd) _sibling = sibling; } --(void)setPassive: (BOOL)passive +-(void) setPassive: (BOOL)passive { _passive = passive; } @@ -1354,7 +1478,24 @@ static void setNonblocking(SOCKET fd) { int writeLen; + if (buffer == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"null pointer for buffer"]; + } + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"zero byte length write requested"]; + } + _events &= ~NSStreamEventHasSpaceAvailable; + + if ([self streamStatus] == NSStreamStatusClosed) + { + return 0; + } + writeLen = send(_sock, buffer, len, 0); if (writeLen == SOCKET_ERROR) { @@ -1376,13 +1517,6 @@ static void setNonblocking(SOCKET fd) return writeLen; } -- (BOOL) hasSpaceAvailable -{ - if ([self streamStatus] == NSStreamStatusOpen) - return YES; - return NO; -} - - (void) open { // could be opened because of sibling @@ -1691,16 +1825,37 @@ static void setNonblocking(SOCKET fd) SECURITY_ATTRIBUTES saAttr; HANDLE handle; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; + if ([path length] == 0) + { + NSDebugMLog(@"address nil or empty"); + goto done; + } + if ([path length] > 240) + { + NSDebugMLog(@"address (%@) too long", path); + goto done; + } + if ([path rangeOfString: @"\\"].length > 0) + { + NSDebugMLog(@"illegal backslash in (%@)", path); + goto done; + } + if ([path rangeOfString: @"/"].length > 0) + { + NSDebugMLog(@"illegal slash in (%@)", path); + goto done; + } /* * We allocate a new within the local pipe area */ - name = [[@"\\\\.\\pipe\\" stringByAppendingString: path] + name = [[@"\\\\.\\pipe\\GSLocal" stringByAppendingString: path] fileSystemRepresentation]; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + handle = CreateFileW(name, GENERIC_WRITE|GENERIC_READ, 0, @@ -1720,15 +1875,17 @@ static void setNonblocking(SOCKET fd) outs = AUTORELEASE([GSPipeOutputStream new]); [ins _setHandle: handle]; + [ins setSibling: outs]; [outs _setHandle: handle]; + [outs setSibling: ins]; + +done: if (inputStream) { - [ins setSibling: outs]; *inputStream = ins; } if (outputStream) { - [outs setSibling: ins]; *outputStream = outs; } } @@ -1875,7 +2032,7 @@ static void setNonblocking(SOCKET fd) + (id) inputStreamWithData: (NSData *)data { - return AUTORELEASE([[GSMemoryInputStream alloc] initWithData: data]); + return AUTORELEASE([[GSDataInputStream alloc] initWithData: data]); } + (id) inputStreamWithFileAtPath: (NSString *)path @@ -1883,24 +2040,6 @@ static void setNonblocking(SOCKET fd) return AUTORELEASE([[GSFileInputStream alloc] initWithFileAtPath: path]); } -- (id) initWithData: (NSData *)data -{ - RELEASE(self); - return [[GSMemoryInputStream alloc] initWithData: data]; -} - -- (id) initWithFileAtPath: (NSString *)path -{ - RELEASE(self); - return [[GSFileInputStream alloc] initWithFileAtPath: path]; -} - -- (int) read: (uint8_t *)buffer maxLength: (unsigned int)len -{ - [self subclassResponsibility: _cmd]; - return -1; -} - - (BOOL) getBuffer: (uint8_t **)buffer length: (unsigned int *)len { [self subclassResponsibility: _cmd]; @@ -1913,19 +2052,36 @@ static void setNonblocking(SOCKET fd) return NO; } +- (id) initWithData: (NSData *)data +{ + RELEASE(self); + return [[GSDataInputStream alloc] initWithData: data]; +} + +- (id) initWithFileAtPath: (NSString *)path +{ + RELEASE(self); + return [[GSFileInputStream alloc] initWithFileAtPath: path]; +} + +- (int) read: (uint8_t *)buffer maxLength: (unsigned int)len +{ + [self subclassResponsibility: _cmd]; + return -1; +} + @end @implementation NSOutputStream + (id) outputStreamToMemory { - return AUTORELEASE([[GSMemoryOutputStream alloc] - initToBuffer: NULL capacity: 0]); + return AUTORELEASE([[GSDataOutputStream alloc] init]); } + (id) outputStreamToBuffer: (uint8_t *)buffer capacity: (unsigned int)capacity { - return AUTORELEASE([[GSMemoryOutputStream alloc] + return AUTORELEASE([[GSBufferOutputStream alloc] initToBuffer: buffer capacity: capacity]); } @@ -1935,16 +2091,16 @@ static void setNonblocking(SOCKET fd) initToFileAtPath: path append: shouldAppend]); } -- (id) initToMemory +- (BOOL) hasSpaceAvailable { - RELEASE(self); - return [[GSMemoryOutputStream alloc] initToBuffer: NULL capacity: 0]; + [self subclassResponsibility: _cmd]; + return NO; } - (id) initToBuffer: (uint8_t *)buffer capacity: (unsigned int)capacity { RELEASE(self); - return [[GSMemoryOutputStream alloc] initToBuffer: buffer capacity: capacity]; + return [[GSBufferOutputStream alloc] initToBuffer: buffer capacity: capacity]; } - (id) initToFileAtPath: (NSString *)path append: (BOOL)shouldAppend @@ -1954,18 +2110,18 @@ static void setNonblocking(SOCKET fd) append: shouldAppend]; } +- (id) initToMemory +{ + RELEASE(self); + return [[GSDataOutputStream alloc] init]; +} + - (int) write: (const uint8_t *)buffer maxLength: (unsigned int)len { [self subclassResponsibility: _cmd]; return -1; } -- (BOOL) hasSpaceAvailable -{ - [self subclassResponsibility: _cmd]; - return NO; -} - @end @implementation GSServerStream @@ -2213,9 +2369,30 @@ static void setNonblocking(SOCKET fd) - (id) initToAddr: (NSString*)addr { + if ([addr length] == 0) + { + NSDebugMLog(@"address nil or empty"); + DESTROY(self); + } + if ([addr length] > 246) + { + NSDebugMLog(@"address (%@) too long", addr); + DESTROY(self); + } + if ([addr rangeOfString: @"\\"].length > 0) + { + NSDebugMLog(@"illegal backslash in (%@)", addr); + DESTROY(self); + } + if ([addr rangeOfString: @"/"].length > 0) + { + NSDebugMLog(@"illegal slash in (%@)", addr); + DESTROY(self); + } + if ((self = [super init]) != nil) { - path = RETAIN([@"\\\\.\\pipe\\" stringByAppendingString: addr]); + path = RETAIN([@"\\\\.\\pipe\\GSLocal" stringByAppendingString: addr]); _loopID = INVALID_HANDLE_VALUE; handle = INVALID_HANDLE_VALUE; } @@ -2245,7 +2422,7 @@ static void setNonblocking(SOCKET fd) handle = CreateNamedPipeW([path fileSystemRepresentation], PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_BYTE, + PIPE_TYPE_MESSAGE, PIPE_UNLIMITED_INSTANCES, BUFSIZ*64, BUFSIZ*64,