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