source: tags/ms_r16q2/CORE/arb_cs.cxx

Last change on this file was 14516, checked in by westram, 8 years ago
  • fix wrong assertion
File size: 14.7 KB
Line 
1// ============================================================= //
2//                                                               //
3//   File      : arb_cs.cxx                                      //
4//   Purpose   : Basics for client/server communication          //
5//                                                               //
6//   Coded by Ralf Westram (coder@reallysoft.de) in March 2011   //
7//   Institute of Microbiology (Technical University Munich)     //
8//   http://www.arb-home.de/                                     //
9//                                                               //
10// ============================================================= //
11
12#include "arb_cs.h"
13#include "arb_msg.h"
14#include "arb_pattern.h"
15#include <smartptr.h>
16
17#include <unistd.h>
18#include <netdb.h>
19#include <sys/types.h>
20#include <sys/socket.h>
21#include <sys/un.h>
22#include <sys/stat.h>
23#include <netinet/tcp.h>
24
25// We need one of the below to prevent SIGPIPE on writes to
26// closed socket. For systems that have neither (Solaris),
27// we'd need to implement ignoring the signal in the write
28// loop (not done).
29#ifndef SO_NOSIGPIPE
30#ifndef MSG_NOSIGNAL
31#error Neither SO_NOSIGPIPE nor MSG_NOSIGNAL available!
32#endif
33#endif
34
35void arb_gethostbyname(const char *name, struct hostent *& he, GB_ERROR& err) {
36    he = gethostbyname(name);
37    // Note: gethostbyname is marked obsolete.
38    // replacement getnameinfo seems less portable atm.
39
40    if (he) {
41        err = NULL;
42    }
43    else {
44        err = GBS_global_string("Cannot resolve hostname: '%s' (h_errno=%i='%s')",
45                                name, h_errno, hstrerror(h_errno));
46    }
47}
48
49const char *arb_gethostname() {
50    static SmartCharPtr hostname;
51    if (hostname.isNull()) {
52        char buffer[4096];
53        gethostname(buffer, 4095);
54        hostname = strdup(buffer);
55    }
56    return &*hostname;
57}
58
59size_t arb_socket_read(int socket, char* ptr, size_t size) {
60    size_t to_read = size;
61    while(to_read) {
62        ssize_t read_len = read(socket, ptr, to_read);
63        if (read_len <= 0) { // read failed
64            // FIXME: GB_export_error!
65            return 0;
66        }
67        ptr += read_len;
68        to_read -= read_len;
69    }
70    return size;
71}
72
73ssize_t arb_socket_write(int socket, const char* ptr, size_t size) {
74    size_t to_write = size;
75
76    while (to_write) {
77#ifdef MSG_NOSIGNAL
78        // Linux has MSG_NOSIGNAL, but not SO_NOSIGPIPE
79        // prevent SIGPIPE here
80        ssize_t write_len = send(socket, ptr, to_write, MSG_NOSIGNAL);
81        // Note: if valgrind warns about uninitialized bytes sent,
82        // one common reason are parameters passed as int (instead of long).
83        // Affected functions are aisc_put, aisc_nput and aisc_create.
84#else
85        ssize_t write_len = write(socket, ptr, to_write);
86#endif
87        if (write_len <= 0) { // write failed
88            if (errno == EPIPE) {
89                fputs("pipe broken\n", stderr);
90            }
91
92            // FIXME: GB_export_error!
93            return -1;
94        }
95        ptr += write_len;
96        to_write -= write_len;
97    }
98    return 0;
99}
100
101static GB_ERROR arb_open_unix_socket(char* name, bool do_connect, int *fd);
102static GB_ERROR arb_open_tcp_socket(char* name, bool do_connect, int *fd);
103
104/**
105 * Opens and prepares a socket
106 *
107 * If @param name begins with ":", the remainder is shell expanded and
108 * a unix socket is created. If @param contains no ":" it must be numeric,
109 * giving the TCPport number to open. If @param contains a ":" in the middle,
110 * the first part is considered the hostname and the latter part the port.
111 *
112 * @param  name          name of port   {[<host>:]<port>|:<filename>}
113 * @param  do_connect    connect if true (client), otherwise bind (server)
114 * @param  *fd           file descriptor of opened socket (out) or 0 (never returns <0!)
115 * @param  filename_out  filename of unix socket (out)
116 *                       must be NULL or allocated (will be freed)
117 *
118 * @result NULL if all went fine     -> *fd>0
119 *         "" if could not connect   -> *fd==0
120 *         otherwise error message   -> *fd==0
121 */
122GB_ERROR arb_open_socket(const char* name, bool do_connect, int *fd, char** filename_out) {
123    GB_ERROR error = NULL;
124    *fd            = 0;
125
126    if (!name || strlen(name) == 0) {
127        error = "Error opening socket: empty name";
128    }
129    else if (name[0] == ':') {
130        // expand variables in path
131        char *filename = arb_shell_expand(name+1);
132        if (GB_have_error()) {
133            error = GB_await_error();
134        }
135        else {
136            error = arb_open_unix_socket(filename, do_connect, fd);
137            if (error) {
138                free(filename);
139            }
140            else {
141                reassign(*filename_out, filename);
142            }
143        }
144    } 
145    else {
146        char *socket_name = strdup(name);
147        error = arb_open_tcp_socket(socket_name, do_connect, fd);
148        free(socket_name);
149        freenull(*filename_out);
150    }
151
152    if (error) {
153        *fd = 0;
154    }
155    else {
156        arb_assert(*fd>0);
157    }
158
159    return error;
160}
161
162static GB_ERROR arb_open_unix_socket(char* filename, bool do_connect, int *fd) {
163    GB_ERROR error = NULL;
164
165    // create structure for connect/bind
166    sockaddr_un unix_socket;
167    unix_socket.sun_family = AF_UNIX;
168    if (strlen(filename)+1 > sizeof(unix_socket.sun_path)) {
169        error = GBS_global_string("Failed to create unix socket: "
170                                  "\"%s\" is longer than the allowed %zu characters",
171                                  filename, sizeof(unix_socket.sun_path));
172    }
173    else {
174        strncpy(unix_socket.sun_path, filename, sizeof(unix_socket.sun_path));
175
176        // create socket
177        *fd = socket(PF_UNIX, SOCK_STREAM, 0);
178        if (*fd < 0) {
179            error = GBS_global_string("Failed to create unix socket: %s", strerror(errno));
180        }
181        else {
182            // connect or bind socket
183            if (do_connect) {
184                if (connect(*fd, (sockaddr*)&unix_socket, sizeof(sockaddr_un))) {
185                    if (errno == ECONNREFUSED || errno == ENOENT) {
186                        error = "";
187                    }
188                    else {
189                        error = GBS_global_string("Failed to connect unix socket \"%s\": %s (%i)",
190                                                  filename, strerror(errno), errno);
191                    }
192                }
193            }
194            else {
195                struct stat stt;
196                if (!stat(filename, &stt)) {
197                    if (!S_ISSOCK(stt.st_mode)) {
198                        error = GBS_global_string("Failed to create unix socket at \"%s\": file exists"
199                                                  " and is not a socket", filename);
200                    }
201                    else if (unlink(filename)) {
202                        error = GBS_global_string("Failed to create unix socket at \"%s\": cannot remove"
203                                                  " existing socket", filename);
204                    }
205                }
206                if (!error && bind(*fd, (sockaddr*)&unix_socket, sizeof(sockaddr_un))) {
207                    error = GBS_global_string("Failed to bind unix socket \"%s\": %s",
208                                              filename, strerror(errno));
209                }
210            }
211
212#ifdef SO_NOSIGPIPE
213            if (!error) {
214                // OSX has SO_NOSIGPIPE but not MSG_NOSIGNAL
215                // prevent SIGPIPE here:
216                int one = 1;
217                if (setsockopt(*fd, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&one, sizeof(one))){
218                    fprintf(stderr, "Warning: setsockopt(...NOSIGPIPE...) failed: %s", strerror(errno));
219                }
220            }
221#endif
222
223            if (error) {
224                close(*fd);
225                *fd = -1;
226            }
227        }
228    }
229
230    return error;
231}
232
233static GB_ERROR arb_open_tcp_socket(char* name, bool do_connect, int *fd) {
234    GB_ERROR error = NULL;
235
236    // create socket
237    *fd = socket(PF_INET, SOCK_STREAM, 0);
238    if (*fd < 0) {
239        error = GBS_global_string("Failed to create tcp socket: %s", strerror(errno));
240    }
241    else {
242        // create sockaddr struct
243        sockaddr_in tcp_socket;
244        tcp_socket.sin_family = AF_INET;
245
246        struct hostent *he;
247        // get port and host
248        char *p = strchr(name, ':');
249        if (!p) { // <port>
250            tcp_socket.sin_port = htons(atoi(name));
251            arb_gethostbyname(arb_gethostname(), he, error);
252        }
253        else { // <host>:<port>
254            tcp_socket.sin_port = htons(atoi(p+1));
255            p[0]='\0';
256            arb_gethostbyname(name, he, error);
257            p[0]=':';
258        }
259        if (tcp_socket.sin_port == 0) {
260            error = "Cannot open tcp socket on port 0. Is the port name malformed?";
261        }
262        if (!error) {
263            memcpy(&tcp_socket.sin_addr, he->h_addr_list[0], he->h_length);
264
265            int one = 1;
266            if (do_connect) {
267                if (connect(*fd, (sockaddr*)&tcp_socket, sizeof(tcp_socket))) {
268                    if (errno == ECONNREFUSED) {
269                        error = "";
270                    } else {
271                        error = GBS_global_string("Failed to connect TCP socket \"%s\": %s",
272                                                  name, strerror(errno));
273                    }
274                }
275            }
276            else { // no connect (bind)
277                if (setsockopt(*fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
278                    fprintf(stderr, "Warning: setsockopt(...REUSEADDR...) failed: %s", strerror(errno));
279                }
280                if (bind(*fd, (sockaddr*)&tcp_socket, sizeof(tcp_socket))) {
281                    error = GBS_global_string("Failed to bind TCP socket \"%s\": %s",
282                                              name, strerror(errno));
283                }
284            }
285
286            if (setsockopt(*fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one))) {
287                fprintf(stderr, "Warning: setsockopt(...TCP_NODELAY...) failed: %s", strerror(errno));
288            }
289        }
290
291        if (error) {
292            close(*fd);
293            *fd = -1;
294        }
295    }
296    return error;
297}
298
299////////// UNIT TESTS ///////////
300
301#ifdef UNIT_TESTS
302#ifndef TEST_UNIT_H
303#include <test_unit.h>
304#include <sys/wait.h>
305#endif
306
307int echo_server(const char* portname) {
308    int mypid = fork();
309    if (mypid) return mypid;
310   
311    int fd;
312    char *filename = NULL;
313    GB_ERROR error = arb_open_socket(portname, false, &fd, &filename);
314    if (error) {
315        exit(1);
316    }
317
318    if (listen(fd, 1)) {
319        exit(2);
320    }
321
322    {
323        int cli_fd = accept(fd, NULL, NULL);
324        if (cli_fd < 0) {
325            exit(3);
326        }
327
328        char buf[500];
329        ssize_t n;
330        while(1) {
331            n = sizeof(buf);
332            n = arb_socket_read(cli_fd, buf, n);
333            if (n == 0) break;
334            n = arb_socket_write(cli_fd, buf, n);
335            if (n == -1) break;;
336            if (strcmp(buf, "exit") == 0) break;
337        }
338
339        close(cli_fd);
340    }
341
342    close(fd);
343    if (filename) {
344        unlink(filename);
345        free(filename);
346    }
347   
348    exit(0);
349}
350
351#if !defined(DEVEL_JENKINS)
352// this test fails randomly (disabled in jenkins)
353void TEST_open_socket() {
354    int fd;
355    char *filename = NULL;
356    int server_pid, server_status;
357
358    // set up port names
359    char *unix_socket = arb_shell_expand(":$ARBHOME/UNIT_TESTER/sockets/test.socket");
360    char tcp_socket[sizeof("65536")], tcp_socket2[sizeof("localhost:65536")];
361    int port = 32039;
362    for (; port < 32139; port++) {
363        snprintf(tcp_socket, sizeof(tcp_socket), "%i", port);
364        const char *err = arb_open_socket(tcp_socket, true, &fd, &filename);
365        if (!err) { // connected
366            TEST_EXPECT_EQUAL(close(fd), 0);
367        } 
368        else if (err[0] == '\0') { // could not connect
369            // found a free socket
370            break;
371        } 
372        else { // other error
373            TEST_EXPECT_NULL(err);
374        }
375    }
376    snprintf(tcp_socket2, sizeof(tcp_socket2), "localhost:%i", port);
377
378   
379    // Test opening server sockets
380    TEST_EXPECT_NULL(arb_open_socket(tcp_socket, false, &fd, &filename));
381    TEST_EXPECT(fd>0);
382    TEST_EXPECT_NULL(filename);
383    TEST_EXPECT_EQUAL(close(fd), 0);
384
385    TEST_EXPECT_NULL(arb_open_socket(tcp_socket2, false, &fd, &filename));
386    TEST_EXPECT(fd>0);
387    TEST_EXPECT_NULL(filename);
388    TEST_EXPECT_EQUAL(close(fd), 0);
389
390    TEST_EXPECT_NULL(arb_open_socket(unix_socket, false, &fd, &filename));
391    TEST_EXPECT(fd>0);
392    TEST_REJECT_NULL(filename);
393    TEST_EXPECT_EQUAL(close(fd), 0);
394    TEST_EXPECT_EQUAL(unlink(filename), 0);
395    freenull(filename);
396
397    // Test connecting to existing tcp socket
398    server_pid = echo_server(tcp_socket);
399    TEST_REJECT_NULL(server_pid);
400    usleep(10000);
401    TEST_EXPECT_NULL(arb_open_socket(tcp_socket, true, &fd, &filename)); // @@@ randomly fails on waltz (11/Aug/14, 12/Aug/14)
402    TEST_EXPECT(fd>0);
403    TEST_EXPECT_NULL(filename);
404    TEST_EXPECT_EQUAL(close(fd), 0);
405    TEST_EXPECT_EQUAL(server_pid, waitpid(server_pid, &server_status, 0));
406
407    // Test connecting to closed socket
408    TEST_EXPECT_EQUAL("", arb_open_socket(tcp_socket, true, &fd, &filename));
409    TEST_EXPECT_EQUAL("", arb_open_socket(unix_socket, true, &fd, &filename));
410   
411    // Test connecting to existing unix socket
412    server_pid = echo_server(unix_socket);
413    usleep(20000);
414    TEST_EXPECT_NULL(arb_open_socket(unix_socket, true, &fd, &filename)); // @@@ randomly fails in jenkins (build820/u1304/DEBUG, build817/cent5/DEBUG+cent6/NDEBUG, failed again in build 876/u1304/NDEBUG)
415    TEST_EXPECT(fd>0);
416
417    // Test read/write
418    char send_buf[500], recv_buf[500];
419    for (unsigned int i=0; i < sizeof(send_buf); i++) {
420        send_buf[i]=i % 64 + '0';
421    }
422    send_buf[sizeof(send_buf)-1]='\0';
423
424    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
425    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
426    TEST_EXPECT_EQUAL(send_buf, recv_buf);
427    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
428    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
429    TEST_EXPECT_EQUAL(send_buf, recv_buf);
430
431    // Test sigpipe (writing to closed socket)
432    // tell server to die:
433    strcpy(send_buf, "exit"); 
434    TEST_EXPECT_NULL(arb_socket_write(fd, send_buf, sizeof(send_buf)));
435    TEST_EXPECT_EQUAL(sizeof(recv_buf), arb_socket_read(fd, recv_buf, sizeof(recv_buf)));
436    // wait for server to die
437    TEST_EXPECT_EQUAL(server_pid, waitpid(server_pid, &server_status, 0));
438    // try writing to closed pipe
439    TEST_EXPECT_EQUAL(-1, arb_socket_write(fd, send_buf, sizeof(send_buf)));
440
441    TEST_EXPECT_EQUAL(close(fd), 0);
442    freenull(filename);
443   
444    free(unix_socket);
445}
446TEST_PUBLISH(TEST_open_socket);
447#endif
448
449#endif // UNIT_TESTS
450
451
Note: See TracBrowser for help on using the repository browser.