From 30146f8f24960da1c769868397cca1818aec9b1f Mon Sep 17 00:00:00 2001 From: CaS Date: Tue, 15 Jul 2003 05:21:34 +0000 Subject: [PATCH] iUpdates for local private connections git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@17217 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 8 + Headers/gnustep/base/NSPort.h | 61 +- Headers/gnustep/base/NSPortNameServer.h | 21 +- Source/GNUmakefile | 9 +- Source/NSMessagePort.m | 1943 ++++++++++++++++++ Source/NSMessagePortNameServer.m | 361 ++++ Source/NSPort.m | 22 +- Source/NSPortNameServer.m | 1101 +---------- Source/NSSocketPort.m | 2416 ++++++++++++++++++++--- Source/NSSocketPortNameServer.m | 1199 +++++++++++ Source/libgnustep-base.def.in | 3 +- 11 files changed, 5745 insertions(+), 1399 deletions(-) create mode 100644 Source/NSMessagePort.m create mode 100644 Source/NSMessagePortNameServer.m create mode 100644 Source/NSSocketPortNameServer.m diff --git a/ChangeLog b/ChangeLog index 91835909d..0492f94dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,14 @@ * Source/NSConnection.m: When creating new connection with registered name on host, ensure that send and receive ports are of same class. + * Source/NSMessagePort.m: New for private local connections. + * Source/NSMessagePortNameServer.m: New for private local connections. + * Source/NSPortNameServer.m: Made abstract + * Source/NSSocketPortNameServer.m: Concrete version based on original. + * Source/NSSocketPort.m: GSTcpPort.m renamed + Alexanders modifications added by RFM and altered slightly to use + NSSocketPort by default and to refrain from building new code under + windows, so this *shouldn't* break any existing code I hope. 2003-07-15 Richard Frith-Macdonald diff --git a/Headers/gnustep/base/NSPort.h b/Headers/gnustep/base/NSPort.h index 3b63435b3..83f90e83f 100644 --- a/Headers/gnustep/base/NSPort.h +++ b/Headers/gnustep/base/NSPort.h @@ -28,6 +28,7 @@ #define __NSPort_h_GNUSTEP_BASE_INCLUDE #include +#include #ifdef __MINGW__ #include @@ -42,6 +43,8 @@ @class NSDate; @class NSRunLoop; @class NSString; +@class NSPortMessage; +@class NSHost; GS_EXPORT NSString * const NSPortTimeoutException; /* OPENSTEP */ @@ -100,7 +103,35 @@ GS_EXPORT NSString* NSPortDidBecomeInvalidNotification; typedef SOCKET NSSocketNativeHandle; -@interface NSSocketPort : NSPort +@class GSTcpHandle; +@interface NSSocketPort : NSPort +{ + NSRecursiveLock *myLock; + NSHost *host; /* OpenStep host for this port. */ + NSString *address; /* Forced internet address. */ + gsu16 portNum; /* TCP port in host byte order. */ + SOCKET listener; + NSMapTable *handles; /* Handles indexed by socket. */ +} + ++ (NSSocketPort*) existingPortWithNumber: (gsu16)number + onHost: (NSHost*)host; ++ (NSSocketPort*) portWithNumber: (gsu16)number + onHost: (NSHost*)host + forceAddress: (NSString*)addr + listener: (BOOL)shouldListen; + +- (void) addHandle: (GSTcpHandle*)handle forSend: (BOOL)send; +- (NSString*) address; +- (void) getFds: (SOCKET*)fds count: (int*)count; +- (GSTcpHandle*) handleForPort: (NSSocketPort*)recvPort + beforeDate: (NSDate*)when; +- (void) handlePortMessage: (NSPortMessage*)m; +- (NSHost*) host; +- (gsu16) portNumber; +- (void) removeHandle: (GSTcpHandle*)h; + +/* { NSSocketNativeHandle _socket; int _protocolFamily; @@ -108,7 +139,6 @@ typedef SOCKET NSSocketNativeHandle; int _protocol; NSData *_remoteAddrData; } - - (id) init; - (id) initWithTCPPort: (unsigned short)portNumber; - (id) initWithProtocolFamily: (int)family @@ -131,8 +161,35 @@ typedef SOCKET NSSocketNativeHandle; - (int) protocolFamily; - (NSSocketNativeHandle) socket; - (int) socketType; +*/ @end + + +@class GSMessageHandle; + +@interface NSMessagePort : NSPort +{ + NSData *name; + NSRecursiveLock *myLock; + NSMapTable *handles; /* Handles indexed by socket. */ + int listener; /* Descriptor to listen on. */ +} + +- (int) _listener; +- (const unsigned char *) _name; ++ (NSMessagePort*) _portWithName: (const unsigned char *)name + listener: (BOOL)shouldListen; + +- (void) addHandle: (GSMessageHandle*)handle forSend: (BOOL)send; +- (void) removeHandle: (GSMessageHandle*)handle; +- (void) handlePortMessage: (NSPortMessage*)m; + +@end + + #endif + #endif /* __NSPort_h_GNUSTEP_BASE_INCLUDE */ + diff --git a/Headers/gnustep/base/NSPortNameServer.h b/Headers/gnustep/base/NSPortNameServer.h index dd437b15c..81d879290 100644 --- a/Headers/gnustep/base/NSPortNameServer.h +++ b/Headers/gnustep/base/NSPortNameServer.h @@ -24,14 +24,13 @@ #ifndef __NSPortNameServer_h_GNUSTEP_BASE_INCLUDE #define __NSPortNameServer_h_GNUSTEP_BASE_INCLUDE -#include +#include +#include @class NSPort, NSString, NSMutableArray; @interface NSPortNameServer : NSObject { - NSMapTable *_portMap; /* Registered ports information. */ - NSMapTable *_nameMap; /* Registered names information. */ } + (id) systemDefaultPortNameServer; - (NSPort*) portForName: (NSString*)name; @@ -46,9 +45,25 @@ @interface NSPortNameServer (GNUstep) - (NSArray*) namesForPort: (NSPort*)port; /* return all names for port */ - (BOOL) removePort: (NSPort*)port; /* remove all names for port */ + +/* Remove the name if and only if it is registered by the given port. */ - (BOOL) removePort: (NSPort*)port forName: (NSString*)name; @end #endif + +@interface NSSocketPortNameServer : NSPortNameServer +{ + NSMapTable *_portMap; /* Registered ports information. */ + NSMapTable *_nameMap; /* Registered names information. */ +} ++ (id) sharedInstance; +@end + + +@interface NSMessagePortNameServer : NSPortNameServer ++ (id) sharedInstance; +@end + #endif diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 29bab09fa..6bc48a9b1 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -194,7 +194,6 @@ NSObject.m \ NSPage.m \ NSPipe.m \ NSPort.m \ -GSTcpPort.m \ NSPortCoder.m \ NSPortMessage.m \ NSPortNameServer.m \ @@ -206,6 +205,8 @@ NSRunLoop.m \ NSScanner.m \ NSSerializer.m \ NSSet.m \ +NSSocketPort.m \ +NSSocketPortNameServer.m \ NSString.m \ NSTask.m \ NSThread.m \ @@ -222,6 +223,12 @@ NSZone.m \ externs.m \ objc-load.m +ifneq ($(GNUSTEP_TARGET_OS), mingw32) +BASE_MFILES += \ +NSMessagePort.m \ +NSMessagePortNameServer.m +endif + ifeq ($(WITH_FFI),libffi) GNU_MFILES += cifframe.m BASE_MFILES += GSFFIInvocation.m diff --git a/Source/NSMessagePort.m b/Source/NSMessagePort.m new file mode 100644 index 000000000..654baab7d --- /dev/null +++ b/Source/NSMessagePort.m @@ -0,0 +1,1943 @@ +/** Implementation of network port object based on unix domain sockets + Copyright (C) 2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Based on code by: Andrew Kachites McCallum + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + */ + +#include "config.h" +#include "gnustep/base/preface.h" +#include "Foundation/NSArray.h" +#include "Foundation/NSNotification.h" +#include "Foundation/NSException.h" +#include "Foundation/NSRunLoop.h" +#include "Foundation/NSByteOrder.h" +#include "Foundation/NSData.h" +#include "Foundation/NSDate.h" +#include "Foundation/NSMapTable.h" +#include "Foundation/NSPortMessage.h" +#include "Foundation/NSPortNameServer.h" +#include "Foundation/NSLock.h" +#include "Foundation/NSThread.h" +#include "Foundation/NSConnection.h" +#include "Foundation/NSDebug.h" +#include "Foundation/NSPathUtilities.h" +#include +#include +#ifdef HAVE_SYS_SIGNAL_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include /* for gethostname() */ +#endif + +#ifndef __MINGW__ +#include /* for MAXHOSTNAMELEN */ +#include +#include +#include /* for inet_ntoa() */ +#endif /* !__MINGW__ */ +#include +#include +#include /* for strchr() */ +#include /* for strchr() */ +#include +#ifdef __MINGW__ +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +/* + * Stuff for setting the sockets into non-blocking mode. + */ +#ifdef __POSIX_SOURCE +#define NBLK_OPT O_NONBLOCK +#else +#define NBLK_OPT FNDELAY +#endif + +#include +#include +#if !defined(SIOCGIFCONF) || defined(__CYGWIN__) +#include +#ifndef SIOCGIFCONF +#include +#endif +#endif + +#if defined(__svr4__) +#include +#endif +#endif /* !__MINGW__ */ + +#ifdef __MINGW__ +#define close closesocket +#endif + + +static BOOL multi_threaded = NO; + +/* + * Largest chunk of data possible in DO + */ +static gsu32 maxDataLength = 10 * 1024 * 1024; + +#if 0 +#define M_LOCK(X) {NSDebugMLLog(@"NSMessagePort",@"lock %@",X); [X lock];} +#define M_UNLOCK(X) {NSDebugMLLog(@"NSMessagePort",@"unlock %@",X); [X unlock];} +#else +#define M_LOCK(X) {[X lock];} +#define M_UNLOCK(X) {[X unlock];} +#endif + +#define GS_CONNECTION_MSG 0 +#define NETBLOCK 8192 + +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + +/* + * Theory of operation + * + * + */ + + +/* Private interfaces */ + +/* + * The GSPortItemType constant is used to identify the type of data in + * each packet read. All data transmitted is in a packet, each packet + * has an initial packet type and packet length. + */ +typedef enum { + GSP_NONE, + GSP_PORT, /* Simple port item. */ + GSP_DATA, /* Simple data item. */ + GSP_HEAD /* Port message header + initial data. */ +} GSPortItemType; + +/* + * The GSPortItemHeader structure defines the header for each item transmitted. + * Its contents are transmitted in network byte order. + */ +typedef struct { + gsu32 type; /* A GSPortItemType as a 4-byte number. */ + gsu32 length; /* The length of the item (excluding header). */ +} GSPortItemHeader; + +/* + * The GSPortMsgHeader structure is at the start of any item of type GSP_HEAD. + * Its contents are transmitted in network byte order. + * Any additional data in the item is an NSData object. + * NB. additional data counts as part of the same item. + */ +typedef struct { + gsu32 mId; /* The ID for the message starting with this. */ + gsu32 nItems; /* Number of items (including this one). */ +} GSPortMsgHeader; + +typedef struct { + char version; + char addr[0]; /* name of the socket in the port directory */ +} GSPortInfo; + +/* + * Here is how data is transmitted over a socket - + * Initially the process making the connection sends an item of type + * GSP_PORT to tell the remote end what port is connecting to it. + * Therafter, all communication is via port messages. Each port message + * consists of an item of type GSP_HEAD followed by zero or more items + * of type GSP_PORT or GSP_DATA. The number of items in a port message + * is encoded in the 'nItems' field of the header. + */ + +typedef enum { + GS_H_UNCON = 0, // Currently idle and unconnected. + GS_H_TRYCON, // Trying connection (outgoing). + GS_H_ACCEPT, // Making initial connection (incoming). + GS_H_CONNECTED // Currently connected. +} GSHandleState; + +@interface GSMessageHandle : NSObject +{ + int desc; /* File descriptor for I/O. */ + unsigned wItem; /* Index of item being written. */ + NSMutableData *wData; /* Data object being written. */ + unsigned wLength; /* Ammount written so far. */ + NSMutableArray *wMsgs; /* Message in progress. */ + NSMutableData *rData; /* Buffer for incoming data */ + gsu32 rLength; /* Amount read so far. */ + gsu32 rWant; /* Amount desired. */ + NSMutableArray *rItems; /* Message in progress. */ + GSPortItemType rType; /* Type of data being read. */ + gsu32 rId; /* Id of incoming message. */ + unsigned nItems; /* Number of items to be read. */ + GSHandleState state; /* State of the handle. */ + unsigned int addrNum; /* Address number within host. */ +@public + NSRecursiveLock *myLock; /* Lock for this handle. */ + BOOL caller; /* Did we connect to other end? */ + BOOL valid; + NSMessagePort *recvPort; + NSMessagePort *sendPort; + struct sockaddr_un sockAddr; /* Far end of connection. */ +} + ++ (GSMessageHandle*) handleWithDescriptor: (int)d; +- (BOOL) connectToPort: (NSMessagePort*)aPort beforeDate: (NSDate*)when; +- (int) descriptor; +- (void) invalidate; +- (BOOL) isValid; +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode; +- (NSMessagePort*) recvPort; +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when; +- (NSMessagePort*) sendPort; +- (void) setState: (GSHandleState)s; +- (GSHandleState) state; +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode; +@end + + +/* + * Utility functions for encoding and decoding ports. + */ +static NSMessagePort* +decodePort(NSData *data) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + + pih = (GSPortItemHeader*)[data bytes]; + NSCAssert(GSSwapBigI32ToHost(pih->type) == GSP_PORT, + NSInternalInconsistencyException); + pi = (GSPortInfo*)&pih[1]; + if (pi->version != 0) + { + NSLog(@"Remote version of GNUstep is more recent than this one (%i)", + pi->version); + return nil; + } + + NSDebugFLLog(@"NSMessagePort", @"Decoded port as '%s'", pi->addr); + + return [NSMessagePort _portWithName: pi->addr + listener: NO]; +} + +static NSData* +newDataWithEncodedPort(NSMessagePort *port) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + NSMutableData *data; + unsigned plen; + const unsigned char *name = [port _name]; + + plen = 2 + strlen(name); + + data = [[NSMutableData alloc] initWithLength: sizeof(GSPortItemHeader)+plen]; + pih = (GSPortItemHeader*)[data mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_PORT); + pih->length = GSSwapHostI32ToBig(plen); + pi = (GSPortInfo*)&pih[1]; + strcpy(pi->addr, name); + + NSDebugFLLog(@"NSMessagePort", @"Encoded port as '%s'", pi->addr); + + return data; +} + + + +@implementation GSMessageHandle + +static Class mutableArrayClass; +static Class mutableDataClass; +static Class portMessageClass; +static Class runLoopClass; + + ++ (id) allocWithZone: (NSZone*)zone +{ + [NSException raise: NSGenericException + format: @"attempt to alloc a GSMessageHandle!"]; + return nil; +} + ++ (GSMessageHandle*) handleWithDescriptor: (int)d +{ + GSMessageHandle *handle; +#ifdef __MINGW__ + unsigned long dummy; +#else + int e; +#endif /* __MINGW__ */ + + if (d < 0) + { + NSLog(@"illegal descriptor (%d) for message handle", d); + return nil; + } +#ifdef __MINGW__ + dummy = 1; + if (ioctlsocket(d, FIONBIO, &dummy) < 0) + { + NSLog(@"unable to set non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } +#else /* !__MINGW__ */ + if ((e = fcntl(d, F_GETFL, 0)) >= 0) + { + e |= NBLK_OPT; + if (fcntl(d, F_SETFL, e) < 0) + { + NSLog(@"unable to set non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } + } + else + { + NSLog(@"unable to get non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } +#endif + handle = (GSMessageHandle*)NSAllocateObject(self, 0, NSDefaultMallocZone()); + handle->desc = d; + handle->wMsgs = [NSMutableArray new]; + if (multi_threaded == YES) + { + handle->myLock = [NSRecursiveLock new]; + } + handle->valid = YES; + return AUTORELEASE(handle); +} + ++ (void) initialize +{ + if (self == [GSMessageHandle class]) + { +#ifdef __MINGW__ + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 0); + WSAStartup(wVersionRequested, &wsaData); +#else + /* + * If SIGPIPE is not ignored, we will abort on any attempt to + * write to a pipe/socket that has been closed by the other end! + */ + signal(SIGPIPE, SIG_IGN); +#endif + mutableArrayClass = [NSMutableArray class]; + mutableDataClass = [NSMutableData class]; + portMessageClass = [NSPortMessage class]; + runLoopClass = [NSRunLoop class]; + } +} + +- (BOOL) connectToPort: (NSMessagePort*)aPort beforeDate: (NSDate*)when +{ + NSRunLoop *l; + const unsigned char *name; + + M_LOCK(myLock); + NSDebugMLLog(@"NSMessagePort", @"Connecting on 0x%x in thread 0x%x before %@", + self, GSCurrentThread(), when); + if (state != GS_H_UNCON) + { + BOOL result; + + if (state == GS_H_CONNECTED) /* Already connected. */ + { + NSLog(@"attempting connect on connected handle"); + result = YES; + } + else if (state == GS_H_ACCEPT) /* Impossible. */ + { + NSLog(@"attempting connect with accepting handle"); + result = NO; + } + else /* Already connecting. */ + { + NSLog(@"attempting connect while connecting"); + result = NO; + } + M_UNLOCK(myLock); + return result; + } + + if (recvPort == nil || aPort == nil) + { + NSLog(@"attempting connect with port(s) unset"); + M_UNLOCK(myLock); + return NO; /* impossible. */ + } + + + name = [aPort _name]; + memset(&sockAddr, '\0', sizeof(sockAddr)); + sockAddr.sun_family = AF_LOCAL; + strncpy(sockAddr.sun_path, name, sizeof(sockAddr.sun_path)); + + if (connect(desc, (struct sockaddr*)&sockAddr, SUN_LEN(&sockAddr)) < 0) + { +#ifdef __MINGW__ + if (WSAGetLastError() != WSAEWOULDBLOCK) +#else + if (errno != EINPROGRESS) +#endif + { + NSLog(@"unable to make connection to %s - %s", + sockAddr.sun_path, + GSLastErrorStr(errno)); + M_UNLOCK(myLock); + return NO; + } + } + + state = GS_H_TRYCON; + l = [NSRunLoop currentRunLoop]; + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSConnectionReplyMode]; + [l addEvent: (void*)(gsaddr)desc + type: ET_EDESC + watcher: self + forMode: NSConnectionReplyMode]; + while (valid == YES && state == GS_H_TRYCON + && [when timeIntervalSinceNow] > 0) + { + [l runMode: NSConnectionReplyMode beforeDate: when]; + } + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: NSConnectionReplyMode + all: NO]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_EDESC + forMode: NSConnectionReplyMode + all: NO]; + + if (state == GS_H_TRYCON) + { + state = GS_H_UNCON; + addrNum = 0; + M_UNLOCK(myLock); + return NO; /* Timed out */ + } + else if (state == GS_H_UNCON) + { + addrNum = 0; + state = GS_H_UNCON; + M_UNLOCK(myLock); + return NO; + } + else + { + addrNum = 0; + caller = YES; + [aPort addHandle: self forSend: YES]; + M_UNLOCK(myLock); + return YES; + } +} + +- (void) dealloc +{ + [self gcFinalize]; + DESTROY(rData); + DESTROY(rItems); + DESTROY(wMsgs); + DESTROY(myLock); + [super dealloc]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat: @"", + self, desc, sockAddr.sun_path]; +} + +- (int) descriptor +{ + return desc; +} + +- (void) gcFinalize +{ + [self invalidate]; + (void)close(desc); + desc = -1; +} + +- (void) invalidate +{ + if (valid == YES) + { + M_LOCK(myLock); + if (valid == YES) + { + NSRunLoop *l; + + valid = NO; + l = [runLoopClass currentRunLoop]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_RDESC + forMode: nil + all: YES]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: nil + all: YES]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_EDESC + forMode: nil + all: YES]; + NSDebugMLLog(@"NSMessagePort", @"invalidated 0x%x in thread 0x%x", + self, GSCurrentThread()); + [[self recvPort] removeHandle: self]; + [[self sendPort] removeHandle: self]; + } + M_UNLOCK(myLock); + } +} + +- (BOOL) isValid +{ + return valid; +} + +- (NSMessagePort*) recvPort +{ + if (recvPort == nil) + return nil; + else + return GS_GC_UNHIDE(recvPort); +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + NSDebugMLLog(@"NSMessagePort_details", @"received %s event on 0x%x in thread 0x%x", + type == ET_RPORT ? "read" : "write", self, GSCurrentThread()); + /* + * If we have been invalidated (desc < 0) then we should ignore this + * event and remove ourself from the runloop. + */ + if (desc < 0) + { + NSRunLoop *l = [runLoopClass currentRunLoop]; + + [l removeEvent: data + type: ET_WDESC + forMode: mode + all: YES]; + [l removeEvent: data + type: ET_EDESC + forMode: mode + all: YES]; + return; + } + + M_LOCK(myLock); + + if (type == ET_RPORT) + { + unsigned want; + void *bytes; + int res; + + /* + * Make sure we have a buffer big enough to hold all the data we are + * expecting, or NETBLOCK bytes, whichever is greater. + */ + if (rData == nil) + { + rData = [[mutableDataClass alloc] initWithLength: NETBLOCK]; + rWant = sizeof(GSPortItemHeader); + rLength = 0; + want = NETBLOCK; + } + else + { + want = [rData length]; + if (want < rWant) + { + want = rWant; + [rData setLength: want]; + } + if (want < NETBLOCK) + { + want = NETBLOCK; + [rData setLength: want]; + } + } + + /* + * Now try to fill the buffer with data. + */ + bytes = [rData mutableBytes]; +#ifdef __MINGW__ + res = recv(desc, bytes + rLength, want - rLength, 0); +#else + res = read(desc, bytes + rLength, want - rLength); +#endif + if (res <= 0) + { + if (res == 0) + { + NSDebugMLLog(@"NSMessagePort", @"read eof on 0x%x in thread 0x%x", + self, GSCurrentThread()); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + else if (errno != EINTR && errno != EAGAIN) + { + NSDebugMLLog(@"NSMessagePort", + @"read failed - %s on 0x%x in thread 0x%x", + GSLastErrorStr(errno), self, GSCurrentThread()); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + res = 0; /* Interrupted - continue */ + } + NSDebugMLLog(@"NSMessagePort_details", @"read %d bytes on 0x%x in thread 0x%x", + res, self, GSCurrentThread()); + rLength += res; + + while (valid == YES && rLength >= rWant) + { + BOOL shouldDispatch = NO; + + switch (rType) + { + case GSP_NONE: + { + GSPortItemHeader *h; + unsigned l; + + /* + * We have read an item header - set up to read the + * remainder of the item. + */ + h = (GSPortItemHeader*)bytes; + rType = GSSwapBigI32ToHost(h->type); + l = GSSwapBigI32ToHost(h->length); + if (rType == GSP_PORT) + { + if (l > 512) + { + NSLog(@"%@ - unreasonable length (%u) for port", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * For a port, we leave the item header in the data + * so that our decode function can check length info. + */ + rWant += l; + } + else if (rType == GSP_DATA) + { + if (l == 0) + { + NSData *d; + + /* + * For a zero-length data chunk, we create an empty + * data object and add it to the current message. + */ + rType = GSP_NONE; /* ready for a new item */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + d = [mutableDataClass new]; + [rItems addObject: d]; + RELEASE(d); + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + else + { + if (l > maxDataLength) + { + NSLog(@"%@ - unreasonable length (%u) for data", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * If not a port or zero length data, + * we discard the data read so far and fill the + * data object with the data item from the msg. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = l; + } + } + else if (rType == GSP_HEAD) + { + if (l > maxDataLength) + { + NSLog(@"%@ - unreasonable length (%u) for data", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * If not a port or zero length data, + * we discard the data read so far and fill the + * data object with the data item from the msg. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = l; + } + else + { + NSLog(@"%@ - bad data received on port handle, rType=%i", self, rType); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + } + break; + + case GSP_HEAD: + { + GSPortMsgHeader *h; + + rType = GSP_NONE; /* ready for a new item */ + /* + * We have read a message header - set up to read the + * remainder of the message. + */ + h = (GSPortMsgHeader*)bytes; + rId = GSSwapBigI32ToHost(h->mId); + nItems = GSSwapBigI32ToHost(h->nItems); + NSAssert(nItems >0, NSInternalInconsistencyException); + rItems + = [mutableArrayClass allocWithZone: NSDefaultMallocZone()]; + rItems = [rItems initWithCapacity: nItems]; + if (rWant > sizeof(GSPortMsgHeader)) + { + NSData *d; + + /* + * The first data item of the message was included in + * the header - so add it to the rItems array. + */ + rWant -= sizeof(GSPortMsgHeader); + d = [mutableDataClass alloc]; + d = [d initWithBytes: bytes + sizeof(GSPortMsgHeader) + length: rWant]; + [rItems addObject: d]; + RELEASE(d); + rWant += sizeof(GSPortMsgHeader); + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + if (nItems == 1) + { + shouldDispatch = YES; + } + } + else + { + /* + * want to read another item + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + } + } + break; + + case GSP_DATA: + { + NSData *d; + + rType = GSP_NONE; /* ready for a new item */ + d = [mutableDataClass allocWithZone: NSDefaultMallocZone()]; + d = [d initWithBytes: bytes length: rWant]; + [rItems addObject: d]; + RELEASE(d); + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + break; + + case GSP_PORT: + { + NSMessagePort *p; + + rType = GSP_NONE; /* ready for a new item */ + p = decodePort(rData); + if (p == nil) + { + NSLog(@"%@ - unable to decode remote port", self); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * Set up to read another item header. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + + if (state == GS_H_ACCEPT) + { + /* + * This is the initial port information on a new + * connection - set up port relationships. + */ + state = GS_H_CONNECTED; + [p addHandle: self forSend: YES]; + } + else + { + /* + * This is a port within a port message - add + * it to the message components. + */ + [rItems addObject: p]; + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + } + break; + } + + if (shouldDispatch == YES) + { + NSPortMessage *pm; + NSMessagePort *rp = [self recvPort]; + + pm = [portMessageClass allocWithZone: NSDefaultMallocZone()]; + pm = [pm initWithSendPort: [self sendPort] + receivePort: rp + components: rItems]; + [pm setMsgid: rId]; + rId = 0; + DESTROY(rItems); + NSDebugMLLog(@"NSMessagePort_details", + @"got message %@ on 0x%x in thread 0x%x", + pm, self, GSCurrentThread()); + RETAIN(rp); + M_UNLOCK(myLock); + NS_DURING + { + [rp handlePortMessage: pm]; + } + NS_HANDLER + { + M_LOCK(myLock); + RELEASE(pm); + RELEASE(rp); + [localException raise]; + } + NS_ENDHANDLER + M_LOCK(myLock); + RELEASE(pm); + RELEASE(rp); + bytes = [rData mutableBytes]; + } + } + } + else + { + if (state == GS_H_TRYCON) /* Connection attempt. */ + { + int res = 0; + int len = sizeof(res); + + if (getsockopt(desc, SOL_SOCKET, SO_ERROR, (char*)&res, &len) == 0 + && res != 0) + { + state = GS_H_UNCON; + NSLog(@"connect attempt failed - %s", GSLastErrorStr(res)); + } + else + { + NSData *d = newDataWithEncodedPort([self recvPort]); + +#ifdef __MINGW__ + len = send(desc, [d bytes], [d length], 0); +#else + len = write(desc, [d bytes], [d length]); +#endif + if (len == (int)[d length]) + { + NSDebugMLLog(@"NSMessagePort_details", + @"wrote %d bytes on 0x%x in thread 0x%x", + len, self, GSCurrentThread()); + state = GS_H_CONNECTED; + } + else + { + state = GS_H_UNCON; + NSLog(@"connect write attempt failed - %s", + GSLastErrorStr(errno)); + } + RELEASE(d); + } + } + else + { + int res; + unsigned l; + const void *b; + + if (wData == nil) + { + if ([wMsgs count] > 0) + { + NSArray *components = [wMsgs objectAtIndex: 0]; + + wData = [components objectAtIndex: wItem++]; + wLength = 0; + } + else + { + // NSLog(@"No messages to write on 0x%x.", self); + return; + } + } + b = [wData bytes]; + l = [wData length]; +#ifdef __MINGW__ + res = send(desc, b + wLength, l - wLength, 0); +#else + res = write(desc, b + wLength, l - wLength); +#endif + if (res < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + NSLog(@"write attempt failed - %s", GSLastErrorStr(errno)); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + } + else + { + NSDebugMLLog(@"NSMessagePort_details", + @"wrote %d bytes on 0x%x in thread 0x%x", + res, self, GSCurrentThread()); + wLength += res; + if (wLength == l) + { + NSArray *components; + + /* + * We have completed a data item so see what is + * left of the message components. + */ + components = [wMsgs objectAtIndex: 0]; + wLength = 0; + if ([components count] > wItem) + { + /* + * More to write - get next item. + */ + wData = [components objectAtIndex: wItem++]; + } + else + { + NSRunLoop *l = [runLoopClass currentRunLoop]; + + /* + * message completed - remove from list. + */ + NSDebugMLLog(@"NSMessagePort_details", + @"completed 0x%x on 0x%x in thread 0x%x", + components, self, GSCurrentThread()); + wData = nil; + wItem = 0; + [wMsgs removeObjectAtIndex: 0]; + + [l removeEvent: data + type: ET_WDESC + forMode: mode + all: NO]; + [l removeEvent: data + type: ET_EDESC + forMode: mode + all: NO]; + } + } + } + } + } + + M_UNLOCK(myLock); +} + +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when +{ + NSRunLoop *l; + BOOL sent = NO; + + NSAssert([components count] > 0, NSInternalInconsistencyException); + NSDebugMLLog(@"NSMessagePort_details", + @"Sending message 0x%x %@ on 0x%x(%d) in thread 0x%x before %@", + components, components, self, desc, GSCurrentThread(), when); + M_LOCK(myLock); + [wMsgs addObject: components]; + + l = [runLoopClass currentRunLoop]; + + RETAIN(self); + + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSConnectionReplyMode]; + while (valid == YES + && [wMsgs indexOfObjectIdenticalTo: components] != NSNotFound + && [when timeIntervalSinceNow] > 0) + { + M_UNLOCK(myLock); + [l runMode: NSConnectionReplyMode beforeDate: when]; + M_LOCK(myLock); + } + /* + * NB. We will remove ourself from the run loop when the message send + * is completed, so we don't need to do it here. + */ + if ([wMsgs indexOfObjectIdenticalTo: components] == NSNotFound) + { + sent = YES; + } + M_UNLOCK(myLock); + RELEASE(self); + NSDebugMLLog(@"NSMessagePort_details", + @"Message send 0x%x on 0x%x in thread 0x%x status %d", + components, self, GSCurrentThread(), sent); + return sent; +} + +- (NSMessagePort*) sendPort +{ + if (sendPort == nil) + return nil; + else if (caller == YES) + return GS_GC_UNHIDE(sendPort); // We called, so port is not retained. + else + return sendPort; // Retained port. +} + +- (void) setState: (GSHandleState)s +{ + state = s; +} + +- (GSHandleState) state +{ + return state; +} + +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode +{ + return nil; +} + +@end + + + +@interface NSMessagePort (RunLoop) +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode; +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode; +@end + + +@implementation NSMessagePort + +static NSRecursiveLock *messagePortLock = nil; + +/* +Maps NSData objects with the socket name to NSMessagePort objects. +*/ +static NSMapTable *messagePortMap = 0; +static Class messagePortClass; + + +static void clean_up_sockets(void) +{ + NSMessagePort *port; + NSData *name; + NSMapEnumerator mEnum; + + mEnum = NSEnumerateMapTable(messagePortMap); + while (NSNextMapEnumeratorPair(&mEnum, (void *)&name, (void *)&port)) + { + if ([port _listener] != -1) + unlink([name bytes]); + } + NSEndMapTableEnumeration(&mEnum); +} + + +/* + * When the system becomes multithreaded, we set a flag to say so and + * make sure that port and handle locking is enabled. + */ ++ (void) _becomeThreaded: (NSNotification*)notification +{ + if (multi_threaded == NO) + { + NSMapEnumerator mEnum; + NSMessagePort *p; + void *dummy; + + multi_threaded = YES; + if (messagePortLock == nil) + { + messagePortLock = [NSRecursiveLock new]; + } + mEnum = NSEnumerateMapTable(messagePortMap); + while (NSNextMapEnumeratorPair(&mEnum, &dummy, (void**)&p)) + { + if ([p isValid] == YES) + { + NSMapEnumerator hEnum; + GSMessageHandle *h; + + if (p->myLock == nil) + { + p->myLock = [NSRecursiveLock new]; + } + hEnum = NSEnumerateMapTable(p->handles); + while (NSNextMapEnumeratorPair(&hEnum, &dummy, (void**)&h)) + { + if ([h isValid] == YES && h->myLock == nil) + { + h->myLock = [NSRecursiveLock new]; + } + } + NSEndMapTableEnumeration(&hEnum); + } + } + NSEndMapTableEnumeration(&mEnum); + } + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSWillBecomeMultiThreadedNotification + object: nil]; +} + +#if NEED_WORD_ALIGNMENT +static unsigned wordAlign; +#endif + ++ (void) initialize +{ + if (self == [NSMessagePort class]) + { +#if NEED_WORD_ALIGNMENT + wordAlign = objc_alignof_type(@encode(gsu32)); +#endif + messagePortClass = self; + messagePortMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + + if ([NSThread isMultiThreaded]) + { + [self _becomeThreaded: nil]; + } + else + { + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(_becomeThreaded:) + name: NSWillBecomeMultiThreadedNotification + object: nil]; + } + atexit(clean_up_sockets); + } +} + ++ (id) new +{ +static int unique_index = 0; + NSString *path; + + path = NSTemporaryDirectory(); + + path = [path stringByAppendingPathComponent: @"NSMessagePort"]; + mkdir([path fileSystemRepresentation], 0700); + + path = [path stringByAppendingPathComponent: @"ports"]; + mkdir([path fileSystemRepresentation], 0700); + + M_LOCK(messagePortLock); + path = [path stringByAppendingPathComponent: + [NSString stringWithFormat: @"%i.%i", getpid(), unique_index++]]; + M_UNLOCK(messagePortLock); + + return RETAIN([self _portWithName: [path fileSystemRepresentation] + listener: YES]); +} + +/* + * This is the preferred initialisation method for NSMessagePort + * + * 'name' is the name of the socket in the port directory + */ ++ (NSMessagePort*) _portWithName: (const unsigned char *)socketName + listener: (BOOL)shouldListen +{ + unsigned i; + NSMessagePort *port = nil; + NSData *theName = [[NSData alloc] initWithBytes: socketName length: strlen(socketName)+1]; + + M_LOCK(messagePortLock); + + /* + * First try to find a pre-existing port. + */ + port = (NSMessagePort*)NSMapGet(messagePortMap, theName); + + if (port == nil) + { + port = (NSMessagePort*)NSAllocateObject(self, 0, NSDefaultMallocZone()); + port->name = theName; + port->listener = -1; + port->handles = NSCreateMapTable(NSIntMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + if (multi_threaded == YES) + { + port->myLock = [NSRecursiveLock new]; + } + port->_is_valid = YES; + + if (shouldListen == YES) + { + int desc; + struct sockaddr_un sockaddr; + + /* + * Creating a new port on the local host - so we must create a + * listener socket to accept incoming connections. + */ + memset(&sockaddr, '\0', sizeof(sockaddr)); + sockaddr.sun_family = AF_LOCAL; + strncpy(sockaddr.sun_path, socketName, sizeof(sockaddr.sun_path)); + + /* + * Need size of buffer for getsockbyname() later. + */ + i = sizeof(sockaddr); + + if ((desc = socket(PF_LOCAL, SOCK_STREAM, PF_UNSPEC)) < 0) + { + NSLog(@"unable to create socket - %s", GSLastErrorStr(errno)); + DESTROY(port); + } + else if (bind(desc, (struct sockaddr *)&sockaddr, + sizeof(sockaddr)) < 0) + { + NSLog(@"unable to bind to %s - %s", + sockaddr.sun_path, GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else if (listen(desc, 5) < 0) + { + NSLog(@"unable to listen on port - %s", GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else if (getsockname(desc, (struct sockaddr*)&sockaddr, &i) < 0) + { + NSLog(@"unable to get socket name - %s", GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else + { + /* + * Set up the listening descriptor and the actual message port + * number (which will have been set to a real port number when + * we did the 'bind' call. + */ + port->listener = desc; + /* + * Make sure we have the map table for this port. + */ + NSMapInsert(messagePortMap, (void*)theName, (void*)port); + NSDebugMLLog(@"NSMessagePort", @"Created listening port: %@", port); + } + } + else + { + /* + * Make sure we have the map table for this port. + */ + NSMapInsert(messagePortMap, (void*)theName, (void*)port); + NSDebugMLLog(@"NSMessagePort", @"Created speaking port: %@", port); + } + } + else + { + RELEASE(theName); + RETAIN(port); + NSDebugMLLog(@"NSMessagePort", @"Using pre-existing port: %@", port); + } + IF_NO_GC(AUTORELEASE(port)); + + M_UNLOCK(messagePortLock); + return port; +} + +- (void) addHandle: (GSMessageHandle*)handle forSend: (BOOL)send +{ + M_LOCK(myLock); + if (send == YES) + { + if (handle->caller == YES) + handle->sendPort = GS_GC_HIDE(self); + else + ASSIGN(handle->sendPort, self); + } + else + { + handle->recvPort = GS_GC_HIDE(self); + } + NSMapInsert(handles, (void*)(gsaddr)[handle descriptor], (void*)handle); + M_UNLOCK(myLock); +} + +- (id) copyWithZone: (NSZone*)zone +{ + return RETAIN(self); +} + +- (void) dealloc +{ + [self gcFinalize]; + DESTROY(name); + [super dealloc]; +} + +- (NSString*) description +{ + NSString *desc; + + desc = [NSString stringWithFormat: @"", + self, [name bytes]]; + return desc; +} + +- (void) gcFinalize +{ + NSDebugMLLog(@"NSMessagePort", @"NSMessagePort 0x%x finalized", self); + [self invalidate]; +} + +/* + * This is a callback method used by the NSRunLoop class to determine which + * descriptors to watch for the port. + */ +- (void) getFds: (int*)fds count: (int*)count +{ + NSMapEnumerator me; + int sock; + GSMessageHandle *handle; + id recvSelf; + + M_LOCK(myLock); + + /* + * Make sure there is enough room in the provided array. + */ + NSAssert(*count > (int)NSCountMapTable(handles), + NSInternalInconsistencyException); + + /* + * Put in our listening socket. + */ + *count = 0; + if (listener >= 0) + { + fds[(*count)++] = listener; + } + + /* + * Enumerate all our socket handles, and put them in as long as they + * are to be used for receiving. + */ + recvSelf = GS_GC_HIDE(self); + me = NSEnumerateMapTable(handles); + while (NSNextMapEnumeratorPair(&me, (void*)&sock, (void*)&handle)) + { + if (handle->recvPort == recvSelf) + { + fds[(*count)++] = sock; + } + } + NSEndMapTableEnumeration(&me); + M_UNLOCK(myLock); +} + +- (GSMessageHandle*) handleForPort: (NSMessagePort*)recvPort beforeDate: (NSDate*)when +{ + NSMapEnumerator me; + int sock; + int opt = 1; + GSMessageHandle *handle = nil; + + M_LOCK(myLock); + /* + * Enumerate all our socket handles, and look for one with port. + */ + me = NSEnumerateMapTable(handles); + while (NSNextMapEnumeratorPair(&me, (void*)&sock, (void*)&handle)) + { + if ([handle recvPort] == recvPort) + { + M_UNLOCK(myLock); + NSEndMapTableEnumeration(&me); + return handle; + } + } + NSEndMapTableEnumeration(&me); + + /* + * Not found ... create a new handle. + */ + handle = nil; + if ((sock = socket(PF_LOCAL, SOCK_STREAM, PF_UNSPEC)) < 0) + { + NSLog(@"unable to create socket - %s", GSLastErrorStr(errno)); + } +#ifndef BROKEN_SO_REUSEADDR + /* + * Under decent systems, SO_REUSEADDR means that the port can be reused + * immediately that this process exits. Under some it means + * that multiple processes can serve the same port simultaneously. + * We don't want that broken behavior! + */ + else if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, + sizeof(opt)) < 0) + { + (void)close(sock); + NSLog(@"unable to set reuse on socket - %s", GSLastErrorStr(errno)); + } +#endif + else if ((handle = [GSMessageHandle handleWithDescriptor: sock]) == nil) + { + (void)close(sock); + NSLog(@"unable to create GSMessageHandle - %s", GSLastErrorStr(errno)); + } + else + { + [recvPort addHandle: handle forSend: NO]; + } + M_UNLOCK(myLock); + /* + * If we succeeded in creating a new handle - connect to remote host. + */ + if (handle != nil) + { + if ([handle connectToPort: self beforeDate: when] == NO) + { + [handle invalidate]; + handle = nil; + } + } + return handle; +} + +- (void) handlePortMessage: (NSPortMessage*)m +{ + id d = [self delegate]; + + if (d == nil) + { + NSDebugMLLog(@"NSMessagePort", @"No delegate to handle incoming message", 0); + return; + } + if ([d respondsToSelector: @selector(handlePortMessage:)] == NO) + { + NSDebugMLLog(@"NSMessagePort", @"delegate doesn't handle messages", 0); + return; + } + [d handlePortMessage: m]; +} + +- (unsigned) hash +{ + return [name hash]; +} + +- (id) init +{ + RELEASE(self); + self = [messagePortClass new]; + return self; +} + +- (void) invalidate +{ + if ([self isValid] == YES) + { + M_LOCK(myLock); + + if ([self isValid] == YES) + { + NSArray *handleArray; + unsigned i; + + M_LOCK(messagePortLock); + if (listener >= 0) + { + (void) close(listener); + unlink([name bytes]); + listener = -1; + } + NSMapRemove(messagePortMap, (void*)name); + M_UNLOCK(messagePortLock); + + if (handles != 0) + { + handleArray = NSAllMapTableValues(handles); + i = [handleArray count]; + while (i-- > 0) + { + GSMessageHandle *handle = [handleArray objectAtIndex: i]; + + [handle invalidate]; + } + /* + * We permit mutual recursive invalidation, so the handles map + * may already have been destroyed. + */ + if (handles != 0) + { + NSFreeMapTable(handles); + handles = 0; + } + } + [[NSMessagePortNameServer sharedInstance] removePort: self]; + [super invalidate]; + } + M_UNLOCK(myLock); + } +} + +- (BOOL) isEqual: (id)anObject +{ + if (anObject == self) + { + return YES; + } + if ([anObject class] == [self class]) + { + NSMessagePort *o = (NSMessagePort*)anObject; + + return [o->name isEqual: name]; + } + return NO; +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + int desc = (int)(gsaddr)extra; + GSMessageHandle *handle; + + if (desc == listener) + { + struct sockaddr_un sockAddr; + int size = sizeof(sockAddr); + + desc = accept(listener, (struct sockaddr*)&sockAddr, &size); + if (desc < 0) + { + NSDebugMLLog(@"NSMessagePort", @"accept failed - handled in other thread?"); + } + else + { + /* + * Create a handle for the socket and set it up so we are its + * receiving port, and it's waiting to get the port name from + * the other end. + */ + handle = [GSMessageHandle handleWithDescriptor: desc]; + memcpy(&handle->sockAddr, &sockAddr, sizeof(sockAddr)); + + [handle setState: GS_H_ACCEPT]; + [self addHandle: handle forSend: NO]; + } + } + else + { + M_LOCK(myLock); + handle = (GSMessageHandle*)NSMapGet(handles, (void*)(gsaddr)desc); + IF_NO_GC(AUTORELEASE(RETAIN(handle))); + M_UNLOCK(myLock); + if (handle == nil) + { + const char *t; + + if (type == ET_RDESC) t = "rdesc"; + else if (type == ET_WDESC) t = "wdesc"; + else if (type == ET_EDESC) t = "edesc"; + else if (type == ET_RPORT) t = "rport"; + else t = "unknown"; + NSLog(@"No handle for event %s on descriptor %d", t, desc); + [[runLoopClass currentRunLoop] removeEvent: extra + type: type + forMode: mode + all: YES]; + } + else + { + [handle receivedEvent: data type: type extra: extra forMode: mode]; + } + } +} + +/* + * This is called when a tcp/ip socket connection is broken. We remove the + * connection handle from this port and, if this was the last handle to a + * remote port, we invalidate the port. + */ +- (void) removeHandle: (GSMessageHandle*)handle +{ + M_LOCK(myLock); + if ([handle sendPort] == self) + { + if (handle->caller != YES) + { + /* + * This is a handle for a send port, and the handle was not formed + * by calling the remote process, so this port object must have + * been created to deal with an incoming connection and will have + * been retained - we must therefore release this port since the + * handle no longer uses it. + */ + AUTORELEASE(self); + } + handle->sendPort = nil; + } + if ([handle recvPort] == self) + { + handle->recvPort = nil; + } + NSMapRemove(handles, (void*)(gsaddr)[handle descriptor]); + if (listener < 0 && NSCountMapTable(handles) == 0) + { + [self invalidate]; + } + M_UNLOCK(myLock); +} + +/* + * This returns the amount of space that a port coder should reserve at the + * start of its encoded data so that the NSMessagePort can insert header info + * into the data. + * The idea is that a message consisting of a single data item with space at + * the start can be written directly without having to copy data to another + * buffer etc. + */ +- (unsigned int) reservedSpaceLength +{ + return sizeof(GSPortItemHeader) + sizeof(GSPortMsgHeader); +} + +- (BOOL) sendBeforeDate: (NSDate*)when + msgid: (int)msgId + components: (NSMutableArray*)components + from: (NSPort*)receivingPort + reserved: (unsigned)length +{ + BOOL sent = NO; + GSMessageHandle *h; + unsigned rl; + + if ([self isValid] == NO) + { + return NO; + } + if ([components count] == 0) + { + NSLog(@"empty components sent"); + return NO; + } + /* + * If the reserved length in the first data object is wrong - we have to + * fail, unless it's zero, in which case we can insert a data object for + * the header. + */ + rl = [self reservedSpaceLength]; + if (length != 0 && length != rl) + { + NSLog(@"bad reserved length - %u", length); + return NO; + } + if ([receivingPort isKindOfClass: messagePortClass] == NO) + { + NSLog(@"woah there - receiving port is not the correct type"); + return NO; + } + + h = [self handleForPort: (NSMessagePort*)receivingPort beforeDate: when]; + if (h != nil) + { + NSMutableData *header; + unsigned hLength; + unsigned l; + GSPortItemHeader *pih; + GSPortMsgHeader *pmh; + unsigned c = [components count]; + unsigned i; + BOOL pack = YES; + + /* + * Ok - ensure we have space to insert header info. + */ + if (length == 0 && rl != 0) + { + header = [[mutableDataClass alloc] initWithCapacity: NETBLOCK]; + + [header setLength: rl]; + [components insertObject: header atIndex: 0]; + RELEASE(header); + } + + header = [components objectAtIndex: 0]; + /* + * The Item header contains the item type and the length of the + * data in the item (excluding the item header itsself). + */ + hLength = [header length]; + l = hLength - sizeof(GSPortItemHeader); + pih = (GSPortItemHeader*)[header mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_HEAD); + pih->length = GSSwapHostI32ToBig(l); + + /* + * The message header contains the message Id and the original count + * of components in the message (excluding any extra component added + * simply to hold the header). + */ + pmh = (GSPortMsgHeader*)&pih[1]; + pmh->mId = GSSwapHostI32ToBig(msgId); + pmh->nItems = GSSwapHostI32ToBig(c); + + /* + * Now insert item header information as required. + * Pack as many items into the initial data object as possible, up to + * a maximum of NETBLOCK bytes. This is to try to get a single, + * efficient write operation if possible. + */ + for (i = 1; i < c; i++) + { + id o = [components objectAtIndex: i]; + + if ([o isKindOfClass: [NSData class]]) + { + GSPortItemHeader *pih; + unsigned h = sizeof(GSPortItemHeader); + unsigned l = [o length]; + void *b; + + if (pack == YES && hLength + l + h <= NETBLOCK) + { + [header setLength: hLength + l + h]; + b = [header mutableBytes]; + b += hLength; +#if NEED_WORD_ALIGNMENT + /* + * When packing data, an item may not be aligned on a + * word boundary, so we work with an aligned buffer + * and use memcmpy() + */ + if ((hLength % wordAlign) != 0) + { + GSPortItemHeader itemHeader; + + pih = (GSPortItemHeader*)&itemHeader; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + memcpy(b, (void*)pih, h); + } + else + { + pih = (GSPortItemHeader*)b; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + } +#else + pih = (GSPortItemHeader*)b; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); +#endif + memcpy(b+h, [o bytes], l); + [components removeObjectAtIndex: i--]; + c--; + hLength += l + h; + } + else + { + NSMutableData *d; + + pack = NO; + d = [[NSMutableData alloc] initWithLength: l + h]; + b = [d mutableBytes]; + pih = (GSPortItemHeader*)b; + memcpy(b+h, [o bytes], l); + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + [components replaceObjectAtIndex: i + withObject: d]; + RELEASE(d); + } + } + else if ([o isKindOfClass: messagePortClass]) + { + NSData *d = newDataWithEncodedPort(o); + unsigned dLength = [d length]; + + if (pack == YES && hLength + dLength <= NETBLOCK) + { + void *b; + + [header setLength: hLength + dLength]; + b = [header mutableBytes]; + b += hLength; + hLength += dLength; + memcpy(b, [d bytes], dLength); + [components removeObjectAtIndex: i--]; + c--; + } + else + { + pack = NO; + [components replaceObjectAtIndex: i withObject: d]; + } + RELEASE(d); + } + } + + /* + * Now send the message. + */ + sent = [h sendMessage: components beforeDate: when]; + } + return sent; +} + +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode +{ + return nil; +} + + +-(const unsigned char *) _name +{ + return [name bytes]; +} + +-(int) _listener +{ + return listener; +} + +@end + diff --git a/Source/NSMessagePortNameServer.m b/Source/NSMessagePortNameServer.m new file mode 100644 index 000000000..8bf4109f0 --- /dev/null +++ b/Source/NSMessagePortNameServer.m @@ -0,0 +1,361 @@ +#include "Foundation/NSPortNameServer.h" + +#include "Foundation/NSAutoreleasePool.h" +#include "Foundation/NSDebug.h" +#include "Foundation/NSException.h" +#include "Foundation/NSLock.h" +#include "Foundation/NSMapTable.h" +#include "Foundation/NSPathUtilities.h" +#include "Foundation/NSPort.h" + +#include +#include +#include +#include +#include +#include + + +static NSRecursiveLock *serverLock = nil; +static NSMessagePortNameServer *defaultServer = nil; + +/* +Maps NSMessagePort objects to NSMutableArray:s of NSString:s. The array +is an array of names the port has been registered under by _us_. + +Note that this map holds the names the port has been registered under at +some time. If the name is been unregistered by some other program, we can't +update the table, so we have to deal with the case where the array contains +names that the port isn't registered under. + +Since we _have_to_ deal with this anyway, we handle it in -removePort: and +-removePort:forName:, and we don't bother removing entries in the map when +unregistering a name not for a specific port. +*/ +static NSMapTable portToNamesMap; + + +@interface NSMessagePortNameServer (private) ++(NSString *) _pathForName: (NSString *)name; +@end + + +static void clean_up_names(void) +{ + CREATE_AUTORELEASE_POOL(arp); + NSMapEnumerator mEnum; + NSMessagePort *port; + NSString *name; + + mEnum = NSEnumerateMapTable(portToNamesMap); + while (NSNextMapEnumeratorPair(&mEnum, (void *)&port, (void *)&name)) + { + [defaultServer removePort: port]; + } + NSEndMapTableEnumeration(&mEnum); + DESTROY(arp); +} + + +@implementation NSMessagePortNameServer + ++ (void) initialize +{ + if (self == [NSMessagePortNameServer class]) + { + serverLock = [NSRecursiveLock new]; + portToNamesMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + atexit(clean_up_names); + } +} + ++ (id) sharedInstance +{ + if (defaultServer == nil) + { + NSMessagePortNameServer *s; + + [serverLock lock]; + if (defaultServer) + { + [serverLock unlock]; + return defaultServer; + } + s = (NSMessagePortNameServer *)NSAllocateObject(self, 0, NSDefaultMallocZone()); + defaultServer = s; + [serverLock unlock]; + } + return defaultServer; +} + + ++(NSString *) _pathForName: (NSString *)name +{ + NSString *path; +static NSString *base_path = nil; + + [serverLock lock]; + if (!base_path) + { + path=NSTemporaryDirectory(); + + path = [path stringByAppendingPathComponent: @"NSMessagePort"]; + mkdir([path fileSystemRepresentation], 0700); + + path = [path stringByAppendingPathComponent: @"names"]; + mkdir([path fileSystemRepresentation], 0700); + + base_path = RETAIN(path); + } + else + { + path = base_path; + } + [serverLock unlock]; + + path = [path stringByAppendingPathComponent: name]; + return path; +} + + ++(BOOL) _livePort: (NSString *)path +{ + FILE *f; + char socket_path[512]; + int pid; + struct stat sb; + + NSDebugLLog(@"NSMessagePort", @"_livePort: %@", path); + + f = fopen([path fileSystemRepresentation], "rt"); + if (!f) + { + NSDebugLLog(@"NSMessagePort", @"not live, couldn't open file (%m)"); + return NO; + } + + fgets(socket_path, sizeof(socket_path), f); + if (strlen(socket_path) > 0) socket_path[strlen(socket_path) - 1] = 0; + + fscanf(f, "%i", &pid); + + fclose(f); + + if (stat(socket_path,&sb)<0) + { + unlink([path fileSystemRepresentation]); + NSDebugLLog(@"NSMessagePort", @"not live, couldn't stat socket (%m)"); + return NO; + } + + if (kill(pid,0)<0) + { + unlink([path fileSystemRepresentation]); + unlink(socket_path); + NSDebugLLog(@"NSMessagePort", @"not live, no such process (%m)"); + return NO; + } + + { + struct sockaddr_un sockAddr; + int desc; + + memset(&sockAddr, '\0', sizeof(sockAddr)); + sockAddr.sun_family = AF_LOCAL; + strncpy(sockAddr.sun_path, socket_path, sizeof(sockAddr.sun_path)); + + if ((desc = socket(PF_LOCAL, SOCK_STREAM, PF_UNSPEC)) < 0) + { + unlink([path fileSystemRepresentation]); + unlink(socket_path); + NSDebugLLog(@"NSMessagePort", @"couldn't create socket, assuming not live (%m)"); + return NO; + } + if (connect(desc, (struct sockaddr*)&sockAddr, SUN_LEN(&sockAddr)) < 0) + { + unlink([path fileSystemRepresentation]); + unlink(socket_path); + NSDebugLLog(@"NSMessagePort", @"not live, can't connect (%m)"); + return NO; + } + close(desc); + } + + + NSDebugLLog(@"NSMessagePort", @"port is live"); + return YES; +} + + +- (NSPort *) portForName: (NSString *)name + onHost: (NSString *)host +{ + NSString *path; + FILE *f; + char socket_path[512]; + + NSDebugLLog(@"NSMessagePort", @"portForName: %@ host: %@", name, host); + + if ([host length] && ![host isEqual: @"*"]) + { + NSDebugLLog(@"NSMessagePort", @"non-local host"); + return nil; + } + + path = [isa _pathForName: name]; + if (![isa _livePort: path]) + { + NSDebugLLog(@"NSMessagePort", @"not a live port"); + return nil; + } + + f = fopen([path fileSystemRepresentation], "rt"); + if (!f) + { + NSDebugLLog(@"NSMessagePort", @"can't open file (%m)"); + return nil; + } + + fgets(socket_path, sizeof(socket_path), f); + if (strlen(socket_path) > 0) socket_path[strlen(socket_path) - 1] = 0; + fclose(f); + + NSDebugLLog(@"NSMessagePort", @"got %s", socket_path); + + return [NSMessagePort _portWithName: socket_path + listener: NO]; +} + +- (BOOL) registerPort: (NSPort *)port + forName: (NSString *)name +{ + int fd; + unsigned char buf[32]; + NSString *path; + const unsigned char *socket_name; + + NSDebugLLog(@"NSMessagePort", @"register %@ as %@\n", port, name); + if (![port isKindOfClass: [NSMessagePort class]]) + { + [NSException raise: NSInvalidArgumentException + format: @"Attempted to register a non-NSMessagePort with NSMessagePortNameServer"]; + return NO; + } + + path=[isa _pathForName: name]; + + if ([isa _livePort: path]) + { + NSDebugLLog(@"NSMessagePort", @"fail, is a live port"); + return NO; + } + + fd = open([path fileSystemRepresentation], O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + { + NSDebugLLog(@"NSMessagePort", @"fail, can't open file (%m)"); + return NO; + } + + socket_name = [(NSMessagePort *)port _name]; + + write(fd, socket_name, strlen(socket_name)); + write(fd, "\n", 1); + sprintf(buf, "%i\n", getpid()); + write(fd, buf, strlen(buf)); + + close(fd); + + { + NSMutableArray *a; + + [serverLock lock]; + a = NSMapGet(portToNamesMap, port); + if (!a) + { + a = [[NSMutableArray alloc] init]; + NSMapInsert(portToNamesMap, port, a); + RELEASE(a); + } + + [a addObject: [name copy]]; + [serverLock unlock]; + } + + return YES; +} + +- (BOOL) removePortForName: (NSString *)name +{ + NSString *path; + + NSDebugLLog(@"NSMessagePort", @"removePortForName: %@", name); + path=[isa _pathForName: name]; + unlink([path fileSystemRepresentation]); + return YES; +} + +- (NSArray *) namesForPort: (NSPort *)port +{ + NSMutableArray *a; + [serverLock lock]; + a = NSMapGet(portToNamesMap, port); + a = [a copy]; + [serverLock unlock]; + return a; +} + +- (BOOL) removePort: (NSPort *)port +{ + NSMutableArray *a; + int i; + + NSDebugLLog(@"NSMessagePort", @"removePort: %@", port); + + [serverLock lock]; + a = NSMapGet(portToNamesMap, port); + + for (i = 0; i < [a count]; i++) + { + [self removePort: port forName: [a objectAtIndex: i]]; + } + + NSMapRemove(portToNamesMap, port); + [serverLock unlock]; + + return YES; +} + +- (BOOL) removePort: (NSPort*)port forName: (NSString*)name +{ + FILE *f; + char socket_path[512]; + NSString *path; + const unsigned char *port_path; + + NSDebugLLog(@"NSMessagePort", @"removePort: %@ forName: %@", port, name); + + path = [isa _pathForName: name]; + + f = fopen([path fileSystemRepresentation], "rt"); + if (!f) + return YES; + + fgets(socket_path, sizeof(socket_path), f); + if (strlen(socket_path) > 0) socket_path[strlen(socket_path) - 1] = 0; + + fclose(f); + + port_path = [(NSMessagePort *)port _name]; + + if (!strcmp(socket_path, port_path)) + { + unlink([path fileSystemRepresentation]); + } + + return YES; +} + +@end + diff --git a/Source/NSPort.m b/Source/NSPort.m index 0d79d3556..c671f8050 100644 --- a/Source/NSPort.m +++ b/Source/NSPort.m @@ -34,7 +34,8 @@ #include "Foundation/NSRunLoop.h" #include "Foundation/NSAutoreleasePool.h" -@class GSTcpPort; + +@class NSMessagePort; @implementation NSPort @@ -60,13 +61,17 @@ Class NSPort_concrete_class; if (self == [NSPort class]) { NSPort_abstract_class = self; - NSPort_concrete_class = [GSTcpPort class]; + NSPort_concrete_class = [NSSocketPort class]; +// NSPort_concrete_class = [NSMessagePort class]; } } + (NSPort*) port { - return AUTORELEASE([NSPort_concrete_class new]); + if (self == NSPort_abstract_class) + return AUTORELEASE([NSPort_concrete_class new]); + else + return AUTORELEASE([self new]); } + (NSPort*) portWithMachPort: (int)machPort @@ -119,20 +124,13 @@ Class NSPort_concrete_class; */ - (void) invalidate { - NSAutoreleasePool *arp; + CREATE_AUTORELEASE_POOL(arp); - [[NSPortNameServer systemDefaultPortNameServer] removePort: self]; _is_valid = NO; - /* - * Use a local autorelease pool when invalidating so that we know that - * anything refering to this port during the invalidation process is - * released immediately. - */ - arp = [NSAutoreleasePool new]; [[NSNotificationCenter defaultCenter] postNotificationName: NSPortDidBecomeInvalidNotification object: self]; - [arp release]; + RELEASE(arp); } - (BOOL) isValid diff --git a/Source/NSPortNameServer.m b/Source/NSPortNameServer.m index 8c056c461..877d0324e 100644 --- a/Source/NSPortNameServer.m +++ b/Source/NSPortNameServer.m @@ -59,472 +59,6 @@ #define stringify_it(X) #X #define make_gdomap_port(X) stringify_it(X) -/* - * to suppress warnings about using private methods. - */ -@class GSTcpPort; -@interface NSPort (Hack) -+ (GSTcpPort*) portWithNumber: (gsu16)number - onHost: (NSHost*)host - forceAddress: (NSString*)addr - listener: (BOOL)shouldListen; -- (gsu16) portNumber; -@end - -/* - * class-wide variables. - */ -static unsigned maxHandles = 4; -static NSTimeInterval timeout = 20.0; -static NSString *serverPort = @"gdomap"; -static NSString *mode = @"NSPortServerLookupMode"; -static NSArray *modes = nil; -static NSRecursiveLock *serverLock = nil; -static NSPortNameServer *defaultServer = nil; -static NSString *launchCmd = nil; -static Class portClass = 0; - - - -typedef enum { - GSPC_NONE, - GSPC_LOPEN, - GSPC_ROPEN, - GSPC_RETRY, - GSPC_WRITE, - GSPC_READ1, - GSPC_READ2, - GSPC_FAIL, - GSPC_DONE -} GSPortComState; - -@interface GSPortCom : NSObject -{ - gdo_req msg; - unsigned expecting; - NSMutableData *data; - NSFileHandle *handle; - GSPortComState state; - struct in_addr addr; -} -- (struct in_addr) addr; -- (void) close; -- (NSData*) data; -- (void) didConnect: (NSNotification*)notification; -- (void) didRead: (NSNotification*)notification; -- (void) didWrite: (NSNotification*)notification; -- (void) fail; -- (BOOL) isActive; -- (void) open: (NSString*)host; -- (void) setAddr: (struct in_addr)addr; -- (GSPortComState) state; -- (void) startListNameServers; -- (void) startPortLookup: (NSString*)name onHost: (NSString*)addr; -- (void) startPortRegistration: (gsu32)portNumber withName: (NSString*)name; -- (void) startPortUnregistration: (gsu32)portNumber withName: (NSString*)name; -@end - -@implementation GSPortCom - -- (struct in_addr) addr -{ - return addr; -} - -- (void) close -{ - if (handle != nil) - { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - [nc removeObserver: self - name: GSFileHandleConnectCompletionNotification - object: handle]; - [nc removeObserver: self - name: NSFileHandleReadCompletionNotification - object: handle]; - [nc removeObserver: self - name: GSFileHandleWriteCompletionNotification - object: handle]; - [handle closeFile]; - DESTROY(handle); - } -} - -- (NSData*) data -{ - return data; -} - -- (void) dealloc -{ - [self close]; - TEST_RELEASE(data); - [super dealloc]; -} - -- (void) didConnect: (NSNotification*)notification -{ - NSDictionary *userInfo = [notification userInfo]; - NSString *e; - - e = [userInfo objectForKey: GSFileHandleNotificationError]; - if (e != nil) - { - NSDebugMLLog(@"NSPortNameServer", @"failed connect to gdomap on %@ - %@", - [[notification object] socketAddress], e); - /* - * Remove our file handle, then either retry or fail. - */ - [self close]; - if (launchCmd == nil) - { - launchCmd = RETAIN([[NSSearchPathForDirectoriesInDomains( - GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0] - stringByAppendingPathComponent: @"gdomap"]); - } - if (state == GSPC_LOPEN && launchCmd != nil) - { - NSRunLoop *loop = [NSRunLoop currentRunLoop]; - NSTimer *timer; - - NSLog(@"NSPortNameServer attempting to start gdomap on local host\n" -@"This will take a few seconds.\n" -@"Trying to launch gdomap from %@ or a machine/operating-system subdirectory.\n" -@"It is recommended that you start up gdomap at login time or (better) when\n" -@"your computer is started instead.", -[launchCmd stringByDeletingLastPathComponent]); - [NSTask launchedTaskWithLaunchPath: launchCmd arguments: nil]; - timer = [NSTimer timerWithTimeInterval: 5.0 - invocation: nil - repeats: NO]; - [loop addTimer: timer forMode: [loop currentMode]]; - [loop runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 5.0]]; - NSDebugMLLog(@"NSPortNameServer", - @"retrying local connection to gdomap"); - state = GSPC_RETRY; - [self open: nil]; - } - else - { - [self fail]; - } - } - else - { - [[NSNotificationCenter defaultCenter] - removeObserver: self - name: GSFileHandleConnectCompletionNotification - object: handle]; - /* - * Now we have established a connection, we can write the request - * to the name server. - */ - state = GSPC_WRITE; - [handle writeInBackgroundAndNotify: data - forModes: modes]; - DESTROY(data); - } -} - -- (void) didRead: (NSNotification*)notification -{ - NSDictionary *userInfo = [notification userInfo]; - NSData *d; - - d = [userInfo objectForKey: NSFileHandleNotificationDataItem]; - - if (d == nil || [d length] == 0) - { - [self fail]; - NSLog(@"NSPortNameServer lost connection to gdomap on %@", - [[notification object] socketAddress]); - } - else - { - if (data == nil) - { - data = [d mutableCopy]; - } - else - { - [data appendData: d]; - } - if ([data length] < expecting) - { - /* - * Not enough data read yet - go read some more. - */ - [handle readInBackgroundAndNotifyForModes: modes]; - } - else if (state == GSPC_READ1 && msg.rtype == GDO_SERVERS) - { - gsu32 numSvrs = GSSwapBigI32ToHost(*(gsu32*)[data bytes]); - - if (numSvrs == 0) - { - [self fail]; - NSLog(@"failed to get list of name servers on net"); - } - else - { - /* - * Now read in the addresses of the servers. - */ - expecting += numSvrs * sizeof(struct in_addr); - if ([data length] < expecting) - { - state = GSPC_READ2; - [handle readInBackgroundAndNotifyForModes: modes]; - } - else - { - [[NSNotificationCenter defaultCenter] - removeObserver: self - name: NSFileHandleReadCompletionNotification - object: handle]; - state = GSPC_DONE; - } - } - } - else - { - [[NSNotificationCenter defaultCenter] - removeObserver: self - name: NSFileHandleReadCompletionNotification - object: handle]; - state = GSPC_DONE; - } - } -} - -- (void) didWrite: (NSNotification*)notification -{ - NSDictionary *userInfo = [notification userInfo]; - NSString *e; - - e = [userInfo objectForKey: GSFileHandleNotificationError]; - if (e != nil) - { - [self fail]; - NSLog(@"NSPortNameServer failed write to gdomap on %@ - %@", - [[notification object] socketAddress], e); - } - else - { - state = GSPC_READ1; - data = [NSMutableData new]; - expecting = 4; - [handle readInBackgroundAndNotifyForModes: modes]; - } -} - -- (void) fail -{ - [self close]; - if (data != nil) - { - DESTROY(data); - } - msg.rtype = 0; - state = GSPC_FAIL; -} - -- (BOOL) isActive -{ - if (handle == nil) - return NO; - if (state == GSPC_FAIL) - return NO; - if (state == GSPC_NONE) - return NO; - if (state == GSPC_DONE) - return NO; - return YES; -} - -- (void) open: (NSString*)hostname -{ - NSNotificationCenter *nc; - - NSAssert(state == GSPC_NONE || state == GSPC_RETRY, @"open in bad state"); - - if (state == GSPC_NONE) - { - state = GSPC_ROPEN; /* Assume we are connection to remote system */ - if (hostname == nil || [hostname isEqual: @""]) - { - hostname = @"localhost"; - state = GSPC_LOPEN; - } - else - { - NSHost *local = [NSHost localHost]; - NSHost *host = [NSHost hostWithName: hostname]; - - if (host == nil) - { - host = [NSHost hostWithAddress: hostname]; - } - if ([local isEqual: host]) - { - state = GSPC_LOPEN; - } - else - { - NSHost *loopback = [NSHost hostWithAddress: @"127.0.0.1"]; - - if ([loopback isEqual: host]) - { - state = GSPC_LOPEN; - } - } - } - } - - NS_DURING - { - handle = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: - hostname service: serverPort protocol: @"tcp" forModes: modes]; - } - NS_HANDLER - { - NSLog(@"Exception looking up port for gdomap - %@", localException); - if ([[localException name] isEqual: NSInvalidArgumentException]) - { - handle = nil; - } - else - { - [self fail]; - } - } - NS_ENDHANDLER - - if (state == GSPC_FAIL) - return; - - if (handle == nil) - { - if (state == GSPC_LOPEN) - { - NSLog(@"Failed to find gdomap port with name '%@',\nperhaps your " - @"/etc/services file is not correctly set up?\n" - @"Retrying with default (IANA allocated) port number 538", - serverPort); - NS_DURING - { - handle = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: - hostname service: @"538" protocol: @"tcp" forModes: modes]; - } - NS_HANDLER - { - NSLog(@"Exception creating handle for gdomap - %@", - localException); - [self fail]; - } - NS_ENDHANDLER - if (handle) - { - RELEASE(serverPort); - serverPort = @"538"; - } - } - else - { - [self fail]; - } - } - - if (state == GSPC_FAIL) - return; - - IF_NO_GC(RETAIN(handle)); - nc = [NSNotificationCenter defaultCenter]; - [nc addObserver: self - selector: @selector(didConnect:) - name: GSFileHandleConnectCompletionNotification - object: handle]; - [nc addObserver: self - selector: @selector(didRead:) - name: NSFileHandleReadCompletionNotification - object: handle]; - [nc addObserver: self - selector: @selector(didWrite:) - name: GSFileHandleWriteCompletionNotification - object: handle]; -} - -- (void) setAddr: (struct in_addr)anAddr -{ - addr = anAddr; -} - -- (GSPortComState) state -{ - return state; -} - -- (void) startListNameServers -{ - msg.rtype = GDO_SERVERS; /* Get a list of name servers. */ - msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ - msg.nsize = 0; - msg.port = 0; - TEST_RELEASE(data); - data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; - IF_NO_GC(RETAIN(data)); - [self open: nil]; -} - -- (void) startPortLookup: (NSString*)name onHost: (NSString*)host -{ - msg.rtype = GDO_LOOKUP; /* Find the named port. */ - msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ - msg.port = 0; - msg.nsize = [name cStringLength]; - [name getCString: msg.name]; - TEST_RELEASE(data); - data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; - IF_NO_GC(RETAIN(data)); - [self open: host]; -} - -- (void) startPortRegistration: (gsu32)portNumber withName: (NSString*)name -{ - msg.rtype = GDO_REGISTER; /* Register a port. */ - msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ - msg.nsize = [name cStringLength]; - [name getCString: msg.name]; - msg.port = GSSwapHostI32ToBig(portNumber); - TEST_RELEASE(data); - data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; - IF_NO_GC(RETAIN(data)); - [self open: nil]; -} - -- (void) startPortUnregistration: (gsu32)portNumber withName: (NSString*)name -{ - msg.rtype = GDO_UNREG; - msg.ptype = GDO_TCP_GDO; - if (name == nil) - { - msg.nsize = 0; - } - else - { - msg.nsize = [name cStringLength]; - [name getCString: msg.name]; - } - msg.port = GSSwapHostI32ToBig(portNumber); - TEST_RELEASE(data); - data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; - IF_NO_GC(RETAIN(data)); - [self open: nil]; -} - -@end - @implementation NSPortNameServer @@ -540,37 +74,13 @@ typedef enum { { if (self == [NSPortNameServer class]) { - serverLock = [NSRecursiveLock new]; - modes = [[NSArray alloc] initWithObjects: &mode count: 1]; -#ifdef GDOMAP_PORT_OVERRIDE - serverPort = RETAIN([NSString stringWithCString: - make_gdomap_port(GDOMAP_PORT_OVERRIDE)]); -#endif - portClass = [GSTcpPort class]; } } + (id) systemDefaultPortNameServer { - if (defaultServer == nil) - { - NSPortNameServer *s; - - [serverLock lock]; - if (defaultServer) - { - [serverLock unlock]; - return defaultServer; - } - s = (NSPortNameServer*)NSAllocateObject(self, 0, NSDefaultMallocZone()); - s->_portMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, - NSObjectMapValueCallBacks, 0); - s->_nameMap = NSCreateMapTable(NSObjectMapKeyCallBacks, - NSNonOwnedPointerMapValueCallBacks, 0); - defaultServer = s; - [serverLock unlock]; - } - return defaultServer; + return [NSSocketPortNameServer sharedInstance]; + // return [NSMessagePortNameServer sharedInstance]; } - (void) dealloc @@ -584,617 +94,66 @@ typedef enum { return [self portForName: name onHost: nil]; } -- (BOOL) _lookupName: (NSString*)name onHost: (NSString*)host - intoAddress: (NSString**)addr andPort: (unsigned*)port -{ - GSPortCom *com = nil; - NSRunLoop *loop = [NSRunLoop currentRunLoop]; - struct in_addr singleServer; - struct in_addr *svrs = &singleServer; - unsigned numSvrs = 1; - unsigned count; - unsigned len; - NSMutableArray *array; - NSDate *limit; - - *port = 0; - if (name == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to lookup port with nil name"]; - } - - len = [name cStringLength]; - if (len == 0) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to lookup port with no name"]; - } - if (len > GDO_NAME_MAX_LEN) - { - [NSException raise: NSInvalidArgumentException - format: @"name of port is too long (max %d) bytes", - GDO_NAME_MAX_LEN]; - } - - limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; - - /* - * get one or more host addresses in network byte order. - */ - if (host == nil || [host isEqual: @""]) - { - /* - * Query a single nameserver - on the local host. - */ -#ifndef HAVE_INET_ATON - svrs->s_addr = inet_addr("127.0.0.1"); -#else - inet_aton("127.0.0.1", (struct in_addr *)&svrs->s_addr); -#endif - } - else if ([host isEqual: @"*"]) - { - GSPortCom *com = [GSPortCom new]; - - [serverLock lock]; - NS_DURING - { - GSPortCom *tmp; - NSData *dat; - - [com startListNameServers]; - while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) - { - [loop runMode: mode - beforeDate: limit]; - } - [com close]; - if ([com state] != GSPC_DONE) - { - [NSException raise: NSPortTimeoutException - format: @"timed out listing name servers"]; - } - /* - * Retain and autorelease the data item so the buffer won't disappear - * when the 'com' object is destroyed. - */ - dat = AUTORELEASE(RETAIN([com data])); - svrs = (struct in_addr*)([dat bytes] + 4); - numSvrs = GSSwapBigI32ToHost(*(gsu32*)[dat bytes]); - if (numSvrs == 0) - { - [NSException raise: NSInternalInconsistencyException - format: @"failed to get list of name servers"]; - } - tmp = com; - com = nil; - RELEASE(tmp); - } - NS_HANDLER - { - /* - * If we had a problem - unlock before continueing. - */ - RELEASE(com); - [serverLock unlock]; - [localException raise]; - } - NS_ENDHANDLER - [serverLock unlock]; - } - else - { - NSHost *h; - - /* - * Query a single nameserver - on the specified host. - */ - numSvrs = 1; - h = [NSHost hostWithName: host]; - if (h) - host = [h address]; -#ifndef HAVE_INET_ATON - svrs->s_addr = inet_addr([host cString]); -#else - inet_aton([host cString], (struct in_addr *)&svrs->s_addr); -#endif - } - - /* - * Ok, 'svrs'now points to one or more internet addresses in network - * byte order, and numSvrs tells us how many there are. - */ - array = [NSMutableArray arrayWithCapacity: maxHandles]; - [serverLock lock]; - NS_DURING - { - unsigned i; - - *port = 0; - count = 0; - do - { - /* - * Make sure that all the array slots are full if possible - */ - while (count < numSvrs && [array count] < maxHandles) - { - NSString *addr; - - com = [GSPortCom new]; - [array addObject: com]; - RELEASE(com); - [com setAddr: svrs[count]]; - addr = [NSString stringWithCString: - (char*)inet_ntoa(svrs[count])]; - [com startPortLookup: name onHost: addr]; - count++; - } - - /* - * Handle I/O on the file handles. - */ - i = [array count]; - if (i == 0) - { - break; /* No servers left to try! */ - } - [loop runMode: mode beforeDate: limit]; - - /* - * Check for completed operations. - */ - while (*port == 0 && i-- > 0) - { - com = [array objectAtIndex: i]; - if ([com isActive] == NO) - { - [com close]; - if ([com state] == GSPC_DONE) - { - *port = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); - if (*port != 0) - { - singleServer = [com addr]; - } - } - [array removeObjectAtIndex: i]; - } - } - } - while (*port == 0 && [limit timeIntervalSinceNow] > 0); - - /* - * Make sure that any outstanding lookups are cancelled. - */ - i = [array count]; - while (i-- > 0) - { - [[array objectAtIndex: i] fail]; - [array removeObjectAtIndex: i]; - } - } - NS_HANDLER - { - /* - * If we had a problem - unlock before continueing. - */ - [serverLock unlock]; - [localException raise]; - } - NS_ENDHANDLER - [serverLock unlock]; - - if (*port) - { - *addr = [NSString stringWithCString: inet_ntoa(singleServer)]; - return YES; - } - else - { - return NO; - } -} - - (NSPort*) portForName: (NSString*)name onHost: (NSString*)host { - NSString *addr; - unsigned portNum = 0; - - if ([self _lookupName: name - onHost: host - intoAddress: &addr - andPort: &portNum] == YES) - { - if (portClass == [GSTcpPort class]) - { - NSHost *host; - - host = [NSHost hostWithAddress: addr]; - return (NSPort*)[GSTcpPort portWithNumber: portNum - onHost: host - forceAddress: addr - listener: NO]; - } - else - { - NSLog(@"Unknown port class (%@) set for new port!", portClass); - return nil; - } - } - else - { - return nil; - } + [self subclassResponsibility: _cmd]; + return nil; } - (BOOL) registerPort: (NSPort*)port forName: (NSString*)name { - NSRunLoop *loop = [NSRunLoop currentRunLoop]; - GSPortCom *com = nil; - unsigned len; - NSDate *limit; - - if (name == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to register port with nil name"]; - } - if (port == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to register nil port"]; - } - len = [name cStringLength]; - if (len == 0) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to register port with no name"]; - } - if (len > GDO_NAME_MAX_LEN) - { - [NSException raise: NSInvalidArgumentException - format: @"name of port is too long (max %d) bytes", - GDO_NAME_MAX_LEN]; - } - - limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; - /* - * Lock out other threads while doing I/O to gdomap - */ - [serverLock lock]; - - NS_DURING - { - NSMutableSet *known = NSMapGet(_portMap, port); - - /* - * If there is no set of names for this port - create one. - */ - if (known == nil) - { - known = [NSMutableSet new]; - NSMapInsert(_portMap, port, known); - RELEASE(known); - } - - /* - * If this port has never been registered under any name, first - * send an unregister message to gdomap to ensure that any old - * names for the port (perhaps from a server that crashed without - * unregistering its ports) are no longer around. - */ - if ([known count] == 0) - { - com = [GSPortCom new]; - [com startPortUnregistration: [port portNumber] withName: nil]; - while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) - { - [loop runMode: mode - beforeDate: limit]; - } - [com close]; - if ([com state] != GSPC_DONE) - { - [NSException raise: NSPortTimeoutException - format: @"timed out unregistering port"]; - } - DESTROY(com); - } - - com = [GSPortCom new]; - [com startPortRegistration: [port portNumber] withName: name]; - while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) - { - [loop runMode: mode beforeDate: limit]; - } - [com close]; - if ([com state] != GSPC_DONE) - { - [NSException raise: NSPortTimeoutException - format: @"timed out registering port %@", name]; - } - else - { - unsigned result; - - result = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); - if (result == 0) - { - unsigned int portNum; - NSString *addr; - BOOL found; - - NS_DURING - { - found = [self _lookupName: name - onHost: @"" - intoAddress: &addr - andPort: &portNum]; - - } - NS_HANDLER - { - found = NO; - } - NS_ENDHANDLER - - if (found == YES) - { - [NSException raise: NSGenericException - format: @"Unable to register name '%@' for the port -\n%@\n" -@"It appears that a process is already registered with this name at port\n" -@"'%d' IP address %@\n" -@"Perhaps this program ran before and was shut down without unregistering,\n" -@"so another process may be running using the same network port. If this is\n" -@"the case, you can use -\n" -@"gdomap -U '%@'\n" -@"to remove the registration so that you can attempt this operation again.", - name, port, portNum, addr, name]; - } - else - { - [NSException raise: NSGenericException - format: @"Unable to register name '%@' for the port -\n%@\n" -@"Typically, this might mean that a process is already running with the name\n" -@"'%@' ...\n" -@"Try the command -\n" -@" gdomap -M localhost -L '%@'\n" -@"to find its network details.\n" -@"Alternatively, it may have been shut down without unregistering, and\n" -@"another process may be running using the same network port. If this is\n" -@"the case, you can use -\n" -@"gdomap -U '%@'\n" -@"to remove the registration so that you can attempt this operation again.", - name, port, name, name, name]; - } - } - else - { - /* - * Add this name to the set of names that the port - * is known by and to the name map. - */ - [known addObject: name]; - NSMapInsert(_nameMap, name, port); - } - } - DESTROY(com); - } - NS_HANDLER - { - /* - * If we had a problem - close and unlock before continueing. - */ - DESTROY(com); - [serverLock unlock]; - [localException raise]; - } - NS_ENDHANDLER - [serverLock unlock]; - return YES; + [self subclassResponsibility: _cmd]; + return NO; } - (BOOL) removePortForName: (NSString*)name { - NSRunLoop *loop = [NSRunLoop currentRunLoop]; - GSPortCom *com = nil; - unsigned len; - NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; - BOOL val = NO; - - if (name == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to remove port with nil name"]; - } - - len = [name cStringLength]; - if (len == 0) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to remove port with no name"]; - } - if (len > GDO_NAME_MAX_LEN) - { - [NSException raise: NSInvalidArgumentException - format: @"name of port is too long (max %d) bytes", - GDO_NAME_MAX_LEN]; - } - - /* - * Lock out other threads while doing I/O to gdomap - */ - [serverLock lock]; - - NS_DURING - { - GSPortCom *tmp; - - com = [GSPortCom new]; - [com startPortUnregistration: 0 withName: name]; - while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) - { - [loop runMode: mode - beforeDate: limit]; - } - [com close]; - if ([com state] != GSPC_DONE) - { - [NSException raise: NSPortTimeoutException - format: @"timed out unregistering port"]; - } - else - { - NSPort *port; - unsigned result; - - result = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); - if (result == 0) - { - NSLog(@"NSPortNameServer unable to unregister '%@'", name); - val = NO; - } - else - { - val = YES; - } - /* - * Find the port that was registered for this name and - * remove the mapping table entries. - */ - port = NSMapGet(_nameMap, name); - if (port) - { - NSMutableSet *known; - - NSMapRemove(_nameMap, name); - known = NSMapGet(_portMap, port); - if (known) - { - [known removeObject: name]; - if ([known count] == 0) - { - NSMapRemove(_portMap, port); - } - } - } - } - tmp = com; - com = nil; - RELEASE(tmp); - } - NS_HANDLER - { - /* - * If we had a problem - unlock before continueing. - */ - RELEASE(com); - [serverLock unlock]; - [localException raise]; - } - NS_ENDHANDLER - [serverLock unlock]; - return val; + [self subclassResponsibility: _cmd]; + return NO; } @end +/** + * Some extensions to make cleaning up port names easier. + */ @implementation NSPortNameServer (GNUstep) - -+ (Class) setPortClass: (Class)c -{ - Class old = portClass; - - portClass = c; - return old; -} - -/* - * Return the names under which the port is currently registered. +/** Return all names for port */ - (NSArray*) namesForPort: (NSPort*)port { - NSArray *names; - - if (port == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"attempt to get names for nil port"]; - } - /* - * Lock out other threads while grabbing port names. - */ - [serverLock lock]; - names = [(NSSet*)NSMapGet(_portMap, port) allObjects]; - [serverLock unlock]; - return names; + [self subclassResponsibility: _cmd]; + return nil; } -/* - * Remove all names for a particular port - used when a port is - * invalidated. +/** + * Remove all names for port. Probably inefficient ... subclasses + * should override this. */ - (BOOL) removePort: (NSPort*)port { - BOOL ok = YES; - [serverLock lock]; - NS_DURING - { - NSMutableSet *known = (NSMutableSet*)NSMapGet(_portMap, port); - NSString *name; + NSEnumerator *e = [[self namesForPort: port] objectEnumerator]; + NSString *n; + BOOL removed = NO; - RETAIN(known); - while ((name = [known anyObject]) != nil) - { - if ([self removePortForName: name] == NO) - { - ok = NO; - } - } - RELEASE(known); - } - NS_HANDLER + while ((n = [e nextObject]) != nil) { - [serverLock unlock]; - [localException raise]; + if ([self removePort: port forName: n] == YES) + { + removed = YES; + } } - NS_ENDHANDLER - [serverLock unlock]; - return ok; + return removed; } -/* - * Remove name for port iff it is registered. +/** + * Remove the name if and only if it is registered by the given port. */ - (BOOL) removePort: (NSPort*)port forName: (NSString*)name { - BOOL ok = YES; - - [serverLock lock]; - NS_DURING - { - NSMutableSet *known = (NSMutableSet*)NSMapGet(_portMap, port); - - if ([known member: name] != nil) - { - if ([self removePortForName: name] == NO) - { - ok = NO; - } - } - } - NS_HANDLER - { - [serverLock unlock]; - [localException raise]; - } - NS_ENDHANDLER - [serverLock unlock]; - return ok; + [self subclassResponsibility: _cmd]; + return NO; } @end - diff --git a/Source/NSSocketPort.m b/Source/NSSocketPort.m index 7dc51dc56..90dedeac0 100644 --- a/Source/NSSocketPort.m +++ b/Source/NSSocketPort.m @@ -1,391 +1,2189 @@ -/** Implementation of a port based on BSD sockets - Copyright (C) 2002 Free Software Foundation, Inc. - - Written by: Jonathan Gapen - Created: December 2002 - +/** Implementation of network port object based on TCP sockets + Copyright (C) 2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Based on code by: Andrew Kachites McCallum + This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. - + You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. - - NSSocketPort class reference - $Date$ $Revision$ - */ + */ #include "config.h" -#include "Foundation/NSAutoreleasePool.h" -#include "Foundation/NSData.h" -#include "Foundation/NSDebug.h" +#include "gnustep/base/preface.h" +#include "Foundation/NSArray.h" +#include "Foundation/NSNotification.h" #include "Foundation/NSException.h" -#include "Foundation/NSHost.h" -#include "Foundation/NSString.h" -#include "Foundation/NSNotificationQueue.h" -#include "Foundation/NSPort.h" -#include "Foundation/NSPortCoder.h" -#include "Foundation/NSPortNameServer.h" #include "Foundation/NSRunLoop.h" -#include "Foundation/NSZone.h" +#include "Foundation/NSByteOrder.h" +#include "Foundation/NSData.h" +#include "Foundation/NSDate.h" +#include "Foundation/NSHost.h" +#include "Foundation/NSMapTable.h" +#include "Foundation/NSPortMessage.h" +#include "Foundation/NSPortNameServer.h" +#include "Foundation/NSLock.h" +#include "Foundation/NSHost.h" +#include "Foundation/NSThread.h" +#include "Foundation/NSConnection.h" +#include "Foundation/NSDebug.h" +#include +#include +#ifdef HAVE_SYS_SIGNAL_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include /* for gethostname() */ +#endif -#ifndef __MINGW__ -#include -#include -#include -#include -#include -#include +#ifdef __MINGW__ +#define close closesocket #else +#include /* for MAXHOSTNAMELEN */ +#include +#include +#include /* for inet_ntoa() */ +#endif /* !__MINGW__ */ +#include +#include +#include /* for strchr() */ +#include /* for strchr() */ +#include +#ifdef __MINGW__ #include #include -#define close closesocket +#include +#include +#else +#include +#include +#include +#include +#include +/* + * Stuff for setting the sockets into non-blocking mode. + */ +#ifdef __POSIX_SOURCE +#define NBLK_OPT O_NONBLOCK +#else +#define NBLK_OPT FNDELAY +#endif + +#include +#include +#if !defined(SIOCGIFCONF) || defined(__CYGWIN__) +#include +#ifndef SIOCGIFCONF +#include +#endif +#endif + +#if defined(__svr4__) +#include +#endif + #define SOCKET int #define SOCKET_ERROR -1 #define INVALID_SOCKET -1 + +#endif /* !__MINGW__ */ + +static BOOL multi_threaded = NO; + +/* + * Largest chunk of data possible in DO + */ +static gsu32 maxDataLength = 10 * 1024 * 1024; + +#if 0 +#define M_LOCK(X) {NSDebugMLLog(@"GSTcpHandle",@"lock %@",X); [X lock];} +#define M_UNLOCK(X) {NSDebugMLLog(@"GSTcpHandle",@"unlock %@",X); [X unlock];} +#else +#define M_LOCK(X) {[X lock];} +#define M_UNLOCK(X) {[X unlock];} +#endif + +#define GS_CONNECTION_MSG 0 +#define NETBLOCK 8192 + +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + +/* + * Theory of operation + * + * + */ + + +/* Private interfaces */ + +/* + * The GSPortItemType constant is used to identify the type of data in + * each packet read. All data transmitted is in a packet, each packet + * has an initial packet type and packet length. + */ +typedef enum { + GSP_NONE, + GSP_PORT, /* Simple port item. */ + GSP_DATA, /* Simple data item. */ + GSP_HEAD /* Port message header + initial data. */ +} GSPortItemType; + +/* + * The GSPortItemHeader structure defines the header for each item transmitted. + * Its contents are transmitted in network byte order. + */ +typedef struct { + gsu32 type; /* A GSPortItemType as a 4-byte number. */ + gsu32 length; /* The length of the item (excluding header). */ +} GSPortItemHeader; + +/* + * The GSPortMsgHeader structure is at the start of any item of type GSP_HEAD. + * Its contents are transmitted in network byte order. + * Any additional data in the item is an NSData object. + * NB. additional data counts as part of the same item. + */ +typedef struct { + gsu32 mId; /* The ID for the message starting with this. */ + gsu32 nItems; /* Number of items (including this one). */ +} GSPortMsgHeader; + +typedef struct { + gsu16 num; /* TCP port num */ + char addr[0]; /* host address */ +} GSPortInfo; + +/* + * Here is how data is transmitted over a socket - + * Initially the process making the connection sends an item of type + * GSP_PORT to tell the remote end what port is connecting to it. + * Therafter, all communication is via port messages. Each port message + * consists of an item of type GSP_HEAD followed by zero or more items + * of type GSP_PORT or GSP_DATA. The number of items in a port message + * is encoded in the 'nItems' field of the header. + */ + +typedef enum { + GS_H_UNCON = 0, // Currently idle and unconnected. + GS_H_TRYCON, // Trying connection (outgoing). + GS_H_ACCEPT, // Making initial connection (incoming). + GS_H_CONNECTED // Currently connected. +} GSHandleState; + +@interface GSTcpHandle : NSObject +{ + SOCKET desc; /* File descriptor for I/O. */ + unsigned wItem; /* Index of item being written. */ + NSMutableData *wData; /* Data object being written. */ + unsigned wLength; /* Ammount written so far. */ + NSMutableArray *wMsgs; /* Message in progress. */ + NSMutableData *rData; /* Buffer for incoming data */ + gsu32 rLength; /* Amount read so far. */ + gsu32 rWant; /* Amount desired. */ + NSMutableArray *rItems; /* Message in progress. */ + GSPortItemType rType; /* Type of data being read. */ + gsu32 rId; /* Id of incoming message. */ + unsigned nItems; /* Number of items to be read. */ + GSHandleState state; /* State of the handle. */ + unsigned int addrNum; /* Address number within host. */ +@public + NSRecursiveLock *myLock; /* Lock for this handle. */ + BOOL caller; /* Did we connect to other end? */ + BOOL valid; + NSSocketPort *recvPort; + NSSocketPort *sendPort; + struct sockaddr_in sockAddr; /* Far end of connection. */ + NSString *defaultAddress; +} + ++ (GSTcpHandle*) handleWithDescriptor: (SOCKET)d; +- (BOOL) connectToPort: (NSSocketPort*)aPort beforeDate: (NSDate*)when; +- (int) descriptor; +- (void) invalidate; +- (BOOL) isValid; +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode; +- (NSSocketPort*) recvPort; +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when; +- (NSSocketPort*) sendPort; +- (void) setState: (GSHandleState)s; +- (GSHandleState) state; +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode; +@end + + +/* + * Utility functions for encoding and decoding ports. + */ +static NSSocketPort* +decodePort(NSData *data, NSString *defaultAddress) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + NSString *addr; + gsu16 pnum; + gsu32 length; + NSHost *host; + unichar c; + + pih = (GSPortItemHeader*)[data bytes]; + NSCAssert(GSSwapBigI32ToHost(pih->type) == GSP_PORT, + NSInternalInconsistencyException); + length = GSSwapBigI32ToHost(pih->length); + pi = (GSPortInfo*)&pih[1]; + pnum = GSSwapBigI16ToHost(pi->num); + if (strncmp(pi->addr, "VER", 3) == 0) + { + NSLog(@"Remote version of GNUstep at %s:%d is more recent than this one", + pi->addr, pnum); + return nil; + } + addr = [NSString stringWithCString: pi->addr]; + + NSDebugFLLog(@"NSPort", @"Decoded port as '%@:%d'", addr, pnum); + + /* + * Special case - the encoded port was on the host from which the + * message was sent. + */ + if ([addr length] == 0) + { + addr = defaultAddress; + } + c = [addr characterAtIndex: 0]; + if (c >= '0' && c <= '9') + { + host = [NSHost hostWithAddress: addr]; + } + else + { + host = [NSHost hostWithName: addr]; + } + + return [NSSocketPort portWithNumber: pnum + onHost: host + forceAddress: nil + listener: NO]; +} + +static NSData* +newDataWithEncodedPort(NSSocketPort *port) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + NSMutableData *data; + unsigned plen; + NSString *addr; + gsu16 pnum; + + pnum = [port portNumber]; + addr = [port address]; + if (addr == nil) + { + static NSHost *local = nil; + + /* + * If the port is not forced to use a specific address ... + * 1. see if it is on the local host, and if so, encode as "". + * 2. see if it s a hostname and if so, use the name. + * 3. pick one of the host addresses. + * 4. use the localhost address. + */ + if (local == nil) + { + local = RETAIN([NSHost localHost]); + } + if ([[port host] isEqual: local] == YES) + { + addr = @""; + } + else if ((addr = [[port host] name]) == nil) + { + addr = [[port host] address]; + if (addr == nil) + { + addr = @"127.0.0.1"; /* resign ourselves to this */ + } + } + } + plen = [addr cStringLength] + 3; + data = [[NSMutableData alloc] initWithLength: sizeof(GSPortItemHeader)+plen]; + pih = (GSPortItemHeader*)[data mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_PORT); + pih->length = GSSwapHostI32ToBig(plen); + pi = (GSPortInfo*)&pih[1]; + pi->num = GSSwapHostI16ToBig(pnum); + [addr getCString: pi->addr]; + + NSDebugFLLog(@"NSPort", @"Encoded port as '%@:%d'", addr, pnum); + + return data; +} + + + +@implementation GSTcpHandle + +static Class mutableArrayClass; +static Class mutableDataClass; +static Class portMessageClass; +static Class runLoopClass; + ++ (id) allocWithZone: (NSZone*)zone +{ + [NSException raise: NSGenericException + format: @"attempt to alloc a GSTcpHandle!"]; + return nil; +} + ++ (GSTcpHandle*) handleWithDescriptor: (SOCKET)d +{ + GSTcpHandle *handle; +#ifdef __MINGW__ + unsigned long dummy; +#else + int e; #endif /* __MINGW__ */ + if (d == INVALID_SOCKET) + { + NSLog(@"illegal descriptor (%d) for Tcp Handle", d); + return nil; + } +#ifdef __MINGW__ + dummy = 1; + if (ioctlsocket(d, FIONBIO, &dummy) == SOCKET_ERROR) + { + NSLog(@"unable to set non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } +#else /* !__MINGW__ */ + if ((e = fcntl(d, F_GETFL, 0)) >= 0) + { + e |= NBLK_OPT; + if (fcntl(d, F_SETFL, e) < 0) + { + NSLog(@"unable to set non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } + } + else + { + NSLog(@"unable to get non-blocking mode on %d - %s", + d, GSLastErrorStr(errno)); + return nil; + } +#endif + handle = (GSTcpHandle*)NSAllocateObject(self, 0, NSDefaultMallocZone()); + handle->desc = d; + handle->wMsgs = [NSMutableArray new]; + if (multi_threaded == YES) + { + handle->myLock = [NSRecursiveLock new]; + } + handle->valid = YES; + return AUTORELEASE(handle); +} -/** - *

- * NSSocketPort on MacOS X(tm) is a concrete subclass of - * NSPort which implements Distributed Objects communication between - * hosts on a network. However, the GNUstep distributed objects system's - * NSPort class uses TCP/IP for all of its communication. The GNUstep - * NSSocketPort, then, is useful only as a method to - * create and encapsulate BSD sockets. However, the GNUstep [NSFileHandle] - * extensions for networking provide a richer and easier to use mechanism - * than that provided by the NSSocketPort class. - *

++ (void) initialize +{ + if (self == [GSTcpHandle class]) + { +#ifdef __MINGW__ + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 0); + WSAStartup(wVersionRequested, &wsaData); +#else + /* + * If SIGPIPE is not ignored, we will abort on any attempt to + * write to a pipe/socket that has been closed by the other end! + */ + signal(SIGPIPE, SIG_IGN); +#endif + mutableArrayClass = [NSMutableArray class]; + mutableDataClass = [NSMutableData class]; + portMessageClass = [NSPortMessage class]; + runLoopClass = [NSRunLoop class]; + } +} + +- (BOOL) connectToPort: (NSSocketPort*)aPort beforeDate: (NSDate*)when +{ + NSArray *addrs; + BOOL gotAddr = NO; + NSRunLoop *l; + + M_LOCK(myLock); + NSDebugMLLog(@"GSTcpHandle", @"Connecting on 0x%x in thread 0x%x before %@", + self, GSCurrentThread(), when); + if (state != GS_H_UNCON) + { + BOOL result; + + if (state == GS_H_CONNECTED) /* Already connected. */ + { + NSLog(@"attempting connect on connected handle"); + result = YES; + } + else if (state == GS_H_ACCEPT) /* Impossible. */ + { + NSLog(@"attempting connect with accepting handle"); + result = NO; + } + else /* Already connecting. */ + { + NSLog(@"attempting connect while connecting"); + result = NO; + } + M_UNLOCK(myLock); + return result; + } + + if (recvPort == nil || aPort == nil) + { + NSLog(@"attempting connect with port(s) unset"); + M_UNLOCK(myLock); + return NO; /* impossible. */ + } + + /* + * Get an IP address to try to connect to. + * If the port has a 'forced' address, just use that. Otherwise we try + * each of the addresses for the host in turn. + */ + if ([aPort address] != nil) + { + addrs = [NSArray arrayWithObject: [aPort address]]; + } + else + { + addrs = [[aPort host] addresses]; + } + while (gotAddr == NO) + { + const char *addr; + + if (addrNum >= [addrs count]) + { + NSLog(@"run out of addresses to try (tried %d) for port %@", + addrNum, aPort); + M_UNLOCK(myLock); + return NO; + } + addr = [[addrs objectAtIndex: addrNum++] cString]; + + memset(&sockAddr, '\0', sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; +#ifndef HAVE_INET_ATON + sockAddr.sin_addr.s_addr = inet_addr(addr); + if (sockAddr.sin_addr.s_addr == INADDR_NONE) +#else + if (inet_aton(addr, &sockAddr.sin_addr) == 0) +#endif + { + NSLog(@"bad ip address - '%s'", addr); + } + else + { + gotAddr = YES; + NSDebugMLLog(@"GSTcpHandle", @"Connecting to %s:%d using desc %d", + addr, [aPort portNumber], desc); + } + } + sockAddr.sin_port = GSSwapHostI16ToBig([aPort portNumber]); + + if (connect(desc, (struct sockaddr*)&sockAddr, sizeof(sockAddr)) + == SOCKET_ERROR) + { +#ifdef __MINGW__ + if (WSAGetLastError() != WSAEWOULDBLOCK) +#else + if (errno != EINPROGRESS) +#endif + { + NSLog(@"unable to make connection to %s:%d - %s", + inet_ntoa(sockAddr.sin_addr), + GSSwapBigI16ToHost(sockAddr.sin_port), GSLastErrorStr(errno)); + if (addrNum < [addrs count]) + { + BOOL result; + + result = [self connectToPort: aPort beforeDate: when]; + M_UNLOCK(myLock); + return result; + } + else + { + M_UNLOCK(myLock); + return NO; /* Tried all addresses */ + } + } + } + + state = GS_H_TRYCON; + l = [NSRunLoop currentRunLoop]; + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSConnectionReplyMode]; + [l addEvent: (void*)(gsaddr)desc + type: ET_EDESC + watcher: self + forMode: NSConnectionReplyMode]; + while (valid == YES && state == GS_H_TRYCON + && [when timeIntervalSinceNow] > 0) + { + [l runMode: NSConnectionReplyMode beforeDate: when]; + } + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: NSConnectionReplyMode + all: NO]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_EDESC + forMode: NSConnectionReplyMode + all: NO]; + + if (state == GS_H_TRYCON) + { + state = GS_H_UNCON; + addrNum = 0; + M_UNLOCK(myLock); + return NO; /* Timed out */ + } + else if (state == GS_H_UNCON) + { + if (addrNum < [addrs count] && [when timeIntervalSinceNow] > 0) + { + BOOL result; + + /* + * The connection attempt failed, but there are still IP addresses + * that we haven't tried. + */ + result = [self connectToPort: aPort beforeDate: when]; + M_UNLOCK(myLock); + return result; + } + addrNum = 0; + state = GS_H_UNCON; + M_UNLOCK(myLock); + return NO; /* connection failed */ + } + else + { + addrNum = 0; + caller = YES; + [aPort addHandle: self forSend: YES]; + M_UNLOCK(myLock); + return YES; + } +} + +- (void) dealloc +{ + [self gcFinalize]; + DESTROY(defaultAddress); + DESTROY(rData); + DESTROY(rItems); + DESTROY(wMsgs); + DESTROY(myLock); + [super dealloc]; +} + +- (NSString*) description +{ + return [NSString stringWithFormat: @"Handle (%d) to %s:%d", + desc, inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port)]; +} + +- (SOCKET) descriptor +{ + return desc; +} + +- (void) gcFinalize +{ + [self invalidate]; + (void)close(desc); + desc = -1; +} + +- (void) invalidate +{ + if (valid == YES) + { + M_LOCK(myLock); + if (valid == YES) + { + NSRunLoop *l; + + valid = NO; + l = [runLoopClass currentRunLoop]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_RDESC + forMode: nil + all: YES]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: nil + all: YES]; + [l removeEvent: (void*)(gsaddr)desc + type: ET_EDESC + forMode: nil + all: YES]; + NSDebugMLLog(@"GSTcpHandle", @"invalidated 0x%x in thread 0x%x", + self, GSCurrentThread()); + [[self recvPort] removeHandle: self]; + [[self sendPort] removeHandle: self]; + } + M_UNLOCK(myLock); + } +} + +- (BOOL) isValid +{ + return valid; +} + +- (NSSocketPort*) recvPort +{ + if (recvPort == nil) + return nil; + else + return GS_GC_UNHIDE(recvPort); +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + NSDebugMLLog(@"GSTcpHandle", @"received %s event on 0x%x in thread 0x%x", + type == ET_RPORT ? "read" : "write", self, GSCurrentThread()); + /* + * If we have been invalidated (desc < 0) then we should ignore this + * event and remove ourself from the runloop. + */ + if (desc == INVALID_SOCKET) + { + NSRunLoop *l = [runLoopClass currentRunLoop]; + + [l removeEvent: data + type: ET_WDESC + forMode: mode + all: YES]; + [l removeEvent: data + type: ET_EDESC + forMode: mode + all: YES]; + return; + } + + M_LOCK(myLock); + + if (type == ET_RPORT) + { + unsigned want; + void *bytes; + int res; + + /* + * Make sure we have a buffer big enough to hold all the data we are + * expecting, or NETBLOCK bytes, whichever is greater. + */ + if (rData == nil) + { + rData = [[mutableDataClass alloc] initWithLength: NETBLOCK]; + rWant = sizeof(GSPortItemHeader); + rLength = 0; + want = NETBLOCK; + } + else + { + want = [rData length]; + if (want < rWant) + { + want = rWant; + [rData setLength: want]; + } + if (want < NETBLOCK) + { + want = NETBLOCK; + [rData setLength: want]; + } + } + + /* + * Now try to fill the buffer with data. + */ + bytes = [rData mutableBytes]; + res = recv(desc, bytes + rLength, want - rLength, 0); + if (res <= 0) + { + if (res == 0) + { + NSDebugMLLog(@"GSTcpHandle", @"read eof on 0x%x in thread 0x%x", + self, GSCurrentThread()); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + else if (errno != EINTR && errno != EAGAIN) + { + NSDebugMLLog(@"GSTcpHandle", + @"read failed - %s on 0x%x in thread 0x%x", + GSLastErrorStr(errno), self, GSCurrentThread()); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + res = 0; /* Interrupted - continue */ + } + NSDebugMLLog(@"GSTcpHandle", @"read %d bytes on 0x%x in thread 0x%x", + res, self, GSCurrentThread()); + rLength += res; + + while (valid == YES && rLength >= rWant) + { + BOOL shouldDispatch = NO; + + switch (rType) + { + case GSP_NONE: + { + GSPortItemHeader *h; + unsigned l; + + /* + * We have read an item header - set up to read the + * remainder of the item. + */ + h = (GSPortItemHeader*)bytes; + rType = GSSwapBigI32ToHost(h->type); + l = GSSwapBigI32ToHost(h->length); + if (rType == GSP_PORT) + { + if (l > 128) + { + NSLog(@"%@ - unreasonable length (%u) for port", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * For a port, we leave the item header in the data + * so that our decode function can check length info. + */ + rWant += l; + } + else if (rType == GSP_DATA) + { + if (l == 0) + { + NSData *d; + + /* + * For a zero-length data chunk, we create an empty + * data object and add it to the current message. + */ + rType = GSP_NONE; /* ready for a new item */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + d = [mutableDataClass new]; + [rItems addObject: d]; + RELEASE(d); + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + else + { + if (l > maxDataLength) + { + NSLog(@"%@ - unreasonable length (%u) for data", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * If not a port or zero length data, + * we discard the data read so far and fill the + * data object with the data item from the msg. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = l; + } + } + else if (rType == GSP_HEAD) + { + if (l > maxDataLength) + { + NSLog(@"%@ - unreasonable length (%u) for data", + self, l); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * If not a port or zero length data, + * we discard the data read so far and fill the + * data object with the data item from the msg. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = l; + } + else + { + NSLog(@"%@ - bad data received on port handle", self); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + } + break; + + case GSP_HEAD: + { + GSPortMsgHeader *h; + + rType = GSP_NONE; /* ready for a new item */ + /* + * We have read a message header - set up to read the + * remainder of the message. + */ + h = (GSPortMsgHeader*)bytes; + rId = GSSwapBigI32ToHost(h->mId); + nItems = GSSwapBigI32ToHost(h->nItems); + NSAssert(nItems >0, NSInternalInconsistencyException); + rItems + = [mutableArrayClass allocWithZone: NSDefaultMallocZone()]; + rItems = [rItems initWithCapacity: nItems]; + if (rWant > sizeof(GSPortMsgHeader)) + { + NSData *d; + + /* + * The first data item of the message was included in + * the header - so add it to the rItems array. + */ + rWant -= sizeof(GSPortMsgHeader); + d = [mutableDataClass alloc]; + d = [d initWithBytes: bytes + sizeof(GSPortMsgHeader) + length: rWant]; + [rItems addObject: d]; + RELEASE(d); + rWant += sizeof(GSPortMsgHeader); + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + if (nItems == 1) + { + shouldDispatch = YES; + } + } + else + { + /* + * want to read another item + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + } + } + break; + + case GSP_DATA: + { + NSData *d; + + rType = GSP_NONE; /* ready for a new item */ + d = [mutableDataClass allocWithZone: NSDefaultMallocZone()]; + d = [d initWithBytes: bytes length: rWant]; + [rItems addObject: d]; + RELEASE(d); + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + break; + + case GSP_PORT: + { + NSSocketPort *p; + + rType = GSP_NONE; /* ready for a new item */ + p = decodePort(rData, defaultAddress); + if (p == nil) + { + NSLog(@"%@ - unable to decode remote port", self); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + /* + * Set up to read another item header. + */ + rLength -= rWant; + if (rLength > 0) + { + memcpy(bytes, bytes + rWant, rLength); + } + rWant = sizeof(GSPortItemHeader); + + if (state == GS_H_ACCEPT) + { + /* + * This is the initial port information on a new + * connection - set up port relationships. + */ + state = GS_H_CONNECTED; + [p addHandle: self forSend: YES]; + } + else + { + /* + * This is a port within a port message - add + * it to the message components. + */ + [rItems addObject: p]; + if (nItems == [rItems count]) + { + shouldDispatch = YES; + } + } + } + break; + } + + if (shouldDispatch == YES) + { + NSPortMessage *pm; + NSSocketPort *rp = [self recvPort]; + + pm = [portMessageClass allocWithZone: NSDefaultMallocZone()]; + pm = [pm initWithSendPort: [self sendPort] + receivePort: rp + components: rItems]; + [pm setMsgid: rId]; + rId = 0; + DESTROY(rItems); + NSDebugMLLog(@"GSTcpHandle", + @"got message %@ on 0x%x in thread 0x%x", + pm, self, GSCurrentThread()); + RETAIN(rp); + M_UNLOCK(myLock); + NS_DURING + { + [rp handlePortMessage: pm]; + } + NS_HANDLER + { + M_LOCK(myLock); + RELEASE(pm); + RELEASE(rp); + [localException raise]; + } + NS_ENDHANDLER + M_LOCK(myLock); + RELEASE(pm); + RELEASE(rp); + bytes = [rData mutableBytes]; + } + } + } + else + { + if (state == GS_H_TRYCON) /* Connection attempt. */ + { + int res = 0; + int len = sizeof(res); + + if (getsockopt(desc, SOL_SOCKET, SO_ERROR, (char*)&res, &len) == 0 + && res != 0) + { + state = GS_H_UNCON; + NSLog(@"connect attempt failed - %s", GSLastErrorStr(res)); + } + else + { + NSData *d = newDataWithEncodedPort([self recvPort]); + + len = send(desc, [d bytes], [d length], 0); + if (len == (int)[d length]) + { + RELEASE(defaultAddress); + defaultAddress = RETAIN([NSString stringWithCString: + inet_ntoa(sockAddr.sin_addr)]); + NSDebugMLLog(@"GSTcpHandle", + @"wrote %d bytes on 0x%x in thread 0x%x", + len, self, GSCurrentThread()); + state = GS_H_CONNECTED; + } + else + { + state = GS_H_UNCON; + NSLog(@"connect write attempt failed - %s", + GSLastErrorStr(errno)); + } + RELEASE(d); + } + } + else + { + int res; + unsigned l; + const void *b; + + if (wData == nil) + { + if ([wMsgs count] > 0) + { + NSArray *components = [wMsgs objectAtIndex: 0]; + + wData = [components objectAtIndex: wItem++]; + wLength = 0; + } + else + { + // NSLog(@"No messages to write on 0x%x.", self); + return; + } + } + b = [wData bytes]; + l = [wData length]; + res = send(desc, b + wLength, l - wLength, 0); + if (res < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + NSLog(@"write attempt failed - %s", GSLastErrorStr(errno)); + M_UNLOCK(myLock); + [self invalidate]; + return; + } + } + else + { + NSDebugMLLog(@"GSTcpHandle", + @"wrote %d bytes on 0x%x in thread 0x%x", + res, self, GSCurrentThread()); + wLength += res; + if (wLength == l) + { + NSArray *components; + + /* + * We have completed a data item so see what is + * left of the message components. + */ + components = [wMsgs objectAtIndex: 0]; + wLength = 0; + if ([components count] > wItem) + { + /* + * More to write - get next item. + */ + wData = [components objectAtIndex: wItem++]; + } + else + { + NSRunLoop *l = [runLoopClass currentRunLoop]; + + /* + * message completed - remove from list. + */ + NSDebugMLLog(@"GSTcpHandle", + @"completed 0x%x on 0x%x in thread 0x%x", + components, self, GSCurrentThread()); + wData = nil; + wItem = 0; + [wMsgs removeObjectAtIndex: 0]; + + [l removeEvent: data + type: ET_WDESC + forMode: mode + all: NO]; + [l removeEvent: data + type: ET_EDESC + forMode: mode + all: NO]; + } + } + } + } + } + + M_UNLOCK(myLock); +} + +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when +{ + NSRunLoop *l; + BOOL sent = NO; + + NSAssert([components count] > 0, NSInternalInconsistencyException); + NSDebugMLLog(@"GSTcpHandle", + @"Sending message 0x%x %@ on 0x%x(%d) in thread 0x%x before %@", + components, components, self, desc, GSCurrentThread(), when); + M_LOCK(myLock); + [wMsgs addObject: components]; + + l = [runLoopClass currentRunLoop]; + + RETAIN(self); + + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSConnectionReplyMode]; + while (valid == YES + && [wMsgs indexOfObjectIdenticalTo: components] != NSNotFound + && [when timeIntervalSinceNow] > 0) + { + M_UNLOCK(myLock); + [l runMode: NSConnectionReplyMode beforeDate: when]; + M_LOCK(myLock); + } + /* + * NB. We will remove ourself from the run loop when the message send + * is completed, so we don't need to do it here. + */ + if ([wMsgs indexOfObjectIdenticalTo: components] == NSNotFound) + { + sent = YES; + } + M_UNLOCK(myLock); + RELEASE(self); + NSDebugMLLog(@"GSTcpHandle", + @"Message send 0x%x on 0x%x in thread 0x%x status %d", + components, self, GSCurrentThread(), sent); + return sent; +} + +- (NSSocketPort*) sendPort +{ + if (sendPort == nil) + return nil; + else if (caller == YES) + return GS_GC_UNHIDE(sendPort); // We called, so port is not retained. + else + return sendPort; // Retained port. +} + +- (void) setState: (GSHandleState)s +{ + state = s; +} + +- (GSHandleState) state +{ + return state; +} + +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode +{ + return nil; +} + +@end + + + +@interface NSSocketPort (RunLoop) +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode; +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode; +@end + +@implementation NSSocketPort + +static NSRecursiveLock *tcpPortLock = nil; +static NSMapTable *tcpPortMap = 0; +static Class tcpPortClass; + +/* + * When the system becomes multithreaded, we set a flag to say so and + * make sure that port and handle locking is enabled. */ -@implementation NSSocketPort ++ (void) _becomeThreaded: (NSNotification*)notification +{ + if (multi_threaded == NO) + { + NSMapEnumerator pEnum; + NSMapTable *m; + void *dummy; + + multi_threaded = YES; + if (tcpPortLock == nil) + { + tcpPortLock = [NSRecursiveLock new]; + } + pEnum = NSEnumerateMapTable(tcpPortMap); + while (NSNextMapEnumeratorPair(&pEnum, &dummy, (void**)&m)) + { + NSMapEnumerator mEnum; + NSSocketPort *p; + + mEnum = NSEnumerateMapTable(m); + while (NSNextMapEnumeratorPair(&mEnum, &dummy, (void**)&p)) + { + if ([p isValid] == YES) + { + NSMapEnumerator hEnum; + GSTcpHandle *h; + + if (p->myLock == nil) + { + p->myLock = [NSRecursiveLock new]; + } + hEnum = NSEnumerateMapTable(p->handles); + while (NSNextMapEnumeratorPair(&hEnum, &dummy, (void**)&h)) + { + if ([h isValid] == YES && h->myLock == nil) + { + h->myLock = [NSRecursiveLock new]; + } + } + NSEndMapTableEnumeration(&hEnum); + } + } + NSEndMapTableEnumeration(&mEnum); + } + NSEndMapTableEnumeration(&pEnum); + } + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSWillBecomeMultiThreadedNotification + object: nil]; +} + +#if NEED_WORD_ALIGNMENT +static unsigned wordAlign; +#endif + (void) initialize { if (self == [NSSocketPort class]) { - [self setVersion: 1]; +#if NEED_WORD_ALIGNMENT + wordAlign = objc_alignof_type(@encode(gsu32)); +#endif + tcpPortClass = self; + tcpPortMap = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + + if ([NSThread isMultiThreaded]) + { + [self _becomeThreaded: nil]; + } + else + { + [[NSNotificationCenter defaultCenter] + addObserver: self + selector: @selector(_becomeThreaded:) + name: NSWillBecomeMultiThreadedNotification + object: nil]; + } } } -/** - * Initialize the receiver with a local socket to accept TCP connections - * on a non-conflicting port number chosen by the system. - */ -- (id) init ++ (id) new { - return [self initWithTCPPort: 0]; + return RETAIN([self portWithNumber: 0 + onHost: nil + forceAddress: nil + listener: YES]); +} + +/* + * Look up an existing NSSocketPort given a host and number + */ ++ (NSSocketPort*) existingPortWithNumber: (gsu16)number + onHost: (NSHost*)aHost +{ + NSSocketPort *port = nil; + NSMapTable *thePorts; + + M_LOCK(tcpPortLock); + + /* + * Get the map table of ports with the specified number. + */ + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, (void*)(gsaddr)number); + if (thePorts != 0) + { + port = (NSSocketPort*)NSMapGet(thePorts, (void*)aHost); + IF_NO_GC(AUTORELEASE(RETAIN(port))); + } + M_UNLOCK(tcpPortLock); + return port; +} + +/* + * This is the preferred initialisation method for NSSocketPort + * + * 'number' should be a TCP/IP port number or may be zero for a port on + * the local host. + * 'aHost' should be the host for the port or may be nil for the local + * host. + * 'addr' is the IP address that MUST be used for this port - if it is nil + * then, for the local host, the port uses ALL IP addresses, and for a + * remote host, the port will use the first address that works. + */ ++ (NSSocketPort*) portWithNumber: (gsu16)number + onHost: (NSHost*)aHost + forceAddress: (NSString*)addr + listener: (BOOL)shouldListen +{ + unsigned i; + NSSocketPort *port = nil; + NSHost *thisHost = [NSHost localHost]; + NSMapTable *thePorts; + + if (thisHost == nil) + { + NSLog(@"attempt to create port on host without networking set up!"); + return nil; + } + if (aHost == nil) + { + aHost = thisHost; + } + if (addr != nil && [[aHost addresses] containsObject: addr] == NO) + { + NSLog(@"attempt to use address '%@' on host without that address", addr); + return nil; + } + if (number == 0 && [thisHost isEqual: aHost] == NO) + { + NSLog(@"attempt to get port zero on remote host"); + return nil; + } + + M_LOCK(tcpPortLock); + + /* + * First try to find a pre-existing port. + */ + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, (void*)(gsaddr)number); + if (thePorts != 0) + { + port = (NSSocketPort*)NSMapGet(thePorts, (void*)aHost); + } + + if (port == nil) + { + port = (NSSocketPort*)NSAllocateObject(self, 0, NSDefaultMallocZone()); + port->listener = -1; + port->host = RETAIN(aHost); + port->address = [addr copy]; + port->handles = NSCreateMapTable(NSIntMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + if (multi_threaded == YES) + { + port->myLock = [NSRecursiveLock new]; + } + port->_is_valid = YES; + + if (shouldListen == YES && [thisHost isEqual: aHost]) + { +#ifndef BROKEN_SO_REUSEADDR + int reuse = 1; /* Should we re-use ports? */ +#endif + SOCKET desc; + BOOL addrOk = YES; + struct sockaddr_in sockaddr; + + /* + * Creating a new port on the local host - so we must create a + * listener socket to accept incoming connections. + */ + memset(&sockaddr, '\0', sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + if (addr == nil) + { + sockaddr.sin_addr.s_addr = GSSwapHostI32ToBig(INADDR_ANY); + } + else + { +#ifndef HAVE_INET_ATON + sockaddr.sin_addr.s_addr = inet_addr([addr cString]); + if (sockaddr.sin_addr.s_addr == INADDR_NONE) +#else + if (inet_aton([addr cString], &sockaddr.sin_addr) == 0) +#endif + { + addrOk = NO; + } + } + + /* + * Need size of buffer for getsockbyname() later. + */ + i = sizeof(sockaddr); + + if (addrOk == NO) + { + NSLog(@"Bad address (%@) specified for listening port", addr); + DESTROY(port); + } + else if ((desc = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) + == INVALID_SOCKET) + { + NSLog(@"unable to create socket - %s", GSLastErrorStr(errno)); + DESTROY(port); + } +#ifndef BROKEN_SO_REUSEADDR + /* + * Under decent systems, SO_REUSEADDR means that the port can be + * reused immediately that this porcess exits. Under some it means + * that multiple processes can serve the same port simultaneously. + * We don't want that broken behavior! + */ + else if (setsockopt(desc, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, + sizeof(reuse)) < 0) + { + (void) close(desc); + NSLog(@"unable to set reuse on socket - %s", + GSLastErrorStr(errno)); + DESTROY(port); + } +#endif + else if (bind(desc, (struct sockaddr *)&sockaddr, + sizeof(sockaddr)) == SOCKET_ERROR) + { + NSLog(@"unable to bind to port %s:%d - %s", + inet_ntoa(sockaddr.sin_addr), number, GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else if (listen(desc, 5) == SOCKET_ERROR) + { + NSLog(@"unable to listen on port - %s", GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else if (getsockname(desc, (struct sockaddr*)&sockaddr, &i) + == SOCKET_ERROR) + { + NSLog(@"unable to get socket name - %s", GSLastErrorStr(errno)); + (void) close(desc); + DESTROY(port); + } + else + { + /* + * Set up the listening descriptor and the actual TCP port + * number (which will have been set to a real port number when + * we did the 'bind' call. + */ + port->listener = desc; + port->portNum = GSSwapBigI16ToHost(sockaddr.sin_port); + /* + * Make sure we have the map table for this port. + */ + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, + (void*)(gsaddr)port->portNum); + if (thePorts == 0) + { + /* + * No known ports with this port number - + * create the map table to add the new port to. + */ + thePorts = NSCreateMapTable(NSObjectMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + NSMapInsert(tcpPortMap, (void*)(gsaddr)port->portNum, + (void*)thePorts); + } + /* + * Ok - now add the port for the host + */ + NSMapInsert(thePorts, (void*)aHost, (void*)port); + NSDebugMLLog(@"NSPort", @"Created listening port: %@", port); + } + } + else + { + /* + * Make sure we have the map table for this port. + */ + port->portNum = number; + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, (void*)(gsaddr)number); + if (thePorts == 0) + { + /* + * No known ports within this port number - + * create the map table to add the new port to. + */ + thePorts = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + NSMapInsert(tcpPortMap, (void*)(gsaddr)number, (void*)thePorts); + } + /* + * Record the port by host. + */ + NSMapInsert(thePorts, (void*)aHost, (void*)port); + NSDebugMLLog(@"NSPort", @"Created speaking port: %@", port); + } + } + else + { + RETAIN(port); + NSDebugMLLog(@"NSPort", @"Using pre-existing port: %@", port); + } + IF_NO_GC(AUTORELEASE(port)); + + M_UNLOCK(tcpPortLock); + return port; +} + +- (void) addHandle: (GSTcpHandle*)handle forSend: (BOOL)send +{ + M_LOCK(myLock); + if (send == YES) + { + if (handle->caller == YES) + handle->sendPort = GS_GC_HIDE(self); + else + ASSIGN(handle->sendPort, self); + } + else + { + handle->recvPort = GS_GC_HIDE(self); + } + NSMapInsert(handles, (void*)(gsaddr)[handle descriptor], (void*)handle); + M_UNLOCK(myLock); +} + +- (NSString*) address +{ + return address; +} + +- (id) copyWithZone: (NSZone*)zone +{ + return RETAIN(self); } - (void) dealloc { - if (_socket > -1) - { - NSDebugMLLog(@"NSSocketPort", @"closing socket descriptor %d", _socket); - close(_socket); - } + [self gcFinalize]; + DESTROY(host); + TEST_RELEASE(address); + [super dealloc]; } -/** - * Initialize the receiver as a local socket to accept connections on - * TCP port portNumber. If portNumber is zero, - * the system will chose a non-conflicting port number.
- * NOTE: This method currently does not support IPv6 connections. - */ -- (id) initWithTCPPort: (unsigned short)portNumber +- (NSString*) description { - struct sockaddr_in sa; - NSData *saData; + NSMutableString *desc; - /* Clear memory, as recommended. */ - memset(&sa, 0, sizeof(struct sockaddr_in)); - sa.sin_len = sizeof(struct sockaddr_in); - sa.sin_family = PF_INET; - sa.sin_port = htons(portNumber); - sa.sin_addr.s_addr = INADDR_ANY; - - saData = [NSData dataWithBytes: &sa length: sizeof(struct sockaddr_in)]; - if (saData == nil) - { - RELEASE(self); - return nil; - } - - return [self initWithProtocolFamily: PF_INET - socketType: SOCK_STREAM - protocol: 0 - address: saData]; -} - -/** - * Initialize the receiver as a local socket to accept connections on a - * socket of type with the protocol from the protocol - * family family. The addrData should contain a copy - * of the protocol family-specific address data in an NSData object. - */ -- (id) initWithProtocolFamily: (int)family - socketType: (int)type - protocol: (int)protocol - address: (NSData *)addrData -{ - int s = -1; - - if (addrData == nil) + desc = [NSMutableString stringWithFormat: @"NSPort on host with details -\n" + @"%@\n", host]; + if (address == nil) { - NSDebugMLLog(@"NSSocketPort", @"Nil value passed for address."); - goto iWPFAFailed; - } - - s = socket(family, type, protocol); - if (s == INVALID_SOCKET) - { - NSLog(@"socket: %s", GSLastErrorStr(errno)); - goto iWPFAFailed; - } - - if (bind(s, (struct sockaddr *)[addrData bytes], [addrData length]) - == SOCKET_ERROR) - { - NSLog(@"bind: %s", GSLastErrorStr(errno)); - goto iWPFAFailed; - } - - if (listen(s, SOMAXCONN) == SOCKET_ERROR) - { - NSLog(@"listen: %s", GSLastErrorStr(errno)); - goto iWPFAFailed; - } - - return [self initWithProtocolFamily: family - socketType: type - protocol: protocol - socket: (NSSocketNativeHandle)s]; - -iWPFAFailed: - if (s > -1) - close(s); - RELEASE(self); - return nil; -} - -/** - * Initialize the receiver with socket, the platform-native handle - * to a previously initialized listen-mode socket of type type - * with the protocol protocol from the protocol family - * family.
- * The receiver will close the socket upon deallocation. - */ -- (id) initWithProtocolFamily: (int)family - socketType: (int)type - protocol: (int)protocol - socket: (NSSocketNativeHandle)socket -{ - _protocolFamily = family; - _socketType = type; - _protocol = protocol; - _socket = socket; - - return self; -} - -/** - * Initialize the receiver to connect to a remote TCP socket on port - * portNumber of host hostname. The receiver delays - * initiation of the connection until it has data to send.
- * NOTE: This method currently does not support IPv6 connections. - */ -- (id) initRemoteWithTCPPort: (unsigned short)portNumber - host: (NSString *)hostname -{ - struct sockaddr_in sa; - const char *address; - NSData *addrData; - - address = [[[NSHost hostWithName: hostname] address] cString]; - if (address == NULL) - { - RELEASE(self); - return nil; - } - - /* Clear memory, as recommended. */ - memset(&sa, 0, sizeof(struct sockaddr_in)); - - sa.sin_len = sizeof(struct sockaddr_in); - sa.sin_family = PF_INET; - sa.sin_port = htons(portNumber); - sa.sin_addr.s_addr = inet_addr(address); - - addrData = [NSData dataWithBytes: &sa length: sizeof(struct sockaddr_in)]; - if (addrData == nil) - { - RELEASE(self); - return nil; - } - - return [self initRemoteWithProtocolFamily: PF_INET - socketType: SOCK_STREAM - protocol: 0 - address: addrData]; -} - -/** - * Initialize the receiver to connect to a remote socket of type - * with protocol from the protocol family family. The - * addrData should contain a copy of the protocol family-specific - * address data in an NSData object. - */ -- (id) initRemoteWithProtocolFamily: (int)family - socketType: (int)type - protocol: (int)protocol - address: (NSData *)addrData -{ - if (addrData == nil) - { - NSDebugMLLog(@"NSSocketPort", @"Nil value passed for address."); - RELEASE(self); - return nil; - } - - _socket = socket(family, type, protocol); - if (_socket == INVALID_SOCKET) - { - NSLog(@"socket: %s", GSLastErrorStr(errno)); - RELEASE(self); - return nil; - } - - _protocolFamily = family; - _socketType = type; - _protocol = protocol; - _remoteAddrData = RETAIN(addrData); - - return self; -} - -/** - * Return the protocol family-specific socket address in an NSData object. - */ -- (NSData *) address -{ - char sa[SOCK_MAXADDRLEN]; - int len = SOCK_MAXADDRLEN; - - if (_remoteAddrData != nil) - { - return _remoteAddrData; - } - else if (getsockname(_socket, (struct sockaddr *)&sa, &len) == 0) - { - return [NSData dataWithBytes: &sa length: len]; + [desc appendFormat: @" IP address - any\n"]; } else - { - NSLog(@"getsockname: %s", GSLastErrorStr(errno)); - return nil; + { + [desc appendFormat: @" IP address - %@\n", address]; } + [desc appendFormat: @" TCP port - %d\n", portNum]; + return desc; } -/** - * Return the socket protocol. - */ -- (int) protocol +- (void) gcFinalize { - return _protocol; + NSDebugMLLog(@"NSPort", @"NSSocketPort 0x%x finalized", self); + [self invalidate]; } -/** - * Return the socket protocol family. +/* + * This is a callback method used by the NSRunLoop class to determine which + * descriptors to watch for the port. */ -- (int) protocolFamily +- (void) getFds: (SOCKET*)fds count: (int*)count { - return _protocolFamily; + NSMapEnumerator me; + SOCKET sock; + GSTcpHandle *handle; + id recvSelf; + + M_LOCK(myLock); + + /* + * Make sure there is enough room in the provided array. + */ + NSAssert(*count > (int)NSCountMapTable(handles), + NSInternalInconsistencyException); + + /* + * Put in our listening socket. + */ + *count = 0; + if (listener >= 0) + { + fds[(*count)++] = listener; + } + + /* + * Enumerate all our socket handles, and put them in as long as they + * are to be used for receiving. + */ + recvSelf = GS_GC_HIDE(self); + me = NSEnumerateMapTable(handles); + while (NSNextMapEnumeratorPair(&me, (void*)&sock, (void*)&handle)) + { + if (handle->recvPort == recvSelf) + { + fds[(*count)++] = sock; + } + } + NSEndMapTableEnumeration(&me); + M_UNLOCK(myLock); } -/** - * Return the platform-native socket handle. - */ -- (NSSocketNativeHandle) socket +- (GSTcpHandle*) handleForPort: (NSSocketPort*)recvPort beforeDate: (NSDate*)when { - return _socket; + NSMapEnumerator me; + SOCKET sock; +#ifndef BROKEN_SO_REUSEADDR + int opt = 1; +#endif + GSTcpHandle *handle = nil; + + M_LOCK(myLock); + /* + * Enumerate all our socket handles, and look for one with port. + */ + me = NSEnumerateMapTable(handles); + while (NSNextMapEnumeratorPair(&me, (void*)&sock, (void*)&handle)) + { + if ([handle recvPort] == recvPort) + { + M_UNLOCK(myLock); + NSEndMapTableEnumeration(&me); + return handle; + } + } + NSEndMapTableEnumeration(&me); + + /* + * Not found ... create a new handle. + */ + handle = nil; + if ((sock = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) == INVALID_SOCKET) + { + NSLog(@"unable to create socket - %s", GSLastErrorStr(errno)); + } +#ifndef BROKEN_SO_REUSEADDR + /* + * Under decent systems, SO_REUSEADDR means that the port can be reused + * immediately that this process exits. Under some it means + * that multiple processes can serve the same port simultaneously. + * We don't want that broken behavior! + */ + else if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, + sizeof(opt)) < 0) + { + (void)close(sock); + NSLog(@"unable to set reuse on socket - %s", GSLastErrorStr(errno)); + } +#endif + else if ((handle = [GSTcpHandle handleWithDescriptor: sock]) == nil) + { + (void)close(sock); + NSLog(@"unable to create GSTcpHandle - %s", GSLastErrorStr(errno)); + } + else + { + [recvPort addHandle: handle forSend: NO]; + } + M_UNLOCK(myLock); + /* + * If we succeeded in creating a new handle - connect to remote host. + */ + if (handle != nil) + { + if ([handle connectToPort: self beforeDate: when] == NO) + { + [handle invalidate]; + handle = nil; + } + } + return handle; } -/** - * Return the socket type. - */ -- (int) socketType +- (void) handlePortMessage: (NSPortMessage*)m { - return _socketType; + id d = [self delegate]; + + if (d == nil) + { + NSDebugMLLog(@"NSPort", @"No delegate to handle incoming message", 0); + return; + } + if ([d respondsToSelector: @selector(handlePortMessage:)] == NO) + { + NSDebugMLLog(@"NSPort", @"delegate doesn't handle messages", 0); + return; + } + [d handlePortMessage: m]; +} + +- (unsigned) hash +{ + return (unsigned)portNum; +} + +- (NSHost*) host +{ + return host; +} + +- (id) init +{ + RELEASE(self); + self = [tcpPortClass new]; + return self; } -/* Concrete NSPort method implementations. */ - (void) invalidate { - /* Sockets don't close when the connection drops, they time out. - Invalidation is not possible; the caller must notice the error. */ - return; -} - -/* Experimentation */ -- (void) doesNotRecognizeSelector: (SEL)aSelector -{ - NSDebugLog(@"NSSocketPort", @"NSSocketPort does not recognize selector %@\n", - NSStringFromSelector(aSelector)); -} - -/* NSCopying */ -/** - * FIXME: The Apple documentation does not explain what it means to copy an - * NSSocketPort and I do not have access to a MacOS X system to check. - */ -- (id) copyWithZone: (NSZone *)zone -{ - if (zone == NULL) - zone = NSDefaultMallocZone(); - - if (NSShouldRetainWithZone(self, zone) == YES) - return RETAIN(self); - else + if ([self isValid] == YES) { - NSSocketPort *copy = NSAllocateObject([self class], 0, zone); + M_LOCK(myLock); - if (copy != nil) + if ([self isValid] == YES) { - /* Insulate against NSPort changes. */ - [copy setDelegate: [self delegate]]; + NSMapTable *thePorts; + NSArray *handleArray; + unsigned i; - copy->_socket = dup(_socket); - copy->_protocolFamily = _protocolFamily; - copy->_socketType = _socketType; - copy->_protocol = _protocol; - _remoteAddrData = [_remoteAddrData copyWithZone: zone]; + M_LOCK(tcpPortLock); + thePorts = NSMapGet(tcpPortMap, (void*)(gsaddr)portNum); + if (thePorts != 0) + { + if (listener >= 0) + { + (void) close(listener); + listener = -1; + } + NSMapRemove(thePorts, (void*)host); + } + M_UNLOCK(tcpPortLock); + + if (handles != 0) + { + handleArray = NSAllMapTableValues(handles); + i = [handleArray count]; + while (i-- > 0) + { + GSTcpHandle *handle = [handleArray objectAtIndex: i]; + + [handle invalidate]; + } + /* + * We permit mutual recursive invalidation, so the handles map + * may already have been destroyed. + */ + if (handles != 0) + { + NSFreeMapTable(handles); + handles = 0; + } + } + [[NSSocketPortNameServer sharedInstance] removePort: self]; + [super invalidate]; } - - return RETAIN(copy); + M_UNLOCK(myLock); } } -/* NSCoding */ -- (void) encodeWithCoder: (NSCoder *)encoder +- (BOOL) isEqual: (id)anObject { - NSParameterAssert([encoder isKindOfClass: [NSPortCoder class]]); - NSDebugMLLog(@"NSSocketPort", @"called"); + if (anObject == self) + { + return YES; + } + if ([anObject class] == [self class]) + { + NSSocketPort *o = (NSSocketPort*)anObject; + + if (o->portNum == portNum && [o->host isEqual: host]) + { + return YES; + } + } + return NO; } -- (id) initWithCoder: (NSCoder *)decoder +- (gsu16) portNumber +{ + return portNum; +} + +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode +{ + SOCKET desc = (SOCKET)(gsaddr)extra; + GSTcpHandle *handle; + + if (desc == listener) + { + struct sockaddr_in sockAddr; + int size = sizeof(sockAddr); + + desc = accept(listener, (struct sockaddr*)&sockAddr, &size); + if (desc == INVALID_SOCKET) + { + NSDebugMLLog(@"NSPort", @"accept failed - handled in other thread?"); + } + else + { + /* + * Create a handle for the socket and set it up so we are its + * receiving port, and it's waiting to get the port name from + * the other end. + */ + handle = [GSTcpHandle handleWithDescriptor: desc]; + memcpy(&handle->sockAddr, &sockAddr, sizeof(sockAddr)); + handle->defaultAddress = RETAIN([NSString stringWithCString: + inet_ntoa(sockAddr.sin_addr)]); + + [handle setState: GS_H_ACCEPT]; + [self addHandle: handle forSend: NO]; + } + } + else + { + M_LOCK(myLock); + handle = (GSTcpHandle*)NSMapGet(handles, (void*)(gsaddr)desc); + IF_NO_GC(AUTORELEASE(RETAIN(handle))); + M_UNLOCK(myLock); + if (handle == nil) + { + const char *t; + + if (type == ET_RDESC) t = "rdesc"; + else if (type == ET_WDESC) t = "wdesc"; + else if (type == ET_EDESC) t = "edesc"; + else if (type == ET_RPORT) t = "rport"; + else t = "unknown"; + NSLog(@"No handle for event %s on descriptor %d", t, desc); + [[runLoopClass currentRunLoop] removeEvent: extra + type: type + forMode: mode + all: YES]; + } + else + { + [handle receivedEvent: data type: type extra: extra forMode: mode]; + } + } +} + +/* + * This is called when a tcp/ip socket connection is broken. We remove the + * connection handle from this port and, if this was the last handle to a + * remote port, we invalidate the port. + */ +- (void) removeHandle: (GSTcpHandle*)handle +{ + M_LOCK(myLock); + if ([handle sendPort] == self) + { + if (handle->caller != YES) + { + /* + * This is a handle for a send port, and the handle was not formed + * by calling the remote process, so this port object must have + * been created to deal with an incoming connection and will have + * been retained - we must therefore release this port since the + * handle no longer uses it. + */ + AUTORELEASE(self); + } + handle->sendPort = nil; + } + if ([handle recvPort] == self) + { + handle->recvPort = nil; + } + NSMapRemove(handles, (void*)(gsaddr)[handle descriptor]); + if (listener < 0 && NSCountMapTable(handles) == 0) + { + [self invalidate]; + } + M_UNLOCK(myLock); +} + +/* + * This returns the amount of space that a port coder should reserve at the + * start of its encoded data so that the NSSocketPort can insert header info + * into the data. + * The idea is that a message consisting of a single data item with space at + * the start can be written directly without having to copy data to another + * buffer etc. + */ +- (unsigned int) reservedSpaceLength +{ + return sizeof(GSPortItemHeader) + sizeof(GSPortMsgHeader); +} + +- (BOOL) sendBeforeDate: (NSDate*)when + msgid: (int)msgId + components: (NSMutableArray*)components + from: (NSPort*)receivingPort + reserved: (unsigned)length +{ + BOOL sent = NO; + GSTcpHandle *h; + unsigned rl; + + if ([self isValid] == NO) + { + return NO; + } + if ([components count] == 0) + { + NSLog(@"empty components sent"); + return NO; + } + /* + * If the reserved length in the first data object is wrong - we have to + * fail, unless it's zero, in which case we can insert a data object for + * the header. + */ + rl = [self reservedSpaceLength]; + if (length != 0 && length != rl) + { + NSLog(@"bad reserved length - %u", length); + return NO; + } + if ([receivingPort isKindOfClass: tcpPortClass] == NO) + { + NSLog(@"woah there - receiving port is not the correct type"); + return NO; + } + + h = [self handleForPort: (NSSocketPort*)receivingPort beforeDate: when]; + if (h != nil) + { + NSMutableData *header; + unsigned hLength; + unsigned l; + GSPortItemHeader *pih; + GSPortMsgHeader *pmh; + unsigned c = [components count]; + unsigned i; + BOOL pack = YES; + + /* + * Ok - ensure we have space to insert header info. + */ + if (length == 0 && rl != 0) + { + header = [[mutableDataClass alloc] initWithCapacity: NETBLOCK]; + + [header setLength: rl]; + [components insertObject: header atIndex: 0]; + RELEASE(header); + } + + header = [components objectAtIndex: 0]; + /* + * The Item header contains the item type and the length of the + * data in the item (excluding the item header itsself). + */ + hLength = [header length]; + l = hLength - sizeof(GSPortItemHeader); + pih = (GSPortItemHeader*)[header mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_HEAD); + pih->length = GSSwapHostI32ToBig(l); + + /* + * The message header contains the message Id and the original count + * of components in the message (excluding any extra component added + * simply to hold the header). + */ + pmh = (GSPortMsgHeader*)&pih[1]; + pmh->mId = GSSwapHostI32ToBig(msgId); + pmh->nItems = GSSwapHostI32ToBig(c); + + /* + * Now insert item header information as required. + * Pack as many items into the initial data object as possible, up to + * a maximum of NETBLOCK bytes. This is to try to get a single, + * efficient write operation if possible. + */ + for (i = 1; i < c; i++) + { + id o = [components objectAtIndex: i]; + + if ([o isKindOfClass: [NSData class]]) + { + GSPortItemHeader *pih; + unsigned h = sizeof(GSPortItemHeader); + unsigned l = [o length]; + void *b; + + if (pack == YES && hLength + l + h <= NETBLOCK) + { + [header setLength: hLength + l + h]; + b = [header mutableBytes]; + b += hLength; +#if NEED_WORD_ALIGNMENT + /* + * When packing data, an item may not be aligned on a + * word boundary, so we work with an aligned buffer + * and use memcmpy() + */ + if ((hLength % wordAlign) != 0) + { + GSPortItemHeader itemHeader; + + pih = (GSPortItemHeader*)&itemHeader; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + memcpy(b, (void*)pih, h); + } + else + { + pih = (GSPortItemHeader*)b; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + } +#else + pih = (GSPortItemHeader*)b; + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); +#endif + memcpy(b+h, [o bytes], l); + [components removeObjectAtIndex: i--]; + c--; + hLength += l + h; + } + else + { + NSMutableData *d; + + pack = NO; + d = [[NSMutableData alloc] initWithLength: l + h]; + b = [d mutableBytes]; + pih = (GSPortItemHeader*)b; + memcpy(b+h, [o bytes], l); + pih->type = GSSwapHostI32ToBig(GSP_DATA); + pih->length = GSSwapHostI32ToBig(l); + [components replaceObjectAtIndex: i + withObject: d]; + RELEASE(d); + } + } + else if ([o isKindOfClass: tcpPortClass]) + { + NSData *d = newDataWithEncodedPort(o); + unsigned dLength = [d length]; + + if (pack == YES && hLength + dLength <= NETBLOCK) + { + void *b; + + [header setLength: hLength + dLength]; + b = [header mutableBytes]; + b += hLength; + hLength += dLength; + memcpy(b, [d bytes], dLength); + [components removeObjectAtIndex: i--]; + c--; + } + else + { + pack = NO; + [components replaceObjectAtIndex: i withObject: d]; + } + RELEASE(d); + } + } + + /* + * Now send the message. + */ + sent = [h sendMessage: components beforeDate: when]; + } + return sent; +} + +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode { - NSParameterAssert([decoder isKindOfClass: [NSPortCoder class]]); - NSDebugMLLog(@"NSSocketPort", @"called"); return nil; } @end + + diff --git a/Source/NSSocketPortNameServer.m b/Source/NSSocketPortNameServer.m new file mode 100644 index 000000000..0437a3250 --- /dev/null +++ b/Source/NSSocketPortNameServer.m @@ -0,0 +1,1199 @@ +/** Implementation of NSSocketPortNameServer class for Distributed Objects + Copyright (C) 1998,1999,2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Created: October 1998 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + NSSocketPortNameServer class reference + $Date$ $Revision$ + */ + +#include "config.h" +#include "Foundation/NSString.h" +#include "Foundation/NSByteOrder.h" +#include "Foundation/NSException.h" +#include "Foundation/NSAutoreleasePool.h" +#include "Foundation/NSLock.h" +#include "Foundation/NSFileHandle.h" +#include "Foundation/NSRunLoop.h" +#include "Foundation/NSNotificationQueue.h" +#include "Foundation/NSPort.h" +#include "Foundation/NSMapTable.h" +#include "Foundation/NSSet.h" +#include "Foundation/NSHost.h" +#include "Foundation/NSTask.h" +#include "Foundation/NSDate.h" +#include "Foundation/NSTimer.h" +#include "Foundation/NSPathUtilities.h" +#include "Foundation/NSPortNameServer.h" +#include "Foundation/NSDebug.h" +#ifdef __MINGW__ +#include +#include +#else +#include +#include +#endif + +/* + * Protocol definition stuff for talking to gdomap process. + */ +#include "../Tools/gdomap.h" + +#define stringify_it(X) #X +#define make_gdomap_port(X) stringify_it(X) + +/* + * to suppress warnings about using private methods. + */ +@class NSSocketPort; +@interface NSSocketPort (Hack) ++ (NSSocketPort*) portWithNumber: (gsu16)number + onHost: (NSHost*)host + forceAddress: (NSString*)addr + listener: (BOOL)shouldListen; +- (gsu16) portNumber; +@end + +/* + * class-wide variables. + */ +static unsigned maxHandles = 4; +static NSTimeInterval timeout = 20.0; +static NSString *serverPort = @"gdomap"; +static NSString *mode = @"NSPortServerLookupMode"; +static NSArray *modes = nil; +static NSRecursiveLock *serverLock = nil; +static NSSocketPortNameServer *defaultServer = nil; +static NSString *launchCmd = nil; +static Class portClass = 0; + + + +typedef enum { + GSPC_NONE, + GSPC_LOPEN, + GSPC_ROPEN, + GSPC_RETRY, + GSPC_WRITE, + GSPC_READ1, + GSPC_READ2, + GSPC_FAIL, + GSPC_DONE +} GSPortComState; + +@interface GSPortCom : NSObject +{ + gdo_req msg; + unsigned expecting; + NSMutableData *data; + NSFileHandle *handle; + GSPortComState state; + struct in_addr addr; +} +- (struct in_addr) addr; +- (void) close; +- (NSData*) data; +- (void) didConnect: (NSNotification*)notification; +- (void) didRead: (NSNotification*)notification; +- (void) didWrite: (NSNotification*)notification; +- (void) fail; +- (BOOL) isActive; +- (void) open: (NSString*)host; +- (void) setAddr: (struct in_addr)addr; +- (GSPortComState) state; +- (void) startListNameServers; +- (void) startPortLookup: (NSString*)name onHost: (NSString*)addr; +- (void) startPortRegistration: (gsu32)portNumber withName: (NSString*)name; +- (void) startPortUnregistration: (gsu32)portNumber withName: (NSString*)name; +@end + +@implementation GSPortCom + +- (struct in_addr) addr +{ + return addr; +} + +- (void) close +{ + if (handle != nil) + { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc removeObserver: self + name: GSFileHandleConnectCompletionNotification + object: handle]; + [nc removeObserver: self + name: NSFileHandleReadCompletionNotification + object: handle]; + [nc removeObserver: self + name: GSFileHandleWriteCompletionNotification + object: handle]; + [handle closeFile]; + DESTROY(handle); + } +} + +- (NSData*) data +{ + return data; +} + +- (void) dealloc +{ + [self close]; + TEST_RELEASE(data); + [super dealloc]; +} + +- (void) didConnect: (NSNotification*)notification +{ + NSDictionary *userInfo = [notification userInfo]; + NSString *e; + + e = [userInfo objectForKey: GSFileHandleNotificationError]; + if (e != nil) + { + NSDebugMLLog(@"NSSocketPortNameServer", @"failed connect to gdomap on %@ - %@", + [[notification object] socketAddress], e); + /* + * Remove our file handle, then either retry or fail. + */ + [self close]; + if (launchCmd == nil) + { + launchCmd = RETAIN([[NSSearchPathForDirectoriesInDomains( + GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0] + stringByAppendingPathComponent: @"gdomap"]); + } + if (state == GSPC_LOPEN && launchCmd != nil) + { + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + NSTimer *timer; + + NSLog(@"NSSocketPortNameServer attempting to start gdomap on local host\n" +@"This will take a few seconds.\n" +@"Trying to launch gdomap from %@ or a machine/operating-system subdirectory.\n" +@"It is recommended that you start up gdomap at login time or (better) when\n" +@"your computer is started instead.", +[launchCmd stringByDeletingLastPathComponent]); + [NSTask launchedTaskWithLaunchPath: launchCmd arguments: nil]; + timer = [NSTimer timerWithTimeInterval: 5.0 + invocation: nil + repeats: NO]; + [loop addTimer: timer forMode: [loop currentMode]]; + [loop runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 5.0]]; + NSDebugMLLog(@"NSSocketPortNameServer", + @"retrying local connection to gdomap"); + state = GSPC_RETRY; + [self open: nil]; + } + else + { + [self fail]; + } + } + else + { + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: GSFileHandleConnectCompletionNotification + object: handle]; + /* + * Now we have established a connection, we can write the request + * to the name server. + */ + state = GSPC_WRITE; + [handle writeInBackgroundAndNotify: data + forModes: modes]; + DESTROY(data); + } +} + +- (void) didRead: (NSNotification*)notification +{ + NSDictionary *userInfo = [notification userInfo]; + NSData *d; + + d = [userInfo objectForKey: NSFileHandleNotificationDataItem]; + + if (d == nil || [d length] == 0) + { + [self fail]; + NSLog(@"NSSocketPortNameServer lost connection to gdomap on %@", + [[notification object] socketAddress]); + } + else + { + if (data == nil) + { + data = [d mutableCopy]; + } + else + { + [data appendData: d]; + } + if ([data length] < expecting) + { + /* + * Not enough data read yet - go read some more. + */ + [handle readInBackgroundAndNotifyForModes: modes]; + } + else if (state == GSPC_READ1 && msg.rtype == GDO_SERVERS) + { + gsu32 numSvrs = GSSwapBigI32ToHost(*(gsu32*)[data bytes]); + + if (numSvrs == 0) + { + [self fail]; + NSLog(@"failed to get list of name servers on net"); + } + else + { + /* + * Now read in the addresses of the servers. + */ + expecting += numSvrs * sizeof(struct in_addr); + if ([data length] < expecting) + { + state = GSPC_READ2; + [handle readInBackgroundAndNotifyForModes: modes]; + } + else + { + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSFileHandleReadCompletionNotification + object: handle]; + state = GSPC_DONE; + } + } + } + else + { + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSFileHandleReadCompletionNotification + object: handle]; + state = GSPC_DONE; + } + } +} + +- (void) didWrite: (NSNotification*)notification +{ + NSDictionary *userInfo = [notification userInfo]; + NSString *e; + + e = [userInfo objectForKey: GSFileHandleNotificationError]; + if (e != nil) + { + [self fail]; + NSLog(@"NSSocketPortNameServer failed write to gdomap on %@ - %@", + [[notification object] socketAddress], e); + } + else + { + state = GSPC_READ1; + data = [NSMutableData new]; + expecting = 4; + [handle readInBackgroundAndNotifyForModes: modes]; + } +} + +- (void) fail +{ + [self close]; + if (data != nil) + { + DESTROY(data); + } + msg.rtype = 0; + state = GSPC_FAIL; +} + +- (BOOL) isActive +{ + if (handle == nil) + return NO; + if (state == GSPC_FAIL) + return NO; + if (state == GSPC_NONE) + return NO; + if (state == GSPC_DONE) + return NO; + return YES; +} + +- (void) open: (NSString*)hostname +{ + NSNotificationCenter *nc; + + NSAssert(state == GSPC_NONE || state == GSPC_RETRY, @"open in bad state"); + + if (state == GSPC_NONE) + { + state = GSPC_ROPEN; /* Assume we are connection to remote system */ + if (hostname == nil || [hostname isEqual: @""]) + { + hostname = @"localhost"; + state = GSPC_LOPEN; + } + else + { + NSHost *local = [NSHost localHost]; + NSHost *host = [NSHost hostWithName: hostname]; + + if (host == nil) + { + host = [NSHost hostWithAddress: hostname]; + } + if ([local isEqual: host]) + { + state = GSPC_LOPEN; + } + else + { + NSHost *loopback = [NSHost hostWithAddress: @"127.0.0.1"]; + + if ([loopback isEqual: host]) + { + state = GSPC_LOPEN; + } + } + } + } + + NS_DURING + { + handle = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: + hostname service: serverPort protocol: @"tcp" forModes: modes]; + } + NS_HANDLER + { + NSLog(@"Exception looking up port for gdomap - %@", localException); + if ([[localException name] isEqual: NSInvalidArgumentException]) + { + handle = nil; + } + else + { + [self fail]; + } + } + NS_ENDHANDLER + + if (state == GSPC_FAIL) + return; + + if (handle == nil) + { + if (state == GSPC_LOPEN) + { + NSLog(@"Failed to find gdomap port with name '%@',\nperhaps your " + @"/etc/services file is not correctly set up?\n" + @"Retrying with default (IANA allocated) port number 538", + serverPort); + NS_DURING + { + handle = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: + hostname service: @"538" protocol: @"tcp" forModes: modes]; + } + NS_HANDLER + { + NSLog(@"Exception creating handle for gdomap - %@", + localException); + [self fail]; + } + NS_ENDHANDLER + if (handle) + { + RELEASE(serverPort); + serverPort = @"538"; + } + } + else + { + [self fail]; + } + } + + if (state == GSPC_FAIL) + return; + + IF_NO_GC(RETAIN(handle)); + nc = [NSNotificationCenter defaultCenter]; + [nc addObserver: self + selector: @selector(didConnect:) + name: GSFileHandleConnectCompletionNotification + object: handle]; + [nc addObserver: self + selector: @selector(didRead:) + name: NSFileHandleReadCompletionNotification + object: handle]; + [nc addObserver: self + selector: @selector(didWrite:) + name: GSFileHandleWriteCompletionNotification + object: handle]; +} + +- (void) setAddr: (struct in_addr)anAddr +{ + addr = anAddr; +} + +- (GSPortComState) state +{ + return state; +} + +- (void) startListNameServers +{ + msg.rtype = GDO_SERVERS; /* Get a list of name servers. */ + msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ + msg.nsize = 0; + msg.port = 0; + TEST_RELEASE(data); + data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; + IF_NO_GC(RETAIN(data)); + [self open: nil]; +} + +- (void) startPortLookup: (NSString*)name onHost: (NSString*)host +{ + msg.rtype = GDO_LOOKUP; /* Find the named port. */ + msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ + msg.port = 0; + msg.nsize = [name cStringLength]; + [name getCString: msg.name]; + TEST_RELEASE(data); + data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; + IF_NO_GC(RETAIN(data)); + [self open: host]; +} + +- (void) startPortRegistration: (gsu32)portNumber withName: (NSString*)name +{ + msg.rtype = GDO_REGISTER; /* Register a port. */ + msg.ptype = GDO_TCP_GDO; /* Port is TCP port for GNU DO */ + msg.nsize = [name cStringLength]; + [name getCString: msg.name]; + msg.port = GSSwapHostI32ToBig(portNumber); + TEST_RELEASE(data); + data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; + IF_NO_GC(RETAIN(data)); + [self open: nil]; +} + +- (void) startPortUnregistration: (gsu32)portNumber withName: (NSString*)name +{ + msg.rtype = GDO_UNREG; + msg.ptype = GDO_TCP_GDO; + if (name == nil) + { + msg.nsize = 0; + } + else + { + msg.nsize = [name cStringLength]; + [name getCString: msg.name]; + } + msg.port = GSSwapHostI32ToBig(portNumber); + TEST_RELEASE(data); + data = [NSMutableData dataWithBytes: (void*)&msg length: sizeof(msg)]; + IF_NO_GC(RETAIN(data)); + [self open: nil]; +} + +@end + + + +@implementation NSSocketPortNameServer + ++ (id) allocWithZone: (NSZone*)aZone +{ + [NSException raise: NSGenericException + format: @"attempt to create extra port name server"]; + return nil; +} + ++ (void) initialize +{ + if (self == [NSSocketPortNameServer class]) + { + serverLock = [NSRecursiveLock new]; + modes = [[NSArray alloc] initWithObjects: &mode count: 1]; +#ifdef GDOMAP_PORT_OVERRIDE + serverPort = RETAIN([NSString stringWithCString: + make_gdomap_port(GDOMAP_PORT_OVERRIDE)]); +#endif + portClass = [NSSocketPort class]; + } +} + ++ (id) sharedInstance +{ + if (defaultServer == nil) + { + NSSocketPortNameServer *s; + + [serverLock lock]; + if (defaultServer) + { + [serverLock unlock]; + return defaultServer; + } + s = (NSSocketPortNameServer*)NSAllocateObject(self, 0, NSDefaultMallocZone()); + s->_portMap = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + s->_nameMap = NSCreateMapTable(NSObjectMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + defaultServer = s; + [serverLock unlock]; + } + return defaultServer; +} + +- (void) dealloc +{ + [NSException raise: NSGenericException + format: @"attempt to deallocate default port name server"]; +} + +- (NSPort*) portForName: (NSString*)name +{ + return [self portForName: name onHost: nil]; +} + +- (BOOL) _lookupName: (NSString*)name onHost: (NSString*)host + intoAddress: (NSString**)addr andPort: (unsigned*)port +{ + GSPortCom *com = nil; + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + struct in_addr singleServer; + struct in_addr *svrs = &singleServer; + unsigned numSvrs = 1; + unsigned count; + unsigned len; + NSMutableArray *array; + NSDate *limit; + + *port = 0; + if (name == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to lookup port with nil name"]; + } + + len = [name cStringLength]; + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to lookup port with no name"]; + } + if (len > GDO_NAME_MAX_LEN) + { + [NSException raise: NSInvalidArgumentException + format: @"name of port is too long (max %d) bytes", + GDO_NAME_MAX_LEN]; + } + + limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; + + /* + * get one or more host addresses in network byte order. + */ + if (host == nil || [host isEqual: @""]) + { + /* + * Query a single nameserver - on the local host. + */ +#ifndef HAVE_INET_ATON + svrs->s_addr = inet_addr("127.0.0.1"); +#else + inet_aton("127.0.0.1", (struct in_addr *)&svrs->s_addr); +#endif + } + else if ([host isEqual: @"*"]) + { + GSPortCom *com = [GSPortCom new]; + + [serverLock lock]; + NS_DURING + { + GSPortCom *tmp; + NSData *dat; + + [com startListNameServers]; + while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) + { + [loop runMode: mode + beforeDate: limit]; + } + [com close]; + if ([com state] != GSPC_DONE) + { + [NSException raise: NSPortTimeoutException + format: @"timed out listing name servers"]; + } + /* + * Retain and autorelease the data item so the buffer won't disappear + * when the 'com' object is destroyed. + */ + dat = AUTORELEASE(RETAIN([com data])); + svrs = (struct in_addr*)([dat bytes] + 4); + numSvrs = GSSwapBigI32ToHost(*(gsu32*)[dat bytes]); + if (numSvrs == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"failed to get list of name servers"]; + } + tmp = com; + com = nil; + RELEASE(tmp); + } + NS_HANDLER + { + /* + * If we had a problem - unlock before continueing. + */ + RELEASE(com); + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + } + else + { + NSHost *h; + + /* + * Query a single nameserver - on the specified host. + */ + numSvrs = 1; + h = [NSHost hostWithName: host]; + if (h) + host = [h address]; +#ifndef HAVE_INET_ATON + svrs->s_addr = inet_addr([host cString]); +#else + inet_aton([host cString], (struct in_addr *)&svrs->s_addr); +#endif + } + + /* + * Ok, 'svrs'now points to one or more internet addresses in network + * byte order, and numSvrs tells us how many there are. + */ + array = [NSMutableArray arrayWithCapacity: maxHandles]; + [serverLock lock]; + NS_DURING + { + unsigned i; + + *port = 0; + count = 0; + do + { + /* + * Make sure that all the array slots are full if possible + */ + while (count < numSvrs && [array count] < maxHandles) + { + NSString *addr; + + com = [GSPortCom new]; + [array addObject: com]; + RELEASE(com); + [com setAddr: svrs[count]]; + addr = [NSString stringWithCString: + (char*)inet_ntoa(svrs[count])]; + [com startPortLookup: name onHost: addr]; + count++; + } + + /* + * Handle I/O on the file handles. + */ + i = [array count]; + if (i == 0) + { + break; /* No servers left to try! */ + } + [loop runMode: mode beforeDate: limit]; + + /* + * Check for completed operations. + */ + while (*port == 0 && i-- > 0) + { + com = [array objectAtIndex: i]; + if ([com isActive] == NO) + { + [com close]; + if ([com state] == GSPC_DONE) + { + *port = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); + if (*port != 0) + { + singleServer = [com addr]; + } + } + [array removeObjectAtIndex: i]; + } + } + } + while (*port == 0 && [limit timeIntervalSinceNow] > 0); + + /* + * Make sure that any outstanding lookups are cancelled. + */ + i = [array count]; + while (i-- > 0) + { + [[array objectAtIndex: i] fail]; + [array removeObjectAtIndex: i]; + } + } + NS_HANDLER + { + /* + * If we had a problem - unlock before continueing. + */ + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + + if (*port) + { + *addr = [NSString stringWithCString: inet_ntoa(singleServer)]; + return YES; + } + else + { + return NO; + } +} + +- (NSPort*) portForName: (NSString*)name + onHost: (NSString*)host +{ + NSString *addr; + unsigned portNum = 0; + + if ([self _lookupName: name + onHost: host + intoAddress: &addr + andPort: &portNum] == YES) + { + if (portClass == [NSSocketPort class]) + { + NSHost *host; + + host = [NSHost hostWithAddress: addr]; + return (NSPort*)[NSSocketPort portWithNumber: portNum + onHost: host + forceAddress: addr + listener: NO]; + } + else + { + NSLog(@"Unknown port class (%@) set for new port!", portClass); + return nil; + } + } + else + { + return nil; + } +} + +- (BOOL) registerPort: (NSPort*)port + forName: (NSString*)name +{ + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + GSPortCom *com = nil; + unsigned len; + NSDate *limit; + + if (name == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to register port with nil name"]; + } + if (port == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to register nil port"]; + } + len = [name cStringLength]; + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to register port with no name"]; + } + if (len > GDO_NAME_MAX_LEN) + { + [NSException raise: NSInvalidArgumentException + format: @"name of port is too long (max %d) bytes", + GDO_NAME_MAX_LEN]; + } + + limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; + /* + * Lock out other threads while doing I/O to gdomap + */ + [serverLock lock]; + + NS_DURING + { + NSMutableSet *known = NSMapGet(_portMap, port); + + /* + * If there is no set of names for this port - create one. + */ + if (known == nil) + { + known = [NSMutableSet new]; + NSMapInsert(_portMap, port, known); + RELEASE(known); + } + + /* + * If this port has never been registered under any name, first + * send an unregister message to gdomap to ensure that any old + * names for the port (perhaps from a server that crashed without + * unregistering its ports) are no longer around. + */ + if ([known count] == 0) + { + com = [GSPortCom new]; + [com startPortUnregistration: [(NSSocketPort *)port portNumber] withName: nil]; + while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) + { + [loop runMode: mode + beforeDate: limit]; + } + [com close]; + if ([com state] != GSPC_DONE) + { + [NSException raise: NSPortTimeoutException + format: @"timed out unregistering port"]; + } + DESTROY(com); + } + + com = [GSPortCom new]; + [com startPortRegistration: [(NSSocketPort *)port portNumber] withName: name]; + while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) + { + [loop runMode: mode beforeDate: limit]; + } + [com close]; + if ([com state] != GSPC_DONE) + { + [NSException raise: NSPortTimeoutException + format: @"timed out registering port %@", name]; + } + else + { + unsigned result; + + result = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); + if (result == 0) + { + unsigned int portNum; + NSString *addr; + BOOL found; + + NS_DURING + { + found = [self _lookupName: name + onHost: @"" + intoAddress: &addr + andPort: &portNum]; + + } + NS_HANDLER + { + found = NO; + } + NS_ENDHANDLER + + if (found == YES) + { + [NSException raise: NSGenericException + format: @"Unable to register name '%@' for the port -\n%@\n" +@"It appears that a process is already registered with this name at port\n" +@"'%d' IP address %@\n" +@"Perhaps this program ran before and was shut down without unregistering,\n" +@"so another process may be running using the same network port. If this is\n" +@"the case, you can use -\n" +@"gdomap -U '%@'\n" +@"to remove the registration so that you can attempt this operation again.", + name, port, portNum, addr, name]; + } + else + { + [NSException raise: NSGenericException + format: @"Unable to register name '%@' for the port -\n%@\n" +@"Typically, this might mean that a process is already running with the name\n" +@"'%@' ...\n" +@"Try the command -\n" +@" gdomap -M localhost -L '%@'\n" +@"to find its network details.\n" +@"Alternatively, it may have been shut down without unregistering, and\n" +@"another process may be running using the same network port. If this is\n" +@"the case, you can use -\n" +@"gdomap -U '%@'\n" +@"to remove the registration so that you can attempt this operation again.", + name, port, name, name, name]; + } + } + else + { + /* + * Add this name to the set of names that the port + * is known by and to the name map. + */ + [known addObject: name]; + NSMapInsert(_nameMap, name, port); + } + } + DESTROY(com); + } + NS_HANDLER + { + /* + * If we had a problem - close and unlock before continueing. + */ + DESTROY(com); + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + return YES; +} + +- (BOOL) removePortForName: (NSString*)name +{ + NSRunLoop *loop = [NSRunLoop currentRunLoop]; + GSPortCom *com = nil; + unsigned len; + NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: timeout]; + BOOL val = NO; + + if (name == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to remove port with nil name"]; + } + + len = [name cStringLength]; + if (len == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to remove port with no name"]; + } + if (len > GDO_NAME_MAX_LEN) + { + [NSException raise: NSInvalidArgumentException + format: @"name of port is too long (max %d) bytes", + GDO_NAME_MAX_LEN]; + } + + /* + * Lock out other threads while doing I/O to gdomap + */ + [serverLock lock]; + + NS_DURING + { + GSPortCom *tmp; + + com = [GSPortCom new]; + [com startPortUnregistration: 0 withName: name]; + while ([limit timeIntervalSinceNow] > 0 && [com isActive] == YES) + { + [loop runMode: mode + beforeDate: limit]; + } + [com close]; + if ([com state] != GSPC_DONE) + { + [NSException raise: NSPortTimeoutException + format: @"timed out unregistering port"]; + } + else + { + NSPort *port; + unsigned result; + + result = GSSwapBigI32ToHost(*(gsu32*)[[com data] bytes]); + if (result == 0) + { + NSLog(@"NSSocketPortNameServer unable to unregister '%@'", name); + val = NO; + } + else + { + val = YES; + } + /* + * Find the port that was registered for this name and + * remove the mapping table entries. + */ + port = NSMapGet(_nameMap, name); + if (port) + { + NSMutableSet *known; + + NSMapRemove(_nameMap, name); + known = NSMapGet(_portMap, port); + if (known) + { + [known removeObject: name]; + if ([known count] == 0) + { + NSMapRemove(_portMap, port); + } + } + } + } + tmp = com; + com = nil; + RELEASE(tmp); + } + NS_HANDLER + { + /* + * If we had a problem - unlock before continueing. + */ + RELEASE(com); + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + return val; +} +@end + +@implementation NSSocketPortNameServer (GNUstep) + ++ (Class) setPortClass: (Class)c +{ + Class old = portClass; + + portClass = c; + return old; +} + +/* + * Return the names under which the port is currently registered. + */ +- (NSArray*) namesForPort: (NSPort*)port +{ + NSArray *names; + + if (port == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"attempt to get names for nil port"]; + } + /* + * Lock out other threads while grabbing port names. + */ + [serverLock lock]; + names = [(NSSet*)NSMapGet(_portMap, port) allObjects]; + [serverLock unlock]; + return names; +} + +/* + * Remove all names for a particular port - used when a port is + * invalidated. + */ +- (BOOL) removePort: (NSPort*)port +{ + BOOL ok = YES; + [serverLock lock]; + NS_DURING + { + NSMutableSet *known = (NSMutableSet*)NSMapGet(_portMap, port); + NSString *name; + + RETAIN(known); + while ((name = [known anyObject]) != nil) + { + if ([self removePortForName: name] == NO) + { + ok = NO; + } + } + RELEASE(known); + } + NS_HANDLER + { + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + return ok; +} + +/* + * Remove name for port iff it is registered. + */ +- (BOOL) removePort: (NSPort*)port forName: (NSString*)name +{ + BOOL ok = YES; + + [serverLock lock]; + NS_DURING + { + NSMutableSet *known = (NSMutableSet*)NSMapGet(_portMap, port); + + if ([known member: name] != nil) + { + if ([self removePortForName: name] == NO) + { + ok = NO; + } + } + } + NS_HANDLER + { + [serverLock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [serverLock unlock]; + return ok; +} +@end + diff --git a/Source/libgnustep-base.def.in b/Source/libgnustep-base.def.in index 37638bbac..a3c2bd7c9 100644 --- a/Source/libgnustep-base.def.in +++ b/Source/libgnustep-base.def.in @@ -75,7 +75,7 @@ __objc_class_name_NSGMutableString __objc_class_name_NSGString __objc_class_name_NSImmutableString __objc_class_name_GSTcpHandle -__objc_class_name_GSTcpPort +__objc_class_name_NSSocketPort __objc_class_name_GSValue __objc_class_name_GSNonretainedObjectValue __objc_class_name_GSPointValue @@ -171,6 +171,7 @@ __objc_class_name_NSPortCoder __objc_class_name_NSPortMessage __objc_class_name_GSPortCom __objc_class_name_NSPortNameServer +__objc_class_name_NSSocketPortNameServer __objc_class_name__NSConcreteProcessInfo __objc_class_name_NSProcessInfo __objc_class_name_NSProtocolChecker