From ef7a4af57184e644f07a4bab849d20639e28d6f1 Mon Sep 17 00:00:00 2001 From: Andy Teijelo Date: Wed, 20 Jul 2016 10:26:51 -0400 Subject: [PATCH] Initial commit --- README.md | 1 + command.cpp | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++ command.h | 61 +++++++++++++ 3 files changed, 317 insertions(+) create mode 100644 README.md create mode 100644 command.cpp create mode 100644 command.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..af06f2e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# A simple command class for C++ diff --git a/command.cpp b/command.cpp new file mode 100644 index 0000000..719e380 --- /dev/null +++ b/command.cpp @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "command.h" + +using std::vector; +using std::string; +using std::ostringstream; +using std::cout; +using std::endl; + +command::command() +{ + readbuf = (char *)malloc(bufsize); +} + +command &command::operator <<(int n) +{ + ostringstream s; + s << n; + args.push_back(s.str()); + return *this; +} + +#ifdef COMMAND_BOOST_FILESYSTEM +command& command::operator<<(const boost::filesystem::path& p) { + args.push_back(p.string()); + return *this; +} +#else +command& command::operator<<(const string& s) { + args.push_back(s); + return *this; +} +#endif + +command &command::operator <<(const vector &v) +{ + std::copy(v.begin(), v.end(), std::back_inserter(args)); + return *this; +} + +command &command::operator <<(double d) +{ + ostringstream s; + s << d; + args.push_back(s.str()); + return *this; +} + +void command::clear() +{ + args.clear(); + async = false; +} + +int command::read_from(int fd, std::ostream *stream) +{ + int r = read(fd, readbuf, bufsize); + if (r <= 0) + return r; + stream->write(readbuf, r); + if (r == bufsize) { + bufsize *= 2; + free(readbuf); + readbuf = (char *)malloc(bufsize); + } + return r; +} + +int command::run() { + if (args.empty()) + return -1; + + if (stdout_stream != nullptr) + pipe(stdout_pipe); + + if (stderr_stream != nullptr) + pipe(stderr_pipe); + + + childpid = fork(); + if (childpid == 0) { + char **argv = (char**)malloc((args.size() + 1) * sizeof(char*)); + for (uint i=0; i(args.at(i).c_str()); + } + argv[args.size()] = nullptr; + + if (_silence_stdout || _silence_stderr) { + int f = open("/dev/null", O_WRONLY); + if (_silence_stdout) + dup2(f, STDOUT_FILENO); + if (_silence_stderr) + dup2(f, STDERR_FILENO); + } + + if (stdout_stream != nullptr) { + dup2(stdout_pipe[1], STDOUT_FILENO); + close(stdout_pipe[0]); + } + + if (stderr_stream != nullptr) { + dup2(stderr_pipe[1], STDERR_FILENO); + close(stderr_pipe[0]); + } + + execvp(args.at(0).c_str(), argv); + perror(args.at(0).c_str()); + exit(-1); + } + + int child_out = -1; + int child_err = -1; + // parent won't be writing anything to the pipes + // and keeping the write ends open will prevent + // EOF from happening on the read ends + if (stdout_stream != nullptr) { + close(stdout_pipe[1]); + child_out = stdout_pipe[0]; + } + + if (stderr_stream != nullptr) { + child_err = stderr_pipe[0]; + close(stderr_pipe[1]); + } + + if (async) { + return 0; + } + + // now we'll wait on both ends, using select + while (child_err >=0 || child_out >= 0) { + + fd_set read_fds; + FD_ZERO(&read_fds); + int nfds = 0; + if (child_out >= 0) { + FD_SET(child_out, &read_fds); + nfds = std::max(nfds, child_out); + } + if (child_err >= 0) { + FD_SET(child_err, &read_fds); + nfds = std::max(nfds, child_err); + } + nfds++; + + int r = select(nfds, &read_fds, nullptr, nullptr, nullptr); + if (r < 0) { + perror("select failed"); + break; + } + if (child_out >= 0 && FD_ISSET(child_out, &read_fds)) { + int r = read_from(child_out, stdout_stream); + if (r < 0) { + perror("error reading from child stdout"); + break; + } + if (r == 0) { + close(child_out); + child_out = -1; + } + } + if (child_err >= 0 && FD_ISSET(child_err, &read_fds)) { + read_from(child_err, stderr_stream); + if (r < 0) { + perror("error reading from child stderr"); + break; + } + if (r == 0) { + close(child_err); + child_err = -1; + } + } + } + + return this->wait(); +} + +void command::runbg() +{ + async = true; + run(); +} + +bool command::isrunning() +{ + siginfo_t info; + info.si_pid = 0; + + int r = waitid(P_PID, childpid, &info, WEXITED | WNOHANG | WNOWAIT); + if (r == 0 && info.si_pid != 0) + return true; + + switch (info.si_code) { + case CLD_EXITED: return false; + case CLD_KILLED: return false; + case CLD_DUMPED: return false; + } + return true; +} + +int command::wait() +{ + int status; + ::wait(&status); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + if (WIFSIGNALED(status)) + return WTERMSIG(status) + 127; + return -1; +} + +void command::silence_stdout() +{ + _silence_stdout = true; + stdout_stream = nullptr; +} + +void command::silence_stderr() +{ + stderr_stream = nullptr; + _silence_stderr = true; +} + +void command::silence() +{ + silence_stdout(); + silence_stderr(); +} + +void command::stdout(std::ostream &o) +{ + stdout_stream = &o; + _silence_stdout = false; +} + +void command::stderr(std::ostream &o) +{ + stderr_stream = &o; + _silence_stderr = false; +} + +std::ostream &operator<<(std::ostream &o, const command &cmd) +{ + for (auto& s: cmd.args) { + o << s << " "; + } + return o; +} diff --git a/command.h b/command.h new file mode 100644 index 0000000..21889b5 --- /dev/null +++ b/command.h @@ -0,0 +1,61 @@ +#ifndef COMMAND_H +#define COMMAND_H + +#include +#include +#include +#include + +#if __has_include() +#include +#define COMMAND_BOOST_FILESYSTEM +#endif + +class command { + public: + command(); + command& operator <<(int n); +#ifdef COMMAND_BOOST_FILESYSTEM + command& operator <<(const boost::filesystem::path& p); +#else + command& operator <<(const std::string& s); +#endif + command& operator <<(const std::vector& v); + command& operator <<(double d); + void clear(); + int run(); + void runbg(); + bool isrunning(); + int wait(); + + void silence_stdout(); + void silence_stderr(); + void silence(); + void stdout(std::ostream& o); + void stderr(std::ostream& o); + + private: + std::vector args; + + pid_t childpid; + + int stdout_pipe[2]; + int stderr_pipe[2]; + + bool _silence_stdout = false; + bool _silence_stderr = false; + + std::ostream* stdout_stream = nullptr; + std::ostream* stderr_stream = nullptr; + + friend std::ostream& operator<<(std::ostream& o, const command& cmd); + + char *readbuf; + int bufsize = 1024; + int async = false; + int read_from(int fd, std::ostream *stream); +}; + +std::ostream& operator<<(std::ostream& o, const command& cmd); + +#endif // COMMAND_H