]> err.no Git - sope/blob - sope-core/NGStreams/NGPassiveSocket.m
synced with latest additions and bumped framework versions
[sope] / sope-core / NGStreams / NGPassiveSocket.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "NGPassiveSocket.h"
23 #include "NGSocketExceptions.h"
24 #include "NGActiveSocket.h"
25 #include "NGSocket+private.h"
26
27 #if defined(__APPLE__)
28 #  include <sys/types.h>
29 #  include <sys/socket.h>
30 #endif
31
32 #if HAVE_SYS_ERRNO_H || defined(__APPLE__)
33 #  include <sys/errno.h>
34 #endif
35
36 #include "common.h"
37
38 @interface NGActiveSocket(privateMethods)
39
40 - (id)_initWithDescriptor:(int)_fd
41   localAddress:(id<NGSocketAddress>)_local
42   remoteAddress:(id<NGSocketAddress>)_remote;
43
44 @end
45
46 @implementation NGPassiveSocket
47
48 + (id)socketBoundToAddress:(id<NGSocketAddress>)_address {
49   volatile id sock;
50   
51   sock = [[[self alloc] initWithDomain:[_address domain]] autorelease];
52   [sock bindToAddress:_address];
53   return sock;
54 }
55
56 - (id)initWithDomain:(id<NGSocketDomain>)_domain { // designated initializer
57   if ((self = [super initWithDomain:_domain])) {
58     backlogSize = -1; // -1 means 'not listening'
59     
60     if ([NSThread isMultiThreaded])
61       acceptLock = [[NSLock allocWithZone:[self zone]] init];
62     else {
63       acceptLock = nil;
64       [[NSNotificationCenter defaultCenter]
65                              addObserver:self
66                              selector:@selector(taskNowMultiThreaded:)
67                              name:NSWillBecomeMultiThreadedNotification
68                              object:nil];
69     }
70
71     if (self->fd != NGInvalidSocketDescriptor) {
72       int i_yes = 1;
73       
74       if (setsockopt(self->fd, SOL_SOCKET, SO_REUSEADDR,
75                      (void *)&i_yes, sizeof(int)) != 0) {
76         NSLog(@"WARNING: could not set SO_REUSEADDR option for socket %@: %s",
77               self, strerror(errno));
78       }
79     }
80   }
81   return self;
82 }
83
84 - (void)dealloc {
85   [[NSNotificationCenter defaultCenter]
86                          removeObserver:self
87                          name:NSWillBecomeMultiThreadedNotification
88                          object:nil];
89   
90   [self->acceptLock release];
91   [super dealloc];
92 }
93
94 - (void)taskNowMultiThreaded:(NSNotification *)_notification {
95   if (acceptLock == nil) acceptLock = [[NSLock alloc] init];
96 }
97
98 // accessors
99
100 - (BOOL)isListening {
101   return (backlogSize != -1);
102 }
103 - (BOOL)isOpen {
104   return [self isListening];
105 }
106
107 - (id<NGSocketAddress>)localAddress {
108   return localAddress;
109 }
110
111 - (int)socketType {
112   return SOCK_STREAM;
113 }
114
115 /* operations */
116
117 #if defined(WIN32) && !defined(__CYGWIN32__)
118 - (NSString *)reasonForLastError {
119   int errorCode = WSAGetLastError();
120
121   switch (errorCode) {
122     case WSAEBADF:
123       return @"not a valid socket descriptor";
124     case WSAENOTSOCK:
125       return @"descriptor is not a socket descriptor";
126     case WSAEOPNOTSUPP:
127       return @"socket does not support listen";
128     case WSAEINTR:
129       return\a @"interrupted by signal";
130     case WSAEMFILE:
131       return @"descriptor table is full";
132
133     default:
134       return [NSString stringWithCString:strerror(errorCode)];
135   }
136 }
137 #else
138 - (NSString *)reasonForLastError {
139   int errorCode = errno;
140   
141   switch (errorCode) {
142     case EBADF:
143       return @"not a valid socket descriptor";
144     case ENOTSOCK:
145       return @"descriptor is not a socket descriptor";
146     case EOPNOTSUPP:
147       return @"socket does not support listen";
148     case EINTR:
149       return @"interrupted by signal";
150     case EMFILE:
151       return @"descriptor table is full";
152     case EPROTONOSUPPORT:
153       return @"The protocol is not supported by the address family or "
154              @"implementation";
155     case EPROTOTYPE:
156       return @"The socket type is not supported by the protocol";
157
158     default:
159       return [NSString stringWithCString:strerror(errorCode)];
160   }
161 }
162 #endif
163
164 - (BOOL)listenWithBacklog:(int)_backlogSize {
165   // throws
166   //   NGSocketIsAlreadyListeningException  when the socket is in the listen state
167   //   NGCouldNotListenException            when the listen call failed
168   
169   if ([self isListening]) {
170     [[[NGSocketIsAlreadyListeningException alloc]
171               initWithReason:@"already called listen" socket:self] raise];
172     return NO;
173   }
174
175   if (listen([self fileDescriptor], _backlogSize) != 0) {
176     NSString *reason;
177     reason = [self reasonForLastError];
178     reason = [@"Could not listen: %@" stringByAppendingString:reason];
179     
180     [[[NGCouldNotListenException alloc]
181               initWithReason:reason socket:self] raise];
182     return NO;
183   }
184
185   /* set backlog size (and mark socket as 'listening') */
186   self->backlogSize = _backlogSize;
187   return YES;
188 }
189
190 - (id<NGActiveSocket>)accept {
191   // throws
192   //   NGCouldNotAcceptException  when the socket is not listening
193   //   NGCouldNotAcceptException  when the accept call failed
194
195   id<NGActiveSocket> socket;
196   *(&socket) = nil;
197   
198   if (![self isListening]) {
199     [[[NGCouldNotAcceptException alloc]
200               initWithReason:@"socket is not listening" socket:self] raise];
201   }
202   
203   SYNCHRONIZED(self->acceptLock) {
204     id<NGSocketAddress> local  = nil;
205     id<NGSocketAddress> remote = nil;
206     socklen_t len;
207     char *data;
208     int  newFd = NGInvalidSocketDescriptor;
209
210     len   = [[self domain] addressRepresentationSize];
211     data = calloc(1, len + 1);
212     
213     if ((newFd = accept(fd, (void *)data, &len)) == -1) {
214       // call failed
215       NSString *reason = nil;
216       reason = [self reasonForLastError];
217       reason = [@"Could not accept: " stringByAppendingString:reason];
218       
219       [[[NGCouldNotAcceptException alloc]
220                 initWithReason:reason socket:self] raise];
221     }
222
223     /* produce remote socket address object */
224     remote = [[self domain] addressWithRepresentation:(void *)data
225                             size:len];
226     
227     // getsockname if wildcard-IP-bind to get local IP address assigned
228     // to the connection
229     len = [[self domain] addressRepresentationSize];
230     if (getsockname(newFd, (void *)data, &len) != 0) { // function is MT-safe
231       [[[NGSocketException alloc]
232                 initWithReason:@"could not get local socket name" socket:self]
233                 raise];
234     }
235     local = [[self domain] addressWithRepresentation:(void *)data size:len];
236
237     if (data) {
238       free(data);
239       data = NULL;
240     }
241     
242     socket = [[NGActiveSocket alloc]
243                               _initWithDescriptor:newFd
244                               localAddress:local
245                               remoteAddress:remote];
246     socket = [socket autorelease];
247   }
248   END_SYNCHRONIZED;
249   return socket;
250 }
251
252 // description
253
254 - (NSString *)description {
255   return [NSString stringWithFormat:@"<PassiveSocket: address=%@>",
256                      [self localAddress]];
257 }
258
259 @end /* NGPassiveSocket */