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