added possibility to pass string to stdin of child process

master
Marc Wäckerlin 20 years ago
parent d129e4e5b4
commit 0cac265f4d
  1. 140
      mrw/exec.cpp
  2. 110
      mrw/exec.hpp
  3. 11
      mrw/exec_test.cpp

@ -9,6 +9,9 @@
@license LGPL, see file <a href="license.html">COPYING</a> @license LGPL, see file <a href="license.html">COPYING</a>
$Log$ $Log$
Revision 1.5 2004/12/14 20:30:09 marc
added possibility to pass string to stdin of child process
Revision 1.4 2004/08/28 16:21:25 marc Revision 1.4 2004/08/28 16:21:25 marc
mrw-c++-0.92 (mrw) mrw-c++-0.92 (mrw)
- new file: version.cpp - new file: version.cpp
@ -26,9 +29,11 @@
#include <mrw/exec.hpp> #include <mrw/exec.hpp>
#include <mrw/unistd.hpp> #include <mrw/unistd.hpp>
#include <mrw/exception.hpp> #include <mrw/exception.hpp>
#include <mrw/stdext.hpp> // max
#include <sys/wait.h> // waitpid #include <sys/wait.h> // waitpid
#include <unistd.h> // fork, exec #include <unistd.h> // fork, exec
#include <string.h> // memcpy #include <string.h> // memcpy
#include <assert.h> // assert
mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w, mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w,
const std::string& c) const std::string& c)
@ -73,8 +78,13 @@ mrw::Cmd::operator mrw::Exec() const throw(std::bad_exception) {
return mrw::Exec(*this); return mrw::Exec(*this);
} }
mrw::Exec mrw::Cmd::execute(bool throwExc) const throw(std::exception) { mrw::Exec mrw::Cmd::execute(bool exc) const throw(std::exception) {
return mrw::Exec(*this).execute(throwExc); return mrw::Exec(*this).execute(exc);
}
mrw::Exec mrw::Cmd::execute(const std::string& input, bool exc) const
throw(std::exception) {
return mrw::Exec(*this).execute(input, exc);
} }
const char* mrw::Cmd::path() const throw(std::bad_exception) { const char* mrw::Cmd::path() const throw(std::bad_exception) {
@ -109,10 +119,11 @@ mrw::Exec& mrw::Exec::operator=(const mrw::Exec& e) throw(std::bad_exception) {
return *this; return *this;
} }
mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) { mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) {
/** This method calls @c fork, sets up a pipe connection to pass @c /** This method calls @c fork, sets up a pipe connection to pass @c
stdout and @c stderr from the child process to the parent process stdout and @c stderr from the child process to the parent
using mrw::Pipe and calls @c execvp to execute the program. */ process using mrw::Pipe and calls @c execvp to execute the
program. */
_success = false; _success = false;
_res = _err = ""; _res = _err = "";
mrw::Pipe stdOut, stdErr; mrw::Pipe stdOut, stdErr;
@ -128,17 +139,18 @@ mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) {
throw ExecutionFailedExc("cannot close pipe", *_cmd); throw ExecutionFailedExc("cannot close pipe", *_cmd);
int num1(0), num2(0); int num1(0), num2(0);
for (char buf1[4096], buf2[4096]; for (char buf1[4096], buf2[4096];
(num1=read(stdOut.istream(), buf1, sizeof(buf1)))>0 || (num1=read(stdOut.istream(), buf1, sizeof(buf1)))>0
num1==-1 && errno==EINTR || || num1==-1 && (errno==EINTR||errno==EAGAIN)
(num2=read(stdErr.istream(), buf2, sizeof(buf2)))>0 || || (num2=read(stdErr.istream(), buf2, sizeof(buf2)))>0
num2==-1 && errno==EINTR; || num2==-1 && (errno==EINTR||errno==EAGAIN);) {
_res += std::string(buf1, num1), _err += std::string(buf2, num2)); if (num1>0) _res += std::string(buf1, num1);
if (num1==-1 || num2==-1) if (num2>0) _err += std::string(buf2, num2);
throw ExecutionFailedExc("cannot_ read pipe", *_cmd); }
// wait for child to get return code // wait for child to get return code
int s(0); int s(0);
if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) { if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0
if (throwExc) { || num1==-1 || num2==-1) {
if (exc) {
throw ExecutionFailedExc("execution failed", *_cmd); throw ExecutionFailedExc("execution failed", *_cmd);
} else { } else {
_success = false; _success = false;
@ -157,6 +169,106 @@ mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) {
return *this; return *this;
} }
mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc)
throw(std::exception) {
/// @c input length must be smaller than @c SSIZE_MAX.
/// I'll only add support for longer strings upon request.
assert(input.size()<=SSIZE_MAX &&
"sdin input exeeds C library limit in mrw::Exec "
"please contact the author of the library");
/** This method calls @c fork, sets up a pipe connection to pass @c
stdin, @c stdout and @c stderr from the child process to the
parent process using mrw::Pipe and calls @c execvp to execute
the program. */
_success = false;
_res = _err = "";
mrw::Pipe stdIn(true), // child terminates if stdin is non-blocking
stdOut, stdErr;
if (!stdIn || !stdOut || !stdErr)
throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd);
pid_t pid(fork());
if (pid<0) throw ExecutionFailedExc("cannot fork", *_cmd);
if (pid) { // parent
stdIn.close_in();
stdOut.close_out();
stdErr.close_out();
if (!stdIn || !stdOut || !stdErr)
throw ExecutionFailedExc("cannot close pipes", *_cmd);
ssize_t num0(-1), num1(-1), num2(-1);
std::string in(input);
fd_set writefds, readfds;
char buf[4096];
while (num0 || num1 || num2) try { // no end of file in stdin, -out, -err
int res(0), s(0);
if ((res=waitpid(pid, &s, WNOHANG))) {
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0)
throw ExecutionFailedExc("execution failed", *_cmd);
_success = true;
return *this;
}
// select...
FD_ZERO(&writefds);
FD_ZERO(&readfds);
if (num0) FD_SET(stdIn.ostream(), &writefds);
if (num1) FD_SET(stdOut.istream(), &readfds);
if (num2) FD_SET(stdErr.istream(), &readfds);
timeval tm = {1, 0};
int n(mrw::max((num0?stdIn.ostream():0),
mrw::max((num1?stdOut.istream():0),
(num2?stdErr.istream():0))));
int num = select(n, &readfds, &writefds, 0, &tm);
if (num<0) throw ExecutionFailedExc("select failed", *_cmd);
// check and handle stdin
if (num0 && num>0
&& FD_ISSET(stdIn.ostream(), &writefds))
if ((num0=write(stdIn.ostream(), in.c_str(), in.size()))>0) {
if ((unsigned int)num0<in.size())
in = in.substr(num0);
else if ((unsigned int)num0==in.size())
num0=0, stdIn.close_out();
} else if (num0==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("writing stdin", *_cmd);
// check and handle stdout
if (num>0 && FD_ISSET(stdOut.istream(), &readfds))
if ((num1=read(stdOut.istream(), buf, sizeof(buf)))>0)
_res += std::string(buf, num1);
else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stdout", *_cmd);
// check and handle stderr
if (num>0 && FD_ISSET(stdErr.istream(), &readfds))
if ((num2=read(stdErr.istream(), buf, sizeof(buf)))>0)
_err += std::string(buf, num2);
else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN))
throw ExecutionFailedExc("readin stderr", *_cmd);
} catch (...) {
if (exc) throw;
_success = false;
return *this;
}
// wait for child to get return code
int s(0);
if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) {
if (exc) {
throw ExecutionFailedExc("execution failed", *_cmd);
} else {
_success = false;
return *this;
}
}
} else { // child
stdIn.close_out();
stdOut.close_in();
stdErr.close_in();
stdIn.connect_cin();
stdOut.connect_cout(); // if cin is non blocking, child terminates here?!?
stdErr.connect_cerr();
execvp(_cmd->path(), _cmd->args());
exit(1); // execute failed
}
_success = true;
return *this;
}
mrw::Exec& mrw::Exec::operator>>(std::string& res) throw(std::exception) { mrw::Exec& mrw::Exec::operator>>(std::string& res) throw(std::exception) {
execute(); execute();
res += _res; res += _res;

@ -9,6 +9,9 @@
@license LGPL, see file <a href="license.html">COPYING</a> @license LGPL, see file <a href="license.html">COPYING</a>
$Log$ $Log$
Revision 1.5 2004/12/14 20:30:09 marc
added possibility to pass string to stdin of child process
Revision 1.4 2004/10/07 09:27:01 marc Revision 1.4 2004/10/07 09:27:01 marc
errors in documentation errors in documentation
@ -67,6 +70,18 @@ namespace mrw {
// you can trace x.what() and x.stacktrace() // you can trace x.what() and x.stacktrace()
} }
@endcode @endcode
It is also possible to pass an @c stdin input argument to the
subprocess called:
@code
try {
mrw::Exec cat = mrw::Cmd("/bin/cat")
.execute("this is passed to stdin");
// "cat" passes all from stdin to stdout, therefore:
assert(cat.result()=="this is passed to stdin");
} catch (...) {} // ignore
@endcode
*/ */
//@{ //@{
@ -99,6 +114,9 @@ namespace mrw {
and returns the two streams @c cout and @c cerr, also known as @c and returns the two streams @c cout and @c cerr, also known as @c
stderr and @c stdout. stderr and @c stdout.
Method @c execute can optionally also take a string parameter
that is passed to @c stdin of the child process.
There are different ways of usage for this class. A simple way, There are different ways of usage for this class. A simple way,
one line of code, to get only the resulting stream (no error) one line of code, to get only the resulting stream (no error)
is: is:
@ -146,6 +164,42 @@ namespace mrw {
*/ */
Exec& execute(bool exc=true) throw(std::exception); Exec& execute(bool exc=true) throw(std::exception);
/** @brief Execute the command, pass @c stdin.
@param input Input that is passed to @c stdin of the child process.
@param exc
- @c true throw an exception if return status is not zero
- @c false throw only an exception in case of a fatal error
@throw ExecutionFailedExc is thrown if
- fork fails
- creation or setup of pipes failed
- if given parameter is @c true (the default) also if the
executed program terminates with an error
*/
Exec& execute(const std::string& input, bool exc=true)
throw(std::exception);
/** @brief Execute the command, pass @c stdin.
@param input Input that is passed to @c stdin of the child process.
@param exc
- @c true throw an exception if return status is not zero
- @c false throw only an exception in case of a fatal error
@throw ExecutionFailedExc is thrown if
- fork fails
- creation or setup of pipes failed
- if given parameter is @c true (the default) also if the
executed program terminates with an error
*/
Exec& execute(char const*const input, bool exc=true)
throw(std::exception) {
return execute(std::string(input), exc);
}
/** @brief Executes the command if not done, streams @c stdout into a string /** @brief Executes the command if not done, streams @c stdout into a string
If the command has not yet been executed successfully, it is If the command has not yet been executed successfully, it is
@ -220,7 +274,7 @@ namespace mrw {
/** @brief A system command to be executed /** @brief A system command to be executed
@pre #include <mrw/exec.hpp> @pre #include <mrw/exec.hpp>
This class is used in conjunction with mrw::Exec. It mus be This class is used in conjunction with mrw::Exec. It must be
initialized with the command name, then the command parameters initialized with the command name, then the command parameters
are appended either with commas, or by streaming them into the are appended either with commas, or by streaming them into the
command, whatever you like. command, whatever you like.
@ -284,6 +338,60 @@ namespace mrw {
*/ */
Exec execute(bool exc=true) const throw(std::exception); Exec execute(bool exc=true) const throw(std::exception);
/** @brief Create a mrw::Exec and execute the command given an input
Creates a mrw::Exec, executes the command, passes the input
and the flag to mrw::Exec::execute() and returns the created
mrw::Exec. The result of the execution can be retrieved
through the returned mrw::Exec object: The methods
mrw::Exec::success(), mrw::Exec::result() and
mrw::Exec::error() provide the necessary information.
@param input Input that is passed to @c stdin of the child process.
@param exc
- @c true throw an exception if return status is not zero
- @c false throw only an exception in case of a fatal error
@return the mrw::Exec that has executed the command
@throw ExecutionFailedExc is thrown if
- fork fails
- creation or setup of pipes failed
- if given parameter is @c true (the default) also if the
executed program terminates with an error
*/
Exec execute(const std::string& input, bool exc=true) const
throw(std::exception);
/** @brief Create a mrw::Exec and execute the command given an input
Creates a mrw::Exec, executes the command, passes the input
and the flag to mrw::Exec::execute() and returns the created
mrw::Exec. The result of the execution can be retrieved
through the returned mrw::Exec object: The methods
mrw::Exec::success(), mrw::Exec::result() and
mrw::Exec::error() provide the necessary information.
@param input Input that is passed to @c stdin of the child process.
@param exc
- @c true throw an exception if return status is not zero
- @c false throw only an exception in case of a fatal error
@return the mrw::Exec that has executed the command
@throw ExecutionFailedExc is thrown if
- fork fails
- creation or setup of pipes failed
- if given parameter is @c true (the default) also if the
executed program terminates with an error
*/
Exec execute(char const*const input, bool exc=true) const
throw(std::exception) {
return execute(std::string(input), exc);
}
private: private:
/// Exec is allowed to call @c path() and @c args(). /// Exec is allowed to call @c path() and @c args().
friend class Exec; friend class Exec;

@ -9,6 +9,9 @@
@license LGPL, see file <a href="license.html">COPYING</a> @license LGPL, see file <a href="license.html">COPYING</a>
$Log$ $Log$
Revision 1.6 2004/12/14 20:30:10 marc
added possibility to pass string to stdin of child process
Revision 1.5 2004/10/13 10:43:11 marc Revision 1.5 2004/10/13 10:43:11 marc
test for bad exception specification test for bad exception specification
@ -40,14 +43,20 @@ public:
std::string res = (mrw::Cmd("/bin/ls"), "-l", "..").execute(); std::string res = (mrw::Cmd("/bin/ls"), "-l", "..").execute();
CPPUNIT_ASSERT(res.find("COPYING")<res.size()); CPPUNIT_ASSERT(res.find("COPYING")<res.size());
} }
void catTest() {
std::string res =mrw::Cmd("/bin/cat").execute("This is a test");
CPPUNIT_ASSERT(res=="This is a test");
}
void excTest() { void excTest() {
std::string res = (mrw::Cmd("/bin/false")).execute().result(); std::string res1 = (mrw::Cmd("/bin/false")).execute().result();
std::string res2 = (mrw::Cmd("/bin/false")).execute("").result();
} }
void unexpectedExc() throw(std::bad_exception) { void unexpectedExc() throw(std::bad_exception) {
std::string res = (mrw::Cmd("/bin/false")).execute().result(); std::string res = (mrw::Cmd("/bin/false")).execute().result();
} }
CPPUNIT_TEST_SUITE(ExecTest); CPPUNIT_TEST_SUITE(ExecTest);
CPPUNIT_TEST(lsTest); CPPUNIT_TEST(lsTest);
CPPUNIT_TEST(catTest);
CPPUNIT_TEST_EXCEPTION(excTest, mrw::ExecutionFailedExc); CPPUNIT_TEST_EXCEPTION(excTest, mrw::ExecutionFailedExc);
CPPUNIT_TEST_EXCEPTION(unexpectedExc, std::bad_exception); CPPUNIT_TEST_EXCEPTION(unexpectedExc, std::bad_exception);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();

Loading…
Cancel
Save