1 /*  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2  *
3  *  Data Differential YATL (i.e. libtest)  library
4  *
5  *  Copyright (C) 2012-2013 Data Differential, http://datadifferential.com/
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions are
9  *  met:
10  *
11  *      * Redistributions of source code must retain the above copyright
12  *  notice, this list of conditions and the following disclaimer.
13  *
14  *      * Redistributions in binary form must reproduce the above
15  *  copyright notice, this list of conditions and the following disclaimer
16  *  in the documentation and/or other materials provided with the
17  *  distribution.
18  *
19  *      * The names of its contributors may not be used to endorse or
20  *  promote products derived from this software without specific prior
21  *  written permission.
22  *
23  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26  *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27  *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28  *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29  *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30  *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31  *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32  *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33  *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  *
35  */
36 
37 #include "libtest/yatlcon.h"
38 
39 #include "libtest/common.h"
40 
41 using namespace libtest;
42 
43 #include <cstdlib>
44 #include <cstring>
45 #include <cerrno>
46 #include <fcntl.h>
47 #include <fstream>
48 #include <memory>
49 #ifdef HAVE_POLL_H
50 # include <poll.h>
51 #endif
52 #ifdef HAVE_SPAWN_H
53 # include <spawn.h>
54 #endif
55 #include <sstream>
56 #include <string>
57 #include <sys/stat.h>
58 #include <sys/types.h>
59 #include <unistd.h>
60 
61 #include <algorithm>
62 #include <stdexcept>
63 
64 #ifndef __USE_GNU
65 static char **environ= NULL;
66 #endif
67 
68 #ifndef FD_CLOEXEC
69 # define FD_CLOEXEC 0
70 #endif
71 
72 namespace {
73 
print_argv(libtest::vchar_ptr_t & built_argv)74   std::string print_argv(libtest::vchar_ptr_t& built_argv)
75   {
76     std::stringstream arg_buffer;
77 
78     for (vchar_ptr_t::iterator iter= built_argv.begin();
79          iter != built_argv.end();
80          ++iter)
81     {
82       if (*iter)
83       {
84         arg_buffer << *iter << " ";
85       }
86     }
87 
88     return arg_buffer.str();
89   }
90 
91 #if 0
92   std::string print_argv(char** argv)
93   {
94     std::stringstream arg_buffer;
95 
96     for (char** ptr= argv; *ptr; ++ptr)
97     {
98       arg_buffer << *ptr << " ";
99     }
100 
101     return arg_buffer.str();
102   }
103 #endif
104 
int_to_error_t(int arg)105   static Application::error_t int_to_error_t(int arg)
106   {
107     switch (arg)
108     {
109     case 127:
110       return Application::INVALID_POSIX_SPAWN;
111 
112     case 0:
113       return Application::SUCCESS;
114 
115     case 1:
116       return Application::FAILURE;
117 
118     default:
119       return Application::UNKNOWN;
120     }
121   }
122 }
123 
124 namespace libtest {
125 
Application(const std::string & arg,const bool _use_libtool_arg)126 Application::Application(const std::string& arg, const bool _use_libtool_arg) :
127   _use_libtool(_use_libtool_arg),
128   _use_valgrind(false),
129   _use_gdb(false),
130   _use_ptrcheck(false),
131   _will_fail(false),
132   _argc(0),
133   _exectuble(arg),
134   stdin_fd(STDIN_FILENO),
135   stdout_fd(STDOUT_FILENO),
136   stderr_fd(STDERR_FILENO),
137   _pid(-1),
138   _status(0),
139   _app_exit_state(UNINITIALIZED)
140   {
141     if (_use_libtool)
142     {
143       if (libtool() == NULL)
144       {
145         FATAL("libtool requested, but know libtool was found");
146       }
147     }
148 
149     // Find just the name of the application with no path
150     {
151       size_t found= arg.find_last_of("/\\");
152       if (found)
153       {
154         _exectuble_name= arg.substr(found +1);
155       }
156       else
157       {
158         _exectuble_name= arg;
159       }
160     }
161 
162     if (_use_libtool and getenv("PWD"))
163     {
164       _exectuble_with_path+= getenv("PWD");
165       _exectuble_with_path+= "/";
166     }
167     _exectuble_with_path+= _exectuble;
168   }
169 
~Application()170 Application::~Application()
171 {
172   murder();
173   delete_argv();
174 }
175 
run(const char * args[])176 Application::error_t Application::run(const char *args[])
177 {
178   stdin_fd.reset();
179   stdout_fd.reset();
180   stderr_fd.reset();
181   _stdout_buffer.clear();
182   _stderr_buffer.clear();
183 
184   posix_spawn_file_actions_t file_actions;
185   posix_spawn_file_actions_init(&file_actions);
186 
187   stdin_fd.dup_for_spawn(file_actions);
188   stdout_fd.dup_for_spawn(file_actions);
189   stderr_fd.dup_for_spawn(file_actions);
190 
191   posix_spawnattr_t spawnattr;
192   posix_spawnattr_init(&spawnattr);
193 
194   short flags= 0;
195 
196   // Child should not block signals
197   flags |= POSIX_SPAWN_SETSIGMASK;
198 
199   sigset_t mask;
200   sigemptyset(&mask);
201 
202   fatal_assert(posix_spawnattr_setsigmask(&spawnattr, &mask) == 0);
203 
204 #if defined(POSIX_SPAWN_USEVFORK) || defined(__linux__)
205   // Use USEVFORK on linux
206   flags |= POSIX_SPAWN_USEVFORK;
207 #endif
208 
209   flags |= POSIX_SPAWN_SETPGROUP;
210   fatal_assert(posix_spawnattr_setpgroup(&spawnattr, 0) == 0);
211 
212   fatal_assert(posix_spawnattr_setflags(&spawnattr, flags) == 0);
213 
214   create_argv(args);
215 
216   int spawn_ret;
217   if (_use_gdb)
218   {
219     std::string gdb_run_file= create_tmpfile(_exectuble_name);
220     std::fstream file_stream;
221     file_stream.open(gdb_run_file.c_str(), std::fstream::out | std::fstream::trunc);
222 
223     _gdb_filename= create_tmpfile(_exectuble_name);
224     file_stream
225       << "set logging redirect on" << std::endl
226       << "set logging file " << _gdb_filename << std::endl
227       << "set logging overwrite on" << std::endl
228       << "set logging on" << std::endl
229       << "set environment LIBTEST_IN_GDB=1" << std::endl
230       << "run " << arguments() << std::endl
231       << "thread apply all bt" << std::endl
232       << "quit" << std::endl;
233 
234     fatal_assert(file_stream.good());
235     file_stream.close();
236 
237     if (_use_libtool)
238     {
239       // libtool --mode=execute gdb -f -x binary
240       char *argv[]= {
241         const_cast<char *>(libtool()),
242         const_cast<char *>("--mode=execute"),
243         const_cast<char *>("gdb"),
244         const_cast<char *>("-batch"),
245         const_cast<char *>("-f"),
246         const_cast<char *>("-x"),
247         const_cast<char *>(gdb_run_file.c_str()),
248         const_cast<char *>(_exectuble_with_path.c_str()),
249         0};
250 
251       spawn_ret= posix_spawnp(&_pid, libtool(), &file_actions, &spawnattr, argv, environ);
252     }
253     else
254     {
255       // gdb binary
256       char *argv[]= {
257         const_cast<char *>("gdb"),
258         const_cast<char *>("-batch"),
259         const_cast<char *>("-f"),
260         const_cast<char *>("-x"),
261         const_cast<char *>(gdb_run_file.c_str()),
262         const_cast<char *>(_exectuble_with_path.c_str()),
263         0};
264       spawn_ret= posix_spawnp(&_pid, "gdb", &file_actions, &spawnattr, argv, environ);
265     }
266   }
267   else
268   {
269     spawn_ret= posix_spawn(&_pid, built_argv[0], &file_actions, &spawnattr, &built_argv[0], NULL);
270   }
271 
272   posix_spawn_file_actions_destroy(&file_actions);
273   posix_spawnattr_destroy(&spawnattr);
274 
275   stdin_fd.close(Application::Pipe::READ);
276   stdout_fd.close(Application::Pipe::WRITE);
277   stderr_fd.close(Application::Pipe::WRITE);
278 
279   if (spawn_ret != 0)
280   {
281     if (_will_fail == false)
282     {
283       Error << strerror(spawn_ret) << "(" << spawn_ret << ")";
284     }
285     _pid= -1;
286     return Application::INVALID_POSIX_SPAWN;
287   }
288 
289   assert(_pid != -1);
290   if (_pid == -1)
291   {
292     return Application::INVALID_POSIX_SPAWN;
293   }
294 
295 #if 0
296   app_thread_st* _app_thread= new app_thread_st(_pid, _status, built_argv[0], _app_exit_state);
297   int error;
298   if ((error= pthread_create(&_thread, NULL, &app_thread, _app_thread)) != 0)
299   {
300     Error << "pthread_create() died during pthread_create(" << strerror(error) << ")";
301     return Application::FAILURE;
302   }
303 #endif
304 
305   return Application::SUCCESS;
306 }
307 
check() const308 bool Application::check() const
309 {
310   if (_pid > 1 and kill(_pid, 0) == 0)
311   {
312     return true;
313   }
314 
315   return false;
316 }
317 
murder()318 void Application::murder()
319 {
320   if (check())
321   {
322     int count= 5;
323     while ((count--) > 0 and check())
324     {
325       if (kill(_pid, SIGTERM) == 0)
326       {
327         join();
328       }
329       else
330       {
331         Error << "kill(pid, SIGTERM) failed after kill with error of " << strerror(errno);
332         continue;
333       }
334 
335       break;
336     }
337 
338     // If for whatever reason it lives, kill it hard
339     if (check())
340     {
341       Error << "using SIGKILL, things will likely go poorly from this point";
342       (void)kill(_pid, SIGKILL);
343     }
344   }
345   slurp();
346 }
347 
348 // false means that no data was returned
slurp()349 bool Application::slurp()
350 {
351   struct pollfd fds[2];
352   fds[0].fd= stdout_fd.fd();
353   fds[0].events= POLLRDNORM;
354   fds[0].revents= 0;
355   fds[1].fd= stderr_fd.fd();
356   fds[1].events= POLLRDNORM;
357   fds[1].revents= 0;
358 
359   int active_fd;
360   if ((active_fd= poll(fds, 2, 0)) == -1)
361   {
362     int error;
363     switch ((error= errno))
364     {
365 #ifdef __linux
366     case ERESTART:
367 #endif
368     case EINTR:
369       break;
370 
371     case EFAULT:
372     case ENOMEM:
373       FATAL(strerror(error));
374       break;
375 
376     case EINVAL:
377       FATAL("RLIMIT_NOFILE exceeded, or if OSX the timeout value was invalid");
378       break;
379 
380     default:
381       FATAL(strerror(error));
382       break;
383     }
384 
385     return false;
386   }
387 
388   if (active_fd == 0)
389   {
390     return false;
391   }
392 
393   bool data_was_read= false;
394   if (fds[0].revents & POLLRDNORM)
395   {
396     if (stdout_fd.read(_stdout_buffer) == true)
397     {
398       data_was_read= true;
399     }
400   }
401 
402   if (fds[1].revents & POLLRDNORM)
403   {
404     if (stderr_fd.read(_stderr_buffer) == true)
405     {
406       data_was_read= true;
407     }
408   }
409 
410   return data_was_read;
411 }
412 
join()413 Application::error_t Application::join()
414 {
415   pid_t waited_pid= waitpid(_pid, &_status, WUNTRACED);
416   slurp();
417   if (waited_pid == _pid and WIFEXITED(_status) == false)
418   {
419     /*
420       What we are looking for here is how the exit status happened.
421       - 127 means that posix_spawn() itself had an error.
422       - If WEXITSTATUS is positive we need to see if it is a signal that we sent to kill the process. If not something bad happened in the process itself.
423       - Finally something has happened that we don't currently understand.
424     */
425     if (WEXITSTATUS(_status) == 127)
426     {
427       _app_exit_state= Application::INVALID_POSIX_SPAWN;
428       std::string error_string("posix_spawn() failed pid:");
429       error_string+= _pid;
430       error_string+= " name:";
431       error_string+= print_argv(built_argv);
432       if (stderr_result_length())
433       {
434         error_string+= " stderr: ";
435         error_string+= stderr_c_str();
436       }
437       throw std::logic_error(error_string);
438     }
439     else if (WIFSIGNALED(_status))
440     {
441       if (WTERMSIG(_status) != SIGTERM and WTERMSIG(_status) != SIGHUP)
442       {
443         slurp();
444         _app_exit_state= Application::INVALID_POSIX_SPAWN;
445         std::string error_string(print_argv(built_argv));
446         error_string+= " was killed by signal ";
447         error_string+= strsignal(WTERMSIG(_status));
448 
449         if (stdout_result_length())
450         {
451           error_string+= " stdout: ";
452           error_string+= stdout_c_str();
453         }
454 
455         if (stderr_result_length())
456         {
457           error_string+= " stderr: ";
458           error_string+= stderr_c_str();
459         }
460 
461         throw std::runtime_error(error_string);
462       }
463 
464       // If we terminted it on purpose then it counts as a success.
465 #if defined(DEBUG)
466       if (DEBUG)
467       {
468         Out << "waitpid() application terminated at request"
469           << " pid:" << _pid
470           << " name:" << built_argv[0];
471       }
472 #endif
473     }
474     else
475     {
476       _app_exit_state= Application::UNKNOWN;
477       Error << "Unknown logic state at exit:" << WEXITSTATUS(_status)
478         << " pid:" << _pid
479         << " name:" << built_argv[0];
480     }
481   }
482   else if (waited_pid == _pid and WIFEXITED(_status))
483   {
484     _app_exit_state= int_to_error_t(WEXITSTATUS(_status));
485   }
486   else if (waited_pid == -1)
487   {
488     std::string error_string;
489     if (stdout_result_length())
490     {
491       error_string+= " stdout: ";
492       error_string+= stdout_c_str();
493     }
494 
495     if (stderr_result_length())
496     {
497       error_string+= " stderr: ";
498       error_string+= stderr_c_str();
499     }
500     Error << "waitpid() returned errno:" << strerror(errno) << " " << error_string;
501     _app_exit_state= Application::UNKNOWN;
502   }
503   else
504   {
505     _app_exit_state= Application::UNKNOWN;
506     throw std::logic_error("waitpid() returned an unknown value");
507   }
508 
509   return _app_exit_state;
510 }
511 
add_long_option(const std::string & name,const std::string & option_value)512 void Application::add_long_option(const std::string& name, const std::string& option_value)
513 {
514   std::string arg(name);
515   arg+= option_value;
516   _options.push_back(std::make_pair(arg, std::string()));
517 }
518 
add_option(const std::string & arg)519 void Application::add_option(const std::string& arg)
520 {
521   _options.push_back(std::make_pair(arg, std::string()));
522 }
523 
add_option(const std::string & name,const std::string & value)524 void Application::add_option(const std::string& name, const std::string& value)
525 {
526   _options.push_back(std::make_pair(name, value));
527 }
528 
Pipe(int arg)529 Application::Pipe::Pipe(int arg) :
530   _std_fd(arg)
531 {
532   _pipe_fd[READ]= -1;
533   _pipe_fd[WRITE]= -1;
534   _open[READ]= false;
535   _open[WRITE]= false;
536 }
537 
fd()538 int Application::Pipe::Pipe::fd()
539 {
540   if (_std_fd == STDOUT_FILENO)
541   {
542     return _pipe_fd[READ];
543   }
544   else if (_std_fd == STDERR_FILENO)
545   {
546     return _pipe_fd[READ];
547   }
548 
549   return _pipe_fd[WRITE]; // STDIN_FILENO
550 }
551 
552 
read(libtest::vchar_t & arg)553 bool Application::Pipe::read(libtest::vchar_t& arg)
554 {
555   fatal_assert(_std_fd == STDOUT_FILENO or _std_fd == STDERR_FILENO);
556 
557   bool data_was_read= false;
558 
559   libtest::vchar_t buffer;
560   buffer.resize(1024);
561   ssize_t read_length;
562   while ((read_length= ::read(_pipe_fd[READ], &buffer[0], buffer.size())))
563   {
564     if (read_length == -1)
565     {
566       switch(errno)
567       {
568       case EAGAIN:
569         break;
570 
571       default:
572         Error << strerror(errno);
573         break;
574       }
575 
576       break;
577     }
578 
579     data_was_read= true;
580     arg.reserve(read_length +1);
581     for (size_t x= 0; x < size_t(read_length); ++x)
582     {
583       arg.push_back(buffer[x]);
584     }
585     // @todo Suck up all errput code here
586   }
587 
588   return data_was_read;
589 }
590 
nonblock()591 void Application::Pipe::nonblock()
592 {
593   int flags;
594   do
595   {
596     flags= fcntl(_pipe_fd[READ], F_GETFL, 0);
597   } while (flags == -1 and (errno == EINTR or errno == EAGAIN));
598 
599   if (flags == -1)
600   {
601     Error << "fcntl(F_GETFL) " << strerror(errno);
602     throw strerror(errno);
603   }
604 
605   int rval;
606   do
607   {
608     rval= fcntl(_pipe_fd[READ], F_SETFL, flags | O_NONBLOCK);
609   } while (rval == -1 and (errno == EINTR or errno == EAGAIN));
610 
611   if (rval == -1)
612   {
613     Error << "fcntl(F_SETFL) " << strerror(errno);
614     throw strerror(errno);
615   }
616 }
617 
reset()618 void Application::Pipe::reset()
619 {
620   close(READ);
621   close(WRITE);
622 
623 #ifdef HAVE_PIPE2
624   if (pipe2(_pipe_fd, O_NONBLOCK|O_CLOEXEC) == -1)
625 #endif
626   {
627     if (pipe(_pipe_fd) == -1)
628     {
629       FATAL(strerror(errno));
630     }
631 
632     // Since either pipe2() was not found/called we set the pipe directly
633     nonblock();
634     cloexec();
635   }
636   _open[0]= true;
637   _open[1]= true;
638 }
639 
cloexec()640 void Application::Pipe::cloexec()
641 {
642   //if (SOCK_CLOEXEC == 0)
643   {
644     if (FD_CLOEXEC)
645     {
646       int flags;
647       do
648       {
649         flags= fcntl(_pipe_fd[WRITE], F_GETFD, 0);
650       } while (flags == -1 and (errno == EINTR or errno == EAGAIN));
651 
652       if (flags == -1)
653       {
654         Error << "fcntl(F_GETFD) " << strerror(errno);
655         throw strerror(errno);
656       }
657 
658       int rval;
659       do
660       {
661         rval= fcntl(_pipe_fd[WRITE], F_SETFD, flags | FD_CLOEXEC);
662       } while (rval == -1 && (errno == EINTR or errno == EAGAIN));
663 
664       if (rval == -1)
665       {
666         Error << "fcntl(F_SETFD) " << strerror(errno);
667         throw strerror(errno);
668       }
669     }
670   }
671 }
672 
~Pipe()673 Application::Pipe::~Pipe()
674 {
675   if (_pipe_fd[0] != -1)
676   {
677     ::close(_pipe_fd[0]);
678   }
679 
680   if (_pipe_fd[1] != -1)
681   {
682     ::close(_pipe_fd[1]);
683   }
684 }
685 
dup_for_spawn(posix_spawn_file_actions_t & file_actions)686 void Application::Pipe::dup_for_spawn(posix_spawn_file_actions_t& file_actions)
687 {
688   int type= STDIN_FILENO == _std_fd ? 0 : 1;
689 
690   int ret;
691   if ((ret= posix_spawn_file_actions_adddup2(&file_actions, _pipe_fd[type], _std_fd )) < 0)
692   {
693     FATAL("posix_spawn_file_actions_adddup2(%s)", strerror(ret));
694   }
695 
696   if ((ret= posix_spawn_file_actions_addclose(&file_actions, _pipe_fd[type])) < 0)
697   {
698     FATAL("posix_spawn_file_actions_addclose(%s)", strerror(ret));
699   }
700 }
701 
close(const close_t & arg)702 void Application::Pipe::close(const close_t& arg)
703 {
704   int type= int(arg);
705 
706   if (_open[type])
707   {
708     if (::close(_pipe_fd[type]) == -1)
709     {
710       Error << "close(" << strerror(errno) << ")";
711     }
712     _open[type]= false;
713     _pipe_fd[type]= -1;
714   }
715 }
716 
create_argv(const char * args[])717 void Application::create_argv(const char *args[])
718 {
719   delete_argv();
720   if (_use_libtool)
721   {
722     assert(libtool());
723     vchar::append(built_argv, libtool());
724     vchar::append(built_argv, "--mode=execute");
725   }
726 
727   if (_use_valgrind)
728   {
729     /*
730       valgrind --error-exitcode=1 --leak-check=yes --track-fds=yes --malloc-fill=A5 --free-fill=DE
731     */
732     vchar::append(built_argv, "valgrind");
733     vchar::append(built_argv, "--error-exitcode=1");
734     vchar::append(built_argv, "--leak-check=yes");
735 #if 0
736     vchar::append(built_argv, "--show-reachable=yes"));
737 #endif
738     vchar::append(built_argv, "--track-fds=yes");
739 #if 0
740     built_argv[x++]= strdup("--track-origin=yes");
741 #endif
742     vchar::append(built_argv, "--malloc-fill=A5");
743     vchar::append(built_argv, "--free-fill=DE");
744 
745     std::string log_file= create_tmpfile("valgrind");
746     libtest::vchar_t buffer;
747     buffer.resize(1024);
748     int length= snprintf(&buffer[0], buffer.size(), "--log-file=%s", log_file.c_str());
749     fatal_assert(length > 0 and size_t(length) < buffer.size());
750     vchar::append(built_argv, &buffer[0]);
751   }
752   else if (_use_ptrcheck)
753   {
754     /*
755       valgrind --error-exitcode=1 --tool=exp-ptrcheck --log-file=
756     */
757     vchar::append(built_argv, "valgrind");
758     vchar::append(built_argv, "--error-exitcode=1");
759     vchar::append(built_argv, "--tool=exp-ptrcheck");
760     std::string log_file= create_tmpfile("ptrcheck");
761     libtest::vchar_t buffer;
762     buffer.resize(1024);
763     int length= snprintf(&buffer[0], buffer.size(), "--log-file=%s", log_file.c_str());
764     fatal_assert(length > 0 and size_t(length) < buffer.size());
765     vchar::append(built_argv, &buffer[0]);
766   }
767   else if (_use_gdb)
768   {
769     vchar::append(built_argv, "gdb");
770   }
771 
772   vchar::append(built_argv, _exectuble_with_path.c_str());
773 
774   for (Options::const_iterator iter= _options.begin(); iter != _options.end(); ++iter)
775   {
776     vchar::append(built_argv, (*iter).first.c_str());
777     if ((*iter).second.empty() == false)
778     {
779       vchar::append(built_argv, (*iter).second.c_str());
780     }
781   }
782 
783   if (args)
784   {
785     for (const char **ptr= args; *ptr; ++ptr)
786     {
787       vchar::append(built_argv, *ptr);
788     }
789   }
790   built_argv.push_back(NULL);
791 }
792 
print()793 std::string Application::print()
794 {
795   return print_argv(built_argv);
796 }
797 
arguments()798 std::string Application::arguments()
799 {
800   std::stringstream arg_buffer;
801 
802   // Skip printing out the libtool reference
803   for (size_t x= _use_libtool ? 2 : 0; x < _argc; ++x)
804   {
805     if (built_argv[x])
806     {
807       arg_buffer << built_argv[x] << " ";
808     }
809   }
810 
811   return arg_buffer.str();
812 }
813 
delete_argv()814 void Application::delete_argv()
815 {
816   std::for_each(built_argv.begin(), built_argv.end(), FreeFromVector());
817 
818   built_argv.clear();
819   _argc= 0;
820 }
821 
822 
exec_cmdline(const std::string & command,const char * args[],bool use_libtool)823 int exec_cmdline(const std::string& command, const char *args[], bool use_libtool)
824 {
825   Application app(command, use_libtool);
826 
827   Application::error_t ret= app.run(args);
828 
829   if (ret != Application::SUCCESS)
830   {
831     return int(ret);
832   }
833 
834   return int(app.join());
835 }
836 
837 } // namespace exec_cmdline
838