Introduce io::spawn API

This is a wrapper for posix_spawn that sets up pipes for stdin, out, and error.
This commit is contained in:
Allan Odgaard
2013-03-21 14:14:05 +01:00
parent 2ca999976e
commit 8ce1c48fe7
2 changed files with 89 additions and 48 deletions

View File

@@ -11,92 +11,121 @@ OAK_DEBUG_VAR(IO_Exec);
namespace io
{
// takes NULL-terminated list of arguments
static std::string vexec (std::map<std::string, std::string> const& environment, std::string const& cmd, va_list args)
process_t spawn (std::vector<std::string> const& args, std::map<std::string, std::string> const& environment)
{
std::vector<char*> command(1, (char*)cmd.c_str());
char* arg = NULL;
while((arg = va_arg(args, char*)) && *arg)
command.push_back(arg);
va_end(args);
char* argv[command.size() + 1];
std::copy(command.begin(), command.end(), &argv[0]);
argv[command.size()] = NULL;
oak::c_array env(environment);
int out[2], err[2];
int in[2], out[2], err[2];
posix_spawn_file_actions_t fileActions;
posix_spawnattr_t flags;
pid_t pid = -1;
short closeOnExecFlag = (oak::os_major() == 10 && oak::os_minor() == 7) ? 0 : POSIX_SPAWN_CLOEXEC_DEFAULT;
OAK_CHECK(pipe(&in[0]));
OAK_CHECK(pipe(&out[0]));
OAK_CHECK(pipe(&err[0]));
OAK_CHECK(fcntl(in[1], F_SETFD, FD_CLOEXEC));
OAK_CHECK(fcntl(out[0], F_SETFD, FD_CLOEXEC));
OAK_CHECK(fcntl(err[0], F_SETFD, FD_CLOEXEC));
OAK_CHECK(posix_spawn_file_actions_init(&fileActions));
OAK_CHECK(posix_spawn_file_actions_addclose(&fileActions, 0));
OAK_CHECK(posix_spawn_file_actions_addopen(&fileActions, 0, "/dev/null", O_RDONLY, 0));
OAK_CHECK(posix_spawn_file_actions_adddup2(&fileActions, in[0], 0));
OAK_CHECK(posix_spawn_file_actions_adddup2(&fileActions, out[1], 1));
OAK_CHECK(posix_spawn_file_actions_adddup2(&fileActions, err[1], 2));
OAK_CHECK(posix_spawn_file_actions_addclose(&fileActions, in[0]));
OAK_CHECK(posix_spawn_file_actions_addclose(&fileActions, out[1]));
OAK_CHECK(posix_spawn_file_actions_addclose(&fileActions, err[1]));
OAK_CHECK(posix_spawnattr_init(&flags));
OAK_CHECK(posix_spawnattr_setflags(&flags, POSIX_SPAWN_SETSIGDEF|closeOnExecFlag));
int rc = posix_spawn(&pid, argv[0], &fileActions, &flags, argv, env);
char* argv[args.size() + 1];
std::transform(args.begin(), args.end(), &argv[0], [](std::string const& str){ return (char*)str.c_str(); });
argv[args.size()] = NULL;
process_t res;
int rc = posix_spawn(&res.pid, argv[0], &fileActions, &flags, argv, oak::c_array(environment));
if(rc != 0)
perror(text::format("posix_spawn(\"%s\")", cmd.c_str()).c_str());
perror(text::format("posix_spawn(\"%s\")", argv[0]).c_str());
OAK_CHECK(posix_spawnattr_destroy(&flags));
OAK_CHECK(posix_spawn_file_actions_destroy(&fileActions));
OAK_CHECK(close(in[0]));
OAK_CHECK(close(out[1]));
OAK_CHECK(close(err[1]));
if(rc != 0)
if(rc == 0)
{
close(out[0]);
close(err[0]);
return NULL_STR;
res.in = in[1];
res.out = out[0];
res.err = err[0];
}
else
{
OAK_CHECK(close(in[1]));
OAK_CHECK(close(out[0]));
OAK_CHECK(close(err[0]));
}
std::string output, error;
return res;
}
process_t spawn (std::vector<std::string> const& args)
{
return spawn(args, oak::basic_environment());
}
void exhaust_fd (int fd, std::string* out)
{
char buf[255];
while(ssize_t len = read(out[0], buf, sizeof(buf)))
while(ssize_t len = read(fd, buf, sizeof(buf)))
{
if(len < 0)
break;
output.insert(output.end(), buf, buf + len);
out->insert(out->end(), buf, buf + len);
}
close(out[0]);
close(fd);
}
while(ssize_t len = read(err[0], buf, sizeof(buf)))
{
if(len < 0)
break;
error.insert(error.end(), buf, buf + len);
}
close(err[0]);
// takes NULL-terminated list of arguments
static std::string vexec (std::map<std::string, std::string> const& environment, std::string const& cmd, va_list args)
{
std::vector<std::string> command(1, cmd);
char* arg = NULL;
while((arg = va_arg(args, char*)) && *arg)
command.push_back(arg);
va_end(args);
process_t process = spawn(command, environment);
close(process.in);
__block std::string output, error;
__block bool success = false;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
exhaust_fd(process.out, &output);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
exhaust_fd(process.err, &error);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int status = 0;
if(waitpid(process.pid, &status, 0) != process.pid)
perror("waitpid");
else if(!WIFEXITED(status))
fprintf(stderr, "*** abnormal exit (%d) from %s\n", status, text::join(command, " ").c_str());
else if(WEXITSTATUS(status) != 0)
fprintf(stderr, "*** exit code %d from %s\n", WEXITSTATUS(status), text::join(command, " ").c_str());
else
success = true;
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
D(DBF_IO_Exec, if(!error.empty()) bug("error from command: “%s”\n", error.c_str()););
int status = 0;
bool didTerminate = waitpid(pid, &status, 0) == pid;
if(didTerminate && WIFEXITED(status) && WEXITSTATUS(status) == 0)
return output;
if(!didTerminate)
perror("waitpid");
else if(!WIFEXITED(status))
fprintf(stderr, "*** abnormal exit (%d) from %s\n", status, text::join(command, " ").c_str());
else
fprintf(stderr, "*** exit code %d from %s\n", WEXITSTATUS(status), text::join(command, " ").c_str());
return NULL_STR;
return success ? output : NULL_STR;
}
std::string exec (std::map<std::string, std::string> const& env, std::string const& cmd, ...)

View File

@@ -5,6 +5,18 @@
namespace io
{
struct PUBLIC process_t
{
explicit operator bool () const { return pid != -1; }
pid_t pid = -1;
int in = -1, out = -1, err = -1;
};
PUBLIC process_t spawn (std::vector<std::string> const& args);
PUBLIC process_t spawn (std::vector<std::string> const& args, std::map<std::string, std::string> const& environment);
PUBLIC void exhaust_fd (int fd, std::string* out);
// takes NULL-terminated list of arguments
PUBLIC std::string exec (std::string const& cmd, ...);
PUBLIC std::string exec (std::map<std::string, std::string> const& environment, std::string const& cmd, ...);