mirror of
https://github.com/textmate/textmate.git
synced 2026-01-20 20:27:59 -05:00
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:
@@ -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, ...)
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
Reference in New Issue
Block a user