| // Copyright 2019 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | #ifndef WIN32_LEAN_AND_MEAN | 
 | #define WIN32_LEAN_AND_MEAN | 
 | #endif | 
 |  | 
 | #include "src/main/native/windows/process.h" | 
 |  | 
 | #include <windows.h> | 
 | #include <VersionHelpers.h> | 
 |  | 
 | #include <memory> | 
 | #include <sstream> | 
 |  | 
 | namespace bazel { | 
 | namespace windows { | 
 |  | 
 | template <typename T> | 
 | static std::wstring ToString(const T& e) { | 
 |   std::wstringstream s; | 
 |   s << e; | 
 |   return s.str(); | 
 | } | 
 |  | 
 | static bool DupeHandle(HANDLE h, AutoHandle* out, std::wstring* error) { | 
 |   HANDLE dup; | 
 |   if (!DuplicateHandle(GetCurrentProcess(), h, GetCurrentProcess(), &dup, 0, | 
 |                        TRUE, DUPLICATE_SAME_ACCESS)) { | 
 |     DWORD err = GetLastError(); | 
 |     *error = | 
 |         MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DupeHandle", L"", err); | 
 |     return false; | 
 |   } | 
 |   *out = dup; | 
 |   return true; | 
 | } | 
 |  | 
 | bool WaitableProcess::Create(const std::wstring& argv0, | 
 |                              const std::wstring& argv_rest, void* env, | 
 |                              const std::wstring& wcwd, std::wstring* error) { | 
 |   // On Windows 7, the subprocess cannot inherit console handles via the | 
 |   // attribute list (CreateProcessW fails with ERROR_NO_SYSTEM_RESOURCES). | 
 |   // If we pass only invalid handles here, the Create method creates an | 
 |   // AutoAttributeList with 0 handles and initializes the STARTUPINFOEXW as | 
 |   // empty and disallowing handle inheritance. At the same time, CreateProcessW | 
 |   // specifies neither CREATE_NEW_CONSOLE nor DETACHED_PROCESS, so the | 
 |   // subprocess *does* inherit the console, which is exactly what we want, and | 
 |   // it works on all supported Windows versions. | 
 |   // Fixes https://github.com/bazelbuild/bazel/issues/8676 | 
 |   return Create(argv0, argv_rest, env, wcwd, INVALID_HANDLE_VALUE, | 
 |                 INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, nullptr, true, true, | 
 |                 error); | 
 | } | 
 |  | 
 | bool WaitableProcess::Create(const std::wstring& argv0, | 
 |                              const std::wstring& argv_rest, void* env, | 
 |                              const std::wstring& wcwd, HANDLE stdin_process, | 
 |                              HANDLE stdout_process, HANDLE stderr_process, | 
 |                              LARGE_INTEGER* opt_out_start_time, | 
 |                              std::wstring* error) { | 
 |   return Create(argv0, argv_rest, env, wcwd, stdin_process, stdout_process, | 
 |                 stderr_process, opt_out_start_time, false, false, error); | 
 | } | 
 |  | 
 | bool WaitableProcess::Create(const std::wstring& argv0, | 
 |                              const std::wstring& argv_rest, void* env, | 
 |                              const std::wstring& wcwd, HANDLE stdin_process, | 
 |                              HANDLE stdout_process, HANDLE stderr_process, | 
 |                              LARGE_INTEGER* opt_out_start_time, | 
 |                              bool create_window, bool handle_signals, | 
 |                              std::wstring* error) { | 
 |   std::wstring cwd; | 
 |   std::wstring error_msg(AsShortPath(wcwd, &cwd)); | 
 |   if (!error_msg.empty()) { | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, error_msg); | 
 |     return false; | 
 |   } | 
 |  | 
 |   std::wstring argv0short; | 
 |   error_msg = AsExecutablePathForCreateProcess(argv0, &argv0short); | 
 |   if (!error_msg.empty()) { | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, error_msg); | 
 |     return false; | 
 |   } | 
 |  | 
 |   std::wstring commandline = | 
 |       argv_rest.empty() ? argv0short : (argv0short + L" " + argv_rest); | 
 |   std::unique_ptr<WCHAR[]> mutable_commandline( | 
 |       new WCHAR[commandline.size() + 1]); | 
 |   wcsncpy(mutable_commandline.get(), commandline.c_str(), | 
 |           commandline.size() + 1); | 
 |   // MDSN says that the default for job objects is that breakaway is not | 
 |   // allowed. Thus, we don't need to do any more setup here. | 
 |   job_ = CreateJobObject(nullptr, nullptr); | 
 |   if (!job_.IsValid()) { | 
 |     DWORD err_code = GetLastError(); | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, err_code); | 
 |     return false; | 
 |   } | 
 |  | 
 |   JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0}; | 
 |   job_info.BasicLimitInformation.LimitFlags = | 
 |       JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; | 
 |   if (!SetInformationJobObject(job_, JobObjectExtendedLimitInformation, | 
 |                                &job_info, sizeof(job_info))) { | 
 |     DWORD err_code = GetLastError(); | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, err_code); | 
 |     return false; | 
 |   } | 
 |  | 
 |   ioport_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1); | 
 |   if (!ioport_.IsValid()) { | 
 |     DWORD err_code = GetLastError(); | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, err_code); | 
 |     return false; | 
 |   } | 
 |   JOBOBJECT_ASSOCIATE_COMPLETION_PORT port; | 
 |   port.CompletionKey = job_; | 
 |   port.CompletionPort = ioport_; | 
 |   if (!SetInformationJobObject(job_, | 
 |                                JobObjectAssociateCompletionPortInformation, | 
 |                                &port, sizeof(port))) { | 
 |     DWORD err_code = GetLastError(); | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, err_code); | 
 |     return false; | 
 |   } | 
 |  | 
 |   std::unique_ptr<AutoAttributeList> attr_list; | 
 |   if (!AutoAttributeList::Create(stdin_process, stdout_process, stderr_process, | 
 |                                  &attr_list, &error_msg)) { | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", L"", error_msg); | 
 |     return false; | 
 |   } | 
 |  | 
 |   // kMaxCmdline value: see lpCommandLine parameter of CreateProcessW. | 
 |   static constexpr size_t kMaxCmdline = 32767; | 
 |  | 
 |   std::wstring cmd_sample = mutable_commandline.get(); | 
 |   if (cmd_sample.size() > 500) { | 
 |     cmd_sample = cmd_sample.substr(0, 495) + L"(...)"; | 
 |   } | 
 |   if (wcsnlen_s(mutable_commandline.get(), kMaxCmdline) == kMaxCmdline) { | 
 |     std::wstringstream error_msg; | 
 |     error_msg << L"command is longer than CreateProcessW's limit (" | 
 |               << kMaxCmdline << L" characters)"; | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"CreateProcessWithExplicitHandles", cmd_sample, | 
 |                               error_msg.str().c_str()); | 
 |     return false; | 
 |   } | 
 |  | 
 |   PROCESS_INFORMATION process_info = {0}; | 
 |   STARTUPINFOEXW info; | 
 |   attr_list->InitStartupInfoExW(&info); | 
 |   if (!CreateProcessW( | 
 |           /* lpApplicationName */ nullptr, | 
 |           /* lpCommandLine */ mutable_commandline.get(), | 
 |           /* lpProcessAttributes */ nullptr, | 
 |           /* lpThreadAttributes */ nullptr, | 
 |           /* bInheritHandles */ attr_list->InheritAnyHandles() ? TRUE : FALSE, | 
 |           /* dwCreationFlags */ (create_window ? 0 : CREATE_NO_WINDOW) | | 
 |               (handle_signals ? 0 | 
 |                               : CREATE_NEW_PROCESS_GROUP)  // So that Ctrl-Break | 
 |                                                            // isn't propagated | 
 |               | CREATE_SUSPENDED  // So that it doesn't start a new job itself | 
 |               | EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, | 
 |           /* lpEnvironment */ env, | 
 |           /* lpCurrentDirectory */ cwd.empty() ? nullptr : cwd.c_str(), | 
 |           /* lpStartupInfo */ &info.StartupInfo, | 
 |           /* lpProcessInformation */ &process_info)) { | 
 |     DWORD err = GetLastError(); | 
 |  | 
 |     std::wstring errmsg; | 
 |     if (err == ERROR_NO_SYSTEM_RESOURCES && !IsWindows8OrGreater() && | 
 |         attr_list->HasConsoleHandle()) { | 
 |       errmsg = | 
 |           L"Unrecoverable error: host OS is Windows 7 and subprocess" | 
 |           " got an inheritable console handle"; | 
 |     } else { | 
 |       errmsg = GetLastErrorString(err) + L" (error: " + ToString(err) + L")"; | 
 |     } | 
 |  | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateProcessW", | 
 |                               cmd_sample, errmsg); | 
 |     return false; | 
 |   } | 
 |  | 
 |   pid_ = process_info.dwProcessId; | 
 |   process_ = process_info.hProcess; | 
 |   AutoHandle thread(process_info.hThread); | 
 |  | 
 |   if (!AssignProcessToJobObject(job_, process_)) { | 
 |     BOOL is_in_job = false; | 
 |     if (IsProcessInJob(process_, nullptr, &is_in_job) && is_in_job && | 
 |         !IsWindows8OrGreater()) { | 
 |       // Pre-Windows 8 systems don't support nested jobs, and Bazel is already | 
 |       // in a job.  We can't create nested jobs, so just revert to | 
 |       // TerminateProcess() and hope for the best. In batch mode, the launcher | 
 |       // puts Bazel in a job so that will take care of cleanup once the | 
 |       // command finishes. | 
 |       job_ = INVALID_HANDLE_VALUE; | 
 |       ioport_ = INVALID_HANDLE_VALUE; | 
 |     } else { | 
 |       DWORD err_code = GetLastError(); | 
 |       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                 L"WaitableProcess::Create", argv0, err_code); | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   // Now that we put the process in a new job object, we can start executing | 
 |   // it | 
 |   if (ResumeThread(thread) == -1) { | 
 |     DWORD err_code = GetLastError(); | 
 |     *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                               L"WaitableProcess::Create", argv0, err_code); | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (opt_out_start_time) { | 
 |     QueryPerformanceCounter(opt_out_start_time); | 
 |   } | 
 |   *error = L""; | 
 |   return true; | 
 | } | 
 |  | 
 | int WaitableProcess::WaitFor(int64_t timeout_msec, | 
 |                              LARGE_INTEGER* opt_out_end_time, | 
 |                              std::wstring* error) { | 
 |   struct Defer { | 
 |     LARGE_INTEGER* t; | 
 |     Defer(LARGE_INTEGER* cnt) : t(cnt) {} | 
 |     ~Defer() { | 
 |       if (t) { | 
 |         QueryPerformanceCounter(t); | 
 |       } | 
 |     } | 
 |   } defer_query_end_time(opt_out_end_time); | 
 |  | 
 |   DWORD win32_timeout = timeout_msec < 0 ? INFINITE : timeout_msec; | 
 |   int result; | 
 |   switch (WaitForSingleObject(process_, win32_timeout)) { | 
 |     case WAIT_OBJECT_0: | 
 |       result = kWaitSuccess; | 
 |       break; | 
 |  | 
 |     case WAIT_TIMEOUT: | 
 |       result = kWaitTimeout; | 
 |       break; | 
 |  | 
 |     // Any other case is an error and should be reported back to Bazel. | 
 |     default: | 
 |       DWORD err_code = GetLastError(); | 
 |       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                 L"WaitableProcess::WaitFor", ToString(pid_), | 
 |                                 err_code); | 
 |       return kWaitError; | 
 |   } | 
 |  | 
 |   // Ensure that the process is really terminated (if WaitForSingleObject | 
 |   // above timed out, we have to explicitly kill it) and that it doesn't | 
 |   // leave behind any subprocesses. | 
 |   if (!Terminate(error)) { | 
 |     return kWaitError; | 
 |   } | 
 |  | 
 |   if (job_.IsValid()) { | 
 |     // Wait for the job object to complete, signalling that all subprocesses | 
 |     // have exited. | 
 |     DWORD CompletionCode; | 
 |     ULONG_PTR CompletionKey; | 
 |     LPOVERLAPPED Overlapped; | 
 |     while (GetQueuedCompletionStatus(ioport_, &CompletionCode, | 
 |                                      &CompletionKey, &Overlapped, INFINITE) && | 
 |            !((HANDLE)CompletionKey == (HANDLE)job_ && | 
 |              CompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)) { | 
 |       // Still waiting... | 
 |     } | 
 |  | 
 |     job_ = INVALID_HANDLE_VALUE; | 
 |     ioport_ = INVALID_HANDLE_VALUE; | 
 |   } | 
 |  | 
 |   // Fetch and store the exit code in case Bazel asks us for it later, | 
 |   // because we cannot do this anymore after we closed the handle. | 
 |   GetExitCode(error); | 
 |  | 
 |   if (process_.IsValid()) { | 
 |     process_ = INVALID_HANDLE_VALUE; | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | int WaitableProcess::GetExitCode(std::wstring* error) { | 
 |   if (exit_code_ == STILL_ACTIVE) { | 
 |     if (!GetExitCodeProcess(process_, &exit_code_)) { | 
 |       DWORD err_code = GetLastError(); | 
 |       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                 L"WaitableProcess::GetExitCode", ToString(pid_), | 
 |                                 err_code); | 
 |       return -1; | 
 |     } | 
 |   } | 
 |  | 
 |   return exit_code_; | 
 | } | 
 |  | 
 | bool WaitableProcess::Terminate(std::wstring* error) { | 
 |   static constexpr UINT exit_code = 130;  // 128 + SIGINT, like on Linux | 
 |  | 
 |   if (job_.IsValid()) { | 
 |     if (!TerminateJobObject(job_, exit_code)) { | 
 |       DWORD err_code = GetLastError(); | 
 |       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                 L"WaitableProcess::Terminate", ToString(pid_), | 
 |                                 err_code); | 
 |       return false; | 
 |     } | 
 |   } else if (process_.IsValid()) { | 
 |     if (!TerminateProcess(process_, exit_code)) { | 
 |       DWORD err_code = GetLastError(); | 
 |       std::wstring our_error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                                 L"WaitableProcess::Terminate", | 
 |                                                 ToString(pid_), err_code); | 
 |  | 
 |       // If the process exited, despite TerminateProcess having failed, we're | 
 |       // still happy and just ignore the error. It might have been a race | 
 |       // where the process exited by itself just before we tried to kill it. | 
 |       // However, if the process is *still* running at this point (evidenced | 
 |       // by its exit code still being STILL_ACTIVE) then something went | 
 |       // really unexpectedly wrong and we should report that error. | 
 |       if (GetExitCode(error) == STILL_ACTIVE) { | 
 |         // Restore the error message from TerminateProcess - it will be much | 
 |         // more helpful for debugging in case something goes wrong here. | 
 |         *error = our_error; | 
 |         return false; | 
 |       } | 
 |     } | 
 |  | 
 |     if (WaitForSingleObject(process_, INFINITE) != WAIT_OBJECT_0) { | 
 |       DWORD err_code = GetLastError(); | 
 |       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
 |                                 L"WaitableProcess::Terminate", ToString(pid_), | 
 |                                 err_code); | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   *error = L""; | 
 |   return true; | 
 | } | 
 |  | 
 | // Escape arguments for CreateProcessW. | 
 | // | 
 | // This algorithm is based on information found in | 
 | // http://daviddeley.com/autohotkey/parameters/parameters.htm | 
 | // | 
 | // The following source specifies a similar algorithm: | 
 | // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ | 
 | // unfortunately I found this algorithm only after creating the one below, but | 
 | // fortunately they seem to do the same. | 
 | std::wstring WindowsEscapeArg(const std::wstring& s) { | 
 |   if (s.empty()) { | 
 |     return L"\"\""; | 
 |   } else { | 
 |     bool needs_escaping = false; | 
 |     for (const auto& c : s) { | 
 |       if (c == ' ' || c == '"') { | 
 |         needs_escaping = true; | 
 |         break; | 
 |       } | 
 |     } | 
 |     if (!needs_escaping) { | 
 |       return s; | 
 |     } | 
 |   } | 
 |  | 
 |   std::wostringstream result; | 
 |   result << L'"'; | 
 |   int start = 0; | 
 |   for (int i = 0; i < s.size(); ++i) { | 
 |     char c = s[i]; | 
 |     if (c == '"' || c == '\\') { | 
 |       // Copy the segment since the last special character. | 
 |       if (start >= 0) { | 
 |         result << s.substr(start, i - start); | 
 |         start = -1; | 
 |       } | 
 |  | 
 |       // Handle the current special character. | 
 |       if (c == '"') { | 
 |         // This is a quote character. Escape it with a single backslash. | 
 |         result << L"\\\""; | 
 |       } else { | 
 |         // This is a backslash (or the first one in a run of backslashes). | 
 |         // Whether we escape it depends on whether the run ends with a quote. | 
 |         int run_len = 1; | 
 |         int j = i + 1; | 
 |         while (j < s.size() && s[j] == '\\') { | 
 |           run_len++; | 
 |           j++; | 
 |         } | 
 |         if (j == s.size()) { | 
 |           // The run of backslashes goes to the end. | 
 |           // We have to escape every backslash with another backslash. | 
 |           for (int k = 0; k < run_len * 2; ++k) { | 
 |             result << L'\\'; | 
 |           } | 
 |           break; | 
 |         } else if (j < s.size() && s[j] == '"') { | 
 |           // The run of backslashes is terminated by a quote. | 
 |           // We have to escape every backslash with another backslash, and | 
 |           // escape the quote with one backslash. | 
 |           for (int k = 0; k < run_len * 2; ++k) { | 
 |             result << L'\\'; | 
 |           } | 
 |           result << L"\\\""; | 
 |           i += run_len;  // 'i' is also increased in the loop iteration step | 
 |         } else { | 
 |           // No quote found. Each backslash counts for itself, they must not be | 
 |           // escaped. | 
 |           for (int k = 0; k < run_len; ++k) { | 
 |             result << L'\\'; | 
 |           } | 
 |           i += run_len - 1;  // 'i' is also increased in the loop iteration step | 
 |         } | 
 |       } | 
 |     } else { | 
 |       // This is not a special character. Start the segment if necessary. | 
 |       if (start < 0) { | 
 |         start = i; | 
 |       } | 
 |     } | 
 |   } | 
 |   // Save final segment after the last special character. | 
 |   if (start != -1) { | 
 |     result << s.substr(start); | 
 |   } | 
 |   result << L'"'; | 
 |   return result.str(); | 
 | } | 
 |  | 
 | }  // namespace windows | 
 | }  // namespace bazel |