Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 15 | #include <fcntl.h> |
Laszlo Csomor | d86ae8c | 2016-12-05 13:54:59 +0000 | [diff] [blame] | 16 | #include <stdarg.h> // va_start, va_end, va_list |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 17 | |
| 18 | #ifndef COMPILER_MSVC |
Laszlo Csomor | 6e2ccb7 | 2017-03-01 16:53:35 +0000 | [diff] [blame] | 19 | #include <errno.h> |
| 20 | #include <limits.h> |
Mostyn Bramley-Moore | 44e8d10 | 2015-11-10 11:27:39 +0000 | [diff] [blame] | 21 | #include <sys/cygwin.h> |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 22 | #include <sys/ioctl.h> |
Googler | 11565c1 | 2015-07-23 12:03:53 +0000 | [diff] [blame] | 23 | #include <sys/socket.h> |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 24 | #include <sys/stat.h> |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 25 | #include <sys/statfs.h> |
| 26 | #include <unistd.h> |
László Csomor | fa27b50 | 2017-03-23 15:09:34 +0000 | [diff] [blame] | 27 | #endif // COMPILER_MSVC |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 28 | |
László Csomor | fa27b50 | 2017-03-23 15:09:34 +0000 | [diff] [blame] | 29 | #include <windows.h> |
laszlocsomor | 704fb66 | 2017-03-29 09:36:30 +0000 | [diff] [blame] | 30 | #include <lmcons.h> // UNLEN |
| 31 | #include <versionhelpers.h> // IsWindows8OrGreater |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 32 | |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 33 | #ifdef COMPILER_MSVC |
László Csomor | fa27b50 | 2017-03-23 15:09:34 +0000 | [diff] [blame] | 34 | #include <io.h> // _open |
| 35 | #include <knownfolders.h> // FOLDERID_Profile |
| 36 | #include <objbase.h> // CoTaskMemFree |
| 37 | #include <shlobj.h> // SHGetKnownFolderPath |
| 38 | #endif |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 39 | |
László Csomor | e64ed19 | 2017-02-23 15:43:54 +0000 | [diff] [blame] | 40 | #include <algorithm> |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 41 | #include <cstdio> |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 42 | #include <cstdlib> |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 43 | #include <sstream> |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 44 | #include <thread> // NOLINT (to slience Google-internal linter) |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 45 | #include <type_traits> // static_assert |
Laszlo Csomor | e152f72 | 2017-02-03 10:05:36 +0000 | [diff] [blame] | 46 | #include <vector> |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 47 | |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 48 | #include "src/main/cpp/blaze_util.h" |
Thiago Farina | 7f9357f | 2015-04-23 13:57:43 +0000 | [diff] [blame] | 49 | #include "src/main/cpp/blaze_util_platform.h" |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 50 | #include "src/main/cpp/global_variables.h" |
| 51 | #include "src/main/cpp/startup_options.h" |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 52 | #include "src/main/cpp/util/errors.h" |
Thiago Farina | 7f9357f | 2015-04-23 13:57:43 +0000 | [diff] [blame] | 53 | #include "src/main/cpp/util/exit_code.h" |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 54 | #include "src/main/cpp/util/file.h" |
Laszlo Csomor | 49970e0 | 2016-11-28 08:55:47 +0000 | [diff] [blame] | 55 | #include "src/main/cpp/util/file_platform.h" |
Laszlo Csomor | 6bf9576 | 2016-11-16 13:29:22 +0000 | [diff] [blame] | 56 | #include "src/main/cpp/util/md5.h" |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 57 | #include "src/main/cpp/util/numbers.h" |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 58 | #include "src/main/cpp/util/strings.h" |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 59 | #include "src/main/native/windows/file.h" |
| 60 | #include "src/main/native/windows/util.h" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 61 | |
| 62 | namespace blaze { |
| 63 | |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 64 | // Ensure we can safely cast (const) wchar_t* to LP(C)WSTR. |
| 65 | // This is true with MSVC but usually not with GCC. |
| 66 | static_assert(sizeof(wchar_t) == sizeof(WCHAR), |
| 67 | "wchar_t and WCHAR should be the same size"); |
| 68 | |
| 69 | // When using widechar Win32 API functions the maximum path length is 32K. |
| 70 | // Add 4 characters for potential UNC prefix and a couple more for safety. |
| 71 | static const size_t kWindowsPathBufferSize = 0x8010; |
| 72 | |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 73 | using bazel::windows::AutoHandle; |
| 74 | using bazel::windows::CreateJunction; |
| 75 | |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 76 | // TODO(bazel-team): get rid of die/pdie, handle errors on the caller side. |
| 77 | // die/pdie are exit points in the code and they make it difficult to follow the |
| 78 | // control flow, plus it's not clear whether they call destructors on local |
| 79 | // variables in the call stack. |
Thiago Farina | 241f46c | 2015-04-13 14:33:30 +0000 | [diff] [blame] | 80 | using blaze_util::die; |
| 81 | using blaze_util::pdie; |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 82 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 83 | using std::string; |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 84 | using std::unique_ptr; |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 85 | using std::wstring; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 86 | |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 87 | SignalHandler SignalHandler::INSTANCE; |
| 88 | |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 89 | class WindowsClock { |
| 90 | public: |
| 91 | uint64_t GetMilliseconds() const; |
| 92 | uint64_t GetProcessMilliseconds() const; |
| 93 | |
| 94 | static const WindowsClock INSTANCE; |
| 95 | |
| 96 | private: |
| 97 | // Clock frequency per seconds. |
| 98 | // It's safe to cache this because (from QueryPerformanceFrequency on MSDN): |
| 99 | // "The frequency of the performance counter is fixed at system boot and is |
| 100 | // consistent across all processors. Therefore, the frequency need only be |
| 101 | // queried upon application initialization, and the result can be cached." |
| 102 | const LARGE_INTEGER kFrequency; |
| 103 | |
| 104 | // Time (in milliseconds) at process start. |
| 105 | const LARGE_INTEGER kStart; |
| 106 | |
| 107 | WindowsClock(); |
| 108 | |
| 109 | static LARGE_INTEGER GetFrequency(); |
| 110 | static LARGE_INTEGER GetMillisecondsAsLargeInt(const LARGE_INTEGER& freq); |
| 111 | }; |
| 112 | |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 113 | #ifdef COMPILER_MSVC |
| 114 | |
Dmitry Lomov | 69ad695 | 2017-03-21 16:20:37 +0000 | [diff] [blame] | 115 | BOOL WINAPI ConsoleCtrlHandler(_In_ DWORD ctrlType) { |
| 116 | static volatile int sigint_count = 0; |
| 117 | switch (ctrlType) { |
| 118 | case CTRL_C_EVENT: |
| 119 | case CTRL_BREAK_EVENT: |
| 120 | if (++sigint_count >= 3) { |
| 121 | SigPrintf( |
| 122 | "\n%s caught third Ctrl+C handler signal; killed.\n\n", |
| 123 | SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| 124 | if (SignalHandler::Get().GetGlobals()->server_pid != -1) { |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 125 | KillServerProcess( |
| 126 | SignalHandler::Get().GetGlobals()->server_pid, |
| 127 | SignalHandler::Get().GetGlobals()->options->output_base); |
Dmitry Lomov | 69ad695 | 2017-03-21 16:20:37 +0000 | [diff] [blame] | 128 | } |
| 129 | _exit(1); |
| 130 | } |
| 131 | SigPrintf( |
| 132 | "\n%s Ctrl+C handler; shutting down.\n\n", |
| 133 | SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| 134 | SignalHandler::Get().CancelServer(); |
| 135 | return TRUE; |
| 136 | |
| 137 | case CTRL_CLOSE_EVENT: |
| 138 | SignalHandler::Get().CancelServer(); |
| 139 | return TRUE; |
| 140 | } |
| 141 | return false; |
| 142 | } |
| 143 | |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 144 | void SignalHandler::Install(GlobalVariables* globals, |
| 145 | SignalHandler::Callback cancel_server) { |
Dmitry Lomov | 69ad695 | 2017-03-21 16:20:37 +0000 | [diff] [blame] | 146 | _globals = globals; |
| 147 | _cancel_server = cancel_server; |
| 148 | ::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE); |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { |
Dmitry Lomov | 69ad695 | 2017-03-21 16:20:37 +0000 | [diff] [blame] | 152 | // We do not handle signals on Windows; always exit with exit_code. |
| 153 | exit(exit_code); |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 154 | } |
| 155 | |
| 156 | #else // not COMPILER_MSVC |
| 157 | |
| 158 | // The number of the last received signal that should cause the client |
| 159 | // to shutdown. This is saved so that the client's WTERMSIG can be set |
| 160 | // correctly. (Currently only SIGPIPE uses this mechanism.) |
| 161 | static volatile sig_atomic_t signal_handler_received_signal = 0; |
| 162 | |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 163 | // Signal handler. |
| 164 | static void handler(int signum) { |
| 165 | int saved_errno = errno; |
| 166 | |
| 167 | static volatile sig_atomic_t sigint_count = 0; |
| 168 | |
| 169 | switch (signum) { |
| 170 | case SIGINT: |
| 171 | if (++sigint_count >= 3) { |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 172 | SigPrintf( |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 173 | "\n%s caught third interrupt signal; killed.\n\n", |
| 174 | SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| 175 | if (SignalHandler::Get().GetGlobals()->server_pid != -1) { |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 176 | KillServerProcess( |
| 177 | SignalHandler::Get().GetGlobals()->server_pid, |
| 178 | SignalHandler::Get().GetGlobals()->options->output_base); |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 179 | } |
Laszlo Csomor | e86b04c | 2016-12-21 17:36:00 +0000 | [diff] [blame] | 180 | _exit(1); |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 181 | } |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 182 | SigPrintf( |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 183 | "\n%s caught interrupt signal; shutting down.\n\n", |
| 184 | SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| 185 | SignalHandler::Get().CancelServer(); |
| 186 | break; |
| 187 | case SIGTERM: |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 188 | SigPrintf( |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 189 | "\n%s caught terminate signal; shutting down.\n\n", |
| 190 | SignalHandler::Get().GetGlobals()->options->product_name.c_str()); |
| 191 | SignalHandler::Get().CancelServer(); |
| 192 | break; |
| 193 | case SIGPIPE: |
| 194 | signal_handler_received_signal = SIGPIPE; |
| 195 | break; |
| 196 | case SIGQUIT: |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 197 | SigPrintf("\nSending SIGQUIT to JVM process %d (see %s).\n\n", |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 198 | SignalHandler::Get().GetGlobals()->server_pid, |
| 199 | SignalHandler::Get().GetGlobals()->jvm_log_file.c_str()); |
| 200 | kill(SignalHandler::Get().GetGlobals()->server_pid, SIGQUIT); |
| 201 | break; |
| 202 | } |
| 203 | |
| 204 | errno = saved_errno; |
| 205 | } |
| 206 | |
| 207 | void SignalHandler::Install(GlobalVariables* globals, |
| 208 | SignalHandler::Callback cancel_server) { |
| 209 | _globals = globals; |
| 210 | _cancel_server = cancel_server; |
| 211 | |
| 212 | // Unblock all signals. |
| 213 | sigset_t sigset; |
| 214 | sigemptyset(&sigset); |
| 215 | sigprocmask(SIG_SETMASK, &sigset, NULL); |
| 216 | |
| 217 | signal(SIGINT, handler); |
| 218 | signal(SIGTERM, handler); |
| 219 | signal(SIGPIPE, handler); |
| 220 | signal(SIGQUIT, handler); |
| 221 | } |
| 222 | |
| 223 | ATTRIBUTE_NORETURN void SignalHandler::PropagateSignalOrExit(int exit_code) { |
| 224 | if (signal_handler_received_signal) { |
| 225 | // Kill ourselves with the same signal, so that callers see the |
| 226 | // right WTERMSIG value. |
| 227 | signal(signal_handler_received_signal, SIG_DFL); |
| 228 | raise(signal_handler_received_signal); |
| 229 | exit(1); // (in case raise didn't kill us for some reason) |
| 230 | } else { |
| 231 | exit(exit_code); |
| 232 | } |
| 233 | } |
| 234 | |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 235 | #endif // COMPILER_MSVC |
| 236 | |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 237 | // A signal-safe version of fprintf(stderr, ...). |
| 238 | // |
| 239 | // WARNING: any output from the blaze client may be interleaved |
| 240 | // with output from the blaze server. In --curses mode, |
| 241 | // the Blaze server often erases the previous line of output. |
| 242 | // So, be sure to end each such message with TWO newlines, |
| 243 | // otherwise it may be erased by the next message from the |
| 244 | // Blaze server. |
| 245 | // Also, it's a good idea to start each message with a newline, |
| 246 | // in case the Blaze server has written a partial line. |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 247 | void SigPrintf(const char *format, ...) { |
| 248 | #ifdef COMPILER_MSVC |
Dmitry Lomov | 5387c73 | 2017-03-22 09:33:28 +0000 | [diff] [blame] | 249 | int stderr_fileno = _fileno(stderr); |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 250 | #else // not COMPILER_MSVC |
Dmitry Lomov | 5387c73 | 2017-03-22 09:33:28 +0000 | [diff] [blame] | 251 | int stderr_fileno = STDERR_FILENO; |
| 252 | #endif |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 253 | char buf[1024]; |
| 254 | va_list ap; |
| 255 | va_start(ap, format); |
| 256 | int r = vsnprintf(buf, sizeof buf, format, ap); |
| 257 | va_end(ap); |
Dmitry Lomov | 5387c73 | 2017-03-22 09:33:28 +0000 | [diff] [blame] | 258 | if (write(stderr_fileno, buf, r) <= 0) { |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 259 | // We don't care, just placate the compiler. |
| 260 | } |
Laszlo Csomor | 3b89d2d | 2016-11-28 14:04:27 +0000 | [diff] [blame] | 261 | } |
Laszlo Csomor | 32086b2 | 2016-11-24 15:23:55 +0000 | [diff] [blame] | 262 | |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 263 | static void PrintErrorW(const wstring& op) { |
| 264 | DWORD last_error = ::GetLastError(); |
| 265 | if (last_error == 0) { |
| 266 | return; |
| 267 | } |
| 268 | |
| 269 | WCHAR* message_buffer; |
| 270 | FormatMessageW( |
| 271 | /* dwFlags */ FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| 272 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
| 273 | /* lpSource */ nullptr, |
| 274 | /* dwMessageId */ last_error, |
| 275 | /* dwLanguageId */ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| 276 | /* lpBuffer */ message_buffer, |
| 277 | /* nSize */ 0, |
| 278 | /* Arguments */ nullptr); |
| 279 | |
| 280 | fwprintf(stderr, L"ERROR: %s: %s (%d)\n", op.c_str(), message_buffer, |
| 281 | last_error); |
| 282 | LocalFree(message_buffer); |
| 283 | } |
| 284 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 285 | void WarnFilesystemType(const string& output_base) { |
| 286 | } |
| 287 | |
Laszlo Csomor | ae16e76 | 2016-11-18 10:16:08 +0000 | [diff] [blame] | 288 | string GetProcessIdAsString() { |
| 289 | return ToString(GetCurrentProcessId()); |
| 290 | } |
| 291 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 292 | string GetSelfPath() { |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 293 | WCHAR buffer[kWindowsPathBufferSize] = {0}; |
| 294 | if (!GetModuleFileNameW(0, buffer, kWindowsPathBufferSize)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 295 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 296 | "GetSelfPath: GetModuleFileNameW"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 297 | } |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 298 | return string(blaze_util::WstringToCstring(buffer).get()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 299 | } |
| 300 | |
Kristina Chodorow | 9396335 | 2015-03-20 19:11:19 +0000 | [diff] [blame] | 301 | string GetOutputRoot() { |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 302 | for (const char* i : {"TMPDIR", "TEMPDIR", "TMP", "TEMP"}) { |
| 303 | string tmpdir(GetEnv(i)); |
| 304 | if (!tmpdir.empty()) { |
| 305 | return tmpdir; |
| 306 | } |
| 307 | } |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 308 | #ifdef COMPILER_MSVC |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 309 | // GetTempPathW and GetEnvironmentVariableW only work properly when Bazel |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 310 | // runs under cmd.exe, not when it's run from msys. |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 311 | // The reason is that MSYS consumes all environment variables and sets its own |
| 312 | // ones. The symptom of this is that GetEnvironmentVariableW returns nothing |
| 313 | // for TEMP under MSYS, though it can retrieve WINDIR. |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 314 | |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 315 | WCHAR buffer[kWindowsPathBufferSize] = {0}; |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 316 | if (!::GetTempPathW(kWindowsPathBufferSize, buffer)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 317 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 318 | "GetOutputRoot: GetTempPathW"); |
Dmitry Lomov | bc84cc8 | 2016-04-15 14:05:24 +0000 | [diff] [blame] | 319 | } |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 320 | return string(blaze_util::WstringToCstring(buffer).get()); |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 321 | #else // not COMPILER_MSVC |
Laszlo Csomor | f926f3e | 2016-11-09 09:05:48 +0000 | [diff] [blame] | 322 | return "/var/tmp"; |
| 323 | #endif // COMPILER_MSVC |
Kristina Chodorow | 9396335 | 2015-03-20 19:11:19 +0000 | [diff] [blame] | 324 | } |
| 325 | |
László Csomor | fa27b50 | 2017-03-23 15:09:34 +0000 | [diff] [blame] | 326 | string GetHomeDir() { |
| 327 | #ifdef COMPILER_MSVC |
| 328 | PWSTR wpath; |
| 329 | if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, |
| 330 | &wpath))) { |
| 331 | string result = string(blaze_util::WstringToCstring(wpath).get()); |
| 332 | ::CoTaskMemFree(wpath); |
| 333 | return result; |
| 334 | } |
| 335 | #endif |
| 336 | return GetEnv("HOME"); // only defined in MSYS/Cygwin |
| 337 | } |
| 338 | |
Laszlo Csomor | 760f786 | 2016-12-19 15:46:47 +0000 | [diff] [blame] | 339 | string FindSystemWideBlazerc() { |
| 340 | #ifdef COMPILER_MSVC |
László Csomor | fa27b50 | 2017-03-23 15:09:34 +0000 | [diff] [blame] | 341 | // TODO(bazel-team): figure out a good path to return here. |
Laszlo Csomor | 760f786 | 2016-12-19 15:46:47 +0000 | [diff] [blame] | 342 | return ""; |
| 343 | #else // not COMPILER_MSVC |
| 344 | string path = "/etc/bazel.bazelrc"; |
Laszlo Csomor | 00549b4 | 2017-01-11 09:12:10 +0000 | [diff] [blame] | 345 | if (blaze_util::CanReadFile(path)) { |
Laszlo Csomor | 760f786 | 2016-12-19 15:46:47 +0000 | [diff] [blame] | 346 | return path; |
| 347 | } |
| 348 | return ""; |
| 349 | #endif // COMPILER_MSVC |
| 350 | } |
| 351 | |
Laszlo Csomor | 00549b4 | 2017-01-11 09:12:10 +0000 | [diff] [blame] | 352 | string GetJavaBinaryUnderJavabase() { return "bin/java.exe"; } |
| 353 | |
Laszlo Csomor | 943d3cf | 2016-11-07 14:27:21 +0000 | [diff] [blame] | 354 | uint64_t GetMillisecondsMonotonic() { |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 355 | return WindowsClock::INSTANCE.GetMilliseconds(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 356 | } |
| 357 | |
Laszlo Csomor | 943d3cf | 2016-11-07 14:27:21 +0000 | [diff] [blame] | 358 | uint64_t GetMillisecondsSinceProcessStart() { |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 359 | return WindowsClock::INSTANCE.GetProcessMilliseconds(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 360 | } |
| 361 | |
| 362 | void SetScheduling(bool batch_cpu_scheduling, int io_nice_level) { |
| 363 | // TODO(bazel-team): There should be a similar function on Windows. |
| 364 | } |
| 365 | |
| 366 | string GetProcessCWD(int pid) { |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 367 | #ifdef COMPILER_MSVC |
| 368 | // TODO(bazel-team) 2016-11-18: decide whether we need this on Windows and |
| 369 | // implement or delete. |
| 370 | return ""; |
| 371 | #else // not COMPILER_MSVC |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 372 | char server_cwd[PATH_MAX] = {}; |
| 373 | if (readlink( |
Googler | 9588b81 | 2015-07-23 11:49:37 +0000 | [diff] [blame] | 374 | ("/proc/" + ToString(pid) + "/cwd").c_str(), |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 375 | server_cwd, sizeof(server_cwd)) < 0) { |
| 376 | return ""; |
| 377 | } |
| 378 | |
| 379 | return string(server_cwd); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 380 | #endif // COMPILER_MSVC |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 381 | } |
| 382 | |
Thiago Farina | 01f3600 | 2015-04-08 15:59:08 +0000 | [diff] [blame] | 383 | bool IsSharedLibrary(const string &filename) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 384 | return blaze_util::ends_with(filename, ".dll"); |
| 385 | } |
| 386 | |
| 387 | string GetDefaultHostJavabase() { |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 388 | string javahome(GetEnv("JAVA_HOME")); |
| 389 | if (javahome.empty()) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 390 | die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 391 | "Error: JAVA_HOME not set."); |
| 392 | } |
| 393 | return javahome; |
| 394 | } |
| 395 | |
Dmitry Lomov | b5ff50b | 2016-02-03 19:35:42 +0000 | [diff] [blame] | 396 | namespace { |
Dmitry Lomov | b5ff50b | 2016-02-03 19:35:42 +0000 | [diff] [blame] | 397 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 398 | // Max command line length is per CreateProcess documentation |
| 399 | // (https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx) |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 400 | // |
| 401 | // Quoting rules are described here: |
| 402 | // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ |
| 403 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 404 | static const int MAX_CMDLINE_LENGTH = 32768; |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 405 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 406 | struct CmdLine { |
| 407 | char cmdline[MAX_CMDLINE_LENGTH]; |
| 408 | }; |
| 409 | static void CreateCommandLine(CmdLine* result, const string& exe, |
Laszlo Csomor | e152f72 | 2017-02-03 10:05:36 +0000 | [diff] [blame] | 410 | const std::vector<string>& args_vector) { |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 411 | std::ostringstream cmdline; |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 412 | string short_exe; |
Laszlo Csomor | 00549b4 | 2017-01-11 09:12:10 +0000 | [diff] [blame] | 413 | if (!blaze_util::AsShortWindowsPath(exe, &short_exe)) { |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 414 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 415 | "CreateCommandLine: AsShortWindowsPath(%s)", exe.c_str()); |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 416 | } |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 417 | bool first = true; |
| 418 | for (const auto& s : args_vector) { |
| 419 | if (first) { |
| 420 | first = false; |
Laszlo Csomor | 00549b4 | 2017-01-11 09:12:10 +0000 | [diff] [blame] | 421 | // Skip first argument, instead use quoted executable name. |
Philipp Wollermann | b78d8c8 | 2017-03-07 10:29:42 +0000 | [diff] [blame] | 422 | cmdline << '\"' << short_exe << '\"'; |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 423 | continue; |
| 424 | } else { |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 425 | cmdline << ' '; |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 426 | } |
Dmitry Lomov | b5ff50b | 2016-02-03 19:35:42 +0000 | [diff] [blame] | 427 | |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 428 | bool has_space = s.find(" ") != string::npos; |
| 429 | |
| 430 | if (has_space) { |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 431 | cmdline << '\"'; |
Dmitry Lomov | b5ff50b | 2016-02-03 19:35:42 +0000 | [diff] [blame] | 432 | } |
| 433 | |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 434 | std::string::const_iterator it = s.begin(); |
| 435 | while (it != s.end()) { |
| 436 | char ch = *it++; |
| 437 | switch (ch) { |
| 438 | case '"': |
| 439 | // Escape double quotes |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 440 | cmdline << "\\\""; |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 441 | break; |
| 442 | |
| 443 | case '\\': |
| 444 | if (it == s.end()) { |
| 445 | // Backslashes at the end of the string are quoted if we add quotes |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 446 | cmdline << (has_space ? "\\\\" : "\\"); |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 447 | } else { |
| 448 | // Backslashes everywhere else are quoted if they are followed by a |
| 449 | // quote or a backslash |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 450 | cmdline << (*it == '"' || *it == '\\' ? "\\\\" : "\\"); |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 451 | } |
| 452 | break; |
| 453 | |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 454 | default: |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 455 | cmdline << ch; |
Lukacs Berki | 83c78b1 | 2016-06-24 12:35:08 +0000 | [diff] [blame] | 456 | } |
| 457 | } |
| 458 | |
| 459 | if (has_space) { |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 460 | cmdline << '\"'; |
Dmitry Lomov | b5ff50b | 2016-02-03 19:35:42 +0000 | [diff] [blame] | 461 | } |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 462 | } |
| 463 | |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 464 | string cmdline_str = cmdline.str(); |
| 465 | if (cmdline_str.size() >= MAX_CMDLINE_LENGTH) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 466 | pdie(blaze_exit_code::INTERNAL_ERROR, "Command line too long (%d > %d): %s", |
| 467 | cmdline_str.size(), MAX_CMDLINE_LENGTH, cmdline_str.c_str()); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 468 | } |
| 469 | |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 470 | // Copy command line into a mutable buffer. |
| 471 | // CreateProcess is allowed to mutate its command line argument. |
Laszlo Csomor | f9f41c7 | 2017-01-09 12:06:45 +0000 | [diff] [blame] | 472 | strncpy(result->cmdline, cmdline_str.c_str(), MAX_CMDLINE_LENGTH - 1); |
Lukacs Berki | 2236f7d | 2016-04-28 08:49:43 +0000 | [diff] [blame] | 473 | result->cmdline[MAX_CMDLINE_LENGTH - 1] = 0; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 474 | } |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 475 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 476 | } // namespace |
| 477 | |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 478 | string GetJvmVersion(const string& java_exe) { |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 479 | HANDLE pipe_read, pipe_write; |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 480 | |
| 481 | SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 482 | if (!::CreatePipe(&pipe_read, &pipe_write, &sa, 0)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 483 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 484 | "GetJvmVersion: CreatePipe"); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 485 | } |
| 486 | |
| 487 | if (!SetHandleInformation(pipe_read, HANDLE_FLAG_INHERIT, 0)) { |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 488 | CloseHandle(pipe_read); |
| 489 | CloseHandle(pipe_write); |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 490 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 491 | "GetJvmVersion: SetHandleInformation"); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 492 | } |
| 493 | |
| 494 | PROCESS_INFORMATION processInfo = {0}; |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 495 | STARTUPINFOA startupInfo = {0}; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 496 | startupInfo.hStdError = pipe_write; |
| 497 | startupInfo.hStdOutput = pipe_write; |
| 498 | startupInfo.dwFlags |= STARTF_USESTDHANDLES; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 499 | |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 500 | string win_java_exe; |
Laszlo Csomor | 00549b4 | 2017-01-11 09:12:10 +0000 | [diff] [blame] | 501 | if (!blaze_util::AsShortWindowsPath(java_exe, &win_java_exe)) { |
Laszlo Csomor | 3cb6368 | 2017-01-05 12:13:48 +0000 | [diff] [blame] | 502 | CloseHandle(pipe_read); |
| 503 | CloseHandle(pipe_write); |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 504 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | a669557 | 2017-01-18 10:58:17 +0000 | [diff] [blame] | 505 | "GetJvmVersion: AsShortWindowsPath(%s)", java_exe.c_str()); |
Laszlo Csomor | 3cb6368 | 2017-01-05 12:13:48 +0000 | [diff] [blame] | 506 | } |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 507 | win_java_exe = string("\"") + win_java_exe + "\" -version"; |
Laszlo Csomor | 3cb6368 | 2017-01-05 12:13:48 +0000 | [diff] [blame] | 508 | |
Laszlo Csomor | 44ecf9a | 2017-01-10 10:41:26 +0000 | [diff] [blame] | 509 | char cmdline[MAX_CMDLINE_LENGTH]; |
| 510 | strncpy(cmdline, win_java_exe.c_str(), win_java_exe.size() + 1); |
| 511 | BOOL ok = CreateProcessA( |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 512 | /* lpApplicationName */ NULL, |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 513 | /* lpCommandLine */ cmdline, |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 514 | /* lpProcessAttributes */ NULL, |
| 515 | /* lpThreadAttributes */ NULL, |
| 516 | /* bInheritHandles */ TRUE, |
| 517 | /* dwCreationFlags */ 0, |
| 518 | /* lpEnvironment */ NULL, |
| 519 | /* lpCurrentDirectory */ NULL, |
| 520 | /* lpStartupInfo */ &startupInfo, |
| 521 | /* lpProcessInformation */ &processInfo); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 522 | |
| 523 | if (!ok) { |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 524 | CloseHandle(pipe_read); |
| 525 | CloseHandle(pipe_write); |
Dmitry Lomov | 42d8290 | 2016-07-15 13:26:57 +0000 | [diff] [blame] | 526 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 527 | "RunProgram: CreateProcess(%s)", cmdline); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 528 | } |
| 529 | |
| 530 | CloseHandle(pipe_write); |
| 531 | std::string result = ""; |
| 532 | DWORD bytes_read; |
| 533 | CHAR buf[1024]; |
| 534 | |
| 535 | for (;;) { |
| 536 | ok = ::ReadFile(pipe_read, buf, 1023, &bytes_read, NULL); |
| 537 | if (!ok || bytes_read == 0) { |
| 538 | break; |
| 539 | } |
| 540 | buf[bytes_read] = 0; |
| 541 | result = result + buf; |
| 542 | } |
| 543 | |
| 544 | CloseHandle(pipe_read); |
| 545 | CloseHandle(processInfo.hProcess); |
| 546 | CloseHandle(processInfo.hThread); |
Laszlo Csomor | f00cee8 | 2016-12-15 10:58:03 +0000 | [diff] [blame] | 547 | return ReadJvmVersion(result); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 548 | } |
| 549 | |
Laszlo Csomor | 5fa18d1 | 2017-03-14 15:25:13 +0000 | [diff] [blame] | 550 | #ifndef COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 551 | // If we pass DETACHED_PROCESS to CreateProcess(), cmd.exe appropriately |
| 552 | // returns the command prompt when the client terminates. msys2, however, in |
| 553 | // its infinite wisdom, waits until the *server* terminates and cannot be |
| 554 | // convinced otherwise. |
| 555 | // |
| 556 | // So, we first pretend to be a POSIX daemon so that msys2 knows about our |
| 557 | // intentions and *then* we call CreateProcess(). Life ain't easy. |
| 558 | static bool DaemonizeOnWindows() { |
| 559 | if (fork() > 0) { |
| 560 | // We are the original client process. |
| 561 | return true; |
| 562 | } |
| 563 | |
| 564 | if (fork() > 0) { |
| 565 | // We are the child of the original client process. Terminate so that the |
| 566 | // actual server is not a child process of the client. |
| 567 | exit(0); |
| 568 | } |
| 569 | |
| 570 | setsid(); |
| 571 | // Contrary to the POSIX version, we are not closing the three standard file |
| 572 | // descriptors here. CreateProcess() will take care of that and it's useful |
| 573 | // to see the error messages in ExecuteDaemon() on the console of the client. |
| 574 | return false; |
| 575 | } |
Laszlo Csomor | 5fa18d1 | 2017-03-14 15:25:13 +0000 | [diff] [blame] | 576 | #endif // not COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 577 | |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 578 | static bool GetProcessStartupTime(HANDLE process, uint64_t* result) { |
| 579 | FILETIME creation_time, dummy1, dummy2, dummy3; |
| 580 | // GetProcessTimes cannot handle NULL arguments. |
| 581 | if (process == INVALID_HANDLE_VALUE || |
| 582 | !::GetProcessTimes(process, &creation_time, &dummy1, &dummy2, &dummy3)) { |
| 583 | return false; |
| 584 | } |
| 585 | *result = static_cast<uint64_t>(creation_time.dwHighDateTime) << 32 | |
| 586 | creation_time.dwLowDateTime; |
| 587 | return true; |
| 588 | } |
| 589 | |
| 590 | static void WriteProcessStartupTime(const string& server_dir, HANDLE process) { |
| 591 | uint64_t start_time = 0; |
| 592 | if (!GetProcessStartupTime(process, &start_time)) { |
| 593 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 594 | "WriteProcessStartupTime(%s): GetProcessStartupTime", |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 595 | server_dir.c_str()); |
| 596 | } |
| 597 | |
| 598 | string start_time_file = blaze_util::JoinPath(server_dir, "server.starttime"); |
| 599 | if (!blaze_util::WriteFile(ToString(start_time), start_time_file)) { |
| 600 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 601 | "WriteProcessStartupTime(%s): WriteFile(%s)", server_dir.c_str(), |
| 602 | start_time_file.c_str()); |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 603 | } |
| 604 | } |
| 605 | |
| 606 | static HANDLE CreateJvmOutputFile(const wstring& path, |
| 607 | SECURITY_ATTRIBUTES* sa) { |
| 608 | // If the previous server process was asked to be shut down (but not killed), |
| 609 | // it takes a while for it to comply, so wait until the JVM output file that |
| 610 | // it held open is closed. There seems to be no better way to wait for a file |
| 611 | // to be closed on Windows. |
| 612 | static const unsigned int timeout_sec = 60; |
| 613 | for (unsigned int waited = 0; waited < timeout_sec; ++waited) { |
| 614 | HANDLE handle = ::CreateFileW( |
| 615 | /* lpFileName */ path.c_str(), |
| 616 | /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 617 | /* dwShareMode */ FILE_SHARE_READ, |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 618 | /* lpSecurityAttributes */ sa, |
| 619 | /* dwCreationDisposition */ CREATE_ALWAYS, |
| 620 | /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| 621 | /* hTemplateFile */ NULL); |
| 622 | if (handle != INVALID_HANDLE_VALUE) { |
| 623 | return handle; |
| 624 | } |
| 625 | if (GetLastError() != ERROR_SHARING_VIOLATION && |
| 626 | GetLastError() != ERROR_LOCK_VIOLATION) { |
| 627 | // Some other error occurred than the file being open; bail out. |
| 628 | break; |
| 629 | } |
| 630 | |
| 631 | // The file is still held open, the server is shutting down. There's a |
| 632 | // chance that another process holds it open, we don't know; in that case |
| 633 | // we just exit after the timeout expires. |
| 634 | if (waited == 5 || waited == 10 || waited == 30) { |
| 635 | fprintf(stderr, |
| 636 | "Waiting for previous Bazel server's log file to close " |
| 637 | "(waited %d seconds, waiting at most %d)\n", |
| 638 | waited, timeout_sec); |
| 639 | } |
| 640 | Sleep(1000); |
| 641 | } |
| 642 | return INVALID_HANDLE_VALUE; |
| 643 | } |
| 644 | |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 645 | #ifdef COMPILER_MSVC |
| 646 | |
| 647 | class ProcessHandleBlazeServerStartup : public BlazeServerStartup { |
| 648 | public: |
| 649 | ProcessHandleBlazeServerStartup(HANDLE _proc) : proc(_proc) {} |
| 650 | |
| 651 | bool IsStillAlive() override { |
| 652 | FILETIME dummy1, exit_time, dummy2, dummy3; |
| 653 | return GetProcessTimes(proc, &dummy1, &exit_time, &dummy2, &dummy3) && |
| 654 | exit_time.dwHighDateTime == 0 && exit_time.dwLowDateTime == 0; |
| 655 | } |
| 656 | |
| 657 | private: |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 658 | AutoHandle proc; |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 659 | }; |
| 660 | |
| 661 | #else // COMPILER_MSVC |
| 662 | |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 663 | // Keeping an eye on the server process on Windows is not implemented yet. |
| 664 | // TODO(lberki): Implement this, because otherwise if we can't start up a server |
| 665 | // process, the client will hang until it times out. |
| 666 | class DummyBlazeServerStartup : public BlazeServerStartup { |
| 667 | public: |
| 668 | DummyBlazeServerStartup() {} |
| 669 | virtual ~DummyBlazeServerStartup() {} |
Lukacs Berki | 9d52bc5 | 2016-06-07 11:11:04 +0000 | [diff] [blame] | 670 | virtual bool IsStillAlive() { return true; } |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 671 | }; |
| 672 | |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 673 | #endif // COMPILER_MSVC |
| 674 | |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 675 | void ExecuteDaemon(const string& exe, const std::vector<string>& args_vector, |
| 676 | const string& daemon_output, const string& server_dir, |
| 677 | BlazeServerStartup** server_startup) { |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 678 | #ifndef COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 679 | if (DaemonizeOnWindows()) { |
| 680 | // We are the client process |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 681 | *server_startup = new DummyBlazeServerStartup(); |
| 682 | return; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 683 | } |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 684 | #endif // not COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 685 | |
Laszlo Csomor | a669557 | 2017-01-18 10:58:17 +0000 | [diff] [blame] | 686 | wstring wdaemon_output; |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 687 | if (!blaze_util::AsAbsoluteWindowsPath(daemon_output, &wdaemon_output)) { |
Laszlo Csomor | a669557 | 2017-01-18 10:58:17 +0000 | [diff] [blame] | 688 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 689 | "ExecuteDaemon(%s): AsAbsoluteWindowsPath(%s)", exe.c_str(), |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 690 | daemon_output.c_str()); |
Laszlo Csomor | a669557 | 2017-01-18 10:58:17 +0000 | [diff] [blame] | 691 | } |
| 692 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 693 | SECURITY_ATTRIBUTES sa; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 694 | sa.nLength = sizeof(SECURITY_ATTRIBUTES); |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 695 | // We redirect stdin to the NUL device, and redirect stdout and stderr to |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 696 | // `stdout_file` and `stderr_file` (opened below) by telling CreateProcess to |
| 697 | // use these file handles, so they must be inheritable. |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 698 | sa.bInheritHandle = TRUE; |
| 699 | sa.lpSecurityDescriptor = NULL; |
| 700 | |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 701 | AutoHandle devnull(::CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ, NULL, |
| 702 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 703 | if (!devnull.IsValid()) { |
| 704 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 705 | "ExecuteDaemon(%s): CreateFileA(NUL)", exe.c_str()); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 706 | } |
| 707 | |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 708 | AutoHandle stdout_file(CreateJvmOutputFile(wdaemon_output.c_str(), &sa)); |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 709 | if (!stdout_file.IsValid()) { |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 710 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 711 | "ExecuteDaemon(%s): CreateJvmOutputFile(%ls)", exe.c_str(), |
| 712 | wdaemon_output.c_str()); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 713 | } |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 714 | HANDLE stderr_handle; |
| 715 | // We must duplicate the handle to stdout, otherwise "bazel clean --expunge" |
| 716 | // won't work, because when it tries to close stdout then stderr, the former |
| 717 | // will succeed but the latter will appear to be valid yet still fail to |
| 718 | // close. |
| 719 | if (!DuplicateHandle( |
| 720 | /* hSourceProcessHandle */ GetCurrentProcess(), |
| 721 | /* hSourceHandle */ stdout_file, |
| 722 | /* hTargetProcessHandle */ GetCurrentProcess(), |
| 723 | /* lpTargetHandle */ &stderr_handle, |
| 724 | /* dwDesiredAccess */ 0, |
| 725 | /* bInheritHandle */ TRUE, |
| 726 | /* dwOptions */ DUPLICATE_SAME_ACCESS)) { |
| 727 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 728 | "ExecuteDaemon(%s): DuplicateHandle(%ls)", exe.c_str(), |
| 729 | wdaemon_output.c_str()); |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 730 | } |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 731 | AutoHandle stderr_file(stderr_handle); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 732 | |
| 733 | PROCESS_INFORMATION processInfo = {0}; |
Laszlo Csomor | d86ae8c | 2016-12-05 13:54:59 +0000 | [diff] [blame] | 734 | STARTUPINFOA startupInfo = {0}; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 735 | |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 736 | startupInfo.hStdInput = devnull; |
Laszlo Csomor | 70ff798a | 2017-06-12 09:39:10 +0200 | [diff] [blame] | 737 | startupInfo.hStdError = stdout_file; |
| 738 | startupInfo.hStdOutput = stderr_handle; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 739 | startupInfo.dwFlags |= STARTF_USESTDHANDLES; |
| 740 | CmdLine cmdline; |
| 741 | CreateCommandLine(&cmdline, exe, args_vector); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 742 | |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 743 | BOOL ok = CreateProcessA( |
| 744 | /* lpApplicationName */ NULL, |
| 745 | /* lpCommandLine */ cmdline.cmdline, |
| 746 | /* lpProcessAttributes */ NULL, |
| 747 | /* lpThreadAttributes */ NULL, |
| 748 | /* bInheritHandles */ TRUE, |
| 749 | /* dwCreationFlags */ DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, |
| 750 | /* lpEnvironment */ NULL, |
| 751 | /* lpCurrentDirectory */ NULL, |
| 752 | /* lpStartupInfo */ &startupInfo, |
| 753 | /* lpProcessInformation */ &processInfo); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 754 | |
| 755 | if (!ok) { |
Dmitry Lomov | 42d8290 | 2016-07-15 13:26:57 +0000 | [diff] [blame] | 756 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 757 | "ExecuteDaemon(%s): CreateProcess(%s)", exe.c_str(), cmdline.cmdline); |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 758 | } |
| 759 | |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 760 | WriteProcessStartupTime(server_dir, processInfo.hProcess); |
| 761 | |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 762 | #ifdef COMPILER_MSVC |
| 763 | // Pass ownership of processInfo.hProcess |
| 764 | *server_startup = new ProcessHandleBlazeServerStartup(processInfo.hProcess); |
| 765 | #endif |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 766 | |
Lukacs Berki | d825a3d | 2016-06-23 11:10:02 +0000 | [diff] [blame] | 767 | string pid_string = ToString(processInfo.dwProcessId); |
Thiago Farina | 048bbfc | 2016-09-21 08:20:41 +0000 | [diff] [blame] | 768 | string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile); |
Laszlo Csomor | 49970e0 | 2016-11-28 08:55:47 +0000 | [diff] [blame] | 769 | if (!blaze_util::WriteFile(pid_string, pid_file)) { |
Lukacs Berki | e33cf0f | 2016-04-28 11:04:59 +0000 | [diff] [blame] | 770 | // Not a lot we can do if this fails |
| 771 | fprintf(stderr, "Cannot write PID file %s\n", pid_file.c_str()); |
| 772 | } |
| 773 | |
Laszlo Csomor | d12fb7a | 2017-04-26 13:41:56 +0200 | [diff] [blame] | 774 | // Don't close processInfo.hProcess here, it's now owned by the |
| 775 | // ProcessHandleBlazeServerStartup instance. |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 776 | CloseHandle(processInfo.hThread); |
| 777 | |
Laszlo Csomor | 5fa18d1 | 2017-03-14 15:25:13 +0000 | [diff] [blame] | 778 | #ifndef COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 779 | exit(0); |
Laszlo Csomor | 5fa18d1 | 2017-03-14 15:25:13 +0000 | [diff] [blame] | 780 | #endif // COMPILER_MSVC |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 781 | } |
| 782 | |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 783 | void BatchWaiterThread(HANDLE java_handle) { |
| 784 | WaitForSingleObject(java_handle, INFINITE); |
| 785 | } |
| 786 | |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 787 | #ifdef COMPILER_MSVC |
| 788 | // TODO(bazel-team): implement signal handling. |
| 789 | #else // not COMPILER_MSVC |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 790 | static void MingwSignalHandler(int signum) { |
| 791 | // Java process will be terminated because we set the job to terminate if its |
| 792 | // handle is closed. |
| 793 | // |
| 794 | // Note that this is different how interruption is handled on Unix, where the |
| 795 | // Java process sets up a signal handler for SIGINT itself. That cannot be |
| 796 | // done on Windows without using native code, and it's better to have as |
| 797 | // little JNI as possible. The most important part of the cleanup after |
| 798 | // termination (killing all child processes) happens automatically on Windows |
| 799 | // anyway, since we put the batch Java process in its own job which does not |
| 800 | // allow breakaway processes. |
| 801 | exit(blaze_exit_code::ExitCode::INTERRUPTED); |
| 802 | } |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 803 | #endif // COMPILER_MSVC |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 804 | |
Lukacs Berki | 23cf396 | 2016-07-19 09:28:23 +0000 | [diff] [blame] | 805 | // Returns whether assigning the given process to a job failed because nested |
| 806 | // jobs are not available on the current system. |
| 807 | static bool IsFailureDueToNestedJobsNotSupported(HANDLE process) { |
| 808 | BOOL is_in_job; |
| 809 | if (!IsProcessInJob(process, NULL, &is_in_job)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 810 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 811 | "IsFailureDueToNestedJobsNotSupported: IsProcessInJob"); |
Lukacs Berki | 23cf396 | 2016-07-19 09:28:23 +0000 | [diff] [blame] | 812 | return false; |
| 813 | } |
| 814 | |
| 815 | if (!is_in_job) { |
| 816 | // Not in a job. |
| 817 | return false; |
| 818 | } |
laszlocsomor | 704fb66 | 2017-03-29 09:36:30 +0000 | [diff] [blame] | 819 | return !IsWindows8OrGreater(); |
Lukacs Berki | 23cf396 | 2016-07-19 09:28:23 +0000 | [diff] [blame] | 820 | } |
| 821 | |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 822 | // Run the given program in the current working directory, using the given |
| 823 | // argument vector, wait for it to finish, then exit ourselves with the exitcode |
| 824 | // of that program. |
Laszlo Csomor | e152f72 | 2017-02-03 10:05:36 +0000 | [diff] [blame] | 825 | void ExecuteProgram(const string& exe, const std::vector<string>& args_vector) { |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 826 | CmdLine cmdline; |
| 827 | CreateCommandLine(&cmdline, exe, args_vector); |
| 828 | |
Laszlo Csomor | d86ae8c | 2016-12-05 13:54:59 +0000 | [diff] [blame] | 829 | STARTUPINFOA startupInfo = {0}; |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 830 | startupInfo.cb = sizeof(STARTUPINFOA); |
| 831 | |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 832 | PROCESS_INFORMATION processInfo = {0}; |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 833 | |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 834 | HANDLE job = CreateJobObject(NULL, NULL); |
| 835 | if (job == NULL) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 836 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 837 | "ExecuteProgram(%s): CreateJobObject", exe.c_str()); |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 838 | } |
| 839 | |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 840 | JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0}; |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 841 | job_info.BasicLimitInformation.LimitFlags = |
| 842 | JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 843 | |
| 844 | if (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, |
| 845 | &job_info, sizeof(job_info))) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 846 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 847 | "ExecuteProgram(%s): SetInformationJobObject", exe.c_str()); |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 848 | } |
| 849 | |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 850 | BOOL success = CreateProcessA( |
| 851 | /* lpApplicationName */ NULL, |
| 852 | /* lpCommandLine */ cmdline.cmdline, |
| 853 | /* lpProcessAttributes */ NULL, |
| 854 | /* lpThreadAttributes */ NULL, |
| 855 | /* bInheritHandles */ TRUE, |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 856 | /* dwCreationFlags */ CREATE_SUSPENDED, |
Laszlo Csomor | 31500b8 | 2016-12-15 10:15:36 +0000 | [diff] [blame] | 857 | /* lpEnvironment */ NULL, |
| 858 | /* lpCurrentDirectory */ NULL, |
| 859 | /* lpStartupInfo */ &startupInfo, |
| 860 | /* lpProcessInformation */ &processInfo); |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 861 | |
| 862 | if (!success) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 863 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 864 | "ExecuteProgram(%s): CreateProcess(%s)", exe.c_str(), cmdline.cmdline); |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 865 | } |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 866 | |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 867 | // We will try to put the launched process into a Job object. This will make |
| 868 | // Windows reliably kill all child processes that the process itself may |
| 869 | // launch once the process exits. On Windows systems that don't support nested |
| 870 | // jobs, this may fail if we are already running inside a job ourselves. In |
| 871 | // this case, we'll continue anyway, because we assume that our parent is |
| 872 | // handling process management for us. |
| 873 | if (!AssignProcessToJobObject(job, processInfo.hProcess) && |
| 874 | !IsFailureDueToNestedJobsNotSupported(processInfo.hProcess)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 875 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 876 | "ExecuteProgram(%s): AssignProcessToJobObject", exe.c_str()); |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 877 | } |
| 878 | |
Philipp Wollermann | e4d977f | 2017-03-15 15:34:11 +0000 | [diff] [blame] | 879 | // Now that we potentially put the process into a new job object, we can start |
| 880 | // running it. |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 881 | if (ResumeThread(processInfo.hThread) == -1) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 882 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 883 | "ExecuteProgram(%s): ResumeThread", exe.c_str()); |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 884 | } |
| 885 | |
| 886 | // msys doesn't deliver signals while a Win32 call is pending so we need to |
| 887 | // do the blocking call in another thread |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 888 | |
| 889 | #ifdef COMPILER_MSVC |
| 890 | // TODO(bazel-team): implement signal handling. |
| 891 | #else // not COMPILER_MSVC |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 892 | signal(SIGINT, MingwSignalHandler); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 893 | #endif // COMPILER_MSVC |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 894 | std::thread batch_waiter_thread([=]() { |
| 895 | BatchWaiterThread(processInfo.hProcess); |
| 896 | }); |
| 897 | |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 898 | // The output base lock is held while waiting |
Lukacs Berki | 68cb41a | 2016-07-06 11:43:37 +0000 | [diff] [blame] | 899 | batch_waiter_thread.join(); |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 900 | DWORD exit_code; |
Lukacs Berki | 7494c92 | 2016-04-27 11:17:51 +0000 | [diff] [blame] | 901 | GetExitCodeProcess(processInfo.hProcess, &exit_code); |
| 902 | CloseHandle(processInfo.hProcess); |
| 903 | CloseHandle(processInfo.hThread); |
| 904 | exit(exit_code); |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 905 | } |
| 906 | |
Thiago Farina | c3aee5a | 2017-04-24 18:02:52 +0200 | [diff] [blame] | 907 | const char kListSeparator = ';'; |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 908 | |
László Csomor | e64ed19 | 2017-02-23 15:43:54 +0000 | [diff] [blame] | 909 | string PathAsJvmFlag(const string& path) { |
Laszlo Csomor | 9d3f989 | 2017-03-07 10:03:44 +0000 | [diff] [blame] | 910 | string spath; |
| 911 | if (!blaze_util::AsShortWindowsPath(path, &spath)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 912 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 913 | "PathAsJvmFlag(%s): AsShortWindowsPath", path.c_str()); |
Laszlo Csomor | 9d3f989 | 2017-03-07 10:03:44 +0000 | [diff] [blame] | 914 | } |
László Csomor | e64ed19 | 2017-02-23 15:43:54 +0000 | [diff] [blame] | 915 | // Convert backslashes to forward slashes, in order to avoid the JVM parsing |
| 916 | // Windows paths as if they contained escaped characters. |
| 917 | // See https://github.com/bazelbuild/bazel/issues/2576 |
Laszlo Csomor | 9d3f989 | 2017-03-07 10:03:44 +0000 | [diff] [blame] | 918 | std::replace(spath.begin(), spath.end(), '\\', '/'); |
| 919 | return spath; |
László Csomor | e64ed19 | 2017-02-23 15:43:54 +0000 | [diff] [blame] | 920 | } |
| 921 | |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 922 | string ConvertPath(const string& path) { |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 923 | #ifdef COMPILER_MSVC |
László Csomor | 78b8be6 | 2017-03-28 09:04:06 +0000 | [diff] [blame] | 924 | // The path may not be Windows-style and may not be normalized, so convert it. |
| 925 | wstring wpath; |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 926 | if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath)) { |
| 927 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 928 | "ConvertPath(%s): AsAbsoluteWindowsPath", path.c_str()); |
László Csomor | 78b8be6 | 2017-03-28 09:04:06 +0000 | [diff] [blame] | 929 | } |
| 930 | std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower); |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 931 | return string(blaze_util::WstringToCstring( |
| 932 | blaze_util::RemoveUncPrefixMaybe(wpath.c_str())) |
| 933 | .get()); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 934 | #else // not COMPILER_MSVC |
Yun Peng | 44fa4c7 | 2016-07-15 08:38:38 +0000 | [diff] [blame] | 935 | // If the path looks like %USERPROFILE%/foo/bar, don't convert. |
| 936 | if (path.empty() || path[0] == '%') { |
László Csomor | 78b8be6 | 2017-03-28 09:04:06 +0000 | [diff] [blame] | 937 | // It's fine to convert to lower-case even if the path contains environment |
| 938 | // variable names, since Windows can look them up case-insensitively. |
| 939 | return blaze_util::AsLower(path); |
Yun Peng | 44fa4c7 | 2016-07-15 08:38:38 +0000 | [diff] [blame] | 940 | } |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 941 | char* wpath = static_cast<char*>(cygwin_create_path( |
| 942 | CCP_POSIX_TO_WIN_A, static_cast<const void*>(path.c_str()))); |
| 943 | string result(wpath); |
| 944 | free(wpath); |
László Csomor | 78b8be6 | 2017-03-28 09:04:06 +0000 | [diff] [blame] | 945 | return blaze_util::AsLower(result); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 946 | #endif // COMPILER_MSVC |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 947 | } |
| 948 | |
Yun Peng | 44fa4c7 | 2016-07-15 08:38:38 +0000 | [diff] [blame] | 949 | // Convert a Unix path list to Windows path list |
| 950 | string ConvertPathList(const string& path_list) { |
Laszlo Csomor | 9d3f989 | 2017-03-07 10:03:44 +0000 | [diff] [blame] | 951 | #ifdef COMPILER_MSVC |
| 952 | // In the MSVC version we use the actual %PATH% value which is separated by |
| 953 | // ";" and contains Windows paths. |
| 954 | return path_list; |
| 955 | #else // not COMPILER_MSVC |
Yun Peng | 44fa4c7 | 2016-07-15 08:38:38 +0000 | [diff] [blame] | 956 | string w_list = ""; |
| 957 | int start = 0; |
| 958 | int pos; |
| 959 | while ((pos = path_list.find(":", start)) != string::npos) { |
| 960 | w_list += ConvertPath(path_list.substr(start, pos - start)) + ";"; |
| 961 | start = pos + 1; |
| 962 | } |
| 963 | if (start < path_list.size()) { |
| 964 | w_list += ConvertPath(path_list.substr(start)); |
| 965 | } |
| 966 | return w_list; |
Laszlo Csomor | 9d3f989 | 2017-03-07 10:03:44 +0000 | [diff] [blame] | 967 | #endif // COMPILER_MSVC |
Yun Peng | 44fa4c7 | 2016-07-15 08:38:38 +0000 | [diff] [blame] | 968 | } |
| 969 | |
Lukacs Berki | 497d824 | 2016-04-28 07:21:26 +0000 | [diff] [blame] | 970 | bool SymlinkDirectories(const string &posix_target, const string &posix_name) { |
Laszlo Csomor | 38db316 | 2017-02-15 13:54:32 +0000 | [diff] [blame] | 971 | wstring name; |
| 972 | wstring target; |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 973 | if (!blaze_util::AsAbsoluteWindowsPath(posix_name, &name)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 974 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 975 | "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)", |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 976 | posix_target.c_str(), posix_name.c_str(), posix_target.c_str()); |
Laszlo Csomor | a669557 | 2017-01-18 10:58:17 +0000 | [diff] [blame] | 977 | return false; |
| 978 | } |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 979 | if (!blaze_util::AsAbsoluteWindowsPath(posix_target, &target)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 980 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 981 | "SymlinkDirectories(%s, %s): AsAbsoluteWindowsPath(%s)", |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 982 | posix_target.c_str(), posix_name.c_str(), posix_name.c_str()); |
Lukacs Berki | 497d824 | 2016-04-28 07:21:26 +0000 | [diff] [blame] | 983 | return false; |
| 984 | } |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 985 | string error(CreateJunction(name, target)); |
Laszlo Csomor | 38db316 | 2017-02-15 13:54:32 +0000 | [diff] [blame] | 986 | if (!error.empty()) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 987 | blaze_util::PrintError("SymlinkDirectories(%s, %s): CreateJunction: %s", |
| 988 | posix_target.c_str(), posix_name.c_str(), |
| 989 | error.c_str()); |
Lukacs Berki | 497d824 | 2016-04-28 07:21:26 +0000 | [diff] [blame] | 990 | return false; |
| 991 | } |
Laszlo Csomor | 38db316 | 2017-02-15 13:54:32 +0000 | [diff] [blame] | 992 | return true; |
Lukacs Berki | 497d824 | 2016-04-28 07:21:26 +0000 | [diff] [blame] | 993 | } |
| 994 | |
Lukacs Berki | 497d824 | 2016-04-28 07:21:26 +0000 | [diff] [blame] | 995 | bool CompareAbsolutePaths(const string& a, const string& b) { |
László Csomor | 78b8be6 | 2017-03-28 09:04:06 +0000 | [diff] [blame] | 996 | return ConvertPath(a) == ConvertPath(b); |
Dmitry Lomov | 47afaab | 2016-02-19 08:21:13 +0000 | [diff] [blame] | 997 | } |
| 998 | |
Laszlo Csomor | 032ddee | 2017-03-21 10:36:03 +0000 | [diff] [blame] | 999 | #ifndef STILL_ACTIVE |
| 1000 | #define STILL_ACTIVE (259) // From MSDN about GetExitCodeProcess. |
| 1001 | #endif |
| 1002 | |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 1003 | // On Windows (and Linux) we use a combination of PID and start time to identify |
| 1004 | // the server process. That is supposed to be unique unless one can start more |
| 1005 | // processes than there are PIDs available within a single jiffy. |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 1006 | bool VerifyServerProcess(int pid, const string& output_base) { |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 1007 | AutoHandle process( |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 1008 | ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); |
| 1009 | if (!process.IsValid()) { |
| 1010 | // Cannot find the server process. Can happen if the PID file is stale. |
| 1011 | return false; |
| 1012 | } |
| 1013 | |
Laszlo Csomor | 032ddee | 2017-03-21 10:36:03 +0000 | [diff] [blame] | 1014 | DWORD exit_code = 0; |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 1015 | uint64_t start_time = 0; |
Laszlo Csomor | 032ddee | 2017-03-21 10:36:03 +0000 | [diff] [blame] | 1016 | if (!::GetExitCodeProcess(process, &exit_code) || exit_code != STILL_ACTIVE || |
| 1017 | !GetProcessStartupTime(process, &start_time)) { |
| 1018 | // Process doesn't exist or died meantime, all is good. No stale server is |
| 1019 | // present. |
Laszlo Csomor | d0512c4 | 2017-03-20 16:10:39 +0000 | [diff] [blame] | 1020 | return false; |
| 1021 | } |
| 1022 | |
| 1023 | string recorded_start_time; |
| 1024 | bool file_present = blaze_util::ReadFile( |
| 1025 | blaze_util::JoinPath(output_base, "server/server.starttime"), |
| 1026 | &recorded_start_time); |
| 1027 | |
| 1028 | // If start time file got deleted, but PID file didn't, assume that this is an |
| 1029 | // old Bazel process that doesn't know how to write start time files yet. |
| 1030 | return !file_present || recorded_start_time == ToString(start_time); |
Lukacs Berki | ee44c38 | 2016-09-14 10:53:37 +0000 | [diff] [blame] | 1031 | } |
| 1032 | |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 1033 | bool KillServerProcess(int pid, const string& output_base) { |
Laszlo Csomor | f070234 | 2017-06-28 16:05:23 +0200 | [diff] [blame] | 1034 | AutoHandle process(::OpenProcess( |
Laszlo Csomor | 032ddee | 2017-03-21 10:36:03 +0000 | [diff] [blame] | 1035 | PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); |
| 1036 | DWORD exitcode = 0; |
| 1037 | if (!process.IsValid() || !::GetExitCodeProcess(process, &exitcode) || |
| 1038 | exitcode != STILL_ACTIVE) { |
| 1039 | // Cannot find the server process (can happen if the PID file is stale) or |
| 1040 | // it already exited. |
Lukacs Berki | 119dd4b | 2016-07-13 15:28:42 +0000 | [diff] [blame] | 1041 | return false; |
Lukacs Berki | d825a3d | 2016-06-23 11:10:02 +0000 | [diff] [blame] | 1042 | } |
| 1043 | |
Laszlo Csomor | c8cd6bd | 2017-03-14 11:10:04 +0000 | [diff] [blame] | 1044 | BOOL result = TerminateProcess(process, /*uExitCode*/ 0); |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 1045 | if (!result || !AwaitServerProcessTermination(pid, output_base, |
| 1046 | kPostKillGracePeriodSeconds)) { |
| 1047 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1048 | "Cannot terminate server process with PID %d, output_base=(%s)", pid, |
| 1049 | output_base.c_str()); |
Lukacs Berki | d825a3d | 2016-06-23 11:10:02 +0000 | [diff] [blame] | 1050 | } |
Lukacs Berki | 119dd4b | 2016-07-13 15:28:42 +0000 | [diff] [blame] | 1051 | return result; |
Lukacs Berki | 1977d92 | 2016-05-02 09:31:37 +0000 | [diff] [blame] | 1052 | } |
| 1053 | |
mschaller | fd37b51 | 2017-07-11 18:21:36 +0200 | [diff] [blame] | 1054 | void TrySleep(unsigned int milliseconds) { |
| 1055 | Sleep(milliseconds); |
| 1056 | } |
| 1057 | |
Dave MacLachlan | 6b747ee | 2016-07-20 10:00:44 +0000 | [diff] [blame] | 1058 | // Not supported. |
| 1059 | void ExcludePathFromBackup(const string &path) { |
| 1060 | } |
| 1061 | |
Laszlo Csomor | 6bf9576 | 2016-11-16 13:29:22 +0000 | [diff] [blame] | 1062 | string GetHashedBaseDir(const string& root, const string& hashable) { |
| 1063 | // Builds a shorter output base dir name for Windows. |
| 1064 | // This algorithm only uses 1/3 of the bits to get 8-char alphanumeric |
| 1065 | // file name. |
| 1066 | |
| 1067 | static const char* alphabet |
| 1068 | // Exactly 64 characters. |
John Cater | b64349e | 2017-01-05 20:26:48 +0000 | [diff] [blame] | 1069 | = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789_-"; |
Laszlo Csomor | 6bf9576 | 2016-11-16 13:29:22 +0000 | [diff] [blame] | 1070 | |
| 1071 | // The length of the resulting filename (8 characters). |
| 1072 | static const int filename_length = blaze_util::Md5Digest::kDigestLength / 2; |
| 1073 | unsigned char buf[blaze_util::Md5Digest::kDigestLength]; |
| 1074 | char coded_name[filename_length + 1]; |
| 1075 | blaze_util::Md5Digest digest; |
| 1076 | digest.Update(hashable.data(), hashable.size()); |
| 1077 | digest.Finish(buf); |
| 1078 | for (int i = 0; i < filename_length; i++) { |
| 1079 | coded_name[i] = alphabet[buf[i] & 0x3F]; |
| 1080 | } |
| 1081 | coded_name[filename_length] = '\0'; |
Laszlo Csomor | 760f786 | 2016-12-19 15:46:47 +0000 | [diff] [blame] | 1082 | return blaze_util::JoinPath(root, string(coded_name)); |
Laszlo Csomor | 6bf9576 | 2016-11-16 13:29:22 +0000 | [diff] [blame] | 1083 | } |
| 1084 | |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1085 | void CreateSecureOutputRoot(const string& path) { |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1086 | // TODO(bazel-team): implement this properly, by mimicing whatever the POSIX |
| 1087 | // implementation does. |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1088 | const char* root = path.c_str(); |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1089 | if (!blaze_util::MakeDirectories(path, 0755)) { |
| 1090 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1091 | "MakeDirectories(%s) failed", root); |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1092 | } |
| 1093 | |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1094 | #ifndef COMPILER_MSVC |
| 1095 | struct stat fileinfo = {}; |
| 1096 | |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1097 | // The path already exists. |
| 1098 | // Check ownership and mode, and verify that it is a directory. |
| 1099 | |
| 1100 | if (lstat(root, &fileinfo) < 0) { |
| 1101 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "lstat('%s')", root); |
| 1102 | } |
| 1103 | |
| 1104 | if (fileinfo.st_uid != geteuid()) { |
| 1105 | die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not owned by me", |
| 1106 | root); |
| 1107 | } |
| 1108 | |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1109 | // Ensure the permission mask is indeed 0755 (rwxr-xr-x). |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1110 | if ((fileinfo.st_mode & 022) != 0) { |
| 1111 | int new_mode = fileinfo.st_mode & (~022); |
| 1112 | if (chmod(root, new_mode) < 0) { |
| 1113 | die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 1114 | "'%s' has mode %o, chmod to %o failed", root, |
| 1115 | fileinfo.st_mode & 07777, new_mode); |
| 1116 | } |
| 1117 | } |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1118 | #endif // not COMPILER_MSVC |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1119 | |
Laszlo Csomor | 5eb3ef9 | 2017-02-07 16:52:38 +0000 | [diff] [blame] | 1120 | if (!blaze_util::IsDirectory(path)) { |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1121 | die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not a directory", |
| 1122 | root); |
| 1123 | } |
| 1124 | |
| 1125 | ExcludePathFromBackup(root); |
Laszlo Csomor | 8a48f61 | 2016-11-17 10:18:34 +0000 | [diff] [blame] | 1126 | } |
| 1127 | |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1128 | string GetEnv(const string& name) { |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 1129 | DWORD size = ::GetEnvironmentVariableA(name.c_str(), NULL, 0); |
| 1130 | if (size == 0) { |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1131 | #ifdef COMPILER_MSVC |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 1132 | return string(); // unset or empty envvar |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1133 | #else // not COMPILER_MSVC |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 1134 | char* result = getenv(name.c_str()); |
| 1135 | return result != NULL ? string(result) : string(); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1136 | #endif // COMPILER_MSVC |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 1137 | } |
| 1138 | |
| 1139 | unique_ptr<char[]> value(new char[size]); |
| 1140 | ::GetEnvironmentVariableA(name.c_str(), value.get(), size); |
| 1141 | return string(value.get()); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1142 | } |
| 1143 | |
| 1144 | void SetEnv(const string& name, const string& value) { |
Dmitry Lomov | 5a0d6ff | 2017-08-22 14:51:49 +0200 | [diff] [blame^] | 1145 | // _putenv_s both calls ::SetEnvionmentVariableA and updates environ(5). |
| 1146 | _putenv_s(name.c_str(), value.c_str()); |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1147 | } |
| 1148 | |
Laszlo Csomor | f2b5f27 | 2017-01-18 09:51:57 +0000 | [diff] [blame] | 1149 | void UnsetEnv(const string& name) { SetEnv(name, ""); } |
Laszlo Csomor | cefa9a2 | 2016-11-22 10:50:07 +0000 | [diff] [blame] | 1150 | |
Laszlo Csomor | c4fb218 | 2017-07-27 16:37:08 +0200 | [diff] [blame] | 1151 | bool WarnIfStartedFromDesktop() { |
| 1152 | // GetConsoleProcessList returns: |
| 1153 | // 0, if no console attached (Bazel runs as a subprocess) |
| 1154 | // 1, if Bazel was started by clicking on its icon |
| 1155 | // 2, if Bazel was started from the command line (even if its output is |
| 1156 | // redirected) |
| 1157 | DWORD dummy[2] = {0}; |
| 1158 | if (GetConsoleProcessList(dummy, 2) != 1) { |
| 1159 | return false; |
| 1160 | } |
| 1161 | printf( |
| 1162 | "Bazel is a command line tool.\n\n" |
| 1163 | "Try opening a console, such as the Windows Command Prompt (cmd.exe) " |
| 1164 | "or PowerShell, and running \"bazel help\".\n\n" |
| 1165 | "Press Enter to close this window..."); |
| 1166 | ReadFile(GetStdHandle(STD_INPUT_HANDLE), dummy, 1, dummy, NULL); |
| 1167 | return true; |
| 1168 | } |
| 1169 | |
Laszlo Csomor | 41ac1e0 | 2017-03-20 15:25:44 +0000 | [diff] [blame] | 1170 | #ifndef ENABLE_PROCESSED_OUTPUT |
| 1171 | // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). |
| 1172 | #define ENABLE_PROCESSED_OUTPUT 0x0001 |
| 1173 | #endif // not ENABLE_PROCESSED_OUTPUT |
| 1174 | |
| 1175 | #ifndef ENABLE_WRAP_AT_EOL_OUTPUT |
| 1176 | // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). |
| 1177 | #define ENABLE_WRAP_AT_EOL_OUTPUT 0x0002 |
| 1178 | #endif // not ENABLE_WRAP_AT_EOL_OUTPUT |
| 1179 | |
| 1180 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING |
| 1181 | // From MSDN about BOOL SetConsoleMode(HANDLE, DWORD). |
| 1182 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 |
| 1183 | #endif // not ENABLE_VIRTUAL_TERMINAL_PROCESSING |
| 1184 | |
Laszlo Csomor | 74ffaf7 | 2016-11-24 12:17:20 +0000 | [diff] [blame] | 1185 | void SetupStdStreams() { |
| 1186 | #ifdef COMPILER_MSVC |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 1187 | static const DWORD stdhandles[] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, |
| 1188 | STD_ERROR_HANDLE}; |
Laszlo Csomor | 3d97f49 | 2017-03-16 12:58:23 +0000 | [diff] [blame] | 1189 | for (int i = 0; i <= 2; ++i) { |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 1190 | HANDLE handle = ::GetStdHandle(stdhandles[i]); |
| 1191 | if (handle == INVALID_HANDLE_VALUE || handle == NULL) { |
| 1192 | // Ensure we have open fds to each std* stream. Otherwise we can end up |
| 1193 | // with bizarre things like stdout going to the lock file, etc. |
| 1194 | _open("NUL", (i == 0) ? _O_RDONLY : _O_WRONLY); |
| 1195 | } |
Laszlo Csomor | 41ac1e0 | 2017-03-20 15:25:44 +0000 | [diff] [blame] | 1196 | DWORD mode = 0; |
| 1197 | if (i > 0 && handle != INVALID_HANDLE_VALUE && handle != NULL && |
| 1198 | ::GetConsoleMode(handle, &mode)) { |
| 1199 | DWORD newmode = mode | ENABLE_PROCESSED_OUTPUT | |
| 1200 | ENABLE_WRAP_AT_EOL_OUTPUT | |
| 1201 | ENABLE_VIRTUAL_TERMINAL_PROCESSING; |
| 1202 | if (mode != newmode) { |
| 1203 | // We don't care about the success of this. Worst that can happen if |
| 1204 | // this method fails is that the console won't understand control |
| 1205 | // characters like color change or carriage return. |
| 1206 | ::SetConsoleMode(handle, newmode); |
| 1207 | } |
| 1208 | } |
Laszlo Csomor | 26f858c | 2017-02-08 17:50:10 +0000 | [diff] [blame] | 1209 | } |
Laszlo Csomor | 74ffaf7 | 2016-11-24 12:17:20 +0000 | [diff] [blame] | 1210 | #else // not COMPILER_MSVC |
| 1211 | // Set non-buffered output mode for stderr/stdout. The server already |
| 1212 | // line-buffers messages where it makes sense, so there's no need to do set |
| 1213 | // line-buffering here. On the other hand the server sometimes sends binary |
| 1214 | // output (when for example a query returns results as proto), in which case |
| 1215 | // we must not perform line buffering on the client side. So turn off |
| 1216 | // buffering here completely. |
| 1217 | setvbuf(stdout, NULL, _IONBF, 0); |
| 1218 | setvbuf(stderr, NULL, _IONBF, 0); |
| 1219 | |
| 1220 | // Ensure we have three open fds. Otherwise we can end up with |
| 1221 | // bizarre things like stdout going to the lock file, etc. |
| 1222 | if (fcntl(STDIN_FILENO, F_GETFL) == -1) open("/dev/null", O_RDONLY); |
| 1223 | if (fcntl(STDOUT_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); |
| 1224 | if (fcntl(STDERR_FILENO, F_GETFL) == -1) open("/dev/null", O_WRONLY); |
| 1225 | #endif // COMPILER_MSVC |
| 1226 | } |
| 1227 | |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 1228 | LARGE_INTEGER WindowsClock::GetFrequency() { |
| 1229 | LARGE_INTEGER result; |
| 1230 | if (!QueryPerformanceFrequency(&result)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1231 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 1232 | "WindowsClock::GetFrequency: QueryPerformanceFrequency"); |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 1233 | } |
| 1234 | |
| 1235 | // On ancient Windows versions (pre-XP) and specific hardware the result may |
| 1236 | // be 0. Since this is pre-XP, we don't handle that, just error out. |
| 1237 | if (result.QuadPart <= 0) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1238 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 1239 | "WindowsClock::GetFrequency: QueryPerformanceFrequency returned " |
| 1240 | "invalid result (%llu)\n", |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 1241 | result.QuadPart); |
| 1242 | } |
| 1243 | |
| 1244 | return result; |
| 1245 | } |
| 1246 | |
| 1247 | LARGE_INTEGER WindowsClock::GetMillisecondsAsLargeInt( |
| 1248 | const LARGE_INTEGER& freq) { |
| 1249 | LARGE_INTEGER counter; |
| 1250 | if (!QueryPerformanceCounter(&counter)) { |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1251 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
| 1252 | "WindowsClock::GetMillisecondsAsLargeInt: QueryPerformanceCounter"); |
Laszlo Csomor | a85f52d | 2016-11-08 13:43:23 +0000 | [diff] [blame] | 1253 | } |
| 1254 | |
| 1255 | LARGE_INTEGER result; |
| 1256 | result.QuadPart = |
| 1257 | // seconds |
| 1258 | (counter.QuadPart / freq.QuadPart) * 1000LL + |
| 1259 | // milliseconds |
| 1260 | (((counter.QuadPart % freq.QuadPart) * 1000LL) / freq.QuadPart); |
| 1261 | |
| 1262 | return result; |
| 1263 | } |
| 1264 | |
| 1265 | const WindowsClock WindowsClock::INSTANCE; |
| 1266 | |
| 1267 | WindowsClock::WindowsClock() |
| 1268 | : kFrequency(GetFrequency()), |
| 1269 | kStart(GetMillisecondsAsLargeInt(kFrequency)) {} |
| 1270 | |
| 1271 | uint64_t WindowsClock::GetMilliseconds() const { |
| 1272 | return GetMillisecondsAsLargeInt(kFrequency).QuadPart; |
| 1273 | } |
| 1274 | |
| 1275 | uint64_t WindowsClock::GetProcessMilliseconds() const { |
| 1276 | return GetMilliseconds() - kStart.QuadPart; |
| 1277 | } |
| 1278 | |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1279 | uint64_t AcquireLock(const string& output_base, bool batch_mode, bool block, |
| 1280 | BlazeLock* blaze_lock) { |
Laszlo Csomor | 760f786 | 2016-12-19 15:46:47 +0000 | [diff] [blame] | 1281 | string lockfile = blaze_util::JoinPath(output_base, "lock"); |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1282 | wstring wlockfile; |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 1283 | if (!blaze_util::AsAbsoluteWindowsPath(lockfile, &wlockfile)) { |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1284 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
laszlocsomor | b69acfa | 2017-07-21 13:18:13 +0200 | [diff] [blame] | 1285 | "AcquireLock(%s): AsAbsoluteWindowsPath(%s)", output_base.c_str(), |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1286 | lockfile.c_str()); |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1287 | } |
| 1288 | |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1289 | blaze_lock->handle = INVALID_HANDLE_VALUE; |
| 1290 | bool first_lock_attempt = true; |
| 1291 | uint64_t st = GetMillisecondsMonotonic(); |
| 1292 | while (true) { |
| 1293 | blaze_lock->handle = ::CreateFileW( |
| 1294 | /* lpFileName */ wlockfile.c_str(), |
| 1295 | /* dwDesiredAccess */ GENERIC_READ | GENERIC_WRITE, |
| 1296 | /* dwShareMode */ FILE_SHARE_READ, |
| 1297 | /* lpSecurityAttributes */ NULL, |
| 1298 | /* dwCreationDisposition */ CREATE_ALWAYS, |
| 1299 | /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| 1300 | /* hTemplateFile */ NULL); |
| 1301 | if (blaze_lock->handle != INVALID_HANDLE_VALUE) { |
| 1302 | // We could open the file, so noone else holds a lock on it. |
| 1303 | break; |
| 1304 | } |
| 1305 | if (GetLastError() == ERROR_SHARING_VIOLATION) { |
| 1306 | // Someone else has the lock. |
| 1307 | if (!block) { |
| 1308 | die(blaze_exit_code::BAD_ARGV, |
| 1309 | "Another command is running. Exiting immediately."); |
| 1310 | } |
| 1311 | if (first_lock_attempt) { |
| 1312 | first_lock_attempt = false; |
| 1313 | fprintf(stderr, |
| 1314 | "Another command is running. Waiting for it to complete..."); |
| 1315 | fflush(stderr); |
| 1316 | } |
| 1317 | Sleep(/* dwMilliseconds */ 200); |
| 1318 | } else { |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1319 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1320 | "AcquireLock(%s): CreateFileW(%ls)", lockfile.c_str(), |
| 1321 | wlockfile.c_str()); |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1322 | } |
| 1323 | } |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1324 | uint64_t wait_time = GetMillisecondsMonotonic() - st; |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1325 | |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1326 | // We have the lock. |
| 1327 | OVERLAPPED overlapped = {0}; |
| 1328 | if (!LockFileEx( |
| 1329 | /* hFile */ blaze_lock->handle, |
| 1330 | /* dwFlags */ LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, |
| 1331 | /* dwReserved */ 0, |
| 1332 | /* nNumberOfBytesToLockLow */ 1, |
| 1333 | /* nNumberOfBytesToLockHigh */ 0, |
| 1334 | /* lpOverlapped */ &overlapped)) { |
| 1335 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1336 | "AcquireLock(%s): LockFileEx(%ls)", lockfile.c_str(), |
| 1337 | wlockfile.c_str()); |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1338 | } |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1339 | // On other platforms we write some info about this process into the lock file |
| 1340 | // such as the server PID. On Windows we don't do that because the file is |
| 1341 | // locked exclusively, meaning other processes may not open the file even for |
| 1342 | // reading. |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1343 | |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1344 | return wait_time; |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1345 | } |
| 1346 | |
| 1347 | void ReleaseLock(BlazeLock* blaze_lock) { |
laszlocsomor | ade79a7 | 2017-04-12 10:08:16 +0000 | [diff] [blame] | 1348 | OVERLAPPED overlapped = {0}; |
| 1349 | UnlockFileEx(blaze_lock->handle, 0, 1, 0, &overlapped); |
| 1350 | CloseHandle(blaze_lock->handle); |
Laszlo Csomor | 7e5fb40 | 2016-11-25 15:11:08 +0000 | [diff] [blame] | 1351 | } |
| 1352 | |
Laszlo Csomor | 99b0154 | 2017-03-02 11:43:21 +0000 | [diff] [blame] | 1353 | #ifdef GetUserName |
| 1354 | // By including <windows.h>, we have GetUserName defined either as |
| 1355 | // GetUserNameA or GetUserNameW. |
| 1356 | #undef GetUserName |
| 1357 | #endif |
| 1358 | |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1359 | string GetUserName() { |
Laszlo Csomor | 41ee591 | 2016-12-15 12:26:06 +0000 | [diff] [blame] | 1360 | WCHAR buffer[UNLEN + 1]; |
| 1361 | DWORD len = UNLEN + 1; |
Laszlo Csomor | 6d47707 | 2017-07-20 10:09:44 +0200 | [diff] [blame] | 1362 | if (!::GetUserNameW(buffer, &len)) { |
| 1363 | pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "GetUserNameW"); |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1364 | } |
Laszlo Csomor | 41ee591 | 2016-12-15 12:26:06 +0000 | [diff] [blame] | 1365 | return string(blaze_util::WstringToCstring(buffer).get()); |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1366 | } |
| 1367 | |
| 1368 | bool IsEmacsTerminal() { |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1369 | string emacs = GetEnv("EMACS"); |
| 1370 | string inside_emacs = GetEnv("INSIDE_EMACS"); |
| 1371 | // GNU Emacs <25.1 (and ~all non-GNU emacsen) set EMACS=t, but >=25.1 doesn't |
| 1372 | // do that and instead sets INSIDE_EMACS=<stuff> (where <stuff> can look like |
| 1373 | // e.g. "25.1.1,comint"). So we check both variables for maximum |
| 1374 | // compatibility. |
| 1375 | return emacs == "t" || !inside_emacs.empty(); |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1376 | } |
| 1377 | |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1378 | // Returns true iff both stdout and stderr are connected to a |
| 1379 | // terminal, and it can support color and cursor movement |
| 1380 | // (this is computed heuristically based on the values of |
| 1381 | // environment variables). |
| 1382 | bool IsStandardTerminal() { |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1383 | #ifdef COMPILER_MSVC |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1384 | for (DWORD i : {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}) { |
| 1385 | DWORD mode = 0; |
| 1386 | HANDLE handle = ::GetStdHandle(i); |
| 1387 | // handle may be invalid when std{out,err} is redirected |
| 1388 | if (handle == INVALID_HANDLE_VALUE || !::GetConsoleMode(handle, &mode) || |
| 1389 | !(mode & ENABLE_PROCESSED_OUTPUT) || |
| 1390 | !(mode & ENABLE_WRAP_AT_EOL_OUTPUT) || |
| 1391 | !(mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { |
| 1392 | return false; |
| 1393 | } |
Laszlo Csomor | 41ac1e0 | 2017-03-20 15:25:44 +0000 | [diff] [blame] | 1394 | } |
| 1395 | return true; |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1396 | #else // not COMPILER_MSVC |
| 1397 | string term = GetEnv("TERM"); |
| 1398 | if (term.empty() || term == "dumb" || term == "emacs" || |
| 1399 | term == "xterm-mono" || term == "symbolics" || term == "9term" || |
| 1400 | IsEmacsTerminal()) { |
| 1401 | return false; |
| 1402 | } |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1403 | return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO); |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1404 | #endif // COMPILER_MSVC |
| 1405 | } |
| 1406 | |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1407 | // Returns the number of columns of the terminal to which stdout is |
| 1408 | // connected, or $COLUMNS (default 80) if there is no such terminal. |
| 1409 | int GetTerminalColumns() { |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 1410 | #ifndef COMPILER_MSVC |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1411 | struct winsize ws; |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1412 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) { |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1413 | return ws.ws_col; |
| 1414 | } |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 1415 | #endif // not COMPILER_MSVC |
| 1416 | |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1417 | string columns_env = GetEnv("COLUMNS"); |
| 1418 | if (!columns_env.empty()) { |
| 1419 | char* endptr; |
| 1420 | int columns = blaze_util::strto32(columns_env.c_str(), &endptr, 10); |
| 1421 | if (*endptr == '\0') { // $COLUMNS is a valid number |
| 1422 | return columns; |
| 1423 | } |
| 1424 | } |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 1425 | |
Googler | d290ece | 2017-07-11 01:23:01 +0200 | [diff] [blame] | 1426 | HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE); |
Laszlo Csomor | 3d97f49 | 2017-03-16 12:58:23 +0000 | [diff] [blame] | 1427 | if (stdout_handle != INVALID_HANDLE_VALUE) { |
| 1428 | // stdout_handle may be invalid when stdout is redirected. |
| 1429 | CONSOLE_SCREEN_BUFFER_INFO screen_info; |
| 1430 | if (GetConsoleScreenBufferInfo(stdout_handle, &screen_info)) { |
| 1431 | int width = 1 + screen_info.srWindow.Right - screen_info.srWindow.Left; |
| 1432 | if (width > 1) { |
| 1433 | return width; |
| 1434 | } |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 1435 | } |
| 1436 | } |
László Csomor | 7287921 | 2017-02-02 15:55:18 +0000 | [diff] [blame] | 1437 | |
| 1438 | return 80; // default if not a terminal. |
Laszlo Csomor | d0a1269 | 2016-11-28 13:35:23 +0000 | [diff] [blame] | 1439 | } |
| 1440 | |
jmmv | a96369c | 2017-07-10 18:14:36 +0200 | [diff] [blame] | 1441 | bool UnlimitResources() { |
| 1442 | return true; // Nothing to do so assume success. |
| 1443 | } |
| 1444 | |
Dmitry Lomov | 891f540 | 2017-07-19 12:26:39 +0200 | [diff] [blame] | 1445 | static const int MAX_KEY_LENGTH = 255; |
| 1446 | // We do not care about registry values longer than MAX_PATH |
| 1447 | static const int REG_VALUE_BUFFER_SIZE = MAX_PATH; |
| 1448 | |
| 1449 | // Implements heuristics to discover msys2 installation. |
| 1450 | static string GetMsysBash() { |
| 1451 | HKEY h_uninstall; |
| 1452 | |
| 1453 | // MSYS2 installer writes its registry into HKCU, although documentation |
| 1454 | // (https://msdn.microsoft.com/en-us/library/ms954376.aspx) |
| 1455 | // clearly states that it should go to HKLM. |
| 1456 | static const char* const key = |
| 1457 | "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; |
| 1458 | if (RegOpenKeyExA(HKEY_CURRENT_USER, // _In_ HKEY hKey, |
| 1459 | key, // _In_opt_ LPCTSTR lpSubKey, |
| 1460 | 0, // _In_ DWORD ulOptions, |
| 1461 | KEY_ENUMERATE_SUB_KEYS | |
| 1462 | KEY_QUERY_VALUE, // _In_ REGSAM samDesired, |
| 1463 | &h_uninstall // _Out_ PHKEY phkResult |
| 1464 | )) { |
| 1465 | debug_log("Cannot open HKCU\\%s", key); |
| 1466 | return string(); |
| 1467 | } |
| 1468 | AutoHandle auto_uninstall(h_uninstall); |
| 1469 | |
| 1470 | // Since MSYS2 decided to generate a new product key for each installation, |
| 1471 | // we enumerate all keys under |
| 1472 | // HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall and find the first |
| 1473 | // with MSYS2 64bit display name. |
| 1474 | static const char* const msys_display_name = "MSYS2 64bit"; |
| 1475 | DWORD n_subkeys; |
| 1476 | |
| 1477 | if (RegQueryInfoKey(h_uninstall, // _In_ HKEY hKey, |
| 1478 | 0, // _Out_opt_ LPTSTR lpClass, |
| 1479 | 0, // _Inout_opt_ LPDWORD lpcClass, |
| 1480 | 0, // _Reserved_ LPDWORD lpReserved, |
| 1481 | &n_subkeys, // _Out_opt_ LPDWORD lpcSubKeys, |
| 1482 | 0, // _Out_opt_ LPDWORD lpcMaxSubKeyLen, |
| 1483 | 0, // _Out_opt_ LPDWORD lpcMaxClassLen, |
| 1484 | 0, // _Out_opt_ LPDWORD lpcValues, |
| 1485 | 0, // _Out_opt_ LPDWORD lpcMaxValueNameLen, |
| 1486 | 0, // _Out_opt_ LPDWORD lpcMaxValueLen, |
| 1487 | 0, // _Out_opt_ LPDWORD lpcbSecurityDescriptor, |
| 1488 | 0 // _Out_opt_ PFILETIME lpftLastWriteTime |
| 1489 | )) { |
| 1490 | debug_log("Cannot query HKCU\\%s", key); |
| 1491 | return string(); |
| 1492 | } |
| 1493 | |
| 1494 | for (DWORD key_index = 0; key_index < n_subkeys; key_index++) { |
| 1495 | char subkey_name[MAX_KEY_LENGTH]; |
| 1496 | if (RegEnumKeyA(h_uninstall, // _In_ HKEY hKey, |
| 1497 | key_index, // _In_ DWORD dwIndex, |
| 1498 | subkey_name, // _Out_ LPTSTR lpName, |
| 1499 | sizeof(subkey_name) // _In_ DWORD cchName |
| 1500 | )) { |
| 1501 | debug_log("Cannot get %d subkey of HKCU\\%s", key_index, key); |
| 1502 | continue; // try next subkey |
| 1503 | } |
| 1504 | |
| 1505 | HKEY h_subkey; |
| 1506 | if (RegOpenKeyEx(h_uninstall, // _In_ HKEY hKey, |
| 1507 | subkey_name, // _In_opt_ LPCTSTR lpSubKey, |
| 1508 | 0, // _In_ DWORD ulOptions, |
| 1509 | KEY_QUERY_VALUE, // _In_ REGSAM samDesired, |
| 1510 | &h_subkey // _Out_ PHKEY phkResult |
| 1511 | )) { |
| 1512 | debug_log("Failed to open subkey HKCU\\%s\\%s", key, subkey_name); |
| 1513 | continue; // try next subkey |
| 1514 | } |
| 1515 | AutoHandle auto_subkey(h_subkey); |
| 1516 | |
| 1517 | BYTE value[REG_VALUE_BUFFER_SIZE]; |
| 1518 | DWORD value_length = sizeof(value); |
| 1519 | DWORD value_type; |
| 1520 | |
| 1521 | if (RegQueryValueEx(h_subkey, // _In_ HKEY hKey, |
| 1522 | "DisplayName", // _In_opt_ LPCTSTR lpValueName, |
| 1523 | 0, // _Reserved_ LPDWORD lpReserved, |
| 1524 | &value_type, // _Out_opt_ LPDWORD lpType, |
| 1525 | value, // _Out_opt_ LPBYTE lpData, |
| 1526 | &value_length // _Inout_opt_ LPDWORD lpcbData |
| 1527 | )) { |
| 1528 | debug_log("Failed to query DisplayName of HKCU\\%s\\%s", key, |
| 1529 | subkey_name); |
| 1530 | continue; // try next subkey |
| 1531 | } |
| 1532 | |
| 1533 | if (value_type == REG_SZ && |
| 1534 | 0 == memcmp(msys_display_name, value, sizeof(msys_display_name))) { |
| 1535 | debug_log("Getting install location of HKCU\\%s\\%s", key, subkey_name); |
| 1536 | BYTE path[REG_VALUE_BUFFER_SIZE]; |
| 1537 | DWORD path_length = sizeof(path); |
| 1538 | DWORD path_type; |
| 1539 | if (RegQueryValueEx( |
| 1540 | h_subkey, // _In_ HKEY hKey, |
| 1541 | "InstallLocation", // _In_opt_ LPCTSTR lpValueName, |
| 1542 | 0, // _Reserved_ LPDWORD lpReserved, |
| 1543 | &path_type, // _Out_opt_ LPDWORD lpType, |
| 1544 | path, // _Out_opt_ LPBYTE lpData, |
| 1545 | &path_length // _Inout_opt_ LPDWORD lpcbData |
| 1546 | )) { |
| 1547 | debug_log("Failed to query InstallLocation of HKCU\\%s\\%s", key, |
| 1548 | subkey_name); |
| 1549 | continue; // try next subkey |
| 1550 | } |
| 1551 | |
| 1552 | if (path_length == 0 || path_type != REG_SZ) { |
| 1553 | debug_log("Zero-length (%d) install location or wrong type (%d)", |
| 1554 | path_length, path_type); |
| 1555 | continue; // try next subkey |
| 1556 | } |
| 1557 | |
| 1558 | debug_log("Install location of HKCU\\%s\\%s is %s", key, subkey_name, |
| 1559 | path); |
| 1560 | string path_as_string(path, path + path_length - 1); |
| 1561 | string bash_exe = path_as_string + "\\usr\\bin\\bash.exe"; |
| 1562 | if (!blaze_util::PathExists(bash_exe)) { |
| 1563 | debug_log("%s does not exist", bash_exe.c_str()); |
| 1564 | continue; // try next subkey |
| 1565 | } |
| 1566 | |
| 1567 | debug_log("Detected msys bash at %s", bash_exe.c_str()); |
| 1568 | return bash_exe; |
| 1569 | } |
| 1570 | } |
| 1571 | return string(); |
| 1572 | } |
| 1573 | |
| 1574 | // Implements heuristics to discover Git-on-Win installation. |
| 1575 | static string GetBashFromGitOnWin() { |
| 1576 | HKEY h_GitOnWin_uninstall; |
| 1577 | |
| 1578 | // Well-known registry key for Git-on-Windows. |
| 1579 | static const char* const key = |
| 1580 | "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"; |
| 1581 | if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, // _In_ HKEY hKey, |
| 1582 | key, // _In_opt_ LPCTSTR lpSubKey, |
| 1583 | 0, // _In_ DWORD ulOptions, |
| 1584 | KEY_QUERY_VALUE, // _In_ REGSAM samDesired, |
| 1585 | &h_GitOnWin_uninstall // _Out_ PHKEY phkResult |
| 1586 | )) { |
| 1587 | debug_log("Cannot open HKCU\\%s", key); |
| 1588 | return string(); |
| 1589 | } |
| 1590 | AutoHandle auto_h_GitOnWin_uninstall(h_GitOnWin_uninstall); |
| 1591 | |
| 1592 | debug_log("Getting install location of HKLM\\%s", key); |
| 1593 | BYTE path[REG_VALUE_BUFFER_SIZE]; |
| 1594 | DWORD path_length = sizeof(path); |
| 1595 | DWORD path_type; |
| 1596 | if (RegQueryValueEx(h_GitOnWin_uninstall, // _In_ HKEY hKey, |
| 1597 | "InstallLocation", // _In_opt_ LPCTSTR lpValueName, |
| 1598 | 0, // _Reserved_ LPDWORD lpReserved, |
| 1599 | &path_type, // _Out_opt_ LPDWORD lpType, |
| 1600 | path, // _Out_opt_ LPBYTE lpData, |
| 1601 | &path_length // _Inout_opt_ LPDWORD lpcbData |
| 1602 | )) { |
| 1603 | debug_log("Failed to query InstallLocation of HKLM\\%s", key); |
| 1604 | return string(); |
| 1605 | } |
| 1606 | |
| 1607 | if (path_length == 0 || path_type != REG_SZ) { |
| 1608 | debug_log("Zero-length (%d) install location or wrong type (%d)", |
| 1609 | path_length, path_type); |
| 1610 | return string(); |
| 1611 | } |
| 1612 | |
| 1613 | debug_log("Install location of HKLM\\%s is %s", key, path); |
| 1614 | string path_as_string(path, path + path_length - 1); |
| 1615 | string bash_exe = path_as_string + "\\usr\\bin\\bash.exe"; |
| 1616 | if (!blaze_util::PathExists(bash_exe)) { |
| 1617 | debug_log("%s does not exist", bash_exe.c_str()); |
| 1618 | return string(); |
| 1619 | } |
| 1620 | |
dslomov | 8970182 | 2017-07-20 10:02:27 +0200 | [diff] [blame] | 1621 | debug_log("Detected git-on-Windows bash at %s", bash_exe.c_str()); |
Dmitry Lomov | 891f540 | 2017-07-19 12:26:39 +0200 | [diff] [blame] | 1622 | return bash_exe; |
| 1623 | } |
| 1624 | |
| 1625 | static string GetBashFromPath() { |
| 1626 | char found[MAX_PATH]; |
| 1627 | string path_list = blaze::GetEnv("PATH"); |
| 1628 | |
| 1629 | // We do not fully replicate all the quirks of search in PATH. |
| 1630 | // There is no system function to do so, and that way lies madness. |
| 1631 | size_t start = 0; |
| 1632 | do { |
| 1633 | // This ignores possibly quoted semicolons in PATH etc. |
| 1634 | size_t end = path_list.find_first_of(";", start); |
| 1635 | string path = path_list.substr( |
| 1636 | start, end != string::npos ? end - start : string::npos); |
| 1637 | // Handle one typical way of quoting (where.exe does not handle this, but |
| 1638 | // CreateProcess does). |
| 1639 | if (path.size() > 1 && path[0] == '"' && path[path.size() - 1] == '"') { |
| 1640 | path = path.substr(1, path.size() - 2); |
| 1641 | } |
| 1642 | if (SearchPathA(path.c_str(), // _In_opt_ LPCTSTR lpPath, |
| 1643 | "bash.exe", // _In_ LPCTSTR lpFileName, |
| 1644 | 0, // LPCTSTR lpExtension, |
| 1645 | sizeof(found), // DWORD nBufferLength, |
| 1646 | found, // _Out_ LPTSTR lpBuffer, |
| 1647 | 0 // _Out_opt_ LPTSTR *lpFilePart |
| 1648 | )) { |
| 1649 | debug_log("bash.exe found on PATH: %s", found); |
| 1650 | return string(found); |
| 1651 | } |
| 1652 | if (end == string::npos) { |
| 1653 | break; |
| 1654 | } |
| 1655 | start = end + 1; |
| 1656 | } while (true); |
| 1657 | |
| 1658 | debug_log("bash.exe not found on PATH"); |
| 1659 | return string(); |
| 1660 | } |
| 1661 | |
| 1662 | static string LocateBash() { |
| 1663 | string msys_bash = GetMsysBash(); |
| 1664 | if (!msys_bash.empty()) { |
| 1665 | return msys_bash; |
| 1666 | } |
| 1667 | |
| 1668 | string git_on_win_bash = GetBashFromGitOnWin(); |
| 1669 | if (!git_on_win_bash.empty()) { |
| 1670 | return git_on_win_bash; |
| 1671 | } |
| 1672 | |
| 1673 | return GetBashFromPath(); |
| 1674 | } |
| 1675 | |
| 1676 | void DetectBashOrDie() { |
| 1677 | if (!blaze::GetEnv("BAZEL_SH").empty()) return; |
| 1678 | |
| 1679 | uint64_t start = blaze::GetMillisecondsMonotonic(); |
| 1680 | |
| 1681 | string bash = LocateBash(); |
| 1682 | uint64_t end = blaze::GetMillisecondsMonotonic(); |
Dmitry Lomov | 5a0d6ff | 2017-08-22 14:51:49 +0200 | [diff] [blame^] | 1683 | debug_log("BAZEL_SH detection took %lu msec, found %s", end - start, |
| 1684 | bash.c_str()); |
Dmitry Lomov | 891f540 | 2017-07-19 12:26:39 +0200 | [diff] [blame] | 1685 | |
| 1686 | if (!bash.empty()) { |
Dmitry Lomov | 5a0d6ff | 2017-08-22 14:51:49 +0200 | [diff] [blame^] | 1687 | // Set process environment variable. |
Dmitry Lomov | 891f540 | 2017-07-19 12:26:39 +0200 | [diff] [blame] | 1688 | blaze::SetEnv("BAZEL_SH", bash); |
| 1689 | } else { |
| 1690 | printf( |
| 1691 | "Bazel on Windows requires bash.exe and other Unix tools, but we could " |
| 1692 | "not find them.\n" |
| 1693 | "If you do not have them installed, the easiest is to install MSYS2 " |
| 1694 | "from\n" |
| 1695 | " http://repo.msys2.org/distrib/msys2-x86_64-latest.exe\n" |
| 1696 | "or git-on-Windows from\n" |
| 1697 | " https://git-scm.com/download/win\n" |
| 1698 | "\n" |
| 1699 | "If you already have bash.exe installed but Bazel cannot find it,\n" |
dslomov | 8970182 | 2017-07-20 10:02:27 +0200 | [diff] [blame] | 1700 | "set BAZEL_SH environment variable to its location:\n" |
Dmitry Lomov | 891f540 | 2017-07-19 12:26:39 +0200 | [diff] [blame] | 1701 | " set BAZEL_SH=c:\\path\\to\\bash.exe\n"); |
| 1702 | exit(1); |
| 1703 | } |
| 1704 | } |
| 1705 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 1706 | } // namespace blaze |