libt2n: (gerd) doxygenize
[libt2n] / src / socket_handler.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 #include <iostream>
38
39 #include "socket_handler.hxx"
40 #include "t2n_exception.hxx"
41
42 using namespace std;
43
44 namespace libt2n
45 {
46
47 /// set options like fast reuse and keepalive every socket should have
48 void socket_handler::set_socket_options(int sock)
49 {
50     int i=1;
51
52     /* fast reuse enable */
53     if (setsockopt(sock,SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0)
54     {
55         string err="error setting socket option: ";
56         err+=strerror(errno);
57         log(error, err);
58         throw t2n_communication_error(err);
59     }
60
61     /* keepalive enable */
62     if (setsockopt(sock,SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)) < 0)
63     {
64         string err="error setting socket option: ";
65         err+=strerror(errno);
66         log(error, err);
67         throw t2n_communication_error(err);
68     }
69
70     /* close on exec */
71     int fdflags;
72     fdflags=fcntl(sock,F_GETFD, 0);
73     if (fdflags < 0)
74     {
75         string err="fcntl error on socket: ";
76         err+=strerror(errno);
77         log(error, err);
78         throw t2n_communication_error(err);
79     }
80     fdflags |= FD_CLOEXEC;
81     if (fcntl(sock,F_SETFD,fdflags) < 0)
82     {
83         string err="fcntl error on socket: ";
84         err+=strerror(errno);
85         log(error, err);
86         throw t2n_communication_error(err);
87     }
88
89     /* non-blocking mode */
90     int flflags;
91     flflags=fcntl(sock,F_GETFL,0);
92     if (flflags < 0)
93     {
94         string err="fcntl error on socket: ";
95         err+=strerror(errno);
96         log(error, err);
97         throw t2n_communication_error(err);
98     }
99     flflags |= O_NONBLOCK;
100     if (fcntl(sock,F_SETFL,flflags) < 0)
101     {
102         string err="fcntl error on socket: ";
103         err+=strerror(errno);
104         log(error, err);
105         throw t2n_communication_error(err);
106     }
107 }
108
109 /// close the underlying socket connection. Don't call directly, use the version provided
110 /// by the connection class you are using.
111 void socket_handler::close()
112 {
113     // graceful shutdown
114     shutdown(sock,SHUT_RDWR);
115     ::close(sock);
116 }
117
118 /// is the underlying socket connection still open?
119 bool socket_handler::is_closed()
120 {
121     int r=fcntl(sock,F_GETFL);
122
123     return !(r & O_ACCMODE);
124 }
125
126 /** @brief check if new data is waiting on the raw socket
127     @param usec_timeout wait until new data is found, max timeout usecs.
128             -1: wait endless
129             NULL: no timeout
130 */
131 bool socket_handler::data_waiting(long long usec_timeout)
132 {
133     // just our socket
134     fd_set active_fd_set;
135     FD_ZERO (&active_fd_set);
136     FD_SET (sock, &active_fd_set);
137
138     /* set timeout */
139     struct timeval tval;
140     struct timeval *timeout_ptr;
141
142     if (usec_timeout == -1)
143         timeout_ptr = NULL;
144     else
145     {
146         timeout_ptr = &tval;
147
148         // timeout von long long usec in int sec + int usec umrechnen
149         tval.tv_sec = usec_timeout / 1000000;
150         tval.tv_usec = usec_timeout % 1000000;
151     }
152
153     if(select (FD_SETSIZE, &active_fd_set, NULL, NULL, timeout_ptr) > 0)
154         return true;
155     else
156         return false;
157 }
158
159 /** @brief read data from the raw socket and copy it into the provided buffer
160     @param buffer the buffer where to append the new data
161     @param usec_timeout wait until new data is found, max timeout usecs.
162             -1: wait endless
163             NULL: no timeout
164 */
165 bool socket_handler::fill_buffer(std::string& buffer, long long usec_timeout)
166 {
167     // fast path for timeout==0
168     if (usec_timeout==0 || data_waiting(usec_timeout))
169         return fill_buffer(buffer);
170     else
171         return false;
172 }
173
174 /** @brief read data from the raw socket and copy it into the provided buffer. Returns
175            instantly if no data is waiting.
176     @param buffer the buffer where to append the new data
177 */
178 bool socket_handler::fill_buffer(std::string& buffer)
179 {
180     bool try_again=false;
181
182     char socket_buffer[recv_buffer_size];
183
184     int nbytes = read (sock, socket_buffer, recv_buffer_size);
185     if (nbytes < 0)
186     {
187         if (errno == EAGAIN)
188             return false;                // no data was waiting
189         else if (errno == EINTR)
190         {
191             // interrupted, try again
192             try_again=true;
193         }
194         else
195         {
196             log(error,string("error reading from socket : ")+strerror(errno));
197             // TODO: exception?
198             return false;
199         }
200     }
201
202     // End-of-file
203     if (nbytes == 0 && !try_again)
204     {
205         close();
206         return false;
207     }
208
209     // Data read -> store it
210     if (nbytes > 0)
211         buffer.assign(socket_buffer,nbytes);
212
213     // more data waiting -> recurse
214     if (data_waiting(0))
215         fill_buffer(buffer);
216
217     if (nbytes > 0)
218         return true;
219     else
220         return false;
221 }
222
223 /// writes raw data to the socket. Don't use directly, use the write() function provided by the 
224 /// connection because it encapsulates the data.
225 void socket_handler::socket_write(const std::string& data)
226 {
227     int offset = 0;
228     while (offset < data.size())
229     {
230         unsigned int write_size=write_block_size;
231
232         if (offset+write_size > data.size())
233             write_size = data.size()-offset;
234
235         int rtn;
236         while ((rtn=::write(sock, data.data()+offset, write_size)) &&
237                rtn == -1 && (errno == EAGAIN || errno == EINTR))
238         {
239             usleep (80000);
240             log(debug,"resuming write() call after EAGAIN or EINTR");
241         }
242
243         if (rtn == -1)
244         {
245             log(error,string("write() returned ")+strerror(errno));
246             // TODO: exception?
247             return;
248         }
249         else if (rtn != write_size)
250         {
251             ostringstream msg;
252             msg << "write() wrote " << rtn << " bytes, should have been " 
253                 << write_size << " (complete: " << data.size() << ")";
254
255             log(error,msg.str());
256
257             // TODO: exception?
258             return;
259         }
260
261         offset += write_size;
262     }
263
264     return;
265 }
266
267 }