From 8ce1c48fe79110330c1a3df710bb9000c9fed1fb Mon Sep 17 00:00:00 2001 From: Allan Odgaard Date: Thu, 21 Mar 2013 14:14:05 +0100 Subject: [PATCH] Introduce io::spawn API This is a wrapper for posix_spawn that sets up pipes for stdin, out, and error. --- Frameworks/io/src/exec.cc | 125 +++++++++++++++++++++++--------------- Frameworks/io/src/exec.h | 12 ++++ 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/Frameworks/io/src/exec.cc b/Frameworks/io/src/exec.cc index 8ed7ddfb..45116bfb 100644 --- a/Frameworks/io/src/exec.cc +++ b/Frameworks/io/src/exec.cc @@ -11,92 +11,121 @@ OAK_DEBUG_VAR(IO_Exec); namespace io { - // takes NULL-terminated list of arguments - static std::string vexec (std::map const& environment, std::string const& cmd, va_list args) + process_t spawn (std::vector const& args, std::map const& environment) { - std::vector 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 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 const& environment, std::string const& cmd, va_list args) + { + std::vector 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 const& env, std::string const& cmd, ...) diff --git a/Frameworks/io/src/exec.h b/Frameworks/io/src/exec.h index 613cc047..fc4eaaca 100644 --- a/Frameworks/io/src/exec.h +++ b/Frameworks/io/src/exec.h @@ -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 const& args); + PUBLIC process_t spawn (std::vector const& args, std::map 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 const& environment, std::string const& cmd, ...);