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