libt2n: (tomj) documented code problems; have to find out if this is the source of...
[libt2n] / src / socket_client.cpp
1 /***************************************************************************
2  *   Copyright (C) 2006 by Gerd v. Egidy                                   *
3  *   gve@intra2net.com                                                     *
4  *                                                                         *
5  *   This library is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU Lesser General Public License version   *
7  *   2.1 as published by the Free Software Foundation.                     *
8  *                                                                         *
9  *   This library is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU Lesser General Public License for more details.                   *
13  *                                                                         *
14  *   You should have received a copy of the GNU Lesser General Public      *
15  *   License along with this program; if not, write to the                 *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19
20 #include <stdio.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <sys/types.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <sys/time.h>
28 #include <arpa/inet.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <fcntl.h>
32 #include <time.h>
33 #include <pwd.h>
34 #include <grp.h>
35
36 #include <sstream>
37
38 #include "socket_client.hxx"
39 #include "t2n_exception.hxx"
40 #include "log.hxx"
41
42 using namespace std;
43
44 namespace libt2n
45 {
46
47 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
48 socket_client_connection::socket_client_connection(int _port, const std::string& _server, 
49             long long _connect_timeout_usec, int _max_retries,
50             std::ostream *_logstream, log_level_values _log_level)
51     : client_connection(), socket_handler(0,tcp_s)
52 {
53     max_retries=_max_retries;
54     connect_timeout_usec=_connect_timeout_usec;
55
56     server=_server;
57     port=_port;
58
59     set_logging(_logstream,_log_level);
60
61     try
62     {
63         tcp_connect(max_retries);
64     }
65     catch (t2n_communication_error &e)
66     {
67         lastErrorMsg=e.what();
68         LOGSTREAM(debug,"tcp connect error: " << lastErrorMsg);
69         close();
70     }
71
72     if (!connection::is_closed())
73         do_callbacks(new_connection);
74 }
75
76 /// returns a closed connection if connection could not be established, call get_last_error_msg() for details
77 socket_client_connection::socket_client_connection(const std::string& _path,
78             long long _connect_timeout_usec, int _max_retries,
79             std::ostream *_logstream, log_level_values _log_level)
80     : client_connection(), socket_handler(0,unix_s)
81 {
82     max_retries=_max_retries;
83     connect_timeout_usec=_connect_timeout_usec;
84
85     path=_path;
86
87     set_logging(_logstream,_log_level);
88
89     try
90     {
91         unix_connect(max_retries);
92     }
93     catch (t2n_communication_error &e)
94     {
95         lastErrorMsg=e.what();
96         LOGSTREAM(debug,"unix connect error: " << lastErrorMsg);
97         // FIXME: Calls virtual function close in constructor
98         close();
99     }
100
101     if (!connection::is_closed())
102         do_callbacks(new_connection);
103 }
104
105 /// establish a connection via tcp
106 void socket_client_connection::tcp_connect(int max_retries)
107 {
108     struct sockaddr_in sock_addr;
109
110     sock_addr.sin_family = AF_INET;
111     sock_addr.sin_port = htons(port);
112
113     // find the target ip
114     if (inet_aton(server.c_str(),&sock_addr.sin_addr)==0)
115     {
116         struct hostent *server_hent;
117         server_hent=gethostbyname(server.c_str());
118         if (server_hent == NULL)
119             throw t2n_connect_error(string("can't find server ")+server);
120
121         memcpy(&sock_addr.sin_addr,server_hent->h_addr_list[0],sizeof(sock_addr.sin_addr));
122     }
123
124     sock = socket(PF_INET, SOCK_STREAM, 0);
125     if (!sock)
126         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
127
128     try
129     {
130         connect_with_timeout((struct sockaddr *) &sock_addr,sizeof(sock_addr));
131     }
132     catch (t2n_connect_error &e)
133     {
134         // recurse if retries left
135         if (max_retries > 0)
136         {
137             LOGSTREAM(debug,"retrying connect after connect error");
138             tcp_connect(max_retries-1);
139         }
140         else
141             throw t2n_connect_error("no more retries left after connect error");
142     }
143 }
144
145 /// establish a connection via unix-socket
146 void socket_client_connection::unix_connect(int max_retries)
147 {
148     struct sockaddr_un unix_addr;
149
150     unix_addr.sun_family = AF_UNIX;
151     strcpy (unix_addr.sun_path, path.c_str());
152
153     sock = socket(PF_UNIX, SOCK_STREAM, 0);
154     if (!sock)
155         throw t2n_connect_error(string("socket() error: ")+strerror(errno));
156
157     try
158     {
159         connect_with_timeout((struct sockaddr *) &unix_addr, sizeof(unix_addr));
160     }
161     catch (t2n_connect_error &e)
162     {
163         // recurse if retries left
164         if (max_retries > 0)
165         {
166             LOGSTREAM(debug,"retrying connect after connect error");
167             unix_connect(max_retries-1);
168         }
169         else
170             throw t2n_connect_error("no more retries left after connect error");
171     }
172 }
173
174 /// execute a connect on a prepared socket (tcp or unix) respecting timeouts
175 void socket_client_connection::connect_with_timeout(struct sockaddr *sock_addr,unsigned int sockaddr_size)
176 {
177     set_socket_options(sock);
178
179    /* non-blocking mode */
180     int flflags;
181     flflags=fcntl(sock,F_GETFL,0);
182     if (flflags < 0)
183         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
184
185     flflags &= (O_NONBLOCK ^ 0xFFFF);
186     if (fcntl(sock,F_SETFL,flflags) < 0)
187         EXCEPTIONSTREAM(error,t2n_communication_error,"fcntl error on socket: " << strerror(errno));
188
189
190     LOGSTREAM(debug,"connect_with_timeout()");
191     int ret=::connect(sock,sock_addr, sockaddr_size);
192
193     if (ret < 0)
194     {
195         if (errno==EINPROGRESS)
196         {
197             LOGSTREAM(debug,"connect_with_timeout(): EINPROGRESS");
198
199             /* set timeout */
200             struct timeval tval;
201             struct timeval *timeout_ptr;
202
203             if (connect_timeout_usec == -1)
204                 timeout_ptr = NULL;
205             else
206             {
207                 timeout_ptr = &tval;
208
209                 // convert timeout from long long usec to int sec + int usec
210                 tval.tv_sec = connect_timeout_usec / 1000000;
211                 tval.tv_usec = connect_timeout_usec % 1000000;
212             }
213
214             fd_set connect_socket_set;
215             FD_ZERO(&connect_socket_set);
216             FD_SET(sock,&connect_socket_set);
217
218             int ret;
219             while ((ret=select(FD_SETSIZE, NULL, &connect_socket_set, NULL, timeout_ptr)) &&
220                     ret < 0 && errno==EINTR);
221
222             if (ret < 0)
223                 throw t2n_connect_error(string("connect() error (select): ")+strerror(errno));
224
225             socklen_t sopt=sizeof(int);
226             int valopt;
227             ret=getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &sopt);
228             if (ret < 0 || valopt)
229                 throw t2n_connect_error(string("connect() error (getsockopt): ")+strerror(errno));
230         }
231         else
232             throw t2n_connect_error(string("connect() error: ")+strerror(errno));
233     }
234
235     LOGSTREAM(debug,"connect_with_timeout(): success");
236 }
237
238 void socket_client_connection::close()
239 {
240     if (!client_connection::is_closed())
241     {
242         socket_handler::close();
243         client_connection::close();
244     }
245 }
246
247 /** @brief try to reconnect the current connection with the same connection credentials (host and port or path)
248
249     @note will throw an exeption if reconnecting not possible
250 */
251 void socket_client_connection::reconnect()
252 {
253     LOGSTREAM(debug,"reconnect()");
254
255     // close the current connection if still open
256     close();
257
258     socket_type_value type=get_type();
259
260     if (type == tcp_s)
261         tcp_connect(max_retries);
262     else if (type == unix_s)
263         unix_connect(max_retries);
264
265     // connection is open now, otherwise an execption would have been thrown
266     reopen();
267
268     LOGSTREAM(debug,"reconnect() done, client_connection::is_closed() now " << client_connection::is_closed());
269 }
270
271 }