testing: honor MEMCACHED_BINARY; use Catch.cmake
[m6w6/libmemcached] / testing / lib / Server.cpp
1 #include "Server.hpp"
2 #include "Connection.hpp"
3 #include "ForkAndExec.hpp"
4
5 #include <sys/wait.h>
6 #include <unistd.h>
7
8 Server::Server(string binary_, Server::argv_t args_)
9 : binary{move(binary_)}
10 , args{move(args_)}
11 {}
12
13 Server::~Server() {
14 stop();
15 wait();
16 if (pipe != -1) {
17 close(pipe);
18 }
19 if (holds_alternative<string>(socket_or_port)) {
20 unlink(get<string>(socket_or_port).c_str());
21 }
22 }
23
24 static inline string extractArg(const Server::arg_t &arg_cont, const string &func_arg) {
25 if (holds_alternative<string>(arg_cont)) {
26 return get<string>(arg_cont);
27 } else {
28 return get<Server::arg_func_t>(arg_cont)(func_arg);
29 }
30 }
31
32 static inline void pushArg(vector<char *> &arr, const string &arg) {
33 auto len = arg.size();
34 auto str = arg.data(), end = str + len + 1;
35 auto ptr = new char[len + 1];
36 copy(str, end, ptr);
37 arr.push_back(ptr);
38 }
39
40 optional<string> Server::handleArg(vector<char *> &arr, const string &arg, const arg_func_t &next_arg) {
41 pushArg(arr, arg);
42 if (arg == "-p" || arg == "--port") {
43 auto port = next_arg(arg);
44 pushArg(arr, port);
45 pushArg(arr, "-U");
46 pushArg(arr, port);
47 socket_or_port = stoi(port);
48 return port;
49 } else if (arg == "-s" || arg == "--unix-socket") {
50 auto sock = next_arg(arg);
51 pushArg(arr, sock);
52 socket_or_port = sock;
53 return sock;
54 }
55 return {};
56 }
57
58 [[nodiscard]]
59 vector<char *> Server::createArgv() {
60 vector<char *> arr;
61
62 pushArg(arr, binary);
63 //pushArg(arr, "-v");
64
65 for (auto it = args.cbegin(); it != args.cend(); ++it) {
66 if (holds_alternative<arg_t>(*it)) {
67 // a single argument
68 auto arg = extractArg(get<arg_t>(*it), binary);
69 handleArg(arr, arg, [&it](const string &arg_) {
70 return extractArg(get<arg_t>(*++it), arg_);
71 });
72 } else {
73 // an argument pair
74 auto &[one, two] = get<arg_pair_t>(*it);
75 auto arg_one = extractArg(one, binary);
76 auto arg_two = extractArg(two, arg_one);
77
78 auto next = handleArg(arr, arg_one, [&arg_two](const string &) {
79 return arg_two;
80 });
81
82 if (!next.has_value()) {
83 pushArg(arr, arg_two);
84 }
85 }
86 }
87
88 arr.push_back(nullptr);
89
90 return arr;
91 }
92
93 optional<Server::ChildProc> Server::start() {
94 if (!pid) {
95 auto argv = createArgv();
96 ForkAndExec fork_and_exec{binary.c_str(), argv.data()};
97
98 pipe = fork_and_exec.createPipe();
99 pid = fork_and_exec();
100
101 for (auto argp : argv) {
102 delete [] argp;
103 }
104
105 if (!pid) {
106 return {};
107 }
108 }
109 return ChildProc{pid, pipe};
110 }
111
112 bool Server::isListening() {
113 Connection conn(socket_or_port);
114
115 if (!conn.open()) {
116 return false;
117 }
118 return conn.isOpen();
119 }
120
121 bool Server::stop() {
122 if (!pid) {
123 return true;
124 }
125 if (signalled[SIGTERM]) {
126 return signal(SIGKILL);
127 } else {
128 return signal(SIGTERM);
129 }
130 }
131
132 bool Server::signal(int signo) {
133 if (!pid) {
134 return false;
135 }
136 signalled[signo] += 1;
137 return 0 <= kill(pid, signo);
138 }
139
140 bool Server::check() {
141 return signal(0);
142 }
143
144 bool Server::wait(int flags) {
145 if (pid && pid == waitpid(pid, &status, flags)) {
146 if (drain().length()) {
147 cerr << "Ouput of " << *this << ":\n" << output << endl;
148 output.clear();
149 }
150 pid = 0;
151 if (pipe != -1) {
152 close(pipe);
153 pipe = -1;
154 }
155 return true;
156 }
157 return false;
158 }
159
160 bool Server::tryWait() {
161 return wait(WNOHANG);
162 }
163
164 Server::Server(const Server &s) {
165 binary = s.binary;
166 args = s.args;
167 socket_or_port = s.socket_or_port;
168 }
169
170 Server &Server::operator=(const Server &s) {
171 binary = s.binary;
172 args = s.args;
173 socket_or_port = s.socket_or_port;
174 return *this;
175 }
176
177 pid_t Server::getPid() const {
178 return pid;
179 }
180
181 const string &Server::getBinary() const {
182 return binary;
183 }
184
185 const Server::argv_t &Server::getArgs() const {
186 return args;
187 }
188
189 const socket_or_port_t &Server::getSocketOrPort() const {
190 return socket_or_port;
191 }
192
193 int Server::getPipe() const {
194 return pipe;
195 }
196
197 string &Server::drain() {
198 if (pipe != -1) {
199 again:
200 char read_buf[1<<12];
201 auto read_len = read(pipe, read_buf, sizeof(read_buf));
202
203 if (read_len > 0) {
204 output.append(read_buf, read_len);
205 goto again;
206 }
207 if (read_len == -1) {
208 switch (errno) {
209 case EINTR:
210 goto again;
211 default:
212 perror("Server::drain read()");
213 [[fallthrough]];
214 case EAGAIN:
215 #if EWOULDBLOCK != EAGAIN
216 case EWOULDBLOCK:
217 #endif
218 break;
219 }
220 }
221 }
222 return output;
223 }