added start of code generator
[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     if (usec_timeout > 0 && usec_timeout_remaining != NULL)
130         *usec_timeout_remaining=(tval.tv_sec*1000000)+tval.tv_usec;
131
132     if (ret > 0)
133         return true;
134     else
135         return false;
136 }
137
138 /** @brief read data from the raw socket and copy it into the provided buffer
139     @param buffer the buffer where to append the new data
140     @param[in,out] usec_timeout wait until new data is found, max timeout usecs.
141             -1: wait endless
142             0: return instantly
143 */
144 bool socket_handler::fill_buffer(std::string& buffer, long long usec_timeout, long long *timeout_remaining)
145 {
146     // fast path for timeout==0
147     if (usec_timeout==0 || data_waiting(usec_timeout,timeout_remaining))
148         return fill_buffer(buffer);
149     else
150         return false;
151 }
152
153 /** @brief read data from the raw socket and copy it into the provided buffer. Returns
154            instantly if no data is waiting.
155     @param buffer the buffer where to append the new data
156 */
157 bool socket_handler::fill_buffer(std::string& buffer)
158 {
159     bool try_again=false;
160
161     char socket_buffer[recv_buffer_size];
162
163     int nbytes = read (sock, socket_buffer, recv_buffer_size);
164     if (nbytes < 0)
165     {
166         if (errno == EAGAIN)
167             return false;                // no data was waiting
168         else if (errno == EINTR)
169         {
170             // interrupted, try again
171             LOGSTREAM(debug,"EINTR received on read(), trying again");
172             try_again=true;
173         }
174         else
175         {
176             LOGSTREAM(error,"error reading from socket : " << strerror(errno));
177             // TODO: exception?
178             return false;
179         }
180     }
181
182     // End-of-file
183     if (nbytes == 0 && !try_again)
184     {
185         LOGSTREAM(debug,"0 bytes received on read(), closing connection");
186         close();
187         return false;
188     }
189
190     // Data read -> store it
191     if (nbytes > 0)
192     {
193         buffer.append(socket_buffer,nbytes);
194         LOGSTREAM(debug,nbytes << " bytes read");
195     }
196
197     // more data waiting -> recurse
198     if (data_waiting(0))
199         fill_buffer(buffer);
200
201     if (nbytes > 0)
202         return true;
203     else
204         return false;
205 }
206
207 /// writes raw data to the socket. Don't use directly, use the write() function provided by the 
208 /// connection because it encapsulates the data.
209 void socket_handler::socket_write(const std::string& data)
210 {
211     int offset = 0;
212     while (offset < data.size())
213     {
214         unsigned int write_size=write_block_size;
215
216         if (offset+write_size > data.size())
217             write_size = data.size()-offset;
218
219         int rtn;
220         while ((rtn=::write(sock, data.data()+offset, write_size)) &&
221                rtn == -1 && (errno == EAGAIN || errno == EINTR))
222         {
223             usleep (80000);
224             LOGSTREAM(debug,"resuming write() call after EAGAIN or EINTR");
225         }
226
227         if (rtn == -1)
228         {
229             LOGSTREAM(error,"write() returned " << strerror(errno));
230             // TODO: exception?
231             return;
232         }
233         else if (rtn != write_size)
234         {
235             LOGSTREAM(error,"write() wrote " << rtn << " bytes, should have been " 
236                 << write_size << " (complete: " << data.size() << ")");
237
238             // TODO: exception?
239             return;
240         }
241
242         offset += write_size;
243     }
244
245     LOGSTREAM(debug,"wrote " << data.size() << " bytes");
246
247     return;
248 }
249
250 }