Skip to content

Commit

Permalink
add support for running an external command
Browse files Browse the repository at this point in the history
Github: related to #130 "interact with SMT solver"
  • Loading branch information
Smattr committed Jun 23, 2019
1 parent 5a33d50 commit bc56159
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
1 change: 1 addition & 0 deletions rumur/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ add_executable(rumur
src/max-simple-width.cc
src/options.cc
src/output.cc
src/process.cc
src/symmetry-reduction.cc
src/utils.cc
src/ValueType.cc)
Expand Down
329 changes: 329 additions & 0 deletions rumur/src/process.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
#include <cassert>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include "process.h"
#include <signal.h>
#include <spawn.h>
#include <sstream>
#include <string>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <vector>

// compile this file with -DTEST_PROCESS to build a test harness
#ifdef TEST_PROCESS
#define debug (&std::cerr)
#else
#include "log.h"
#endif

#ifdef __APPLE__
#include <crt_externs.h>
#endif

enum { READ_FD = 0, WRITE_FD = 1 };

// pipe through which we'll redirect SIGCHLD notifications
static int sigchld_pipe[2] = { -1, -1 };

// signal handler to redirect SIGCHLD into the above pipe
static void handler(int sigchld __attribute__((unused))) {

assert(sigchld == SIGCHLD && "SIGCHLD handler received something other than "
"SIGCHLD");

assert(sigchld_pipe[WRITE_FD] != -1 && "SIGCHLD handler called before "
"SIGCHLD pipe has been setup");

(void)write(sigchld_pipe[WRITE_FD], "\0", 1);
}

static bool inited = false;

static int init(void) {

if (pipe(sigchld_pipe) < 0) {
*debug << "failed to create SIGCHLD pipe\n";
goto fail;
}

// set the SIGCHLD pipe not to block on reading or writing
{
int r = sigchld_pipe[READ_FD];
int w = sigchld_pipe[WRITE_FD];
if (fcntl(r, F_SETFD, fcntl(r, F_GETFD) | O_NONBLOCK) == -1 ||
fcntl(w, F_SETFD, fcntl(w, F_GETFD) | O_NONBLOCK) == -1) {
*debug << "failed to set SIGCHLD pipe non-blocking\n";
goto fail;
}
}

// register our redirecting signal handler
if (signal(SIGCHLD, handler) == SIG_ERR) {
*debug << "failed to set SIGCHLD handler: " << strerror(errno) << "\n";
goto fail;
}

inited = true;

return 0;

fail:
for (int &fd : sigchld_pipe) {
if (fd != -1) {
(void)close(fd);
fd = -1;
}
}
return -1;
}

static char **get_environ() {
#if __APPLE__
/* Bizarrely Apple don't give programs a symbol for environ, but have an
* indirect way of accessing it.
*/
return *_NSGetEnviron();
#else
return environ;
#endif
}

static int max(int a, int b) {
return a > b ? a : b;
}

int run(const std::vector<std::string> &args, const std::string &input,
std::string &output) {

if (!inited) {
if (init() < 0)
return -1;
}

// setup an argument vector
std::vector<char*> argv;
for (const std::string &a : args)
argv.push_back(const_cast<char*>(a.c_str()));
argv.push_back(nullptr);


posix_spawn_file_actions_t fa;
int in[2] = { -1, -1 };
int out[2] = { -1, -1 };
int ret = -1;
std::ostringstream buffered_output;
off_t input_offset = 0;


if (posix_spawn_file_actions_init(&fa) < 0) {
*debug << "failed file_actions_init: " << strerror(errno) << "\n";
return -1;
}

// create some pipes we'll use to communicate with the child
if (pipe(in) < 0 || pipe(out) < 0) {
*debug<< "failed pipe: " << strerror(errno) << "\n";
goto done;
}

// set the ends the parent (us) will use as non-blocking
if (fcntl(in[WRITE_FD], F_SETFD, fcntl(in[WRITE_FD], F_GETFD) | O_NONBLOCK) == -1 ||
fcntl(out[READ_FD], F_SETFD, fcntl(out[READ_FD], F_GETFD) | O_NONBLOCK) == -1) {
*debug << "failed to set O_NONBLOCK: " << strerror(errno) << "\n";
goto done;
}

// have the child close the ends it won't need
if (posix_spawn_file_actions_addclose(&fa, in[WRITE_FD]) < 0 ||
posix_spawn_file_actions_addclose(&fa, out[READ_FD]) < 0) {
*debug << "failed file_actions_addclose: " << strerror(errno) << "\n";
goto done;
}

// replace the child's stdin, stdout and stderr with the pipes
if (posix_spawn_file_actions_adddup2(&fa, in[READ_FD], STDIN_FILENO) < 0 ||
posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDOUT_FILENO) < 0 ||
posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDERR_FILENO) < 0) {
*debug << "failed file_actions_adddup2: " << strerror(errno) << "\n";
goto done;
}

