Change license from LGPL to GPL version 2 + linking exception. This fixes C++ templat...
[libt2n] / src / socket_client.cpp
1 /*
2 Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4 The software in this package is distributed under the GNU General
5 Public License version 2 (with a special exception described below).
6
7 A copy of GNU General Public License (GPL) is included in this distribution,
8 in the file COPYING.GPL.
9
10 As a special exception, if other files instantiate templates or use macros
11 or inline functions from this file, or you compile this file and link it
12 with other works to produce a work based on this file, this file
13 does not by itself cause the resulting work to be covered
14 by the GNU General Public License.
15
16 However the source code for this file must still be made available
17 in accordance with section (3) of the GNU General Public License.
18
19 This exception does not invalidate any other reasons why a work based
20 on 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 <arpa/inet.h>
32 #include <netinet/in.h>
33 #include <netdb.h>
34 #include <fcntl.h>
35 #include <time.h>
36 #include <pwd.h>
37 #include <grp.h>
38
39 #include <sstream>
40
41 #include "socket_client.hxx"
42 #include "t2n_exception.hxx"
43 #include "log.hxx"
44
45 using namespace std;
46
47 namespace libt2n
48 {
49
50 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
51 socket_client_connection::socket_client_connection(int _port, const std::string& _server, 
52             long long _connect_timeout_usec, int _max_retries,
53             std::ostream *_logstream, log_level_values _log_level)
54     : client_connection(), socket_handler(0,tcp_s)
55 {
56     max_retries=_max_retries;
57     connect_timeout_usec=_connect_timeout_usec;
58
59     server=_server;
60     port=_port;
61
62     set_logging(_logstream,_log_level);
63
64     try
65     {
66         tcp_connect(max_retries);
67     }
68     catch (t2n_communication_error &e)
69     {
70         lastErrorMsg=e.what();
71         LOGSTREAM(debug,"tcp connect error: " << lastErrorMsg);
72         // FIXME: Don't call virtual function in constructor. Currently not dangerous but bad design.
73         close();
74     }
75
76     if (!connection::is_closed())
77         do_callbacks(new_connection);
78 }
79
80 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
81 socket_client_connection::socket_client_connection(const std::string& _path,
82             long long _connect_timeout_usec, int _max_retries,
83             std::ostream *_logstream, log_level_values _log_level)
84     : client_connection(), socket_handler(0,unix_s)
85 {
86     max_retries=_max_retries;
87     connect_timeout_usec=_connect_timeout_usec;
88
89     path=_path;
90
91     set_logging(_logstream,_log_level);
92
93     try
94     {
95         unix_connect(max_retries);
96     }
97     catch (t2n_communication_error &e)
98     {
99         lastErrorMsg=e.what();
100         LOGSTREAM(debug,"unix connect error: " << lastErrorMsg);
101         // FIXME: Don't call virtual function in constructor. Currently not dangerous
102         close();
103     }
104
105     if (!connection::is_closed())
106         do_callbacks(new_connection);
107 }
108
109 /**
110  * Destructor. Closes an open connection.
111  */
112 socket_client_connection::~socket_client_connection()
113 {
114     // Destructor of socket_handler will close the socket!
115 }
116
117
118 /// establish a connection via tcp
119 void socket_client_connection::tcp_connect(int max_retries)
120 {
121     struct sockaddr_in sock_addr;
122
123     sock_addr.sin_family = AF_INET;
124     sock_addr.sin_port = htons(port);
125
126     // find the target ip
127     if (inet_aton(server.c_str(),&sock_addr.sin_addr)==0)
128     {
129         struct hostent *server_hent;
130         server_hent=gethostbyname(server.c_str());
131         if (server_hent == NULL)
132             throw t2n_connect_error(string("can't find server ")+server);
133
134         memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
135     }
136
137     sock = socket(PF_INET, SOCK_STREAM, 0);
138     if (!sock)
139         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
140
141     try
142     {
143         connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
144     }
145     catch (t2n_connect_error &e)
146     {
147         // recurse if retries left
148         if (max_retries > 0)
149         {
150             LOGSTREAM(debug,"retrying connect after connect error");
151             tcp_connect(max_retries-1);
152         }
153         else
154             throw t2n_connect_error("no more retries left after connect error");
155     }
156 }
157
158 /// establish a connection via unix-socket
159 void socket_client_connection::unix_connect(int max_retries)
160 {
161     struct sockaddr_un unix_addr;
162
163     unix_addr.sun_family = AF_UNIX;
164     strcpy (unix_addr.sun_path, path.c_str());
165
166     sock = socket(PF_UNIX, SOCK_STREAM, 0);
167     if (!sock)
168         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
169
170     try
171     {
172         connect_with_timeout((struct sockaddr *) &unix_addr, sizeof(unix_addr));
173     }
174     catch (t2n_connect_error &e)
175     {
176         // recurse if retries left
177         if (max_retries > 0)
178         {
179             LOGSTREAM(debug,"retrying connect after connect error");
180             unix_connect(max_retries-1);
181         }
182         else
183             throw t2n_connect_error("no more retries left after connect error");
184     }
185 }
186
187 /// execute a connect on a prepared socket (tcp or unix) respecting timeouts
188 void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
189 {
190     set_socket_options(sock);
191
192    /* non-blocking mode */
193     int flflags;
194     flflags=fcntl(sock,F_GETFL,0);
195     if (flflags < 0)
196         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
197
198     flflags &= (O_NONBLOCK ^ 0xFFFF);
199     if (fcntl(sock,F_SETFL,flflags) < 0)
200         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
201
202
203     LOGSTREAM(debug,"connect_with_timeout()");
204     int ret=::connect(sock,sock_addr, sockaddr_size);
205
206     if (ret < 0)
207     {
208         if (errno==EINPROGRESS)
209         {
210             LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
211
212             /* set timeout */
213             struct timeval tval;
214             struct timeval *timeout_ptr;
215
216             if (connect_timeout_usec == -1)
217                 timeout_ptr = NULL;
218             else
219             {
220                 timeout_ptr = &tval;
221
222                 // convert timeout from long long usec to int sec + int usec
223                 tval.tv_sec = connect_timeout_usec / 1000000;
224                 tval.tv_usec = connect_timeout_usec % 1000000;
225             }
226
227             fd_set connect_socket_set;
228             FD_ZERO(&connect_socket_set);
229             FD_SET(sock,&connect_socket_set);
230
231             int ret;
232             while ((ret=select(FD_SETSIZE, NULL, &connect_socket_set, NULL, timeout_ptr)) &&
233                     ret < 0 && errno==EINTR);
234
235             if (ret < 0)
236                 throw t2n_connect_error(string("connect() error (select): ")+strerror(errno));
237
238             socklen_t sopt=sizeof(int);
239             int valopt;
240             ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &sopt);
241             if (ret < 0 || valopt)
242                 throw t2n_connect_error(string("connect() error (getsockopt): ")+strerror(errno));
243         }
244         else
245             throw t2n_connect_error(string("connect() error: ")+strerror(errno));
246     }
247
248     LOGSTREAM(debug,"connect_with_timeout(): success");
249 }
250
251 void socket_client_connection::close()
252 {
253     if (!client_connection::is_closed())
254     {
255         socket_handler::close();
256         client_connection::close();
257     }
258 }
259
260 /** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
261
262     @note will throw an exeption if reconnecting not possible
263 */
264 void socket_client_connection::reconnect()
265 {
266     LOGSTREAM(debug,"reconnect()");
267
268     // close the current connection if still open
269     close();
270
271     socket_type_value type=get_type();
272
273     if (type == tcp_s)
274         tcp_connect(max_retries);
275     else if (type == unix_s)
276         unix_connect(max_retries);
277
278     // connection is open now, otherwise an execption would have been thrown
279     reopen();
280
281     LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
282 }
283
284 }