diff --git a/ChangeLog b/ChangeLog index 134db1f64..dd5292758 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Thu Feb 24 21:05:00 2000 Richard Frith-Macdonald + + * Source/NSBundle.m: ([-initWithPath:]) added code suggested by + jagapen@whitewater.chem.wisc.edu to handle relative paths. + * Source/NSObject.m: Added catagory of 'Object' so that containers + can safely hold non-NSObject descended objects. + Thu Feb 24 15:55:00 2000 Richard Frith-Macdonald * Source/TcpPort.m: Removed some unused code. diff --git a/Source/GSTcpPort.m b/Source/GSTcpPort.m new file mode 100644 index 000000000..425c9b7ca --- /dev/null +++ b/Source/GSTcpPort.m @@ -0,0 +1,1447 @@ +/* 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(__WIN32__) || defined(__CYGWIN__) +#include /* for gethostname() */ +#include /* for inet_ntoa() */ +#include +#include +#include +#include + +extern int errno; + +#define GS_CONNECTION_MSG 0 + +/* + * Stuff for setting the sockets into non-blocking mode. + */ +#ifdef __POSIX_SOURCE +#define NBLK_OPT O_NONBLOCK +#else +#define NBLK_OPT FNDELAY +#endif + +#endif /* !__WIN32__ */ +#include /* for memset() and strchr() */ +#if !defined(__WIN32__) || defined(__CYGWIN__) +#include +#include +#include +#include +#endif /* !__WIN32__ */ + +@class GSTcpPort; + + +/* 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 +{ + NSLock *myLock; /* Lock for this handle. */ + 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; /* Ammount read so far. */ + 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. */ + GSTcpPort *recvPort; + GSTcpPort *sendPort; + int addrNum; /* Address number within host */ +} + ++ (GSTcpHandle*) handleWithDescriptor: (int)d; +- (BOOL) connectBeforeDate: (NSDate*)when; +- (int) descriptor; +- (void) dispatch; +- (void) invalidate; +- (void) receivedEvent: (void*)data + type: (RunLoopEventType)type + extra: (void*)extra + forMode: (NSString*)mode; +- (GSTcpPort*) recvPort; +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when; +- (GSTcpPort*) sendPort; +- (void) setRecvPort: (GSTcpPort*)port; +- (void) setSendPort: (GSTcpPort*)port; +- (void) setState: (GSHandleState)s; +- (GSHandleState) state; +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode; +@end + +@interface GSTcpPort : NSPort +{ + NSRecursiveLock *myLock; + NSHost *host; /* OpenStep host for this port. */ + NSString *address; /* Forced internet address. */ + gsu16 portNum; /* TCP port in host byte order. */ + int listener; /* Descriptor to listen on. */ + NSMapTable *handles; /* Handles indexed by port. */ +} + ++ (GSTcpPort*) existingPortWithNumber: (gsu16)number + onHost: (NSHost*)host; ++ (GSTcpPort*) portWithNumber: (gsu16)number + onHost: (NSHost*)host + forceAddress: (NSString*)addr; + +- (void) addHandle: (GSTcpHandle*)handle forPort: (GSTcpPort*)other; +- (NSString*) address; +- (void) getFds: (int*)fds count: (int*)count; +- (GSTcpHandle*) handleForPort: (GSTcpPort*)recvPort beforeDate: (NSDate*)when; +- (void) handlePortMessage: (NSPortMessage*)m; +- (NSHost*) host; +- (gsu16) portNumber; +- (void) removeHandle: (GSTcpHandle*)h; +@end + + +/* + * Utility functions for encoding and decoding ports. + */ +static GSTcpPort* +decodePort(NSData *data) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + NSString *addr; + gsu16 pnum; + gsu32 length; + NSHost *host; + + pih = (GSPortItemHeader*)[data bytes]; + NSCAssert(GSSwapBigI32ToHost(pih->type) == GSP_PORT, + NSInternalInconsistencyException); + length = GSSwapBigI32ToHost(pih->length); + NSCAssert(length == [data length] - sizeof(GSPortItemHeader), + NSInternalInconsistencyException); + pi = (GSPortInfo*)&pih[1]; + pnum = GSSwapBigI16ToHost(pi->num); + addr = [NSString stringWithCString: pi->addr]; + + NSDebugLLog(@"NSPort", @"Decoded port as '%@:%d'", addr, pnum); + + host = [NSHost hostWithAddress: addr]; + return [GSTcpPort portWithNumber: pnum onHost: host forceAddress: nil]; +} + +static NSData* +encodePort(GSTcpPort *port) +{ + GSPortItemHeader *pih; + GSPortInfo *pi; + NSMutableData *data; + unsigned plen; + NSString *addr; + gsu16 pnum; + + pnum = [port portNumber]; + addr = [port address]; + if (addr == nil) + { + /* + * If the port is not forced to use a specific address, just try one + * at random, but try not to make it the loopback address! + */ + addr = [[port host] address]; + if ([addr isEqualToString: @"127.0.0.1"] == YES) + { + NSArray *a = [[port host] addresses]; + unsigned i; + + for (i = 0; i < [a count]; i++) + { + addr = [a objectAtIndex: i]; + if ([addr isEqualToString: @"127.0.0.1"] == NO) + { + break; + } + } + } + } + plen = [addr cStringLength] + 3; + data = [NSMutableData dataWithLength: sizeof(GSPortItemHeader) + plen]; + pih = (GSPortItemHeader*)[data mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_PORT); + pih->length = GSSwapHostI32ToBig(sizeof(plen)); + pi = (GSPortInfo*)&pih[1]; + pi->num = GSSwapHostI16ToBig(pnum); + [addr getCString: pi->addr]; + + NSDebugLLog(@"NSPort", @"Encoded port as '%@:%d'", addr, pnum); + + return data; +} + + + +@implementation GSTcpHandle + +static NSRecursiveLock *tcpHandleLock = nil; +static NSMapTable *tcpHandleTable = 0; + ++ (id) allocWithZone: (NSZone*)zone +{ + [NSException raise: NSGenericException + format: @"attempt to alloc a GSTcpHandle!"]; + return nil; +} + ++ (void) initialize +{ + if (tcpHandleLock == nil) + { + [gnustep_global_lock lock]; + if (tcpHandleLock == nil) + { + tcpHandleLock = [NSRecursiveLock new]; + tcpHandleTable = NSCreateMapTable(NSIntMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + } + [gnustep_global_lock unlock]; + } +} + ++ (GSTcpHandle*) handleWithDescriptor: (int)d +{ + GSTcpHandle *handle; + int e; + + if (d < 0) + { + NSLog(@"illegal descriptor (%d) for Tcp Handle", d); + return nil; + } + 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 - %s", strerror(errno)); + return nil; + } + } + else + { + NSLog(@"unable to get non-blocking mode - %s", strerror(errno)); + return nil; + } + [tcpHandleLock lock]; + handle = (GSTcpHandle*)NSMapGet(tcpHandleTable, (void*)(gsaddr)d); + if (handle == nil) + { + handle = (GSTcpHandle*)NSAllocateObject(self,0,NSDefaultMallocZone()); + handle->desc = d; + handle->wMsgs = [NSMutableArray new]; + NSMapInsert(tcpHandleTable, (void*)(gsaddr)d, (void*)handle); + RELEASE(handle); + } + [tcpHandleLock unlock]; + return handle; +} + +- (void) close +{ + (void)close(desc); +} + +- (BOOL) connectBeforeDate: (NSDate*)when +{ + NSArray *addrs; + struct sockaddr_in sin; + BOOL gotAddr = NO; + NSRunLoop *l; + + if (state != GS_H_UNCON) + { + NSLog(@"attempting connect on connected handle"); + return YES; /* Already connected. */ + } + if (recvPort == nil || sendPort == nil) + { + NSLog(@"attempting connect with port(s) unset"); + 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 ([[self sendPort] address] != nil) + { + addrs = [NSArray arrayWithObject: [[self sendPort] address]]; + } + else + { + addrs = [[[self sendPort] host] addresses]; + } + while (gotAddr == NO) + { + const char *addr; + + if (addrNum >= [addrs count]) + { + NSLog(@"run out of addresses to try (tried %d)", addrNum); + return NO; + } + addr = [[addrs objectAtIndex: addrNum++] cString]; + memset(&sin, '\0', sizeof(sin)); + sin.sin_family = AF_INET; +#ifndef HAVE_INET_ATON + sin.sin_addr.s_addr = inet_addr(addr); + if (sin.sin_addr.s_addr == INADDR_NONE) +#else + if (inet_aton(addr, &sin.sin_addr) == 0) +#endif + { + NSLog(@"bad ip address - '%s'", addr); + } + else + { + gotAddr = YES; + } + } + sin.sin_port = GSSwapHostI16ToBig([[self sendPort] portNumber]); + + if (connect(desc, (struct sockaddr*)&sin, sizeof(sin)) < 0) + { + if (errno != EINPROGRESS) + { + NSLog(@"unable to make connection to %s:%d - %s", + inet_ntoa(sin.sin_addr), + GSSwapBigI16ToHost(sin.sin_port), strerror(errno)); + if (addrNum < [addrs count]) + { + return [self connectBeforeDate: when]; + } + else + { + return NO; /* Tried all addresses */ + } + } + } + + state = GS_H_TRYCON; + l = [NSRunLoop currentRunLoop]; + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSDefaultRunLoopMode]; + while (state == GS_H_TRYCON && [when timeIntervalSinceNow] > 0) + { + [l runMode: NSDefaultRunLoopMode beforeDate: when]; + } + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: NSDefaultRunLoopMode + all: NO]; + + if (state == GS_H_TRYCON) + { + state = GS_H_UNCON; + addrNum = 0; + return NO; /* Timed out */ + } + else if (state == GS_H_UNCON) + { + if (addrNum < [addrs count] && [when timeIntervalSinceNow] > 0) + { + /* + * The connection attempt failed, but there are still IP addresses + * that we haven't tried. + */ + return [self connectBeforeDate: when]; + } + addrNum = 0; + state = GS_H_UNCON; + return NO; /* connection failed */ + } + else + { + addrNum = 0; + return YES; + } +} + +- (int) descriptor +{ + return desc; +} + +/* + * Method to pass an incoming message to the recieving port. + */ +- (void) dispatch +{ + NSPortMessage *pm; + + pm = [[NSPortMessage alloc] initWithSendPort: [self sendPort] + receivePort: [self recvPort] + components: rItems]; + [pm setMsgid: rId]; + rId = 0; + DESTROY(rItems); + [[self recvPort] handlePortMessage: AUTORELEASE(pm)]; +} + +- (void) invalidate +{ + [myLock lock]; + if (desc >= 0) + { + int old = desc; + + [[NSNotificationCenter defaultCenter] removeObserver: self]; + [[self recvPort] removeHandle: self]; + [[self sendPort] removeHandle: self]; + (void)close(desc); + desc = -1; + [tcpHandleLock lock]; + NSMapRemove(tcpHandleTable, (void*)(gsaddr)old); + [tcpHandleLock unlock]; + } + [myLock unlock]; +} + +- (void) gcFinalize +{ + [[NSNotificationCenter defaultCenter] removeObserver: self]; + [self close]; + DESTROY(rData); + DESTROY(rItems); + DESTROY(wMsgs); + DESTROY(myLock); +} + +- (GSTcpPort*) 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 +{ + /* + * If we have been invalidated (desc < 0) then we should ignore this + * event and remove ourself from the runloop. + */ + if (desc < 0) + { + NSRunLoop *l = [NSRunLoop currentRunLoop]; + + [l removeEvent: data + type: ET_RDESC + forMode: mode + all: YES]; + [l removeEvent: data + type: ET_WDESC + forMode: mode + all: YES]; + return; + } + + if (type == ET_RDESC) + { + unsigned want; + void *bytes; + int res; + + if (rData == nil) + { + rData = [[NSData alloc] initWithCapacity: 8192]; + [rData setLength: sizeof(GSPortItemHeader)]; + rLength = 0; + } + bytes = [rData mutableBytes]; + want = [rData length]; + res = read(desc, bytes + rLength, want - rLength); + if (res <= 0) + { + NSLog(@"read attempt failed - %s", strerror(errno)); + [self invalidate]; + return; + } + rLength += res; + if (rLength == want) + { + 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); + [rData setLength: l]; + rLength = 0; + } + break; + + case GSP_HEAD: + { + GSPortMsgHeader *h; + + rType = GSP_NONE; + /* + * 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 = [[NSMutableArray alloc] initWithCapacity: nItems]; + if (rLength > sizeof(GSPortMsgHeader)) + { + /* + * The first data item of the message was included in + * the header - so add it to the rItems array. + */ + rLength -= sizeof(GSPortMsgHeader); + memcpy(bytes, bytes + sizeof(GSPortMsgHeader), rLength); + [rData setLength: rLength]; + [rItems addObject: rData]; + rLength = 0; + DESTROY(rData); + if (nItems == 1) + { + [self dispatch]; + } + } + else + { + rLength = 0; + [rData setLength: 0]; + } + } + break; + + case GSP_DATA: + { + rType = GSP_NONE; + [rItems addObject: rData]; + rLength = 0; + DESTROY(rData); + if (nItems == [rItems count]) + { + [self dispatch]; + } + } + break; + + case GSP_PORT: + { + GSTcpPort *p; + + rType = GSP_NONE; + p = decodePort(rData); + [rData setLength: 0]; + rLength = 0; + + if (state == GS_H_ACCEPT) + { + /* + * This is the initial port information on a new + * connection - set up port relationships. + */ + state = GS_H_CONNECTED; + [self setSendPort: p]; + [[self recvPort] addHandle: self forPort: p]; + [p addHandle: self forPort: [self recvPort]]; + } + else + { + /* + * This is a port within a port message - add + * it to the message components. + */ + [rItems addObject: p]; + if (nItems == [rItems count]) + { + [self dispatch]; + } + } + } + break; + } + } + } + else if (type == ET_WDESC) + { + if (state == GS_H_TRYCON) /* Connection attempt. */ + { + int res; + 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", strerror(res)); + } + else + { + NSData *d = encodePort([self recvPort]); + + len = write(desc, [d bytes], [d length]); + if (len == [d length]) + { + state = GS_H_CONNECTED; + } + else + { + state = GS_H_UNCON; + NSLog(@"connect write attempt failed - %s", strerror(errno)); + } + } + } + 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."); + return; + } + } + b = [wData bytes]; + l = [wData length]; + res = write(desc, b + wLength, l - wLength); + if (res <= 0) + { + NSLog(@"write attempt failed - %s", strerror(errno)); + } + else + { + 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 + { + /* + * message completed - remove from list. + */ + wData = nil; + wItem = 0; + [wMsgs removeObjectAtIndex: 0]; + } + } + } + } + } +} + +- (BOOL) sendMessage: (NSArray*)components beforeDate: (NSDate*)when +{ + NSRunLoop *l; + BOOL sent = NO; + + NSAssert([components count] > 0, NSInternalInconsistencyException); + [wMsgs addObject: components]; + + l = [NSRunLoop currentRunLoop]; + + RETAIN(self); + + [l addEvent: (void*)(gsaddr)desc + type: ET_WDESC + watcher: self + forMode: NSDefaultRunLoopMode]; + while ([wMsgs indexOfObjectIdenticalTo: components] != NSNotFound + && [when timeIntervalSinceNow] > 0) + { + [l runMode: NSDefaultRunLoopMode beforeDate: when]; + } + [l removeEvent: (void*)(gsaddr)desc + type: ET_WDESC + forMode: NSDefaultRunLoopMode + all: NO]; + if ([wMsgs indexOfObjectIdenticalTo: components] == NSNotFound) + { + sent = YES; + } + RELEASE(self); + return NO; +} + +- (GSTcpPort*) sendPort +{ + if (sendPort == nil) + return nil; + else + return GS_GC_UNHIDE(sendPort); +} + +- (void) setRecvPort: (GSTcpPort*)port +{ + if (port == nil) + recvPort = nil; + else + recvPort = GS_GC_HIDE(port); +} + +- (void) setSendPort: (GSTcpPort*)port +{ + if (port == nil) + sendPort = nil; + else + sendPort = GS_GC_HIDE(port); +} + +- (void) setState: (GSHandleState)s +{ + state = s; +} + +- (GSHandleState) state +{ + return state; +} + +- (NSDate*) timedOutEvent: (void*)data + type: (RunLoopEventType)type + forMode: (NSString*)mode +{ + return nil; +} + +@end + + + +@implementation GSTcpPort + +static NSRecursiveLock *tcpPortLock = nil; +static NSMapTable *tcpPortMap = 0; + ++ (void) initialize +{ + if (tcpPortLock == nil) + { + [gnustep_global_lock lock]; + if (tcpPortLock == nil) + { + tcpPortLock = [NSRecursiveLock new]; + tcpPortMap = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + } + [gnustep_global_lock unlock]; + } +} + ++ (id) new +{ + return RETAIN([self portWithNumber: 0 onHost: nil forceAddress: nil]); +} + +/* + * Look up an existing GSTcpPort given a host and number + */ ++ (GSTcpPort*) existingPortWithNumber: (gsu16)number + onHost: (NSHost*)aHost +{ + GSTcpPort *port = nil; + NSMapTable *thePorts; + + [tcpPortLock lock]; + + /* + * Get the map table of ports with the specified number. + */ + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, (void*)(gsaddr)number); + if (thePorts != 0) + { + port = (GSTcpPort*)NSMapGet(thePorts, (void*)aHost); + } + [tcpPortLock unlock]; + return port; +} + +/* + * This is the preferred initialisation method for GSTcpPort + * + * '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. + */ ++ (GSTcpPort*) portWithNumber: (gsu16)number + onHost: (NSHost*)aHost + forceAddress: (NSString*)addr +{ + unsigned i; + GSTcpPort *port = nil; + NSHost *thisHost = [NSHost currentHost]; + NSMapTable *thePorts; + struct sockaddr_in sockaddr; + + if (aHost == nil) + { + aHost = thisHost; + } + if (addr != nil && [[aHost addresses] containsObject: addr] == NO) + { + NSLog(@"attempt to use address '%@' on whost without that address", addr); + return nil; + } + if (number == 0 && [thisHost isEqual: aHost] == NO) + { + NSLog(@"attempt to get port zero on remote host"); + return nil; + } + + [tcpPortLock lock]; + + /* + * First try to find a pre-existing port. + */ + thePorts = (NSMapTable*)NSMapGet(tcpPortMap, (void*)(gsaddr)number); + if (thePorts != 0) + { + port = (GSTcpPort*)NSMapGet(thePorts, (void*)aHost); + } + + if (port == nil) + { + port = (GSTcpPort*)NSAllocateObject(self,0,NSDefaultMallocZone()); + port->listener = -1; + port->host = RETAIN(aHost); + port->address = [addr copy]; + port->handles = NSCreateMapTable(NSIntMapKeyCallBacks, + NSNonRetainedObjectMapValueCallBacks, 0); + port->myLock = [NSRecursiveLock new]; + + if ([thisHost isEqual: aHost] == YES) + { + int status = 1; + int desc; + BOOL addrOk = YES; + + /* + * Creating a new port on the local host - so we must create a + * listener socket to accept incoming connections. + */ + 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; + } + } + + if (addrOk == NO) + { + NSLog(@"Bad address (%@) specified for listening port", addr); + DESTROY(port); + } + else if ((desc = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) + { + NSLog(@"unable to create socket - %s", strerror(errno)); + DESTROY(port); + } + else if (setsockopt(desc, SOL_SOCKET, SO_REUSEADDR, (char *)&status, + sizeof(status)) < 0) + { + (void) close(desc); + NSLog(@"unable to set reuse on socket - %s", strerror(errno)); + DESTROY(port); + } + else if (bind(desc, (struct sockaddr *)&sockaddr, + sizeof(sockaddr)) < 0) + { + NSLog(@"unable to bind to port %s:%d - %s", + inet_ntoa(sockaddr.sin_addr), number, strerror(errno)); + (void) close(desc); + DESTROY(port); + } + else if (listen(desc, 5) < 0) + { + NSLog(@"unable to listen on port - %s", strerror(errno)); + (void) close(desc); + DESTROY(port); + } + else if (getsockname(desc, (struct sockaddr*)&sockaddr, &i) < 0) + { + NSLog(@"unable to get socket name - %s", strerror(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); + NSDebugLLog(@"NSPort", @"Created local 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); + NSDebugLLog(@"NSPort", @"Created remote port: %@", port); + } + IF_NO_GC(AUTORELEASE(port)); + } + else + { + NSDebugLLog(@"NSPort", @"Using pre-existing port: %@", port); + } + + [tcpPortLock unlock]; + return port; +} + +- (void) addHandle: (GSTcpHandle*)handle forPort: (GSTcpPort*)other +{ + [myLock lock]; + NSMapInsert(handles, (void*)handle, (void*)other); + [myLock unlock]; +} + +- (NSString*) address +{ + return address; +} + +- (id) copyWithZone: (NSZone*)zone +{ + return RETAIN(self); +} + +- (void) dealloc +{ + [self invalidate]; + DESTROY(host); + [super dealloc]; +} + +- (void) gcFinalize +{ + [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; + GSTcpHandle *handle; + + [myLock lock]; + + /* + * Make sure there is enough room in the provided array. + */ + NSAssert(*count > NSCountMapTable(handles), NSInternalInconsistencyException); + + /* + * Put in our listening socket. + */ + *count = 0; + fds[(*count)++] = listener; + + /* + * Enumerate all our socket handles, and put them in. + */ + me = NSEnumerateMapTable(handles); + while (NSNextMapEnumeratorPair(&me, (void*)&sock, (void*)&handle)) + { + fds[(*count)++] = sock; + } + [myLock unlock]; +} + +- (GSTcpHandle*) handleForPort: (GSTcpPort*)recvPort beforeDate: (NSDate*)when +{ + NSMapEnumerator me; + int sock; + GSTcpHandle *handle = nil; + + [myLock lock]; + /* + * 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) + { + [myLock unlock]; + return handle; + } + } + if (handle == nil) + { + int opt = 1; + + if ((sock = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) + { + NSLog(@"unable to create socket - %s", strerror(errno)); + } + else if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, + sizeof(opt)) < 0) + { + (void)close(sock); + NSLog(@"unable to set reuse on socket - %s", strerror(errno)); + } + else if ((handle = [GSTcpHandle handleWithDescriptor: sock]) == nil) + { + (void)close(sock); + NSLog(@"unable to create GSTcpHandle - %s", strerror(errno)); + } + else + { + [handle setSendPort: self]; + [handle setRecvPort: recvPort]; + NSMapInsert(handles, (void*)handle, (void*)recvPort); + NSMapInsert(recvPort->handles, (void*)handle, (void*)self); + } + } + [myLock unlock]; + /* + * If we succeeded in creating a new handle - connect to remote host. + */ + if (handle != nil) + { + if ([handle connectBeforeDate: when] == NO) + { + handle = nil; + } + } + return handle; +} + +- (void) handlePortMessage: (NSPortMessage*)m +{ + id d = [self delegate]; + + if (d == nil) + { + NSDebugLLog(@"NSPort", @"No delegate to handle incoming message"); + return; + } + if ([d respondsToSelector: @selector(handlePortMessage:)] == NO) + { + NSDebugLLog(@"NSPort", @"delegate doesn't handle messages"); + return; + } + [d handlePortMessage: m]; +} + +- (unsigned) hash +{ + return (unsigned)portNum; +} + +- (NSHost*) host +{ + return host; +} + +- (id) init +{ + RELEASE(self); + self = [GSTcpPort new]; + return self; +} + +- (void) invalidate +{ + [myLock lock]; + + if ([self isValid]) + { + NSMapTable *thePorts; + NSArray *handleArray; + unsigned i; + + [tcpPortLock lock]; + thePorts = NSMapGet(tcpPortMap, (void*)(gsaddr)portNum); + if (thePorts != 0) + { + if (listener >= 0) + { + (void)close(listener); + listener = -1; + } + NSMapRemove(thePorts, (void*)host); + } + [tcpPortLock unlock]; + + handleArray = NSAllMapTableValues(handles); + i = [handleArray count]; + while (i > 0) + { + GSTcpHandle *handle = [handleArray objectAtIndex: i]; + + [handle invalidate]; + } + NSFreeMapTable(handles); + handles = 0; + [super invalidate]; + } + [myLock unlock]; + DESTROY(myLock); +} + +- (BOOL) isEqual: (id)anObject +{ + if (anObject == self) + { + return YES; + } + if ([anObject class] == [self class]) + { + GSTcpPort *o = (GSTcpPort*)anObject; + + if (o->portNum == portNum && [o->host isEqual: host]) + { + return YES; + } + } + return NO; +} + +- (gsu16) portNumber +{ + return portNum; +} + +/* + * 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 +{ + [myLock lock]; + NSMapRemove(handles, (void*)(gsaddr)[handle descriptor]); + if (listener < 0 && NSCountMapTable(handles) == 0) + { + [self invalidate]; + } + [myLock unlock]; +} + +/* + * This returns the amount of space that a port coder should reserve at the + * start of its encoded data so that the GSTcpPort 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 + components: (NSMutableArray*)components + from: (NSPort*)receivingPort + reserved: (unsigned)length + msgId: (int)msgId +{ + BOOL sent = NO; + GSTcpHandle *h; + unsigned rl = [self reservedSpaceLength]; + + /* + * 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. + */ + if (length != 0 && length != rl) + { + NSLog(@"bad reserved length - %u", length); + return NO; + } + if ([receivingPort isKindOfClass: [GSTcpPort class]] == NO) + { + NSLog(@"woah there - receiving port is not the correct type"); + return NO; + } + + h = [self handleForPort: (GSTcpPort*)receivingPort beforeDate: when]; + if (h != nil) + { + NSMutableData *d; + unsigned l; + GSPortItemHeader *pih; + GSPortMsgHeader *pmh; + unsigned c; + unsigned i; + + /* + * Ok - ensure we have space to insert header info. + */ + if (length == 0 && rl != 0) + { + NSMutableData *header = [NSMutableData new]; + + [header setLength: rl]; + [components insertObject: header atIndex: 0]; + RELEASE(header); + } + + d = [components objectAtIndex: 0]; + l = [d length]; + pih = (GSPortItemHeader*)[d mutableBytes]; + pih->type = GSSwapHostI32ToBig(GSP_HEAD); + pih->length = GSSwapHostI32ToBig(l); + pmh = (GSPortMsgHeader*)&pih[1]; + pmh->mId = GSSwapHostI32ToBig(msgId); + pmh->nItems = GSSwapHostI32ToBig([components count]); + + /* + * Now insert item header information as required. + */ + c = [components count]; + for (i = 1; i < c; i++) + { + id o = [components objectAtIndex: i]; + + if ([o isKindOfClass: [NSData class]]) + { + NSMutableData *d; + GSPortItemHeader *pih; + unsigned h = sizeof(GSPortItemHeader); + unsigned l = [o length]; + void *b; + + d = [NSMutableData dataWithLength: 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]; + } + else if ([o isKindOfClass: [GSTcpPort class]]) + { + [components replaceObjectAtIndex: i + withObject: encodePort(o)]; + } + } + + /* + * Now send the message. + */ + sent = [h sendMessage: components beforeDate: when]; + } + return sent; +} + +- (BOOL) sendBeforeDate: (NSDate*)when + components: (NSMutableArray*)components + from: (NSPort*)receivingPort + reserved: (unsigned)length +{ + return [self sendBeforeDate: (NSDate*)when + components: components + from: receivingPort + reserved: length + msgId: GS_CONNECTION_MSG]; +} + +@end + diff --git a/Source/NSBundle.m b/Source/NSBundle.m index e31cd17a3..1403fba1f 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -401,6 +401,12 @@ _bundle_load_callback(Class theClass, Category *theCategory) NSLog(@"No path specified for bundle"); return nil; } + if ([path isAbsolutePath] == NO) + { + NSLog(@"WARNING: NSBundle -initWithPath: requires absolute path names!"); + path = [[[NSFileManager defaultManager] currentDirectoryPath] + stringByAppendingPathComponent: path]; + } /* Check if we were already initialized for this directory */ if (_bundles) diff --git a/Source/NSObject.m b/Source/NSObject.m index a7e2872fa..1cca85746 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -1409,3 +1409,33 @@ _fastMallocBuffer(unsigned size) return ((void*)&o[1])+fastMallocOffset; } + +/* + * Stuff for compatibility with 'Object' derived classes. + */ +@interface Object (NSObjectCompat) ++ (void) release; ++ (id) retain; +- (void) release; +- (id) retain; +@end + +@implementation Object (NSObjectCompat) ++ (void) release +{ + return; +} ++ (id) retain +{ + return self; +} +- (void) release +{ + return; +} +- (id) retain +{ + return self; +} +@end +