1 /*******************************************************************************
2 
3     Test-suite for UnixSockets.
4 
5     The tests involve unix sockets and forking
6     processes, so are placed in this slowtest module.
7 
8     FLAKY: the unittests in this module are very flaky, as they rely on making
9     various system calls (fork(), waitpid(), epoll_wait(), epoll_ctl(), etc)
10     which could, under certain environmental conditions, fail.
11 
12     Copyright:
13         Copyright (c) 2015-2017 dunnhumby Germany GmbH.
14         All rights reserved.
15 
16     License:
17         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
18         Alternatively, this file may be distributed under the terms of the Tango
19         3-Clause BSD License (see LICENSE_BSD.txt for details).
20 
21 *******************************************************************************/
22 
23 module integrationtest.unixsocket.main;
24 
25 import ocean.core.Enforce;
26 import ocean.core.Test;
27 import ocean.math.Math;
28 import ocean.stdc.posix.sys.un;
29 import ocean.sys.socket.UnixSocket;
30 import ocean.text.util.StringC;
31 import ocean.meta.types.Qualifiers;
32 
33 import core.stdc.errno;
34 import core.stdc.stdio;
35 import core.stdc..string;
36 import core.sys.posix.stdlib : mkdtemp;
37 import core.sys.posix.unistd;
38 import core.sys.posix.sys.wait;
39 import core.thread;
40 
41 static immutable istring CLIENT_STRING = "Hello from the client";
42 
43 static immutable istring SERVER_STRING = "Hello from the server";
44 
45 int runClient ( sockaddr_un* socket_address )
46 {
47     auto client = new UnixSocket();
48 
49     scope (exit) client.close();
50 
51     auto socket_fd = client.socket();
52 
53     enforce(socket_fd >= 0, "socket() call failed!");
54 
55     auto connect_result = client.connect(socket_address);
56 
57     enforce(connect_result == 0,
58             "connect() call failed");
59 
60     // send some data
61     client.write(CLIENT_STRING);
62 
63     auto read_buffer = new char[max(SERVER_STRING.length,
64                                     CLIENT_STRING.length) + 1];
65     read_buffer[] = 0;
66 
67     auto buff = cast(void[])read_buffer;
68 
69     // receive some data
70     auto read_bytes = client.recv(buff, 0);
71 
72     enforce(read_bytes > 0);
73 
74     read_buffer.length = read_bytes;
75 
76     test(read_buffer == SERVER_STRING);
77 
78     return 0;
79 }
80 
81 version (unittest) {} else
82 int main ( )
83 {
84     bool in_child = false;
85 
86     auto path = mkdtemp("/tmp/Dunittest-XXXXXX\0".dup.ptr);
87     enforce(path !is null);
88 
89     auto test_dir = StringC.toDString(path);
90 
91     scope (exit)
92     {
93         if (!in_child)
94         {
95             auto r = rmdir(test_dir.ptr);
96             assert(r == 0, "Couldn't remove the temporary directory " ~
97                     test_dir ~ ": " ~ StringC.toDString(strerror(errno)));
98         }
99     }
100 
101     auto socket_path = test_dir ~ "/socket";
102 
103     auto socket_address = sockaddr_un.create(socket_path);
104 
105     auto server = new UnixSocket();
106 
107     // close the socket
108     scope (exit) server.close();
109 
110     auto socket_fd = server.socket();
111     enforce(socket_fd >= 0, "socket() call failed!");
112 
113     auto bind_result = server.bind(&socket_address);
114     enforce(bind_result == 0, "bind() call failed!");
115 
116     int backlog = 10;
117 
118     auto listen_result = server.listen(backlog);
119     enforce(listen_result == 0, "listen() call failed!");
120 
121     // Since the process already called bind and listen,
122     // it is safe to connect from a child process.
123     // NOTE: even though child process inherits the listening socket,
124     // unless it `accept`s, no connect() attempts will be redirected to it
125     pid_t pid = fork();
126 
127     enforce(pid != -1);
128 
129     if (pid == 0)  // client
130     {
131         in_child = true;
132         return runClient(&socket_address);
133     }
134 
135     scope (exit)
136     {
137         auto r = unlink(socket_path.ptr);
138         assert(r == 0, "Couldn't remove the socket file " ~ socket_path ~
139                 ": " ~ StringC.toDString(strerror(errno)));
140     }
141 
142     int connection_fd;
143 
144     auto peer_socket = new UnixSocket();
145 
146     scope (exit) peer_socket.close();
147 
148     if (peer_socket.accept(server) != -1)
149     {
150         connection_handler(peer_socket);
151     }
152 
153     int status;
154 
155     waitpid(pid, &status, 0);
156 
157     enforce(status == 0, "Child exit status should be 0");
158 
159     return 0;
160 }
161 
162 void connection_handler ( UnixSocket peer_socket )
163 {
164     auto read_buffer = new char[max(SERVER_STRING.length,
165                                     CLIENT_STRING.length) + 1];
166     read_buffer[] = '\0';
167 
168     auto buff = cast(void[])read_buffer;
169 
170     auto read_bytes = peer_socket.recv(buff, 0);
171 
172     enforce(read_bytes > 1);
173 
174     read_buffer.length = read_bytes;
175 
176     enforce(read_buffer == CLIENT_STRING,
177             cast(istring) ("Expected: " ~ CLIENT_STRING ~ " Got: " ~ read_buffer));
178 
179     // send the response
180     peer_socket.write(SERVER_STRING);
181 }