// spawn the child
pid_t pid;
if (posix_spawnp(&pid, argv[0], &fa, nullptr, argv.data(), get_environ()) != 0) {
*debug << "failed posix_spawnp: " << strerror(errno) << "\n";
goto done;
}

// close the ends of the pipes we (the parent) don't need
(void)close(in[READ_FD]);
in[READ_FD] = -1;
(void)close(out[WRITE_FD]);
out[WRITE_FD] = -1;

// now we're ready to sit in an event loop interacting with the child
for (;;) {

int nfds;

// create a set of the out pipe and SIGCHLD to monitor for reading
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(out[READ_FD], &readfds);
FD_SET(sigchld_pipe[READ_FD], &readfds);
nfds = max(out[READ_FD], sigchld_pipe[READ_FD]);

// create a set of only the in pipe (if still open) to monitor for writing
fd_set writefds;
FD_ZERO(&writefds);
if (in[WRITE_FD] != -1) {
FD_SET(in[WRITE_FD], &writefds);
nfds = max(nfds, in[WRITE_FD]);
}

// wait for an event
if (select(nfds, &readfds, &writefds, nullptr, nullptr) < 0) {
// if our select call is correct, and any "error" should be an interrupt
assert(errno == EAGAIN || errno == EINTR);
}

// clear any SIGCHLD notification
if (FD_ISSET(sigchld_pipe[READ_FD], &readfds)) {
char ignored[BUFSIZ];
(void)read(sigchld_pipe[READ_FD], ignored, sizeof(ignored));
}

// read any data available from the child
if (FD_ISSET(out[READ_FD], &readfds)) {
ssize_t r;
do {

char buffer[BUFSIZ];
do {
r = read(out[READ_FD], buffer, sizeof(buffer));
} while (r == -1 && errno == EINTR);

if (r == -1) {
if (errno == EAGAIN)
break;
*debug << "failed to read from child: " << strerror(errno) << "\n";
goto done;
}

// retain anything we read
for (size_t i = 0; i < (size_t)r; i++)
buffered_output << buffer[i];

} while (r > 0);
}

// write remaining data if the input pipe is ready
if (in[WRITE_FD] != -1 && FD_ISSET(in[WRITE_FD], &writefds)) {
if ((size_t)input_offset < input.size()) {

ssize_t w;
do {
w = write(in[WRITE_FD], input.c_str() + input_offset,
input.size() - input_offset);
} while (w == -1 && errno == EINTR);

if (w == -1 && errno != EAGAIN) {
*debug << "failed to write to child: " << strerror(errno) << "\n";
goto done;
}

if (w > 0) {
input_offset += (off_t)w;
if ((size_t)input_offset == input.size()) {
// exhausted the input; send the child EOF
(void)close(in[WRITE_FD]);
in[WRITE_FD] = -1;
}
}
}
}

// check if the child has exited
int status;
if (waitpid(pid, &status, WNOHANG) == pid) {
if (WIFEXITED(status))
break;
if (WIFSIGNALED(status)) {
*debug << "child exited due to signal " << WTERMSIG(status) << "\n";
break;
}
}
}

// success if we've reached here

output = buffered_output.str();

ret = 0;

done:

for (int &fd : out) {
if (fd != -1) {
(void)close(fd);
fd = -1;
}
}

for (int &fd : in) {
if (fd != -1) {
(void)close(fd);
fd = -1;
}
}

(void)posix_spawn_file_actions_destroy(&fa);

return ret;
}

static int __attribute__((unused)) test_process(int argc, char **argv) {

if (argc < 2 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0
|| strcmp(argv[1], "-?") == 0) {
std::cerr
<< "Rumur Process.cc tester\n"
<< "\n"
<< " usage: " << argv[0] << " cmd args...\n";
return EXIT_SUCCESS;
}

// construct argument list
std::vector<std::string> args;
for (size_t i = 1; i < (size_t)argc; i++) {
assert(argv[i] != nullptr);
args.emplace_back(argv[i]);
}

// read stdin until EOF
std::ostringstream input;
for (std::string line; std::getline(std::cin, line); ) {
input << line << "\n";
}

// run our designated process
std::string output;
int r = run(args, input.str(), output);

if (r == 0)
std::cout << output;

return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

#ifdef TEST_PROCESS
int main(int argc, char **argv) {
return test_process(argc, argv);
}
#endif
12 changes: 12 additions & 0 deletions rumur/src/process.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <cstddef>
#include <string>
#include <vector>

/* Run an external process, pass it the given input on stdin and wait for it to
* finish. Its stdout data is reported via the output parameter. Note that there
* is currently no reporting of the exit status of the process.
*/
int run(const std::vector<std::string> &args, const std::string &input,
std::string &output);

0 comments on commit bc56159

Please sign in to comment.