jenkins-promote-staging-trunk-libmemcached-2
[awesomized/libmemcached] / libtest / cmdline.cc
1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 *
3 * libtest
4 *
5 * Copyright (C) 2011 Data Differential, http://datadifferential.com/
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include <config.h>
23 #include <libtest/common.h>
24
25 using namespace libtest;
26
27 #include <cstdlib>
28 #include <cstring>
29 #include <cerrno>
30 #include <fcntl.h>
31 #include <fstream>
32 #include <memory>
33 #include <spawn.h>
34 #include <sstream>
35 #include <string>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38
39 extern "C" {
40 static int exited_successfully(int status)
41 {
42 if (status == 0)
43 {
44 return EXIT_SUCCESS;
45 }
46
47 if (WIFEXITED(status) == true)
48 {
49 return WEXITSTATUS(status);
50 }
51 else if (WIFSIGNALED(status) == true)
52 {
53 return WTERMSIG(status);
54 }
55
56 return EXIT_FAILURE;
57 }
58 }
59
60 namespace {
61
62 std::string print_argv(char * * & built_argv, const size_t& argc)
63 {
64 std::stringstream arg_buffer;
65
66 for (size_t x= 0; x < argc; x++)
67 {
68 arg_buffer << built_argv[x] << " ";
69 }
70
71 return arg_buffer.str();
72 }
73
74 std::string print_argv(char** argv)
75 {
76 std::stringstream arg_buffer;
77
78 for (char** ptr= argv; *ptr; ptr++)
79 {
80 arg_buffer << *ptr << " ";
81 }
82
83 return arg_buffer.str();
84 }
85
86 static Application::error_t int_to_error_t(int arg)
87 {
88 switch (arg)
89 {
90 case 127:
91 return Application::INVALID;
92
93 case 0:
94 return Application::SUCCESS;
95
96 default:
97 case 1:
98 return Application::FAILURE;
99 }
100 }
101 }
102
103 namespace libtest {
104
105 Application::Application(const std::string& arg, const bool _use_libtool_arg) :
106 _use_libtool(_use_libtool_arg),
107 _use_valgrind(false),
108 _use_gdb(false),
109 _argc(0),
110 _exectuble(arg),
111 built_argv(NULL),
112 _pid(-1)
113 {
114 if (_use_libtool)
115 {
116 if (libtool() == NULL)
117 {
118 throw "libtool requested, but know libtool was found";
119 }
120 }
121
122 // Find just the name of the application with no path
123 {
124 size_t found= arg.find_last_of("/\\");
125 if (found)
126 {
127 _exectuble_name= arg.substr(found +1);
128 }
129 else
130 {
131 _exectuble_name= arg;
132 }
133 }
134
135 if (_use_libtool and getenv("PWD"))
136 {
137 _exectuble_with_path+= getenv("PWD");
138 _exectuble_with_path+= "/";
139 }
140 _exectuble_with_path+= _exectuble;
141 }
142
143 Application::~Application()
144 {
145 delete_argv();
146 }
147
148 Application::error_t Application::run(const char *args[])
149 {
150 stdin_fd.reset();
151 stdout_fd.reset();
152 stderr_fd.reset();
153 _stdout_buffer.clear();
154 _stderr_buffer.clear();
155
156 posix_spawn_file_actions_t file_actions;
157 posix_spawn_file_actions_init(&file_actions);
158
159 stdin_fd.dup_for_spawn(Application::Pipe::READ, file_actions, STDIN_FILENO);
160 stdout_fd.dup_for_spawn(Application::Pipe::WRITE, file_actions, STDOUT_FILENO);
161 stderr_fd.dup_for_spawn(Application::Pipe::WRITE, file_actions, STDERR_FILENO);
162
163 create_argv(args);
164
165 int spawn_ret;
166 if (_use_gdb)
167 {
168 std::string gdb_run_file= create_tmpfile(_exectuble_name);
169 std::fstream file_stream;
170 file_stream.open(gdb_run_file.c_str(), std::fstream::out | std::fstream::trunc);
171
172 _gdb_filename= create_tmpfile(_exectuble_name);
173 file_stream
174 << "set logging redirect on" << std::endl
175 << "set logging file " << _gdb_filename << std::endl
176 << "set logging overwrite on" << std::endl
177 << "set logging on" << std::endl
178 << "set environment LIBTEST_IN_GDB=1" << std::endl
179 << "run " << arguments() << std::endl
180 << "thread apply all bt" << std::endl
181 << "quit" << std::endl;
182
183 fatal_assert(file_stream.good());
184 file_stream.close();
185
186 if (_use_libtool)
187 {
188 // libtool --mode=execute gdb -f -x binary
189 char *argv[]= {
190 const_cast<char *>(libtool()),
191 const_cast<char *>("--mode=execute"),
192 const_cast<char *>("gdb"),
193 const_cast<char *>("-batch"),
194 const_cast<char *>("-f"),
195 const_cast<char *>("-x"),
196 const_cast<char *>(gdb_run_file.c_str()),
197 const_cast<char *>(_exectuble_with_path.c_str()),
198 0};
199
200 spawn_ret= posix_spawnp(&_pid, libtool(), &file_actions, NULL, argv, NULL);
201 }
202 else
203 {
204 // gdb binary
205 char *argv[]= {
206 const_cast<char *>("gdb"),
207 const_cast<char *>("-batch"),
208 const_cast<char *>("-f"),
209 const_cast<char *>("-x"),
210 const_cast<char *>(gdb_run_file.c_str()),
211 const_cast<char *>(_exectuble_with_path.c_str()),
212 0};
213 spawn_ret= posix_spawnp(&_pid, "gdb", &file_actions, NULL, argv, NULL);
214 }
215 }
216 else
217 {
218 if (_use_libtool)
219 {
220 spawn_ret= posix_spawn(&_pid, built_argv[0], &file_actions, NULL, built_argv, NULL);
221 }
222 else
223 {
224 spawn_ret= posix_spawnp(&_pid, built_argv[0], &file_actions, NULL, built_argv, NULL);
225 }
226 }
227
228 posix_spawn_file_actions_destroy(&file_actions);
229
230 stdin_fd.close(Application::Pipe::READ);
231 stdout_fd.close(Application::Pipe::WRITE);
232 stderr_fd.close(Application::Pipe::WRITE);
233
234 if (spawn_ret)
235 {
236 return Application::INVALID;
237 }
238
239 return Application::SUCCESS;
240 }
241
242 Application::error_t Application::wait()
243 {
244 if (_pid == -1)
245 {
246 Error << "wait() got an invalid pid_t";
247 return Application::INVALID;
248 }
249
250 {
251 ssize_t read_length;
252 char buffer[1024]= { 0 };
253 bool bail= false;
254 while (((read_length= ::read(stdout_fd.fd()[0], buffer, sizeof(buffer))) != 0) or bail)
255 {
256 if (read_length == -1)
257 {
258 switch(errno)
259 {
260 case EAGAIN:
261 continue;
262
263 default:
264 Error << strerror(errno);
265 bail= true;
266 }
267 }
268 _stdout_buffer.reserve(read_length +1);
269 for (size_t x= 0; x < read_length; x++)
270 {
271 _stdout_buffer.push_back(buffer[x]);
272 }
273 // @todo Suck up all output code here
274 }
275 }
276
277 {
278 ssize_t read_length;
279 char buffer[1024]= { 0 };
280 bool bail= false;
281 while (((read_length= ::read(stderr_fd.fd()[0], buffer, sizeof(buffer))) != 0) or bail)
282 {
283 if (read_length == -1)
284 {
285 switch(errno)
286 {
287 case EAGAIN:
288 continue;
289
290 default:
291 Error << strerror(errno);
292 bail= true;
293 }
294 }
295 _stderr_buffer.reserve(read_length +1);
296 for (size_t x= 0; x < read_length; x++)
297 {
298 _stderr_buffer.push_back(buffer[x]);
299 }
300 // @todo Suck up all errput code here
301 }
302 }
303
304 error_t exit_code= FAILURE;
305 {
306 int status= 0;
307 pid_t waited_pid;
308 if ((waited_pid= waitpid(_pid, &status, 0)) == -1)
309 {
310 Error << "Error occured while waitpid(" << strerror(errno) << ") on pid " << int(_pid);
311 }
312 else
313 {
314 if (waited_pid != _pid)
315 {
316 throw libtest::fatal(LIBYATL_DEFAULT_PARAM, "Pid mismatch, %d != %d", int(waited_pid), int(_pid));
317 }
318 exit_code= int_to_error_t(exited_successfully(status));
319 }
320 }
321
322 #if 0
323 if (exit_code == Application::INVALID)
324 {
325 Error << print_argv(built_argv, _argc);
326 }
327 #endif
328
329 return exit_code;
330 }
331
332 void Application::add_option(const std::string& arg)
333 {
334 _options.push_back(std::make_pair(arg, std::string()));
335 }
336
337 void Application::add_option(const std::string& name, const std::string& value)
338 {
339 _options.push_back(std::make_pair(name, value));
340 }
341
342 Application::Pipe::Pipe()
343 {
344 _fd[0]= -1;
345 _fd[1]= -1;
346 _open[0]= false;
347 _open[1]= false;
348 }
349
350 void Application::Pipe::reset()
351 {
352 close(READ);
353 close(WRITE);
354
355 int ret;
356 if (pipe(_fd) == -1)
357 {
358 throw strerror(errno);
359 }
360 _open[0]= true;
361 _open[1]= true;
362
363 {
364 if ((ret= fcntl(_fd[0], F_GETFL, 0)) == -1)
365 {
366 Error << "fcntl(F_GETFL) " << strerror(errno);
367 throw strerror(errno);
368 }
369
370 if ((ret= fcntl(_fd[0], F_SETFL, ret | O_NONBLOCK)) == -1)
371 {
372 Error << "fcntl(F_SETFL) " << strerror(errno);
373 throw strerror(errno);
374 }
375 }
376 }
377
378 Application::Pipe::~Pipe()
379 {
380 close(READ);
381 close(WRITE);
382 }
383
384 void Application::Pipe::dup_for_spawn(const close_t& arg, posix_spawn_file_actions_t& file_actions, const int newfildes)
385 {
386 int type= int(arg);
387
388 int ret;
389 if ((ret= posix_spawn_file_actions_adddup2(&file_actions, _fd[type], newfildes )) < 0)
390 {
391 Error << "posix_spawn_file_actions_adddup2(" << strerror(ret) << ")";
392 throw strerror(ret);
393 }
394
395 if ((ret= posix_spawn_file_actions_addclose(&file_actions, _fd[type])) < 0)
396 {
397 Error << "posix_spawn_file_actions_adddup2(" << strerror(ret) << ")";
398 throw strerror(ret);
399 }
400 }
401
402 void Application::Pipe::close(const close_t& arg)
403 {
404 int type= int(arg);
405
406 if (_open[type])
407 {
408 int ret;
409 if (::close(_fd[type]) == -1)
410 {
411 Error << "close(" << strerror(errno) << ")";
412 }
413 _open[type]= false;
414 _fd[type]= -1;
415 }
416 }
417
418 void Application::create_argv(const char *args[])
419 {
420 _argc= 2 +_use_libtool ? 2 : 0; // +1 for the command, +2 for libtool/mode=execute, +1 for the NULL
421
422 if (_use_libtool)
423 {
424 _argc+= 2; // +2 for libtool --mode=execute
425 }
426
427 /*
428 valgrind --error-exitcode=1 --leak-check=yes --show-reachable=yes --track-fds=yes --malloc-fill=A5 --free-fill=DE
429 */
430 if (_use_valgrind)
431 {
432 _argc+= 7;
433 }
434 else if (_use_gdb) // gdb
435 {
436 _argc+= 1;
437 }
438
439 for (Options::const_iterator iter= _options.begin(); iter != _options.end(); iter++)
440 {
441 _argc++;
442 if ((*iter).second.empty() == false)
443 {
444 _argc++;
445 }
446 }
447
448 if (args)
449 {
450 for (const char **ptr= args; *ptr; ++ptr)
451 {
452 _argc++;
453 }
454 }
455
456 delete_argv();
457 built_argv= new char * [_argc];
458
459 size_t x= 0;
460 if (_use_libtool)
461 {
462 assert(libtool());
463 built_argv[x++]= strdup(libtool());
464 built_argv[x++]= strdup("--mode=execute");
465 }
466
467 if (_use_valgrind)
468 {
469 /*
470 valgrind --error-exitcode=1 --leak-check=yes --show-reachable=yes --track-fds=yes --malloc-fill=A5 --free-fill=DE
471 */
472 built_argv[x++]= strdup("valgrind");
473 built_argv[x++]= strdup("--error-exitcode=1");
474 built_argv[x++]= strdup("--leak-check=yes");
475 built_argv[x++]= strdup("--show-reachable=yes");
476 built_argv[x++]= strdup("--track-fds=yes");
477 built_argv[x++]= strdup("--malloc-fill=A5");
478 built_argv[x++]= strdup("--free-fill=DE");
479 }
480 else if (_use_gdb)
481 {
482 built_argv[x++]= strdup("gdb");
483 }
484
485 built_argv[x++]= strdup(_exectuble_with_path.c_str());
486
487 for (Options::const_iterator iter= _options.begin(); iter != _options.end(); iter++)
488 {
489 built_argv[x++]= strdup((*iter).first.c_str());
490 if ((*iter).second.empty() == false)
491 {
492 built_argv[x++]= strdup((*iter).second.c_str());
493 }
494 }
495
496 if (args)
497 {
498 for (const char **ptr= args; *ptr; ++ptr)
499 {
500 built_argv[x++]= strdup(*ptr);
501 }
502 }
503 built_argv[_argc -1]= NULL;
504 }
505
506 std::string Application::print()
507 {
508 return print_argv(built_argv, _argc);
509 }
510
511 std::string Application::arguments()
512 {
513 std::stringstream arg_buffer;
514
515 for (size_t x= 1 + _use_libtool ? 2 : 0;
516 x < _argc and built_argv[x];
517 x++)
518 {
519 arg_buffer << built_argv[x] << " ";
520 }
521
522 return arg_buffer.str();
523 }
524
525 void Application::delete_argv()
526 {
527 if (built_argv)
528 {
529 for (size_t x= 0; x < _argc; x++)
530 {
531 if (built_argv[x])
532 {
533 ::free(built_argv[x]);
534 }
535 }
536 delete[] built_argv;
537 built_argv= NULL;
538 _argc= 0;
539 }
540 }
541
542
543 int exec_cmdline(const std::string& command, const char *args[], bool use_libtool)
544 {
545 Application app(command, use_libtool);
546
547 Application::error_t ret= app.run(args);
548
549 if (ret != Application::SUCCESS)
550 {
551 return int(ret);
552 }
553
554 return int(app.wait());
555 }
556
557 const char *gearmand_binary()
558 {
559 return GEARMAND_BINARY;
560 }
561
562 } // namespace exec_cmdline