1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 // Author: kenton@google.com (Kenton Varda)
9
10 #include "google/protobuf/compiler/subprocess.h"
11
12 #include <algorithm>
13 #include <cstring>
14 #include <string>
15
16 #ifndef _WIN32
17 #include <errno.h>
18 #include <signal.h>
19 #include <sys/select.h>
20 #include <sys/wait.h>
21 #endif
22
23 #include "absl/log/absl_check.h"
24 #include "absl/log/absl_log.h"
25 #include "absl/strings/escaping.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/substitute.h"
28 #include "google/protobuf/io/io_win32.h"
29 #include "google/protobuf/message.h"
30
31 namespace google {
32 namespace protobuf {
33 namespace compiler {
34
35 #ifdef _WIN32
36
CloseHandleOrDie(HANDLE handle)37 static void CloseHandleOrDie(HANDLE handle) {
38 if (!CloseHandle(handle)) {
39 ABSL_LOG(FATAL) << "CloseHandle: "
40 << Subprocess::Win32ErrorMessage(GetLastError());
41 }
42 }
43
Subprocess()44 Subprocess::Subprocess()
45 : process_start_error_(ERROR_SUCCESS),
46 child_handle_(nullptr),
47 child_stdin_(nullptr),
48 child_stdout_(nullptr) {}
49
~Subprocess()50 Subprocess::~Subprocess() {
51 if (child_stdin_ != nullptr) {
52 CloseHandleOrDie(child_stdin_);
53 }
54 if (child_stdout_ != nullptr) {
55 CloseHandleOrDie(child_stdout_);
56 }
57 }
58
Start(const std::string & program,SearchMode search_mode)59 void Subprocess::Start(const std::string& program, SearchMode search_mode) {
60 // Create the pipes.
61 HANDLE stdin_pipe_read;
62 HANDLE stdin_pipe_write;
63 HANDLE stdout_pipe_read;
64 HANDLE stdout_pipe_write;
65
66 if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, nullptr, 0)) {
67 ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
68 }
69 if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, nullptr, 0)) {
70 ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError());
71 }
72
73 // Make child side of the pipes inheritable.
74 if (!SetHandleInformation(stdin_pipe_read, HANDLE_FLAG_INHERIT,
75 HANDLE_FLAG_INHERIT)) {
76 ABSL_LOG(FATAL) << "SetHandleInformation: "
77 << Win32ErrorMessage(GetLastError());
78 }
79 if (!SetHandleInformation(stdout_pipe_write, HANDLE_FLAG_INHERIT,
80 HANDLE_FLAG_INHERIT)) {
81 ABSL_LOG(FATAL) << "SetHandleInformation: "
82 << Win32ErrorMessage(GetLastError());
83 }
84
85 // Setup STARTUPINFO to redirect handles.
86 STARTUPINFOW startup_info;
87 ZeroMemory(&startup_info, sizeof(startup_info));
88 startup_info.cb = sizeof(startup_info);
89 startup_info.dwFlags = STARTF_USESTDHANDLES;
90 startup_info.hStdInput = stdin_pipe_read;
91 startup_info.hStdOutput = stdout_pipe_write;
92 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
93
94 if (startup_info.hStdError == INVALID_HANDLE_VALUE) {
95 ABSL_LOG(FATAL) << "GetStdHandle: " << Win32ErrorMessage(GetLastError());
96 }
97
98 // get wide string version of program as the path may contain non-ascii characters
99 std::wstring wprogram;
100 if (!io::win32::strings::utf8_to_wcs(program.c_str(), &wprogram)) {
101 ABSL_LOG(FATAL) << "utf8_to_wcs: " << Win32ErrorMessage(GetLastError());
102 }
103
104 // Invoking cmd.exe allows for '.bat' files from the path as well as '.exe'.
105 std::string command_line = absl::StrCat("cmd.exe /c \"", program, "\"");
106
107 // get wide string version of command line as the path may contain non-ascii characters
108 std::wstring wcommand_line;
109 if (!io::win32::strings::utf8_to_wcs(command_line.c_str(), &wcommand_line)) {
110 ABSL_LOG(FATAL) << "utf8_to_wcs: " << Win32ErrorMessage(GetLastError());
111 }
112
113 // Using a malloc'ed string because CreateProcess() can mutate its second
114 // parameter.
115 wchar_t *wcommand_line_copy = _wcsdup(wcommand_line.c_str());
116
117 // Create the process.
118 PROCESS_INFORMATION process_info;
119
120 if (CreateProcessW(
121 (search_mode == SEARCH_PATH) ? nullptr : wprogram.c_str(),
122 (search_mode == SEARCH_PATH) ? wcommand_line_copy : nullptr,
123 nullptr, // process security attributes
124 nullptr, // thread security attributes
125 TRUE, // inherit handles?
126 0, // obscure creation flags
127 nullptr, // environment (inherit from parent)
128 nullptr, // current directory (inherit from parent)
129 &startup_info, &process_info)) {
130 child_handle_ = process_info.hProcess;
131 CloseHandleOrDie(process_info.hThread);
132 child_stdin_ = stdin_pipe_write;
133 child_stdout_ = stdout_pipe_read;
134 } else {
135 process_start_error_ = GetLastError();
136 CloseHandleOrDie(stdin_pipe_write);
137 CloseHandleOrDie(stdout_pipe_read);
138 }
139
140 CloseHandleOrDie(stdin_pipe_read);
141 CloseHandleOrDie(stdout_pipe_write);
142 free(wcommand_line_copy);
143 }
144
Communicate(const Message & input,Message * output,std::string * error)145 bool Subprocess::Communicate(const Message& input, Message* output,
146 std::string* error) {
147 if (process_start_error_ != ERROR_SUCCESS) {
148 *error = Win32ErrorMessage(process_start_error_);
149 return false;
150 }
151
152 ABSL_CHECK(child_handle_ != nullptr) << "Must call Start() first.";
153
154 std::string input_data;
155 if (!input.SerializeToString(&input_data)) {
156 *error = "Failed to serialize request.";
157 return false;
158 }
159 std::string output_data;
160
161 int input_pos = 0;
162
163 while (child_stdout_ != nullptr) {
164 HANDLE handles[2];
165 int handle_count = 0;
166
167 if (child_stdin_ != nullptr) {
168 handles[handle_count++] = child_stdin_;
169 }
170 if (child_stdout_ != nullptr) {
171 handles[handle_count++] = child_stdout_;
172 }
173
174 DWORD wait_result =
175 WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE);
176
177 HANDLE signaled_handle = nullptr;
178 if (wait_result >= WAIT_OBJECT_0 &&
179 wait_result < WAIT_OBJECT_0 + handle_count) {
180 signaled_handle = handles[wait_result - WAIT_OBJECT_0];
181 } else if (wait_result == WAIT_FAILED) {
182 ABSL_LOG(FATAL) << "WaitForMultipleObjects: "
183 << Win32ErrorMessage(GetLastError());
184 } else {
185 ABSL_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: "
186 << wait_result;
187 }
188
189 if (signaled_handle == child_stdin_) {
190 DWORD n;
191 if (!WriteFile(child_stdin_, input_data.data() + input_pos,
192 input_data.size() - input_pos, &n, nullptr)) {
193 // Child closed pipe. Presumably it will report an error later.
194 // Pretend we're done for now.
195 input_pos = input_data.size();
196 } else {
197 input_pos += n;
198 }
199
200 if (input_pos == input_data.size()) {
201 // We're done writing. Close.
202 CloseHandleOrDie(child_stdin_);
203 child_stdin_ = nullptr;
204 }
205 } else if (signaled_handle == child_stdout_) {
206 char buffer[4096];
207 DWORD n;
208
209 if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, nullptr)) {
210 // We're done reading. Close.
211 CloseHandleOrDie(child_stdout_);
212 child_stdout_ = nullptr;
213 } else {
214 output_data.append(buffer, n);
215 }
216 }
217 }
218
219 if (child_stdin_ != nullptr) {
220 // Child did not finish reading input before it closed the output.
221 // Presumably it exited with an error.
222 CloseHandleOrDie(child_stdin_);
223 child_stdin_ = nullptr;
224 }
225
226 DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE);
227
228 if (wait_result == WAIT_FAILED) {
229 ABSL_LOG(FATAL) << "WaitForSingleObject: "
230 << Win32ErrorMessage(GetLastError());
231 } else if (wait_result != WAIT_OBJECT_0) {
232 ABSL_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: "
233 << wait_result;
234 }
235
236 DWORD exit_code;
237 if (!GetExitCodeProcess(child_handle_, &exit_code)) {
238 ABSL_LOG(FATAL) << "GetExitCodeProcess: "
239 << Win32ErrorMessage(GetLastError());
240 }
241
242 CloseHandleOrDie(child_handle_);
243 child_handle_ = nullptr;
244
245 if (exit_code != 0) {
246 *error = absl::Substitute("Plugin failed with status code $0.", exit_code);
247 return false;
248 }
249
250 if (!output->ParseFromString(output_data)) {
251 *error = absl::StrCat("Plugin output is unparseable: ",
252 absl::CEscape(output_data));
253 return false;
254 }
255
256 return true;
257 }
258
Win32ErrorMessage(DWORD error_code)259 std::string Subprocess::Win32ErrorMessage(DWORD error_code) {
260 char* message;
261
262 // WTF?
263 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
264 FORMAT_MESSAGE_IGNORE_INSERTS,
265 nullptr, error_code,
266 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
267 (LPSTR)&message, // NOT A BUG!
268 0, nullptr);
269
270 std::string result = message;
271 LocalFree(message);
272 return result;
273 }
274
275 // ===================================================================
276
277 #else // _WIN32
278
279 Subprocess::Subprocess()
280 : child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {}
281
282 Subprocess::~Subprocess() {
283 if (child_stdin_ != -1) {
284 close(child_stdin_);
285 }
286 if (child_stdout_ != -1) {
287 close(child_stdout_);
288 }
289 }
290
291 namespace {
292 char* portable_strdup(const char* s) {
293 char* ns = (char*)malloc(strlen(s) + 1);
294 if (ns != nullptr) {
295 strcpy(ns, s);
296 }
297 return ns;
298 }
299 } // namespace
300
301 void Subprocess::Start(const std::string& program, SearchMode search_mode) {
302 // Note that we assume that there are no other threads, thus we don't have to
303 // do crazy stuff like using socket pairs or avoiding libc locks.
304
305 // [0] is read end, [1] is write end.
306 int stdin_pipe[2];
307 int stdout_pipe[2];
308
309 ABSL_CHECK(pipe(stdin_pipe) != -1);
310 ABSL_CHECK(pipe(stdout_pipe) != -1);
311
312 char* argv[2] = {portable_strdup(program.c_str()), nullptr};
313
314 child_pid_ = fork();
315 if (child_pid_ == -1) {
316 ABSL_LOG(FATAL) << "fork: " << strerror(errno);
317 } else if (child_pid_ == 0) {
318 // We are the child.
319 dup2(stdin_pipe[0], STDIN_FILENO);
320 dup2(stdout_pipe[1], STDOUT_FILENO);
321
322 close(stdin_pipe[0]);
323 close(stdin_pipe[1]);
324 close(stdout_pipe[0]);
325 close(stdout_pipe[1]);
326
327 switch (search_mode) {
328 case SEARCH_PATH:
329 execvp(argv[0], argv);
330 break;
331 case EXACT_NAME:
332 execv(argv[0], argv);
333 break;
334 }
335
336 // Write directly to STDERR_FILENO to avoid stdio code paths that may do
337 // stuff that is unsafe here.
338 int ignored;
339 ignored = write(STDERR_FILENO, argv[0], strlen(argv[0]));
340 const char* message =
341 ": program not found or is not executable\n"
342 "Please specify a program using absolute path or make sure "
343 "the program is available in your PATH system variable\n";
344 ignored = write(STDERR_FILENO, message, strlen(message));
345 (void)ignored;
346
347 // Must use _exit() rather than exit() to avoid flushing output buffers
348 // that will also be flushed by the parent.
349 _exit(1);
350 } else {
351 free(argv[0]);
352
353 close(stdin_pipe[0]);
354 close(stdout_pipe[1]);
355
356 child_stdin_ = stdin_pipe[1];
357 child_stdout_ = stdout_pipe[0];
358 }
359 }
360
361 bool Subprocess::Communicate(const Message& input, Message* output,
362 std::string* error) {
363 ABSL_CHECK_NE(child_stdin_, -1) << "Must call Start() first.";
364
365 // The "sighandler_t" typedef is GNU-specific, so define our own.
366 typedef void SignalHandler(int);
367
368 // Make sure SIGPIPE is disabled so that if the child dies it doesn't kill us.
369 SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN);
370
371 std::string input_data;
372 if (!input.SerializeToString(&input_data)) {
373 *error = "Failed to serialize request.";
374 return false;
375 }
376 std::string output_data;
377
378 int input_pos = 0;
379 int max_fd = std::max(child_stdin_, child_stdout_);
380
381 while (child_stdout_ != -1) {
382 fd_set read_fds;
383 fd_set write_fds;
384 FD_ZERO(&read_fds);
385 FD_ZERO(&write_fds);
386 if (child_stdout_ != -1) {
387 FD_SET(child_stdout_, &read_fds);
388 }
389 if (child_stdin_ != -1) {
390 FD_SET(child_stdin_, &write_fds);
391 }
392
393 if (select(max_fd + 1, &read_fds, &write_fds, nullptr, nullptr) < 0) {
394 if (errno == EINTR) {
395 // Interrupted by signal. Try again.
396 continue;
397 } else {
398 ABSL_LOG(FATAL) << "select: " << strerror(errno);
399 }
400 }
401
402 if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) {
403 int n = write(child_stdin_, input_data.data() + input_pos,
404 input_data.size() - input_pos);
405 if (n < 0) {
406 // Child closed pipe. Presumably it will report an error later.
407 // Pretend we're done for now.
408 input_pos = input_data.size();
409 } else {
410 input_pos += n;
411 }
412
413 if (input_pos == input_data.size()) {
414 // We're done writing. Close.
415 close(child_stdin_);
416 child_stdin_ = -1;
417 }
418 }
419
420 if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) {
421 char buffer[4096];
422 int n = read(child_stdout_, buffer, sizeof(buffer));
423
424 if (n > 0) {
425 output_data.append(buffer, n);
426 } else {
427 // We're done reading. Close.
428 close(child_stdout_);
429 child_stdout_ = -1;
430 }
431 }
432 }
433
434 if (child_stdin_ != -1) {
435 // Child did not finish reading input before it closed the output.
436 // Presumably it exited with an error.
437 close(child_stdin_);
438 child_stdin_ = -1;
439 }
440
441 int status;
442 while (waitpid(child_pid_, &status, 0) == -1) {
443 if (errno != EINTR) {
444 ABSL_LOG(FATAL) << "waitpid: " << strerror(errno);
445 }
446 }
447
448 // Restore SIGPIPE handling.
449 signal(SIGPIPE, old_pipe_handler);
450
451 if (WIFEXITED(status)) {
452 if (WEXITSTATUS(status) != 0) {
453 int error_code = WEXITSTATUS(status);
454 *error =
455 absl::Substitute("Plugin failed with status code $0.", error_code);
456 return false;
457 }
458 } else if (WIFSIGNALED(status)) {
459 int signal = WTERMSIG(status);
460 *error = absl::Substitute("Plugin killed by signal $0.", signal);
461 return false;
462 } else {
463 *error = "Neither WEXITSTATUS nor WTERMSIG is true?";
464 return false;
465 }
466
467 if (!output->ParseFromString(output_data)) {
468 *error = absl::StrCat("Plugin output is unparseable: ",
469 absl::CEscape(output_data));
470 return false;
471 }
472
473 return true;
474 }
475
476 #endif // !_WIN32
477
478 } // namespace compiler
479 } // namespace protobuf
480 } // namespace google
481