Fix 'occurred' typo
[libt2n] / src / socket_server.cpp
... / ...
CommitLineData
1/*
2Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4The software in this package is distributed under the GNU General
5Public License version 2 (with a special exception described below).
6
7A copy of GNU General Public License (GPL) is included in this distribution,
8in the file COPYING.GPL.
9
10As a special exception, if other files instantiate templates or use macros
11or inline functions from this file, or you compile this file and link it
12with other works to produce a work based on this file, this file
13does not by itself cause the resulting work to be covered
14by the GNU General Public License.
15
16However the source code for this file must still be made available
17in accordance with section (3) of the GNU General Public License.
18
19This exception does not invalidate any other reasons why a work based
20on this file might be covered by the GNU General Public License.
21*/
22
23#include <stdio.h>
24#include <errno.h>
25#include <stdlib.h>
26#include <unistd.h>
27#include <sys/types.h>
28#include <sys/socket.h>
29#include <sys/un.h>
30#include <sys/time.h>
31#include <sys/stat.h>
32#include <arpa/inet.h>
33#include <netinet/in.h>
34#include <netdb.h>
35#include <fcntl.h>
36#include <time.h>
37#include <pwd.h>
38#include <grp.h>
39
40#include <sstream>
41
42#include "socket_server.hxx"
43#include "t2n_exception.hxx"
44#include "log.hxx"
45
46using namespace std;
47
48namespace libt2n
49{
50
51/** @brief create a new tcp-based server
52 @param port tcp port you want to listen on
53 @param ip the local ip you want to listen on. "0.0.0.0" means all local ips (default).
54*/
55socket_server::socket_server(int port, const std::string& ip)
56 : server(), socket_handler(0,tcp_s)
57{
58 /* Create the socket. */
59 sock = socket (PF_INET, SOCK_STREAM, 0);
60 if (sock < 0)
61 EXCEPTIONSTREAM(error,t2n_server_error,"error opening socket: " << strerror(errno));
62
63 set_socket_options(sock);
64
65 /* Give the socket a name. */
66 struct sockaddr_in sockaddr;
67 sockaddr.sin_family = AF_INET;
68 sockaddr.sin_port = htons(port);
69
70 if (inet_aton(ip.c_str(),&sockaddr.sin_addr) == 0)
71 EXCEPTIONSTREAM(error,t2n_server_error,"failed listening on invalid ip " << ip);
72
73 if (bind (sock, (struct sockaddr *) &sockaddr, sizeof (sockaddr)) < 0)
74 {
75 // FIXME: Calls virtual function socket_server::get_logstream() in constructor
76 EXCEPTIONSTREAM(error,t2n_server_error,"error binding socket: " << strerror(errno));
77 }
78
79 start_listening();
80}
81
82/** @brief create a new unix-socked-based server
83 @param path path of the socket
84 @param filemode permissions you want to open the socket with
85 @param user local username for the socket
86 @param group local groupname for the socket
87*/
88socket_server::socket_server(const std::string& path, mode_t filemode, const std::string& user, const std::string& group)
89 : server(), socket_handler(0,unix_s)
90{
91 unix_path=path;
92
93 // TODO: Every EXCEPTIONSTREAM in here calls virtual function get_logstream()
94
95 /* Create the socket. */
96 sock = socket (PF_UNIX, SOCK_STREAM, 0);
97 if (sock < 0)
98 EXCEPTIONSTREAM(error,t2n_server_error,"error opening socket: " << strerror(errno));
99
100 set_socket_options(sock);
101
102 /* Give the socket a name. */
103 struct sockaddr_un unix_name;
104 unix_name.sun_family = AF_UNIX;
105 strncpy (unix_name.sun_path, unix_path.c_str(),sizeof(unix_name.sun_path));
106
107 /* just to make sure there is no other socket file */
108 unlink (unix_name.sun_path);
109
110 if (bind (sock, (struct sockaddr *) &unix_name, sizeof (unix_name)) < 0)
111 EXCEPTIONSTREAM(error,t2n_server_error,"error binding socket: " << strerror(errno));
112
113 /* change permissions */
114 if (chmod (unix_name.sun_path, filemode) != 0)
115 EXCEPTIONSTREAM(error,t2n_server_error,"error changing permission: " << strerror(errno));
116
117 if (!user.empty() && !group.empty())
118 {
119 // TODO maybe use current user/group if one of them is empty
120
121 struct passwd *socket_user = getpwnam (user.c_str());
122 if (socket_user == NULL)
123 EXCEPTIONSTREAM(error,t2n_server_error,"error getting socket user: " << strerror(errno));
124
125 struct group *socket_group = getgrnam (group.c_str());
126 if (socket_group == NULL)
127 EXCEPTIONSTREAM(error,t2n_server_error,"error getting socket group: " << strerror(errno));
128
129 if (chown (unix_name.sun_path, socket_user->pw_uid, socket_group->gr_gid) != 0)
130 EXCEPTIONSTREAM(error,t2n_server_error,"error changing socket ownership: " << strerror(errno));
131 }
132
133 start_listening();
134}
135
136/**
137 * Destructor
138 */
139socket_server::~socket_server()
140{
141 // close all client connections
142 server::close();
143
144 // server socket will be closed by destructor of socket_handler
145
146 if (get_type()==unix_s)
147 unlink(unix_path.c_str());
148
149 // disconnect connection<->server pointer
150 std::map<unsigned int, server_connection*>::iterator it, it_end = connections.end();
151 for (it = connections.begin(); it != it_end; ++it)
152 {
153 socket_server_connection *conn = dynamic_cast<socket_server_connection*>(it->second);
154 if (conn)
155 conn->my_server = NULL;
156 }
157}
158
159/// start listening on a new server socket (called by the constructors)
160void socket_server::start_listening()
161{
162 if (listen (sock, 5) < 0)
163 EXCEPTIONSTREAM(error,t2n_server_error,"error listening to socket: " << strerror(errno));
164
165 /* clear & insert server sock into the fd_tab to prepare select */
166 FD_ZERO(&connection_set);
167 FD_SET (sock, &connection_set);
168 sockets_set.insert(sock);
169}
170
171/// handle a new connection from a client
172void socket_server::new_connection()
173{
174 struct sockaddr_un clientname;
175
176 unsigned int size = sizeof (clientname);
177 int newsock = accept (sock,(struct sockaddr *) &clientname,&size);
178 if (newsock < 0)
179 {
180 // return on non-fatal errors (list taken from man-page)
181 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR ||
182 errno == EMFILE || errno == ENFILE || errno == ENOBUFS || errno == ENOMEM ||
183 errno == EPROTO || errno == EPERM || errno == ETIMEDOUT)
184 {
185 LOGSTREAM(error,"non-fatal accept error: " << strerror(errno));
186 return;
187 }
188
189 /* fatal error: will usually kill or restart the server */
190 EXCEPTIONSTREAM(error,t2n_server_error,"fatal error accepting connection: " << strerror(errno));
191 }
192
193 FD_SET (newsock, &connection_set);
194 sockets_set.insert(newsock);
195
196 socket_server_connection *nc=new socket_server_connection(newsock, get_type(), get_default_timeout());
197 nc->set_socket_options(newsock);
198
199 add_connection(nc);
200
201 return;
202}
203
204/** @brief look for new connections and new data in any of the existing connections
205 @param usec_timeout wait until new data is found, max timeout usecs.
206 -1: wait endless
207 0: return instantly
208 @param usec_timeout_remaining if non-NULL the function will write the
209 not used time to the given target
210 @retval true if new data was found (does not mean that the received data
211 is a complete packet though)
212*/
213bool socket_server::fill_buffer(long long usec_timeout,long long* usec_timeout_remaining)
214{
215 fd_set used_fdset=connection_set;
216
217 /* set timeout */
218 struct timeval tval;
219 struct timeval *timeout_ptr;
220
221 if (usec_timeout == -1)
222 timeout_ptr = NULL;
223 else
224 {
225 timeout_ptr = &tval;
226
227 // timeout von long long usec in int sec + int usec umrechnen
228 tval.tv_sec = usec_timeout / 1000000;
229 tval.tv_usec = usec_timeout % 1000000;
230 }
231
232 int ret=select (FD_SETSIZE, &used_fdset, NULL, NULL, timeout_ptr);
233
234 // return the timeout we did not use
235 if (usec_timeout > 0 && usec_timeout_remaining != NULL)
236 *usec_timeout_remaining=(tval.tv_sec*1000000)+tval.tv_usec;
237
238 if (ret < 0)
239 {
240 if (errno == EINTR)
241 {
242 // select interrupted by signal
243 ret=0;
244 }
245 else
246 EXCEPTIONSTREAM(error,t2n_server_error,"select error: " << strerror(errno));
247 }
248
249 if (ret > 0)
250 {
251 // we have data pending
252
253 // check for new connection
254 if (FD_ISSET (sock, &used_fdset))
255 {
256 new_connection();
257 }
258
259 // check all connections for pending data
260 return fill_connection_buffers();
261 }
262
263 return false;
264}
265
266/// call fill_buffer() on all connections, called from fill_buffer()
267bool socket_server::fill_connection_buffers()
268{
269 bool data_found = false;
270
271 std::map<unsigned int, server_connection*>::iterator ie=connections.end();
272 for(std::map<unsigned int, server_connection*>::iterator i=connections.begin(); i != ie; i++)
273 if (!i->second->server_connection::is_closed())
274 {
275 // shutdown all connections which throw exceptions to protect the server
276 try
277 {
278 if (i->second->fill_buffer(0))
279 data_found=true;
280 }
281 catch (t2n_transfer_error &e)
282 { i->second->close(); }
283 }
284
285 return data_found;
286}
287
288/// remove the socket of a connection after the connection has been closed
289void socket_server::remove_connection_socket(int sock)
290{
291 FD_CLR(sock, &connection_set);
292 sockets_set.erase(sock);
293}
294
295/**
296 * Destructor
297 */
298socket_server_connection::~socket_server_connection()
299{
300 // Only notify parent server about going down.
301 // The real socket will be closed by the destructor of the base classes.
302 if (my_server && sock != -1)
303 {
304 socket_server *srv = dynamic_cast<socket_server*>(my_server);
305 if (srv)
306 srv->remove_connection_socket(sock);
307 }
308}
309
310/// close this connection. complete data waiting in the buffer can still be retrieved.
311void socket_server_connection::close()
312{
313 if (my_server && sock != -1)
314 {
315 socket_server *srv = dynamic_cast<socket_server*>(my_server);
316 if (srv)
317 srv->remove_connection_socket(sock);
318 }
319
320 if (!server_connection::is_closed())
321 {
322 socket_handler::close();
323 server_connection::close();
324 }
325}
326
327bool socket_server_connection::fill_buffer(long long usec_timeout,long long* usec_timeout_remaining)
328{
329 bool new_data = socket_handler::fill_buffer(buffer,usec_timeout,usec_timeout_remaining);
330 if (new_data)
331 reset_timeout();
332 return new_data;
333}
334}