source: tags/ms_r17q2/CORE/arb_cs.cxx

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