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.sys.socket.UnixSocket;
28 
29 import ocean.transition;
30 import ocean.stdc.posix.sys.socket;
31 import ocean.stdc.posix.sys.un;
32 import ocean.stdc.posix.sys.wait;
33 import core.sys.posix.unistd;
34 import ocean.stdc.posix.stdlib : mkdtemp;
35 import core.stdc.stdio;
36 import ocean.math.Math;
37 import ocean.core.Time;
38 import ocean.stdc.string;
39 import core.stdc.errno;
40 import core.thread;
41 
42 import ocean.text.util.StringC;
43 
44 static immutable istring CLIENT_STRING = "Hello from the client";
45 
46 static immutable istring SERVER_STRING = "Hello from the server";
47 
48 int runClient ( sockaddr_un* socket_address )
49 {
50     auto client = new UnixSocket();
51 
52     scope (exit) client.close();
53 
54     auto socket_fd = client.socket();
55 
56     enforce(socket_fd >= 0, "socket() call failed!");
57 
58     auto connect_result = client.connect(socket_address);
59 
60     enforce(connect_result == 0,
61             "connect() call failed");
62 
63     // send some data
64     client.write(CLIENT_STRING);
65 
66     auto read_buffer = new char[max(SERVER_STRING.length,
67                                     CLIENT_STRING.length) + 1];
68     read_buffer[] = 0;
69 
70     auto buff = cast(void[])read_buffer;
71 
72     // receive some data
73     auto read_bytes = client.recv(buff, 0);
74 
75     enforce(read_bytes > 0);
76 
77     read_buffer.length = read_bytes;
78 
79     test(read_buffer == SERVER_STRING);
80 
81     return 0;
82 }
83 
84 version(UnitTest) {} else
85 int main ( )
86 {
87     bool in_child = false;
88 
89     auto path = mkdtemp("/tmp/Dunittest-XXXXXX\0".dup.ptr);
90     enforce(path !is null);
91 
92     auto test_dir = StringC.toDString(path);
93 
94     scope (exit)
95     {
96         if (!in_child)
97         {
98             auto r = rmdir(test_dir.ptr);
99             assert(r == 0, "Couldn't remove the temporary directory " ~
100                     test_dir ~ ": " ~ StringC.toDString(strerror(errno)));
101         }
102     }
103 
104     auto socket_path = test_dir ~ "/socket";
105 
106     auto socket_address = sockaddr_un.create(socket_path);
107 
108     auto server = new UnixSocket();
109 
110     // close the socket
111     scope (exit) server.close();
112 
113     auto socket_fd = server.socket();
114     enforce(socket_fd >= 0, "socket() call failed!");
115 
116     auto bind_result = server.bind(&socket_address);
117     enforce(bind_result == 0, "bind() call failed!");
118 
119     int backlog = 10;
120 
121     auto listen_result = server.listen(backlog);
122     enforce(listen_result == 0, "listen() call failed!");
123 
124     // Since the process already called bind and listen,
125     // it is safe to connect from a child process.
126     // NOTE: even though child process inherits the listening socket,
127     // unless it `accept`s, no connect() attempts will be redirected to it
128     pid_t pid = fork();
129 
130     enforce(pid != -1);
131 
132     if (pid == 0)  // client
133     {
134         in_child = true;
135         return runClient(&socket_address);
136     }
137 
138     scope (exit)
139     {
140         auto r = unlink(socket_path.ptr);
141         assert(r == 0, "Couldn't remove the socket file " ~ socket_path ~
142                 ": " ~ StringC.toDString(strerror(errno)));
143     }
144 
145     int connection_fd;
146 
147     auto peer_socket = new UnixSocket();
148 
149     scope (exit) peer_socket.close();
150 
151     if (peer_socket.accept(server) != -1)
152     {
153         connection_handler(peer_socket);
154     }
155 
156     int status;
157 
158     waitpid(pid, &status, 0);
159 
160     enforce(status == 0, "Child exit status should be 0");
161 
162     return 0;
163 }
164 
165 void connection_handler ( UnixSocket peer_socket )
166 {
167     auto read_buffer = new char[max(SERVER_STRING.length,
168                                     CLIENT_STRING.length) + 1];
169     read_buffer[] = '\0';
170 
171     auto buff = cast(void[])read_buffer;
172 
173     auto read_bytes = peer_socket.recv(buff, 0);
174 
175     enforce(read_bytes > 1);
176 
177     read_buffer.length = read_bytes;
178 
179     enforce(read_buffer == CLIENT_STRING,
180             cast(istring) ("Expected: " ~ CLIENT_STRING ~ " Got: " ~ read_buffer));
181 
182     // send the response
183     peer_socket.write(SERVER_STRING);
184 }