This source file includes following definitions.
- CloseHandleOrDie
- child_stdout_
- Start
- Communicate
- Win32ErrorMessage
- child_stdout_
- Start
- Communicate
#include <google/protobuf/compiler/subprocess.h>
#include <algorithm>
#include <iostream>
#ifndef _WIN32
#include <errno.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <signal.h>
#endif
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/message.h>
#include <google/protobuf/stubs/substitute.h>
namespace google {
namespace protobuf {
namespace compiler {
#ifdef _WIN32
static void CloseHandleOrDie(HANDLE handle) {
if (!CloseHandle(handle)) {
GOOGLE_LOG(FATAL) << "CloseHandle: "
<< Subprocess::Win32ErrorMessage(GetLastError());
}
}
Subprocess::Subprocess()
: process_start_error_(ERROR_SUCCESS),
child_handle_(NULL), child_stdin_(NULL), child_stdout_(NULL) {}
Subprocess::~Subprocess() {
if (child_stdin_ != NULL) {
CloseHandleOrDie(child_stdin_);
}
if (child_stdout_ != NULL) {
CloseHandleOrDie(child_stdout_);
}
}
void Subprocess::Start(const string& program, SearchMode search_mode) {
HANDLE stdin_pipe_read;
HANDLE stdin_pipe_write;
HANDLE stdout_pipe_read;
HANDLE stdout_pipe_write;
if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, NULL, 0)) {
GOOGLE_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
}
if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, NULL, 0)) {
GOOGLE_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
}
if (!SetHandleInformation(stdin_pipe_read,
HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
GOOGLE_LOG(FATAL) << "SetHandleInformation: "
<< Win32ErrorMessage(GetLastError());
}
if (!SetHandleInformation(stdout_pipe_write,
HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
GOOGLE_LOG(FATAL) << "SetHandleInformation: "
<< Win32ErrorMessage(GetLastError());
}
STARTUPINFOA startup_info;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.cb = sizeof(startup_info);
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdInput = stdin_pipe_read;
startup_info.hStdOutput = stdout_pipe_write;
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if (startup_info.hStdError == INVALID_HANDLE_VALUE) {
GOOGLE_LOG(FATAL) << "GetStdHandle: "
<< Win32ErrorMessage(GetLastError());
}
char* name_copy = strdup(program.c_str());
PROCESS_INFORMATION process_info;
if (CreateProcessA((search_mode == SEARCH_PATH) ? NULL : program.c_str(),
(search_mode == SEARCH_PATH) ? name_copy : NULL,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startup_info,
&process_info)) {
child_handle_ = process_info.hProcess;
CloseHandleOrDie(process_info.hThread);
child_stdin_ = stdin_pipe_write;
child_stdout_ = stdout_pipe_read;
} else {
process_start_error_ = GetLastError();
CloseHandleOrDie(stdin_pipe_write);
CloseHandleOrDie(stdout_pipe_read);
}
CloseHandleOrDie(stdin_pipe_read);
CloseHandleOrDie(stdout_pipe_write);
free(name_copy);
}
bool Subprocess::Communicate(const Message& input, Message* output,
string* error) {
if (process_start_error_ != ERROR_SUCCESS) {
*error = Win32ErrorMessage(process_start_error_);
return false;
}
GOOGLE_CHECK(child_handle_ != NULL) << "Must call Start() first.";
string input_data = input.SerializeAsString();
string output_data;
int input_pos = 0;
while (child_stdout_ != NULL) {
HANDLE handles[2];
int handle_count = 0;
if (child_stdin_ != NULL) {
handles[handle_count++] = child_stdin_;
}
if (child_stdout_ != NULL) {
handles[handle_count++] = child_stdout_;
}
DWORD wait_result =
WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
HANDLE signaled_handle;
if (wait_result >= WAIT_OBJECT_0 &&
wait_result < WAIT_OBJECT_0 + handle_count) {
signaled_handle = handles[wait_result - WAIT_OBJECT_0];
} else if (wait_result == WAIT_FAILED) {
GOOGLE_LOG(FATAL) << "WaitForMultipleObjects: "
<< Win32ErrorMessage(GetLastError());
} else {
GOOGLE_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: "
<< wait_result;
}
if (signaled_handle == child_stdin_) {
DWORD n;
if (!WriteFile(child_stdin_,
input_data.data() + input_pos,
input_data.size() - input_pos,
&n, NULL)) {
input_pos = input_data.size();
} else {
input_pos += n;
}
if (input_pos == input_data.size()) {
CloseHandleOrDie(child_stdin_);
child_stdin_ = NULL;
}
} else if (signaled_handle == child_stdout_) {
char buffer[4096];
DWORD n;
if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, NULL)) {
CloseHandleOrDie(child_stdout_);
child_stdout_ = NULL;
} else {
output_data.append(buffer, n);
}
}
}
if (child_stdin_ != NULL) {
CloseHandleOrDie(child_stdin_);
child_stdin_ = NULL;
}
DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE);
if (wait_result == WAIT_FAILED) {
GOOGLE_LOG(FATAL) << "WaitForSingleObject: "
<< Win32ErrorMessage(GetLastError());
} else if (wait_result != WAIT_OBJECT_0) {
GOOGLE_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: "
<< wait_result;
}
DWORD exit_code;
if (!GetExitCodeProcess(child_handle_, &exit_code)) {
GOOGLE_LOG(FATAL) << "GetExitCodeProcess: "
<< Win32ErrorMessage(GetLastError());
}
CloseHandleOrDie(child_handle_);
child_handle_ = NULL;
if (exit_code != 0) {
*error = strings::Substitute(
"Plugin failed with status code $0.", exit_code);
return false;
}
if (!output->ParseFromString(output_data)) {
*error = "Plugin output is unparseable: " + CEscape(output_data);
return false;
}
return true;
}
string Subprocess::Win32ErrorMessage(DWORD error_code) {
char* message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error_code, 0,
(LPTSTR)&message,
0, NULL);
string result = message;
LocalFree(message);
return result;
}
#else
Subprocess::Subprocess()
: child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {}
Subprocess::~Subprocess() {
if (child_stdin_ != -1) {
close(child_stdin_);
}
if (child_stdout_ != -1) {
close(child_stdout_);
}
}
void Subprocess::Start(const string& program, SearchMode search_mode) {
int stdin_pipe[2];
int stdout_pipe[2];
GOOGLE_CHECK(pipe(stdin_pipe) != -1);
GOOGLE_CHECK(pipe(stdout_pipe) != -1);
char* argv[2] = { strdup(program.c_str()), NULL };
child_pid_ = fork();
if (child_pid_ == -1) {
GOOGLE_LOG(FATAL) << "fork: " << strerror(errno);
} else if (child_pid_ == 0) {
dup2(stdin_pipe[0], STDIN_FILENO);
dup2(stdout_pipe[1], STDOUT_FILENO);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
switch (search_mode) {
case SEARCH_PATH:
execvp(argv[0], argv);
break;
case EXACT_NAME:
execv(argv[0], argv);
break;
}
int ignored;
ignored = write(STDERR_FILENO, argv[0], strlen(argv[0]));
const char* message = ": program not found or is not executable\n";
ignored = write(STDERR_FILENO, message, strlen(message));
(void) ignored;
_exit(1);
} else {
free(argv[0]);
close(stdin_pipe[0]);
close(stdout_pipe[1]);
child_stdin_ = stdin_pipe[1];
child_stdout_ = stdout_pipe[0];
}
}
bool Subprocess::Communicate(const Message& input, Message* output,
string* error) {
GOOGLE_CHECK_NE(child_stdin_, -1) << "Must call Start() first.";
typedef void SignalHandler(int);
SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN);
string input_data = input.SerializeAsString();
string output_data;
int input_pos = 0;
int max_fd = max(child_stdin_, child_stdout_);
while (child_stdout_ != -1) {
fd_set read_fds;
fd_set write_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
if (child_stdout_ != -1) {
FD_SET(child_stdout_, &read_fds);
}
if (child_stdin_ != -1) {
FD_SET(child_stdin_, &write_fds);
}
if (select(max_fd + 1, &read_fds, &write_fds, NULL, NULL) < 0) {
if (errno == EINTR) {
continue;
} else {
GOOGLE_LOG(FATAL) << "select: " << strerror(errno);
}
}
if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) {
int n = write(child_stdin_, input_data.data() + input_pos,
input_data.size() - input_pos);
if (n < 0) {
input_pos = input_data.size();
} else {
input_pos += n;
}
if (input_pos == input_data.size()) {
close(child_stdin_);
child_stdin_ = -1;
}
}
if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) {
char buffer[4096];
int n = read(child_stdout_, buffer, sizeof(buffer));
if (n > 0) {
output_data.append(buffer, n);
} else {
close(child_stdout_);
child_stdout_ = -1;
}
}
}
if (child_stdin_ != -1) {
close(child_stdin_);
child_stdin_ = -1;
}
int status;
while (waitpid(child_pid_, &status, 0) == -1) {
if (errno != EINTR) {
GOOGLE_LOG(FATAL) << "waitpid: " << strerror(errno);
}
}
signal(SIGPIPE, old_pipe_handler);
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
int error_code = WEXITSTATUS(status);
*error = strings::Substitute(
"Plugin failed with status code $0.", error_code);
return false;
}
} else if (WIFSIGNALED(status)) {
int signal = WTERMSIG(status);
*error = strings::Substitute(
"Plugin killed by signal $0.", signal);
return false;
} else {
*error = "Neither WEXITSTATUS nor WTERMSIG is true?";
return false;
}
if (!output->ParseFromString(output_data)) {
*error = "Plugin output is unparseable.";
return false;
}
return true;
}
#endif
}
}
}