Use posix_spawn instead of fork/exec

Using Apple’s POSIX_SPAWN_CLOEXEC_DEFAULT appears to be the only safe way to spawn a child process in a multi-threaded program.
This commit is contained in:
Allan Odgaard
2013-03-10 16:04:47 +01:00
parent f69ac60b0c
commit 24f6f3bd2c
3 changed files with 61 additions and 81 deletions

View File

@@ -6,14 +6,13 @@
OAK_DEBUG_VAR(IO_Exec);
#define OAK_CHECK(expr) do { if((expr) != 0) { perror(#expr); abort(); } } while(false)
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)
{
std::string output;
std::string error;
std::vector<char*> command(1, (char*)cmd.c_str());
char* arg = NULL;
while((arg = va_arg(args, char*)) && *arg)
@@ -24,73 +23,61 @@ namespace io
std::copy(command.begin(), command.end(), &argv[0]);
argv[command.size()] = NULL;
int outputPipe[2], errorPipe[2];
pipe(outputPipe);
pipe(errorPipe);
fcntl(outputPipe[0], F_SETFD, FD_CLOEXEC);
fcntl(errorPipe[0], F_SETFD, FD_CLOEXEC);
int output_fd = outputPipe[0], error_fd = errorPipe[0];
std::map<std::string, std::string>::const_iterator it = environment.find("PWD");
char const* pwdDir = it != environment.end() ? it->second.c_str() : NULL;
oak::c_array env(environment);
pid_t process_id = vfork();
if(process_id == 0)
{
setpgid(0, getpid());
int const oldOutErr[] = { 0, 1, 2 };
for(int fd : oldOutErr) close(fd);
int out[2], err[2];
posix_spawn_file_actions_t fileActions;
posix_spawnattr_t flags;
pid_t pid = -1;
open("/dev/null", O_RDONLY); // stdin
dup(outputPipe[1]);
close(outputPipe[1]);
dup(errorPipe[1]);
close(errorPipe[1]);
OAK_CHECK(pipe(&out[0]));
OAK_CHECK(pipe(&err[0]));
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, out[1], 1));
OAK_CHECK(posix_spawn_file_actions_adddup2(&fileActions, err[1], 2));
OAK_CHECK(posix_spawnattr_init(&flags));
OAK_CHECK(posix_spawnattr_setflags(&flags, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_CLOEXEC_DEFAULT));
OAK_CHECK(posix_spawnattr_setpgroup(&flags, getpid()));
OAK_CHECK(posix_spawn(&pid, argv[0], &fileActions, &flags, argv, env));
OAK_CHECK(posix_spawnattr_destroy(&flags));
OAK_CHECK(posix_spawn_file_actions_destroy(&fileActions));
OAK_CHECK(close(out[1]));
OAK_CHECK(close(err[1]));
if(pwdDir)
chdir(pwdDir);
execve(argv[0], argv, env);
perror("interpreter failed");
_exit(0);
}
close(outputPipe[1]);
close(errorPipe[1]);
std::string output, error;
char buf[255];
while(ssize_t len = read(output_fd, buf, sizeof(buf)))
while(ssize_t len = read(out[0], buf, sizeof(buf)))
{
if(len < 0)
break;
output.insert(output.end(), buf, buf + len);
}
close(output_fd);
close(out[0]);
char error_buf[255];
while(ssize_t len = read(error_fd, error_buf, sizeof(error_buf)))
while(ssize_t len = read(err[0], buf, sizeof(buf)))
{
if(len < 0)
break;
error.insert(error.end(), error_buf, error_buf + len);
error.insert(error.end(), buf, buf + len);
}
close(error_fd);
close(err[0]);
D(DBF_IO_Exec, if(!error.empty()) bug("error from command: “%s”\n", error.c_str()););
int status = 0;
bool didTerminate = waitpid(process_id, &status, 0) == process_id;
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 in %s\n", status, text::join(command, " ").c_str(), pwdDir);
fprintf(stderr, "*** abnormal exit (%d) from %s\n", status, text::join(command, " ").c_str());
else
fprintf(stderr, "*** exit code %d from %s in %s\n", WEXITSTATUS(status), text::join(command, " ").c_str(), pwdDir);
fprintf(stderr, "*** exit code %d from %s\n", WEXITSTATUS(status), text::join(command, " ").c_str());
return NULL_STR;
}
@@ -104,11 +91,9 @@ namespace io
std::string exec (std::string const& cmd, ...)
{
std::map<std::string, std::string> env = oak::basic_environment();
va_list args;
va_start(args, cmd);
return vexec(env, cmd, args);
return vexec(oak::basic_environment(), cmd, args);
}
} /* io */

View File

@@ -10,44 +10,38 @@ namespace network
{
signal(SIGPIPE, SIG_IGN);
int in[2], out[2];
pipe(&in[0]);
pipe(&out[0]);
fcntl(in[1], F_SETFD, FD_CLOEXEC);
fcntl(out[0], F_SETFD, FD_CLOEXEC);
char const* argv[] = { "/usr/bin/tar", "-jxmkC", dest.c_str(), "--strip-components", "1", NULL };
char const* const argv[] = { "/usr/bin/tar", "-jxmkC", dest.c_str(), "--strip-components", "1", NULL };
oak::c_array env(oak::basic_environment());
pid_t pid = vfork();
if(pid == 0)
int in[2], out[2];
posix_spawn_file_actions_t fileActions;
posix_spawnattr_t flags;
pid_t pid = -1;
bool ok = true;
ok = ok && pipe(&in[0]) == 0;
ok = ok && pipe(&out[0]) == 0;
ok = ok && posix_spawn_file_actions_init(&fileActions) == 0;
ok = ok && posix_spawn_file_actions_adddup2(&fileActions, in[0], 0) == 0;
ok = ok && posix_spawn_file_actions_adddup2(&fileActions, out[1], 1) == 0;
ok = ok && posix_spawn_file_actions_adddup2(&fileActions, out[1], 2) == 0;
ok = ok && posix_spawnattr_init(&flags) == 0;
ok = ok && posix_spawnattr_setflags(&flags, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_CLOEXEC_DEFAULT) == 0;
ok = ok && posix_spawn(&pid, "/usr/bin/tar", &fileActions, &flags, (char* const*)argv, env) == 0;
ok = ok && posix_spawnattr_destroy(&flags) == 0;
ok = ok && posix_spawn_file_actions_destroy(&fileActions) == 0;
ok = ok && close(in[0]) == 0;
ok = ok && close(out[1]) == 0;
if(!ok)
{
close(0); close(1); close(2);
dup(in[0]); dup(out[1]); dup(out[1]);
close(in[0]); close(out[1]);
signal(SIGPIPE, SIG_DFL);
execve(argv[0], (char* const*)argv, env);
_exit(-1);
perror("launch_tbz");
return -1;
}
else
{
close(in[0]);
close(out[1]);
if(pid == -1)
{
close(in[1]);
close(out[0]);
input = in[1];
output = out[0];
error = text::format("Error launching tar: %s", strerror(errno));
}
else
{
input = in[1];
output = out[0];
}
}
return pid;
}
@@ -68,7 +62,7 @@ namespace network
if(WEXITSTATUS(status) == 0 && tbzOut.empty())
return true;
error = "Extracting archive.";
fprintf(stderr, "TextMate: Unexpected exit code from tar %d: %s\n", WEXITSTATUS(status), text::trim(tbzOut).c_str());
// fprintf(stderr, "%s: unexpected exit code from tar %d: %s\n", getprogname(), WEXITSTATUS(status), text::trim(tbzOut).c_str());
}
else
{