• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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