Browse Source

Initial commit

Andy Teijelo 6 years ago
commit
ef7a4af571
3 changed files with 317 additions and 0 deletions
  1. 1 0
      README.md
  2. 255 0
      command.cpp
  3. 61 0
      command.h

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# A simple command class for C++

+ 255 - 0
command.cpp

@@ -0,0 +1,255 @@
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#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<string> &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.size(); i++) {
+            argv[i] = const_cast<char*>(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;
+}

+ 61 - 0
command.h

@@ -0,0 +1,61 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <functional>
+
+#if __has_include(<boost/filesystem.hpp>)
+#include <boost/filesystem.hpp>
+#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<std::string>& 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<std::string> 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