blob: 96d8e56ccb455143ade2cbb8b195cf7915a0df3b [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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//
15// blaze.cc: bootstrap and client code for Blaze server.
16//
17// Responsible for:
18// - extracting the Python, C++ and Java components.
19// - starting the server or finding the existing one.
20// - client options parsing.
21// - passing the argv array, and printing the out/err streams.
22// - signal handling.
23// - exiting with the right error/WTERMSIG code.
24// - debugger + profiler support.
25// - mutual exclusion between batch invocations.
Julio Merino28774852016-09-14 16:59:46 +000026#include "src/main/cpp/blaze.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027
28#include <assert.h>
29#include <ctype.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030#include <fcntl.h>
31#include <limits.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
Lukacs Berkie21e5922016-04-12 12:22:20 +000036
Lukacs Berkie21e5922016-04-12 12:22:20 +000037#include <grpc++/channel.h>
38#include <grpc++/client_context.h>
39#include <grpc++/create_channel.h>
40#include <grpc++/security/credentials.h>
ccalvarin1419dda2017-03-28 21:12:28 +000041#include <grpc/grpc.h>
42#include <grpc/support/log.h>
Lukacs Berkie21e5922016-04-12 12:22:20 +000043
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044#include <algorithm>
Lukacs Berki1b25ce22016-04-15 13:11:21 +000045#include <chrono> // NOLINT (gRPC requires this)
Damien Martin-Guillerez0f3481b2017-07-27 15:51:39 +020046#include <cinttypes>
ccalvarin1419dda2017-03-28 21:12:28 +000047#include <mutex> // NOLINT
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048#include <set>
49#include <string>
Lukacs Berkif1df38a2016-04-19 07:42:22 +000050#include <thread> // NOLINT
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010051#include <utility>
52#include <vector>
53
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000054#include "src/main/cpp/blaze_util.h"
55#include "src/main/cpp/blaze_util_platform.h"
Thiago Farina676cb9f2016-10-06 11:00:43 +000056#include "src/main/cpp/global_variables.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000057#include "src/main/cpp/option_processor.h"
Julio Merino28774852016-09-14 16:59:46 +000058#include "src/main/cpp/startup_options.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000059#include "src/main/cpp/util/errors.h"
Thiago Farina7f9357f2015-04-23 13:57:43 +000060#include "src/main/cpp/util/exit_code.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000061#include "src/main/cpp/util/file.h"
Chloe Calvarin78f1c852016-11-22 21:58:50 +000062#include "src/main/cpp/util/logging.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000063#include "src/main/cpp/util/numbers.h"
64#include "src/main/cpp/util/port.h"
65#include "src/main/cpp/util/strings.h"
Julio Merino211a95c2016-08-29 11:01:35 +000066#include "src/main/cpp/workspace_layout.h"
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +000067#include "third_party/ijar/zip.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068
Lukacs Berkie21e5922016-04-12 12:22:20 +000069#include "src/main/protobuf/command_server.grpc.pb.h"
70
Thiago Farina241f46c2015-04-13 14:33:30 +000071using blaze_util::die;
72using blaze_util::pdie;
Thiago Farinac32ad5e2017-05-08 12:17:04 -040073using blaze_util::PrintWarning;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075namespace blaze {
76
Thiago Farina80bb0f22016-10-17 15:57:13 +000077using std::set;
78using std::string;
79using std::vector;
80
Lukacs Berki1977d922016-05-02 09:31:37 +000081// The following is a treatise on how the interaction between the client and the
82// server works.
83//
84// First, the client unconditionally acquires an flock() lock on
85// $OUTPUT_BASE/lock then verifies if it has already extracted itself by
86// checking if the directory it extracts itself to (install base + a checksum)
87// is present. If not, then it does the extraction. Care is taken that this
88// process is atomic so that Blazen in multiple output bases do not clash.
89//
90// Then the client tries to connect to the currently executing server and kills
91// it if at least one of the following conditions is true:
92//
93// - The server is of the wrong version (as determined by the
94// $OUTPUT_BASE/install symlink)
95// - The server has different startup options than the client wants
96// - The client wants to run the command in batch mode
97//
98// Then, if needed, the client adjusts the install link to indicate which
99// version of the server it is running.
100//
101// In batch mode, the client then simply executes the server while taking care
102// that the output base lock is kept until it finishes.
103//
104// If in server mode, the client starts up a server if needed then sends the
Thiago Farina69dac862016-11-02 09:48:27 +0000105// command to the client and streams back stdout and stderr. The output base
106// lock is released after the command is sent to the server (the server
107// implements its own locking mechanism).
Lukacs Berki1977d922016-05-02 09:31:37 +0000108
109// Synchronization between the client and the server is a little precarious
110// because the client needs to know the PID of the server and it is not
111// available using a Java API and we don't have JNI on Windows at the moment,
112// so the server can't just communicate this over the communication channel.
113// Thus, a PID file is used, but care needs to be taken that the contents of
114// this PID file are right.
115//
116// Upon server startup, the PID file is written before the client spawns the
117// server. Thus, when the client can connect, it can be certain that the PID
118// file is up to date.
119//
120// Upon server shutdown, the PID file is deleted using a server shutdown hook.
121// However, this happens *after* the server stopped listening, so it's possible
122// that a client has already started up a server and written a new PID file.
123// In order to avoid this, when the client starts up a new server, it reads the
124// contents of the PID file and kills the process indicated in it (it could do
125// with a bit more care, since PIDs can be reused, but for now, we just believe
126// the PID file)
127//
128// Some more interesting scenarios:
129//
130// - The server receives a kill signal and it does not have a chance to delete
131// the PID file: the client cannot connect, reads the PID file, kills the
132// process indicated in it and starts up a new server.
133//
134// - The server stopped accepting connections but hasn't quit yet and a new
135// client comes around: the new client will kill the server based on the
136// PID file before a new server is started up.
137//
138// Alternative implementations:
139//
140// - Don't deal with PIDs at all. This would make it impossible for the client
141// to deliver a SIGKILL to the server after three SIGINTs. It would only be
142// possible with gRPC anyway.
143//
144// - Have the server check that the PID file containts the correct things
145// before deleting them: there is a window of time between checking the file
146// and deleting it in which a new server can overwrite the PID file. The
147// output base lock cannot be acquired, either, because when starting up a
148// new server, the client already holds it.
149//
150// - Delete the PID file before stopping to accept connections: then a client
151// could come about after deleting the PID file but before stopping accepting
152// connections. It would also not be resilient against a dead server that
153// left a PID file around.
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000154class BlazeServer {
155 public:
156 virtual ~BlazeServer() {}
157
Lukacs Berki1977d922016-05-02 09:31:37 +0000158 // Acquire a lock for the server running in this output base. Returns the
159 // number of milliseconds spent waiting for the lock.
Lukacs Berki415d39a2016-04-28 13:18:54 +0000160 uint64_t AcquireLock();
161
Lukacs Berki1977d922016-05-02 09:31:37 +0000162 // Whether there is an active connection to a server.
163 bool Connected() const { return connected_; }
164
Lukacs Berkie6a34f62016-04-25 12:16:04 +0000165 // Connect to the server. Returns if the connection was successful. Only
166 // call this when this object is in disconnected state. If it returns true,
167 // this object will be in connected state.
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000168 virtual bool Connect() = 0;
Lukacs Berkie6a34f62016-04-25 12:16:04 +0000169
170 // Disconnects from an existing server. Only call this when this object is in
171 // connected state. After this call returns, the object will be in connected
172 // state.
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000173 virtual void Disconnect() = 0;
Lukacs Berkie6a34f62016-04-25 12:16:04 +0000174
175 // Send the command line to the server and forward whatever it says to stdout
176 // and stderr. Returns the desired exit code. Only call this when the server
177 // is in connected state.
178 virtual unsigned int Communicate() = 0;
179
180 // Disconnects and kills an existing server. Only call this when this object
181 // is in connected state.
Lukacs Berki1977d922016-05-02 09:31:37 +0000182 virtual void KillRunningServer() = 0;
Lukacs Berkie6a34f62016-04-25 12:16:04 +0000183
184 // Cancel the currently running command. If there is no command currently
Lukacs Berki1977d922016-05-02 09:31:37 +0000185 // running, the result is unspecified. When called, this object must be in
186 // connected state.
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000187 virtual void Cancel() = 0;
Thiago Farina69dac862016-11-02 09:48:27 +0000188
189 protected:
190 BlazeLock blaze_lock_;
191 bool connected_;
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000192};
193
Lukacs Berki415d39a2016-04-28 13:18:54 +0000194////////////////////////////////////////////////////////////////////////
195// Global Variables
196static GlobalVariables *globals;
197static BlazeServer *blaze_server;
198
Laszlo Csomor32086b22016-11-24 15:23:55 +0000199// TODO(laszlocsomor) 2016-11-24: release the `globals` and `blaze_server`
200// objects. Currently nothing deletes them. Be careful that some functions may
201// call exit(2) or _exit(2) (attributed with ATTRIBUTE_NORETURN) meaning we have
202// to delete the objects before those.
203
Lukacs Berki415d39a2016-04-28 13:18:54 +0000204uint64_t BlazeServer::AcquireLock() {
ccalvarin1419dda2017-03-28 21:12:28 +0000205 return blaze::AcquireLock(globals->options->output_base,
206 globals->options->batch,
207 globals->options->block_for_lock, &blaze_lock_);
Lukacs Berki415d39a2016-04-28 13:18:54 +0000208}
209
Lukacs Berki1977d922016-05-02 09:31:37 +0000210// Communication method that uses gRPC on a socket bound to localhost. More
211// documentation is in command_server.proto .
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000212class GrpcBlazeServer : public BlazeServer {
213 public:
Lukacs Berki71675a52016-11-08 09:48:27 +0000214 GrpcBlazeServer(int connect_timeout_secs);
Lukacs Berki6dd29092016-05-30 14:05:33 +0000215 virtual ~GrpcBlazeServer();
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000216
Lukacs Berki9d52bc52016-06-07 11:11:04 +0000217 virtual bool Connect();
218 virtual void Disconnect();
219 virtual unsigned int Communicate();
220 virtual void KillRunningServer();
221 virtual void Cancel();
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000222
223 private:
Lukacs Berki6dd29092016-05-30 14:05:33 +0000224 enum CancelThreadAction { NOTHING, JOIN, CANCEL, COMMAND_ID_RECEIVED };
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000225
226 std::unique_ptr<command_server::CommandServer::Stub> client_;
227 std::string request_cookie_;
228 std::string response_cookie_;
229 std::string command_id_;
230
Lukacs Berki6dd29092016-05-30 14:05:33 +0000231 // protects command_id_ . Although we always set it before making the cancel
232 // thread do something with it, the mutex is still useful because it provides
233 // a memory fence.
234 std::mutex cancel_thread_mutex_;
Lukacs Berki8b999982016-04-26 15:40:38 +0000235
Lukacs Berki71675a52016-11-08 09:48:27 +0000236 int connect_timeout_secs_;
Laszlo Csomoref5ceef2016-11-18 11:19:02 +0000237
Thiago Farina0bba4c92016-12-14 15:29:11 +0000238 // Pipe that the main thread sends actions to and the cancel thread receives
Laszlo Csomoref5ceef2016-11-18 11:19:02 +0000239 // actions from.
Thiago Farina0bba4c92016-12-14 15:29:11 +0000240 blaze_util::IPipe *pipe_;
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000241
ccalvarin1419dda2017-03-28 21:12:28 +0000242 bool TryConnect(command_server::CommandServer::Stub *client);
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000243 void CancelThread();
Lukacs Berki6dd29092016-05-30 14:05:33 +0000244 void SendAction(CancelThreadAction action);
245 void SendCancelMessage();
Lukacs Berki00cfb7d2016-04-20 09:01:52 +0000246};
247
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100248////////////////////////////////////////////////////////////////////////
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249// Logic
250
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000251// A devtools_ijar::ZipExtractorProcessor to extract the InstallKeyFile
252class GetInstallKeyFileProcessor : public devtools_ijar::ZipExtractorProcessor {
253 public:
Thiago Farina9cb32752015-06-03 15:34:19 +0000254 explicit GetInstallKeyFileProcessor(string *install_base_key)
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000255 : install_base_key_(install_base_key) {}
256
257 virtual bool Accept(const char *filename, const devtools_ijar::u4 attr) {
258 globals->extracted_binaries.push_back(filename);
259 return strcmp(filename, "install_base_key") == 0;
260 }
261
262 virtual void Process(const char *filename, const devtools_ijar::u4 attr,
263 const devtools_ijar::u1 *data, const size_t size) {
264 string str(reinterpret_cast<const char *>(data), size);
265 blaze_util::StripWhitespace(&str);
Lukacs Berki58c29ae2015-10-16 14:48:33 +0000266 if (str.size() != 32) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000267 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Lukacs Berki58c29ae2015-10-16 14:48:33 +0000268 "\nFailed to extract install_base_key: file size mismatch "
ccalvarin1419dda2017-03-28 21:12:28 +0000269 "(should be 32, is %zd)",
270 str.size());
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000271 }
272 *install_base_key_ = str;
273 }
274
275 private:
276 string *install_base_key_;
277};
278
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100279// Returns the install base (the root concatenated with the contents of the file
280// 'install_base_key' contained as a ZIP entry in the Blaze binary); as a side
281// effect, it also populates the extracted_binaries global variable.
282static string GetInstallBase(const string &root, const string &self_path) {
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000283 GetInstallKeyFileProcessor processor(&globals->install_md5);
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000284 std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
285 devtools_ijar::ZipExtractor::Create(self_path.c_str(), &processor));
286 if (extractor.get() == NULL) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
László Csomor6f1e31a2017-01-27 11:01:41 +0000288 "\nFailed to open %s as a zip file: %s",
289 globals->options->product_name.c_str(),
290 blaze_util::GetLastErrorString().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 }
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000292 if (extractor->ProcessAll() < 0) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100293 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000294 "\nFailed to extract install_base_key: %s", extractor->GetError());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295 }
296
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000297 if (globals->install_md5.empty()) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000298 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
299 "\nFailed to find install_base_key's in zip file");
300 }
Laszlo Csomor760f7862016-12-19 15:46:47 +0000301 return blaze_util::JoinPath(root, globals->install_md5);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100302}
303
304// Escapes colons by replacing them with '_C' and underscores by replacing them
305// with '_U'. E.g. "name:foo_bar" becomes "name_Cfoo_Ubar"
ccalvarin1419dda2017-03-28 21:12:28 +0000306static string EscapeForOptionSource(const string &input) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 string result = input;
308 blaze_util::Replace("_", "_U", &result);
309 blaze_util::Replace(":", "_C", &result);
310 return result;
311}
312
Thiago Farina6a2dc2b2016-10-28 13:05:22 +0000313// Returns the installed embedded binaries directory, under the shared
314// install_base location.
315string GetEmbeddedBinariesRoot(const string &install_base) {
316 return blaze_util::JoinPath(install_base, "_embedded_binaries");
317}
318
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100319// Returns the JVM command argument array.
320static vector<string> GetArgumentArray() {
321 vector<string> result;
322
323 // e.g. A Blaze server process running in ~/src/build_root (where there's a
324 // ~/src/build_root/WORKSPACE file) will appear in ps(1) as "blaze(src)".
325 string workspace =
326 blaze_util::Basename(blaze_util::Dirname(globals->workspace));
Julio Merino28774852016-09-14 16:59:46 +0000327 string product = globals->options->product_name;
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000328 blaze_util::ToLower(&product);
329 result.push_back(product + "(" + workspace + ")");
Julio Merino28774852016-09-14 16:59:46 +0000330 globals->options->AddJVMArgumentPrefix(
ccalvarin1419dda2017-03-28 21:12:28 +0000331 blaze_util::Dirname(blaze_util::Dirname(globals->jvm_path)), &result);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100332
333 result.push_back("-XX:+HeapDumpOnOutOfMemoryError");
Julio Merino28774852016-09-14 16:59:46 +0000334 string heap_crash_path = globals->options->output_base;
Laszlo Csomor4875b252017-03-08 17:40:24 +0000335 result.push_back("-XX:HeapDumpPath=" + blaze::PathAsJvmFlag(heap_crash_path));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100336
337 result.push_back("-Xverify:none");
338
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000339 vector<string> user_options;
340
Janak Ramakrishnan0acd1542016-01-06 18:42:30 +0000341 user_options.insert(user_options.begin(),
Julio Merino28774852016-09-14 16:59:46 +0000342 globals->options->host_jvm_args.begin(),
343 globals->options->host_jvm_args.end());
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000344
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100345 // Add JVM arguments particular to building blaze64 and particular JVM
346 // versions.
347 string error;
348 blaze_exit_code::ExitCode jvm_args_exit_code =
Julio Merino28774852016-09-14 16:59:46 +0000349 globals->options->AddJVMArguments(globals->options->GetHostJavabase(),
ccalvarin1419dda2017-03-28 21:12:28 +0000350 &result, user_options, &error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100351 if (jvm_args_exit_code != blaze_exit_code::SUCCESS) {
352 die(jvm_args_exit_code, "%s", error.c_str());
353 }
354
355 // We put all directories on the java.library.path that contain .so files.
356 string java_library_path = "-Djava.library.path=";
Thiago Farina6a2dc2b2016-10-28 13:05:22 +0000357 string real_install_dir =
358 GetEmbeddedBinariesRoot(globals->options->install_base);
359
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100360 bool first = true;
ccalvarin1419dda2017-03-28 21:12:28 +0000361 for (const auto &it : globals->extracted_binaries) {
Thiago Farina01f36002015-04-08 15:59:08 +0000362 if (IsSharedLibrary(it)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100363 if (!first) {
Thiago Farinac3aee5a2017-04-24 18:02:52 +0200364 java_library_path += kListSeparator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100365 }
366 first = false;
Laszlo Csomor4875b252017-03-08 17:40:24 +0000367 java_library_path += blaze::PathAsJvmFlag(
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000368 blaze_util::JoinPath(real_install_dir, blaze_util::Dirname(it)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100369 }
370 }
371 result.push_back(java_library_path);
372
373 // Force use of latin1 for file names.
374 result.push_back("-Dfile.encoding=ISO-8859-1");
375
Julio Merino28774852016-09-14 16:59:46 +0000376 if (globals->options->host_jvm_debug) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100377 fprintf(stderr,
378 "Running host JVM under debugger (listening on TCP port 5005).\n");
379 // Start JVM so that it listens for a connection from a
380 // JDWP-compliant debugger:
381 result.push_back("-Xdebug");
382 result.push_back("-Xrunjdwp:transport=dt_socket,server=y,address=5005");
383 }
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000384 result.insert(result.end(), user_options.begin(), user_options.end());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385
László Csomor41627ad2017-06-07 04:06:44 -0400386 globals->options->AddJVMArgumentSuffix(real_install_dir,
387 globals->ServerJarPath(), &result);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388
Lukacs Berki3d486832016-10-26 12:51:38 +0000389 // JVM arguments are complete. Now pass in Blaze startup options.
390 // Note that we always use the --flag=ARG form (instead of the --flag ARG one)
391 // so that BlazeRuntime#splitStartupOptions has an easy job.
Lukacs Berki71675a52016-11-08 09:48:27 +0000392
393 // TODO(lberki): Test that whatever the list constructed after this line is
394 // actually a list of parseable startup options.
Julio Merino28774852016-09-14 16:59:46 +0000395 if (!globals->options->batch) {
Lukacs Berki3d486832016-10-26 12:51:38 +0000396 result.push_back("--max_idle_secs=" +
397 ToString(globals->options->max_idle_secs));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100398 } else {
Googlerc8c64e72015-03-23 23:22:18 +0000399 // --batch must come first in the arguments to Java main() because
400 // the code expects it to be at args[0] if it's been set.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100401 result.push_back("--batch");
402 }
Lukacs Berkice1445f2016-04-19 15:52:55 +0000403
Julio Merino28774852016-09-14 16:59:46 +0000404 if (globals->options->command_port != 0) {
ccalvarin1419dda2017-03-28 21:12:28 +0000405 result.push_back("--command_port=" +
406 ToString(globals->options->command_port));
Lukacs Berkice1445f2016-04-19 15:52:55 +0000407 }
408
ccalvarin1419dda2017-03-28 21:12:28 +0000409 result.push_back("--connect_timeout_secs=" +
410 ToString(globals->options->connect_timeout_secs));
Lukacs Berki71675a52016-11-08 09:48:27 +0000411
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000412 result.push_back("--install_base=" +
Julio Merino28774852016-09-14 16:59:46 +0000413 blaze::ConvertPath(globals->options->install_base));
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000414 result.push_back("--install_md5=" + globals->install_md5);
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000415 result.push_back("--output_base=" +
Julio Merino28774852016-09-14 16:59:46 +0000416 blaze::ConvertPath(globals->options->output_base));
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000417 result.push_back("--workspace_directory=" +
418 blaze::ConvertPath(globals->workspace));
Marian Lobur6dcdd602015-04-09 09:28:40 +0000419
Julio Merino28774852016-09-14 16:59:46 +0000420 if (globals->options->allow_configurable_attributes) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100421 result.push_back("--allow_configurable_attributes");
422 }
Julio Merino28774852016-09-14 16:59:46 +0000423 if (globals->options->deep_execroot) {
Lukacs Berki5fb98d12015-12-09 15:29:46 +0000424 result.push_back("--deep_execroot");
425 } else {
426 result.push_back("--nodeep_execroot");
427 }
Julio Merino28774852016-09-14 16:59:46 +0000428 if (globals->options->oom_more_eagerly) {
Janak Ramakrishnanadc706f2016-03-07 19:12:48 +0000429 result.push_back("--experimental_oom_more_eagerly");
430 }
Janak Ramakrishnan19fde1f2016-05-23 21:20:16 +0000431 result.push_back("--experimental_oom_more_eagerly_threshold=" +
Julio Merino28774852016-09-14 16:59:46 +0000432 ToString(globals->options->oom_more_eagerly_threshold));
Janak Ramakrishnan8cc772e2016-03-23 17:26:12 +0000433
Michajlo Matijkiwaf79a322016-09-16 15:44:35 +0000434 if (!globals->options->write_command_log) {
435 result.push_back("--nowrite_command_log");
436 }
437
Julio Merino28774852016-09-14 16:59:46 +0000438 if (globals->options->watchfs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100439 result.push_back("--watchfs");
440 }
Julio Merino28774852016-09-14 16:59:46 +0000441 if (globals->options->fatal_event_bus_exceptions) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100442 result.push_back("--fatal_event_bus_exceptions");
443 } else {
444 result.push_back("--nofatal_event_bus_exceptions");
445 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100446
Lukacs Berki71675a52016-11-08 09:48:27 +0000447 // We use this syntax so that the logic in ServerNeedsToBeKilled() that
448 // decides whether the server needs killing is simpler. This is parsed by the
449 // Java code where --noclient_debug and --client_debug=false are equivalent.
450 // Note that --client_debug false (separated by space) won't work either,
451 // because the logic in ServerNeedsToBeKilled() assumes that every argument
452 // is in the --arg=value form.
453 if (globals->options->client_debug) {
454 result.push_back("--client_debug=true");
455 } else {
456 result.push_back("--client_debug=false");
457 }
458
Lukacs Berkibfd9aa32017-03-06 11:21:35 +0000459 if (!globals->options->GetExplicitHostJavabase().empty()) {
460 result.push_back("--host_javabase=" +
461 globals->options->GetExplicitHostJavabase());
462 }
463
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100464 // This is only for Blaze reporting purposes; the real interpretation of the
465 // jvm flags occurs when we set up the java command line.
Julio Merino28774852016-09-14 16:59:46 +0000466 if (globals->options->host_jvm_debug) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100467 result.push_back("--host_jvm_debug");
468 }
Julio Merino28774852016-09-14 16:59:46 +0000469 if (!globals->options->host_jvm_profile.empty()) {
470 result.push_back("--host_jvm_profile=" +
471 globals->options->host_jvm_profile);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100472 }
Julio Merino28774852016-09-14 16:59:46 +0000473 if (!globals->options->host_jvm_args.empty()) {
474 for (const auto &arg : globals->options->host_jvm_args) {
Janak Ramakrishnan533657e2015-11-13 23:34:14 +0000475 result.push_back("--host_jvm_args=" + arg);
476 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100477 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000478
ccalvarin0ddda782017-04-06 21:12:11 +0000479 // Pass in invocation policy as a startup argument for batch mode only.
480 if (globals->options->batch && globals->options->invocation_policy != NULL &&
Julio Merino28774852016-09-14 16:59:46 +0000481 strlen(globals->options->invocation_policy) > 0) {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000482 result.push_back(string("--invocation_policy=") +
Julio Merino28774852016-09-14 16:59:46 +0000483 globals->options->invocation_policy);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000484 }
485
Julio Merino28774852016-09-14 16:59:46 +0000486 result.push_back("--product_name=" + globals->options->product_name);
Luis Fernando Pino Duque623cdf82016-05-31 16:21:46 +0000487
Julio Merino28774852016-09-14 16:59:46 +0000488 globals->options->AddExtraOptions(&result);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100489
490 // The option sources are transmitted in the following format:
491 // --option_sources=option1:source1:option2:source2:...
492 string option_sources = "--option_sources=";
493 first = true;
ccalvarin1419dda2017-03-28 21:12:28 +0000494 for (const auto &it : globals->options->option_sources) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100495 if (!first) {
496 option_sources += ":";
497 }
498
499 first = false;
500 option_sources += EscapeForOptionSource(it.first) + ":" +
ccalvarin1419dda2017-03-28 21:12:28 +0000501 EscapeForOptionSource(it.second);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100502 }
503
504 result.push_back(option_sources);
505 return result;
506}
507
Thiago Farina5735c252016-04-27 16:16:27 +0000508// Add common command options for logging to the given argument array.
ccalvarin1419dda2017-03-28 21:12:28 +0000509static void AddLoggingArgs(vector<string> *args) {
Googler9588b812015-07-23 11:49:37 +0000510 args->push_back("--startup_time=" + ToString(globals->startup_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100511 if (globals->command_wait_time != 0) {
512 args->push_back("--command_wait_time=" +
Googler9588b812015-07-23 11:49:37 +0000513 ToString(globals->command_wait_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100514 }
515 if (globals->extract_data_time != 0) {
516 args->push_back("--extract_data_time=" +
Googler9588b812015-07-23 11:49:37 +0000517 ToString(globals->extract_data_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100518 }
519 if (globals->restart_reason != NO_RESTART) {
janakrfaa9dcf2017-05-17 07:10:41 +0200520 const char *reasons[] = {"no_restart",
521 "no_daemon",
522 "new_version",
523 "new_options",
524 "pid_file_but_no_server",
525 "server_vanished",
526 "server_unresponsive"};
ccalvarin1419dda2017-03-28 21:12:28 +0000527 args->push_back(string("--restart_reason=") +
528 reasons[globals->restart_reason]);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100529 }
ccalvarin1419dda2017-03-28 21:12:28 +0000530 args->push_back(string("--binary_path=") + globals->binary_path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100531}
532
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100533// Join the elements of the specified array with NUL's (\0's), akin to the
534// format of /proc/$PID/cmdline.
ccalvarin1419dda2017-03-28 21:12:28 +0000535static string GetArgumentString(const vector<string> &argument_array) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100536 string result;
537 blaze_util::JoinStrings(argument_array, '\0', &result);
538 return result;
539}
540
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100541// Do a chdir into the workspace, and die if it fails.
ccalvarin1419dda2017-03-28 21:12:28 +0000542static void GoToWorkspace(const WorkspaceLayout *workspace_layout) {
Julio Merinoe3e3bfa2016-12-08 22:22:12 +0000543 if (workspace_layout->InWorkspace(globals->workspace) &&
Laszlo Csomor9c951962016-11-10 13:31:27 +0000544 !blaze_util::ChangeDirectory(globals->workspace)) {
ccalvarin1419dda2017-03-28 21:12:28 +0000545 pdie(blaze_exit_code::INTERNAL_ERROR, "changing directory into %s failed",
546 globals->workspace.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100547 }
548}
549
550// Check the java version if a java version specification is bundled. On
Thiago Farina5735c252016-04-27 16:16:27 +0000551// success, returns the executable path of the java command.
Eric Fellheimer3a695f32016-05-11 17:26:30 +0000552static void VerifyJavaVersionAndSetJvm() {
Julio Merino28774852016-09-14 16:59:46 +0000553 string exe = globals->options->GetJvm();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100554
555 string version_spec_file = blaze_util::JoinPath(
Thiago Farina6a2dc2b2016-10-28 13:05:22 +0000556 GetEmbeddedBinariesRoot(globals->options->install_base), "java.version");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100557 string version_spec = "";
Laszlo Csomor49970e02016-11-28 08:55:47 +0000558 if (blaze_util::ReadFile(version_spec_file, &version_spec)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100559 blaze_util::StripWhitespace(&version_spec);
560 // A version specification is given, get version of java.
561 string jvm_version = GetJvmVersion(exe);
562
563 // Compare that jvm_version is found and at least the one specified.
Googler54a3ac22017-08-29 20:19:18 +0200564 if (jvm_version.empty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100565 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
566 "Java version not detected while at least %s is needed.\n"
ccalvarin1419dda2017-03-28 21:12:28 +0000567 "Please set JAVA_HOME.",
568 version_spec.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100569 } else if (!CheckJavaVersionIsAtLeast(jvm_version, version_spec)) {
570 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
ccalvarin1419dda2017-03-28 21:12:28 +0000571 "Java version is %s while at least %s is needed.\n"
572 "Please set JAVA_HOME.",
573 jvm_version.c_str(), version_spec.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100574 }
575 }
576
Eric Fellheimer3a695f32016-05-11 17:26:30 +0000577 globals->jvm_path = exe;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100578}
579
Thiago Farinac800f842017-05-12 17:58:51 +0200580// Starts the Blaze server.
ccalvarin1419dda2017-03-28 21:12:28 +0000581static void StartServer(const WorkspaceLayout *workspace_layout,
582 BlazeServerStartup **server_startup) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100583 vector<string> jvm_args_vector = GetArgumentArray();
584 string argument_string = GetArgumentString(jvm_args_vector);
Laszlo Csomor760f7862016-12-19 15:46:47 +0000585 string server_dir =
586 blaze_util::JoinPath(globals->options->output_base, "server");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100587 // Write the cmdline argument string to the server dir. If we get to this
588 // point, there is no server running, so we don't overwrite the cmdline file
589 // for the existing server. If might be that the server dies and the cmdline
590 // file stays there, but that is not a problem, since we always check the
591 // server, too.
Laszlo Csomor760f7862016-12-19 15:46:47 +0000592 blaze_util::WriteFile(argument_string,
593 blaze_util::JoinPath(server_dir, "cmdline"));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100594
595 // unless we restarted for a new-version, mark this as initial start
596 if (globals->restart_reason == NO_RESTART) {
597 globals->restart_reason = NO_DAEMON;
598 }
599
László Csomor41627ad2017-06-07 04:06:44 -0400600 string exe =
601 globals->options->GetExe(globals->jvm_path, globals->ServerJarPath());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100602 // Go to the workspace before we daemonize, so
603 // we can still print errors to the terminal.
Julio Merinoe3e3bfa2016-12-08 22:22:12 +0000604 GoToWorkspace(workspace_layout);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100605
Laszlo Csomor49970e02016-11-28 08:55:47 +0000606 ExecuteDaemon(exe, jvm_args_vector, globals->jvm_log_file, server_dir,
607 server_startup);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100608}
609
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100610// Replace this process with blaze in standalone/batch mode.
611// The batch mode blaze process handles the command and exits.
612//
613// This function passes the commands array to the blaze process.
614// This array should start with a command ("build", "info", etc.).
ccalvarin1419dda2017-03-28 21:12:28 +0000615static void StartStandalone(const WorkspaceLayout *workspace_layout,
616 BlazeServer *server) {
Lukacs Berki1977d922016-05-02 09:31:37 +0000617 if (server->Connected()) {
618 server->KillRunningServer();
619 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100620
621 // Wall clock time since process startup.
Laszlo Csomor943d3cf2016-11-07 14:27:21 +0000622 globals->startup_time = GetMillisecondsSinceProcessStart();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100623
624 if (VerboseLogging()) {
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000625 fprintf(stderr, "Starting %s in batch mode.\n",
Julio Merino28774852016-09-14 16:59:46 +0000626 globals->options->product_name.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100627 }
Julio Merino28774852016-09-14 16:59:46 +0000628 string command = globals->option_processor->GetCommand();
lpinoc00ba522017-07-10 19:17:40 +0200629 const vector<string> command_arguments =
630 globals->option_processor->GetCommandArguments();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100631
632 if (!command_arguments.empty() && command == "shutdown") {
Julio Merino28774852016-09-14 16:59:46 +0000633 string product = globals->options->product_name;
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000634 blaze_util::ToLower(&product);
Thiago Farinac32ad5e2017-05-08 12:17:04 -0400635 PrintWarning(
636 "Running command \"shutdown\" in batch mode. Batch mode "
637 "is triggered\nwhen not running %s within a workspace. If you "
638 "intend to shutdown an\nexisting %s server, run \"%s "
639 "shutdown\" from the directory where\nit was started.",
640 globals->options->product_name.c_str(),
641 globals->options->product_name.c_str(), product.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100642 }
643 vector<string> jvm_args_vector = GetArgumentArray();
Googler54a3ac22017-08-29 20:19:18 +0200644 if (!command.empty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100645 jvm_args_vector.push_back(command);
646 AddLoggingArgs(&jvm_args_vector);
647 }
648
ccalvarin1419dda2017-03-28 21:12:28 +0000649 jvm_args_vector.insert(jvm_args_vector.end(), command_arguments.begin(),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100650 command_arguments.end());
651
Julio Merinoe3e3bfa2016-12-08 22:22:12 +0000652 GoToWorkspace(workspace_layout);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100653
László Csomor41627ad2017-06-07 04:06:44 -0400654 string exe =
655 globals->options->GetExe(globals->jvm_path, globals->ServerJarPath());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100656 ExecuteProgram(exe, jvm_args_vector);
657 pdie(blaze_exit_code::INTERNAL_ERROR, "execv of '%s' failed", exe.c_str());
658}
659
Laszlo Csomorae16e762016-11-18 10:16:08 +0000660static void WriteFileToStderrOrDie(const char *file_name) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100661 FILE *fp = fopen(file_name, "r");
662 if (fp == NULL) {
ccalvarin1419dda2017-03-28 21:12:28 +0000663 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "opening %s failed",
664 file_name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100665 }
666 char buffer[255];
667 int num_read;
668 while ((num_read = fread(buffer, 1, sizeof buffer, fp)) > 0) {
669 if (ferror(fp)) {
670 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
671 "failed to read from '%s'", file_name);
672 }
Laszlo Csomorae16e762016-11-18 10:16:08 +0000673 fwrite(buffer, 1, num_read, stderr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100674 }
675 fclose(fp);
676}
677
Lukacs Berki4912f7f2016-06-17 16:12:22 +0000678// After connecting to the Blaze server, return its PID, or -1 if there was an
679// error.
Lukacs Berkid9da60f2016-04-26 11:40:24 +0000680static int GetServerPid(const string &server_dir) {
Lukacs Berki907dbbf2016-04-15 11:30:12 +0000681 // Note: there is no race here on startup since the server creates
682 // the pid file strictly before it binds the socket.
Thiago Farina048bbfc2016-09-21 08:20:41 +0000683 string pid_file = blaze_util::JoinPath(server_dir, kServerPidFile);
Laszlo Csomorae16e762016-11-18 10:16:08 +0000684 string bufstr;
Lukacs Berkiea4c42e2016-04-25 07:22:11 +0000685 int result;
Laszlo Csomor49970e02016-11-28 08:55:47 +0000686 if (!blaze_util::ReadFile(pid_file, &bufstr, 32) ||
Laszlo Csomor6450c182016-11-24 10:28:20 +0000687 !blaze_util::safe_strto32(bufstr, &result)) {
Lukacs Berkiea4c42e2016-04-25 07:22:11 +0000688 return -1;
689 }
690
691 return result;
Doug Rabsond655f2a2015-08-13 14:41:50 +0000692}
693
janakrfaa9dcf2017-05-17 07:10:41 +0200694static void SetRestartReasonIfNotSet(RestartReason restart_reason) {
695 if (globals->restart_reason == NO_RESTART) {
696 globals->restart_reason = restart_reason;
697 }
698}
699
lberkia94aa632017-06-06 08:54:50 -0400700// Starts up a new server and connects to it. Exits if it didn't work out.
ccalvarin1419dda2017-03-28 21:12:28 +0000701static void StartServerAndConnect(const WorkspaceLayout *workspace_layout,
Julio Merinoe3e3bfa2016-12-08 22:22:12 +0000702 BlazeServer *server) {
Laszlo Csomor760f7862016-12-19 15:46:47 +0000703 string server_dir =
704 blaze_util::JoinPath(globals->options->output_base, "server");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100705
706 // The server dir has the socket, so we don't allow access by other
707 // users.
Thiago Farina227369a2016-12-07 12:40:40 +0000708 if (!blaze_util::MakeDirectories(server_dir, 0700)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100709 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
710 "server directory '%s' could not be created", server_dir.c_str());
711 }
712
Lukacs Berki1977d922016-05-02 09:31:37 +0000713 // If we couldn't connect to the server check if there is still a PID file
714 // and if so, kill the server that wrote it. This can happen e.g. if the
715 // server is in a GC pause and therefore cannot respond to ping requests and
716 // having two server instances running in the same output base is a
717 // disaster.
718 int server_pid = GetServerPid(server_dir);
719 if (server_pid > 0) {
mschallerfd37b512017-07-11 18:21:36 +0200720 if (VerifyServerProcess(server_pid, globals->options->output_base)) {
721 if (KillServerProcess(server_pid, globals->options->output_base)) {
janakrfaa9dcf2017-05-17 07:10:41 +0200722 fprintf(stderr, "Killed non-responsive server process (pid=%d)\n",
723 server_pid);
724 SetRestartReasonIfNotSet(SERVER_UNRESPONSIVE);
725 } else {
726 SetRestartReasonIfNotSet(SERVER_VANISHED);
727 }
728 } else {
729 SetRestartReasonIfNotSet(PID_FILE_BUT_NO_SERVER);
Lukacs Berki119dd4b2016-07-13 15:28:42 +0000730 }
Lukacs Berki7e0249e2016-04-21 08:14:08 +0000731 }
732
Julio Merino28774852016-09-14 16:59:46 +0000733 SetScheduling(globals->options->batch_cpu_scheduling,
734 globals->options->io_nice_level);
Lukacs Berkif1df38a2016-04-19 07:42:22 +0000735
ccalvarin1419dda2017-03-28 21:12:28 +0000736 BlazeServerStartup *server_startup;
Julio Merinoe3e3bfa2016-12-08 22:22:12 +0000737 StartServer(workspace_layout, &server_startup);
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000738
739 // Give the server two minutes to start up. That's enough to connect with a
740 // debugger.
ccalvarin1419dda2017-03-28 21:12:28 +0000741 auto try_until_time(std::chrono::system_clock::now() +
742 std::chrono::seconds(120));
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000743 bool had_to_wait = false;
744 while (std::chrono::system_clock::now() < try_until_time) {
ccalvarin1419dda2017-03-28 21:12:28 +0000745 auto next_attempt_time(std::chrono::system_clock::now() +
746 std::chrono::milliseconds(100));
Lukacs Berki1977d922016-05-02 09:31:37 +0000747 if (server->Connect()) {
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000748 if (had_to_wait && !globals->options->client_debug) {
Lukacs Berki1977d922016-05-02 09:31:37 +0000749 fputc('\n', stderr);
750 fflush(stderr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100751 }
Lukacs Berki1977d922016-05-02 09:31:37 +0000752 delete server_startup;
753 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100754 }
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000755
756 had_to_wait = true;
Lukacs Berki71675a52016-11-08 09:48:27 +0000757 if (!globals->options->client_debug) {
758 fputc('.', stderr);
759 fflush(stderr);
760 }
761
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000762 std::this_thread::sleep_until(next_attempt_time);
Lukacs Berki1977d922016-05-02 09:31:37 +0000763 if (!server_startup->IsStillAlive()) {
ccalvarin1cbe62a2017-08-14 21:09:07 +0200764 globals->option_processor->PrintStartupOptionsProvenanceMessage();
László Csomor6f1e31a2017-01-27 11:01:41 +0000765 fprintf(stderr,
766 "\nunexpected pipe read status: %s\n"
767 "Server presumed dead. Now printing '%s':\n",
768 blaze_util::GetLastErrorString().c_str(),
769 globals->jvm_log_file.c_str());
Laszlo Csomorae16e762016-11-18 10:16:08 +0000770 WriteFileToStderrOrDie(globals->jvm_log_file.c_str());
Lukacs Berki1977d922016-05-02 09:31:37 +0000771 exit(blaze_exit_code::INTERNAL_ERROR);
772 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100773 }
Lukacs Berki1977d922016-05-02 09:31:37 +0000774 die(blaze_exit_code::INTERNAL_ERROR,
Lukacs Berki5570bcc2016-11-15 15:45:58 +0000775 "\nError: couldn't connect to server after 120 seconds.");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100776}
777
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000778// A devtools_ijar::ZipExtractorProcessor to extract the files from the blaze
779// zip.
780class ExtractBlazeZipProcessor : public devtools_ijar::ZipExtractorProcessor {
781 public:
Thiago Farina9cb32752015-06-03 15:34:19 +0000782 explicit ExtractBlazeZipProcessor(const string &embedded_binaries)
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000783 : embedded_binaries_(embedded_binaries) {}
784
785 virtual bool Accept(const char *filename, const devtools_ijar::u4 attr) {
786 return !devtools_ijar::zipattr_is_dir(attr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100787 }
788
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000789 virtual void Process(const char *filename, const devtools_ijar::u4 attr,
790 const devtools_ijar::u1 *data, const size_t size) {
791 string path = blaze_util::JoinPath(embedded_binaries_, filename);
Thiago Farina227369a2016-12-07 12:40:40 +0000792 if (!blaze_util::MakeDirectories(blaze_util::Dirname(path), 0777)) {
ccalvarin1419dda2017-03-28 21:12:28 +0000793 pdie(blaze_exit_code::INTERNAL_ERROR, "couldn't create '%s'",
794 path.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100795 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100796
lberki69e74cd2017-06-27 11:13:05 +0200797 if (!blaze_util::WriteFile(data, size, path, 0755)) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000798 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Laszlo Csomorae16e762016-11-18 10:16:08 +0000799 "\nFailed to write zipped file \"%s\": %s", path.c_str(),
László Csomor6f1e31a2017-01-27 11:01:41 +0000800 blaze_util::GetLastErrorString().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100801 }
802 }
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000803
804 private:
805 const string embedded_binaries_;
806};
807
808// Actually extracts the embedded data files into the tree whose root
809// is 'embedded_binaries'.
810static void ActuallyExtractData(const string &argv0,
811 const string &embedded_binaries) {
812 ExtractBlazeZipProcessor processor(embedded_binaries);
Thiago Farina227369a2016-12-07 12:40:40 +0000813 if (!blaze_util::MakeDirectories(embedded_binaries, 0777)) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000814 pdie(blaze_exit_code::INTERNAL_ERROR, "couldn't create '%s'",
815 embedded_binaries.c_str());
816 }
817
818 fprintf(stderr, "Extracting %s installation...\n",
Julio Merino28774852016-09-14 16:59:46 +0000819 globals->options->product_name.c_str());
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000820 std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
821 devtools_ijar::ZipExtractor::Create(argv0.c_str(), &processor));
822 if (extractor.get() == NULL) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100823 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
László Csomor6f1e31a2017-01-27 11:01:41 +0000824 "\nFailed to open %s as a zip file: %s",
825 globals->options->product_name.c_str(),
826 blaze_util::GetLastErrorString().c_str());
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000827 }
828 if (extractor->ProcessAll() < 0) {
829 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
830 "\nFailed to extract %s as a zip file: %s",
Julio Merino28774852016-09-14 16:59:46 +0000831 globals->options->product_name.c_str(), extractor->GetError());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100832 }
833
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100834 // Set the timestamps of the extracted files to the future and make sure (or
835 // at least as sure as we can...) that the files we have written are actually
836 // on the disk.
837
838 vector<string> extracted_files;
Laszlo Csomor251bf032016-11-16 11:01:32 +0000839
840 // Walks the temporary directory recursively and collects full file paths.
841 blaze_util::GetAllFilesUnder(embedded_binaries, &extracted_files);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100842
Laszlo Csomorce1b3e12017-01-19 14:56:30 +0000843 std::unique_ptr<blaze_util::IFileMtime> mtime(blaze_util::CreateFileMtime());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100844 set<string> synced_directories;
Laszlo Csomor251bf032016-11-16 11:01:32 +0000845 for (const auto &it : extracted_files) {
846 const char *extracted_path = it.c_str();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100847
848 // Set the time to a distantly futuristic value so we can observe tampering.
Laszlo Csomorce1b3e12017-01-19 14:56:30 +0000849 // Note that keeping a static, deterministic timestamp, such as the default
850 // timestamp set by unzip (1970-01-01) and using that to detect tampering is
851 // not enough, because we also need the timestamp to change between Bazel
852 // releases so that the metadata cache knows that the files may have
853 // changed. This is essential for the correctness of actions that use
854 // embedded binaries as artifacts.
855 if (!mtime.get()->SetToDistantFuture(it)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100856 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
857 "failed to set timestamp on '%s'", extracted_path);
858 }
859
Laszlo Csomorae16e762016-11-18 10:16:08 +0000860 blaze_util::SyncFile(it);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100861
862 string directory = blaze_util::Dirname(extracted_path);
863
864 // Now walk up until embedded_binaries and sync every directory in between.
865 // synced_directories is used to avoid syncing the same directory twice.
Laszlo Csomor760f7862016-12-19 15:46:47 +0000866 // The !directory.empty() and !blaze_util::IsRootDirectory(directory)
867 // conditions are not strictly needed, but it makes this loop more robust,
868 // because otherwise, if due to some glitch, directory was not under
869 // embedded_binaries, it would get into an infinite loop.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100870 while (directory != embedded_binaries &&
Laszlo Csomor760f7862016-12-19 15:46:47 +0000871 synced_directories.count(directory) == 0 && !directory.empty() &&
872 !blaze_util::IsRootDirectory(directory)) {
Laszlo Csomorae16e762016-11-18 10:16:08 +0000873 blaze_util::SyncFile(directory);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100874 synced_directories.insert(directory);
875 directory = blaze_util::Dirname(directory);
876 }
877 }
878
Laszlo Csomorae16e762016-11-18 10:16:08 +0000879 blaze_util::SyncFile(embedded_binaries);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100880}
881
882// Installs Blaze by extracting the embedded data files, iff necessary.
883// The MD5-named install_base directory on disk is trusted; we assume
884// no-one has modified the extracted files beneath this directory once
885// it is in place. Concurrency during extraction is handled by
886// extracting in a tmp dir and then renaming it into place where it
887// becomes visible automically at the new path.
888// Populates globals->extracted_binaries with their extracted locations.
889static void ExtractData(const string &self_path) {
890 // If the install dir doesn't exist, create it, if it does, we know it's good.
Laszlo Csomor8a48f612016-11-17 10:18:34 +0000891 if (!blaze_util::PathExists(globals->options->install_base)) {
Laszlo Csomor943d3cf2016-11-07 14:27:21 +0000892 uint64_t st = GetMillisecondsMonotonic();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100893 // Work in a temp dir to avoid races.
Julio Merino28774852016-09-14 16:59:46 +0000894 string tmp_install = globals->options->install_base + ".tmp." +
Laszlo Csomorae16e762016-11-18 10:16:08 +0000895 blaze::GetProcessIdAsString();
Laszlo Csomor760f7862016-12-19 15:46:47 +0000896 string tmp_binaries =
897 blaze_util::JoinPath(tmp_install, "_embedded_binaries");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100898 ActuallyExtractData(self_path, tmp_binaries);
899
Laszlo Csomor943d3cf2016-11-07 14:27:21 +0000900 uint64_t et = GetMillisecondsMonotonic();
901 globals->extract_data_time = et - st;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100902
Laszlo Csomor918814e2017-02-08 13:28:47 +0000903 // Now rename the completed installation to its final name.
904 int attempts = 0;
905 while (attempts < 120) {
906 int result = blaze_util::RenameDirectory(
907 tmp_install.c_str(), globals->options->install_base.c_str());
908 if (result == blaze_util::kRenameDirectorySuccess ||
909 result == blaze_util::kRenameDirectoryFailureNotEmpty) {
910 // If renaming fails because the directory already exists and is not
911 // empty, then we assume another good installation snuck in before us.
912 break;
913 } else {
914 // Otherwise the install directory may still be scanned by the antivirus
915 // (in case we're running on Windows) so we need to wait for that to
916 // finish and try renaming again.
917 ++attempts;
918 fprintf(stderr,
919 "install base directory '%s' could not be renamed into place"
920 "after %d second(s), trying again\r",
921 tmp_install.c_str(), attempts);
922 std::this_thread::sleep_for(std::chrono::seconds(1));
923 }
924 }
925
926 // Give up renaming after 120 failed attempts / 2 minutes.
927 if (attempts == 120) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100928 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
929 "install base directory '%s' could not be renamed into place",
930 tmp_install.c_str());
931 }
932 } else {
Laszlo Csomor8a48f612016-11-17 10:18:34 +0000933 if (!blaze_util::IsDirectory(globals->options->install_base)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100934 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
935 "Error: Install base directory '%s' could not be created. "
936 "It exists but is not a directory.",
Julio Merino28774852016-09-14 16:59:46 +0000937 globals->options->install_base.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100938 }
939
Laszlo Csomorce1b3e12017-01-19 14:56:30 +0000940 std::unique_ptr<blaze_util::IFileMtime> mtime(
941 blaze_util::CreateFileMtime());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100942 string real_install_dir = blaze_util::JoinPath(
ccalvarin1419dda2017-03-28 21:12:28 +0000943 globals->options->install_base, "_embedded_binaries");
944 for (const auto &it : globals->extracted_binaries) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100945 string path = blaze_util::JoinPath(real_install_dir, it);
946 // Check that the file exists and is readable.
Laszlo Csomor00549b42017-01-11 09:12:10 +0000947 if (blaze_util::IsDirectory(path)) {
948 continue;
949 }
950 if (!blaze_util::CanReadFile(path)) {
László Csomor932f7a02017-08-28 15:27:23 +0200951 // TODO(laszlocsomor): remove the following `#if 1` block after I or
952 // somebody else fixed https://github.com/bazelbuild/bazel/issues/3618.
953#if 1
954 fprintf(stderr,
955 "DEBUG: corrupt installation: file '%s' missing. "
956 "Dumping debug data.\n",
957 path.c_str());
958 string p = path;
959 while (!p.empty()) {
960 fprintf(stderr, "DEBUG: p=(%s), exists=%d, isdir=%d, canread=%d\n",
961 p.c_str(), blaze_util::PathExists(p) ? 1 : 0,
962 blaze_util::IsDirectory(p) ? 1 : 0,
963 blaze_util::CanReadFile(p) ? 1 : 0);
964 string parent = blaze_util::Dirname(p);
965 if (parent == p) {
966 break;
967 } else {
968 p = parent;
969 }
970 }
971#endif
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100972 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
973 "Error: corrupt installation: file '%s' missing."
974 " Please remove '%s' and try again.",
Julio Merino28774852016-09-14 16:59:46 +0000975 path.c_str(), globals->options->install_base.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100976 }
Laszlo Csomor8a48f612016-11-17 10:18:34 +0000977 // Check that the timestamp is in the future. A past timestamp would
978 // indicate that the file has been tampered with.
979 // See ActuallyExtractData().
Laszlo Csomorce1b3e12017-01-19 14:56:30 +0000980 bool is_in_future = false;
981 if (!mtime.get()->GetIfInDistantFuture(path, &is_in_future)) {
Laszlo Csomor00549b42017-01-11 09:12:10 +0000982 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
983 "Error: could not retrieve mtime of file '%s'. "
984 "Please remove '%s' and try again.",
985 path.c_str(), globals->options->install_base.c_str());
Laszlo Csomorce1b3e12017-01-19 14:56:30 +0000986 }
987 if (!is_in_future) {
Laszlo Csomor00549b42017-01-11 09:12:10 +0000988 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
989 "Error: corrupt installation: file '%s' "
990 "modified. Please remove '%s' and try again.",
991 path.c_str(), globals->options->install_base.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100992 }
993 }
994 }
995}
996
Lukacs Berki71675a52016-11-08 09:48:27 +0000997const char *volatile_startup_options[] = {
ccalvarin1419dda2017-03-28 21:12:28 +0000998 "--option_sources=",
999 "--max_idle_secs=",
1000 "--connect_timeout_secs=",
1001 "--client_debug=",
1002 NULL,
Lukacs Berki71675a52016-11-08 09:48:27 +00001003};
1004
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001005// Returns true if the server needs to be restarted to accommodate changes
1006// between the two argument lists.
ccalvarin1419dda2017-03-28 21:12:28 +00001007static bool ServerNeedsToBeKilled(const vector<string> &args1,
1008 const vector<string> &args2) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001009 // We need not worry about one side missing an argument and the other side
1010 // having the default value, since this command line is already the
1011 // canonicalized one that always contains every switch (with default values
1012 // if it was not present on the real command line). Same applies for argument
1013 // ordering.
1014 if (args1.size() != args2.size()) {
1015 return true;
1016 }
1017
1018 for (int i = 0; i < args1.size(); i++) {
Lukacs Berki71675a52016-11-08 09:48:27 +00001019 bool option_volatile = false;
ccalvarin1419dda2017-03-28 21:12:28 +00001020 for (const char **candidate = volatile_startup_options; *candidate != NULL;
Lukacs Berki71675a52016-11-08 09:48:27 +00001021 candidate++) {
1022 string candidate_string(*candidate);
1023 if (args1[i].substr(0, candidate_string.size()) == candidate_string &&
1024 args2[i].substr(0, candidate_string.size()) == candidate_string) {
1025 option_volatile = true;
1026 break;
1027 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001028 }
1029
Lukacs Berki71675a52016-11-08 09:48:27 +00001030 if (!option_volatile && args1[i] != args2[i]) {
Lukacs Berki3d486832016-10-26 12:51:38 +00001031 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001032 }
1033 }
1034
1035 return false;
1036}
1037
1038// Kills the running Blaze server, if any, if the startup options do not match.
ccalvarin1419dda2017-03-28 21:12:28 +00001039static void KillRunningServerIfDifferentStartupOptions(BlazeServer *server) {
Lukacs Berki1977d922016-05-02 09:31:37 +00001040 if (!server->Connected()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001041 return;
1042 }
1043
Laszlo Csomor760f7862016-12-19 15:46:47 +00001044 string cmdline_path =
1045 blaze_util::JoinPath(globals->options->output_base, "server/cmdline");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001046 string joined_arguments;
1047
1048 // No, /proc/$PID/cmdline does not work, because it is limited to 4K. Even
1049 // worse, its behavior differs slightly between kernels (in some, when longer
1050 // command lines are truncated, the last 4 bytes are replaced with
1051 // "..." + NUL.
Laszlo Csomor49970e02016-11-28 08:55:47 +00001052 blaze_util::ReadFile(cmdline_path, &joined_arguments);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001053 vector<string> arguments = blaze_util::Split(joined_arguments, '\0');
1054
1055 // These strings contain null-separated command line arguments. If they are
1056 // the same, the server can stay alive, otherwise, it needs shuffle off this
1057 // mortal coil.
1058 if (ServerNeedsToBeKilled(arguments, GetArgumentArray())) {
1059 globals->restart_reason = NEW_OPTIONS;
Thiago Farinac32ad5e2017-05-08 12:17:04 -04001060 PrintWarning(
1061 "Running %s server needs to be killed, because the "
1062 "startup options are different.",
1063 globals->options->product_name.c_str());
Lukacs Berki1977d922016-05-02 09:31:37 +00001064 server->KillRunningServer();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001065 }
1066}
1067
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001068// Kills the old running server if it is not the same version as us,
1069// dealing with various combinations of installation scheme
1070// (installation symlink and older MD5_MANIFEST contents).
1071// This function requires that the installation be complete, and the
1072// server lock acquired.
ccalvarin1419dda2017-03-28 21:12:28 +00001073static void EnsureCorrectRunningVersion(BlazeServer *server) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001074 // Read the previous installation's semaphore symlink in output_base. If the
1075 // target dirs don't match, or if the symlink was not present, then kill any
1076 // running servers. Lastly, symlink to our installation so others know which
1077 // installation is running.
Laszlo Csomor760f7862016-12-19 15:46:47 +00001078 string installation_path =
1079 blaze_util::JoinPath(globals->options->output_base, "install");
Lukacs Berki497d8242016-04-28 07:21:26 +00001080 string prev_installation;
laszlocsomor111b3ac2017-03-30 12:38:05 +00001081 bool ok =
1082 blaze_util::ReadDirectorySymlink(installation_path, &prev_installation);
ccalvarin1419dda2017-03-28 21:12:28 +00001083 if (!ok || !CompareAbsolutePaths(prev_installation,
1084 globals->options->install_base)) {
Lukacs Berki1977d922016-05-02 09:31:37 +00001085 if (server->Connected()) {
1086 server->KillRunningServer();
lberkia94aa632017-06-06 08:54:50 -04001087 globals->restart_reason = NEW_VERSION;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001088 }
Lukacs Berki1977d922016-05-02 09:31:37 +00001089
Laszlo Csomor49970e02016-11-28 08:55:47 +00001090 blaze_util::UnlinkPath(installation_path);
1091 if (!SymlinkDirectories(globals->options->install_base,
1092 installation_path)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001093 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1094 "failed to create installation symlink '%s'",
1095 installation_path.c_str());
1096 }
Lukacs Berkib762afd2017-01-17 10:41:52 +00001097
1098 // Update the mtime of the install base so that cleanup tools can
1099 // find install bases that haven't been used for a long time
Laszlo Csomorce1b3e12017-01-19 14:56:30 +00001100 std::unique_ptr<blaze_util::IFileMtime> mtime(
1101 blaze_util::CreateFileMtime());
1102 if (!mtime.get()->SetToNow(globals->options->install_base)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001103 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1104 "failed to set timestamp on '%s'",
Julio Merino28774852016-09-14 16:59:46 +00001105 globals->options->install_base.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001106 }
1107 }
1108}
1109
ccalvarin1419dda2017-03-28 21:12:28 +00001110static void CancelServer() { blaze_server->Cancel(); }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001111
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001112// Performs all I/O for a single client request to the server, and
1113// shuts down the client (by exit or signal).
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001114static ATTRIBUTE_NORETURN void SendServerRequest(
ccalvarin1419dda2017-03-28 21:12:28 +00001115 const WorkspaceLayout *workspace_layout, BlazeServer *server) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001116 while (true) {
Lukacs Berki1977d922016-05-02 09:31:37 +00001117 if (!server->Connected()) {
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001118 StartServerAndConnect(workspace_layout, server);
Lukacs Berki1977d922016-05-02 09:31:37 +00001119 }
1120
Lukacs Berki4de98942016-09-09 09:23:36 +00001121 // Check for the case when the workspace directory deleted and then gets
1122 // recreated while the server is running
1123
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001124 string server_cwd = GetProcessCWD(globals->server_pid);
Lukacs Berki4be230a2015-10-15 13:43:03 +00001125 // If server_cwd is empty, GetProcessCWD failed. This notably occurs when
1126 // running under Docker because then readlink(/proc/[pid]/cwd) returns
1127 // EPERM.
1128 // Docker issue #6687 (https://github.com/docker/docker/issues/6687) fixed
1129 // this, but one still needs the --cap-add SYS_PTRACE command line flag, at
1130 // least according to the discussion on Docker issue #6800
1131 // (https://github.com/docker/docker/issues/6687), and even then, it's a
1132 // non-default Docker flag. Given that this occurs only in very weird
1133 // cases, it's better to assume that everything is alright if we can't get
1134 // the cwd.
1135
1136 if (!server_cwd.empty() &&
ccalvarin1419dda2017-03-28 21:12:28 +00001137 (server_cwd != globals->workspace || // changed
Lukacs Berki4be230a2015-10-15 13:43:03 +00001138 server_cwd.find(" (deleted)") != string::npos)) { // deleted.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001139 // There's a distant possibility that the two paths look the same yet are
1140 // actually different because the two processes have different mount
1141 // tables.
1142 if (VerboseLogging()) {
1143 fprintf(stderr, "Server's cwd moved or deleted (%s).\n",
1144 server_cwd.c_str());
1145 }
Lukacs Berki1977d922016-05-02 09:31:37 +00001146 server->KillRunningServer();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001147 } else {
1148 break;
1149 }
1150 }
1151
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001152 if (VerboseLogging()) {
1153 fprintf(stderr, "Connected (server pid=%d).\n", globals->server_pid);
1154 }
1155
1156 // Wall clock time since process startup.
Laszlo Csomor943d3cf2016-11-07 14:27:21 +00001157 globals->startup_time = GetMillisecondsSinceProcessStart();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001158
Laszlo Csomor32086b22016-11-24 15:23:55 +00001159 SignalHandler::Get().Install(globals, CancelServer);
1160 SignalHandler::Get().PropagateSignalOrExit(server->Communicate());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001161}
1162
1163// Parse the options, storing parsed values in globals.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001164static void ParseOptions(int argc, const char *argv[]) {
lpino2a32a662017-07-11 15:56:42 +02001165 std::string error;
1166 std::vector<std::string> args;
1167 args.insert(args.end(), argv, argv + argc);
1168 const blaze_exit_code::ExitCode parse_exit_code =
1169 globals->option_processor->ParseOptions(
1170 args, globals->workspace, globals->cwd, &error);
1171
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001172 if (parse_exit_code != blaze_exit_code::SUCCESS) {
ccalvarin1cbe62a2017-08-14 21:09:07 +02001173 globals->option_processor->PrintStartupOptionsProvenanceMessage();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001174 die(parse_exit_code, "%s", error.c_str());
1175 }
Julio Merino28774852016-09-14 16:59:46 +00001176 globals->options = globals->option_processor->GetParsedStartupOptions();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001177}
1178
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001179// Compute the globals globals->cwd and globals->workspace.
ccalvarin1419dda2017-03-28 21:12:28 +00001180static void ComputeWorkspace(const WorkspaceLayout *workspace_layout) {
Laszlo Csomorc3545392016-11-24 13:33:28 +00001181 globals->cwd = blaze_util::MakeCanonical(blaze_util::GetCwd().c_str());
1182 if (globals->cwd.empty()) {
1183 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1184 "blaze_util::MakeCanonical('%s') failed",
1185 blaze_util::GetCwd().c_str());
1186 }
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001187 globals->workspace = workspace_layout->GetWorkspace(globals->cwd);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001188}
1189
1190// Figure out the base directories based on embedded data, username, cwd, etc.
Julio Merino28774852016-09-14 16:59:46 +00001191// Sets globals->options->install_base, globals->options->output_base,
Thiago Farina6fd9bf12016-04-26 09:02:18 +00001192// globals->lockfile, globals->jvm_log_file.
ccalvarin1419dda2017-03-28 21:12:28 +00001193static void ComputeBaseDirectories(const WorkspaceLayout *workspace_layout,
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001194 const string &self_path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001195 // Only start a server when in a workspace because otherwise we won't do more
1196 // than emit a help message.
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001197 if (!workspace_layout->InWorkspace(globals->workspace)) {
Julio Merino28774852016-09-14 16:59:46 +00001198 globals->options->batch = true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001199 }
1200
1201 // The default install_base is <output_user_root>/install/<md5(blaze)>
1202 // but if an install_base is specified on the command line, we use that as
1203 // the base instead.
Julio Merino28774852016-09-14 16:59:46 +00001204 if (globals->options->install_base.empty()) {
Laszlo Csomor760f7862016-12-19 15:46:47 +00001205 string install_user_root =
1206 blaze_util::JoinPath(globals->options->output_user_root, "install");
Julio Merino28774852016-09-14 16:59:46 +00001207 globals->options->install_base =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001208 GetInstallBase(install_user_root, self_path);
1209 } else {
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +00001210 // We call GetInstallBase anyway to populate extracted_binaries and
1211 // install_md5.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001212 GetInstallBase("", self_path);
1213 }
1214
Julio Merino28774852016-09-14 16:59:46 +00001215 if (globals->options->output_base.empty()) {
Laszlo Csomor6bf95762016-11-16 13:29:22 +00001216 globals->options->output_base = blaze::GetHashedBaseDir(
Julio Merino28774852016-09-14 16:59:46 +00001217 globals->options->output_user_root, globals->workspace);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001218 }
1219
Julio Merino28774852016-09-14 16:59:46 +00001220 const char *output_base = globals->options->output_base.c_str();
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001221 if (!blaze_util::PathExists(globals->options->output_base)) {
Thiago Farina227369a2016-12-07 12:40:40 +00001222 if (!blaze_util::MakeDirectories(globals->options->output_base, 0777)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001223 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
ccalvarin1419dda2017-03-28 21:12:28 +00001224 "Output base directory '%s' could not be created", output_base);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001225 }
1226 } else {
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001227 if (!blaze_util::IsDirectory(globals->options->output_base)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001228 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1229 "Error: Output base directory '%s' could not be created. "
1230 "It exists but is not a directory.",
Dave MacLachlan6b747ee2016-07-20 10:00:44 +00001231 output_base);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001232 }
1233 }
Laszlo Csomor00549b42017-01-11 09:12:10 +00001234 if (!blaze_util::CanAccessDirectory(globals->options->output_base)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001235 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1236 "Error: Output base directory '%s' must be readable and writable.",
Dave MacLachlan6b747ee2016-07-20 10:00:44 +00001237 output_base);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001238 }
Dave MacLachlan6b747ee2016-07-20 10:00:44 +00001239 ExcludePathFromBackup(output_base);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001240
Laszlo Csomorc3545392016-11-24 13:33:28 +00001241 globals->options->output_base = blaze_util::MakeCanonical(output_base);
1242 if (globals->options->output_base.empty()) {
1243 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1244 "blaze_util::MakeCanonical('%s') failed", output_base);
1245 }
Chloe Calvarin78f1c852016-11-22 21:58:50 +00001246
Laszlo Csomor760f7862016-12-19 15:46:47 +00001247 globals->lockfile =
1248 blaze_util::JoinPath(globals->options->output_base, "lock");
1249 globals->jvm_log_file =
1250 blaze_util::JoinPath(globals->options->output_base, "server/jvm.out");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001251}
1252
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001253// Prepares the environment to be suitable to start a JVM.
1254// Changes made to the environment in this function *will not* be part
1255// of '--client_env'.
1256static void PrepareEnvironmentForJvm() {
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001257 if (!blaze::GetEnv("http_proxy").empty()) {
Thiago Farinac32ad5e2017-05-08 12:17:04 -04001258 PrintWarning("ignoring http_proxy in environment.");
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001259 blaze::UnsetEnv("http_proxy");
Lukacs Berki86a28b02016-10-25 10:34:45 +00001260 }
1261
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001262 if (!blaze::GetEnv("LD_ASSUME_KERNEL").empty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001263 // Fix for bug: if ulimit -s and LD_ASSUME_KERNEL are both
1264 // specified, the JVM fails to create threads. See thread_stack_regtest.
1265 // This is also provoked by LD_LIBRARY_PATH=/usr/lib/debug,
1266 // or anything else that causes the JVM to use LinuxThreads.
Thiago Farinac32ad5e2017-05-08 12:17:04 -04001267 PrintWarning("ignoring LD_ASSUME_KERNEL in environment.");
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001268 blaze::UnsetEnv("LD_ASSUME_KERNEL");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001269 }
1270
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001271 if (!blaze::GetEnv("LD_PRELOAD").empty()) {
Thiago Farinac32ad5e2017-05-08 12:17:04 -04001272 PrintWarning("ignoring LD_PRELOAD in environment.");
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001273 blaze::UnsetEnv("LD_PRELOAD");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001274 }
1275
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001276 if (!blaze::GetEnv("_JAVA_OPTIONS").empty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001277 // This would override --host_jvm_args
Thiago Farinac32ad5e2017-05-08 12:17:04 -04001278 PrintWarning("ignoring _JAVA_OPTIONS in environment.");
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001279 blaze::UnsetEnv("_JAVA_OPTIONS");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001280 }
1281
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001282 if (!blaze::GetEnv("TEST_TMPDIR").empty()) {
ccalvarin1419dda2017-03-28 21:12:28 +00001283 fprintf(stderr,
1284 "INFO: $TEST_TMPDIR defined: output root default is '%s'.\n",
1285 globals->options->output_root.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001286 }
1287
1288 // TODO(bazel-team): We've also seen a failure during loading (creating
1289 // threads?) when ulimit -Hs 8192. Characterize that and check for it here.
1290
1291 // Make the JVM use ISO-8859-1 for parsing its command line because "blaze
1292 // run" doesn't handle non-ASCII command line arguments. This is apparently
1293 // the most reliable way to select the platform default encoding.
Laszlo Csomorcefa9a22016-11-22 10:50:07 +00001294 blaze::SetEnv("LANG", "en_US.ISO-8859-1");
1295 blaze::SetEnv("LANGUAGE", "en_US.ISO-8859-1");
1296 blaze::SetEnv("LC_ALL", "en_US.ISO-8859-1");
1297 blaze::SetEnv("LC_CTYPE", "en_US.ISO-8859-1");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001298}
1299
ccalvarin1419dda2017-03-28 21:12:28 +00001300static string CheckAndGetBinaryPath(const string &argv0) {
Laszlo Csomor760f7862016-12-19 15:46:47 +00001301 if (blaze_util::IsAbsolute(argv0)) {
Laszlo Csomorc3545392016-11-24 13:33:28 +00001302 return argv0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001303 } else {
Laszlo Csomor760f7862016-12-19 15:46:47 +00001304 string abs_path = blaze_util::JoinPath(globals->cwd, argv0);
Laszlo Csomorc3545392016-11-24 13:33:28 +00001305 string resolved_path = blaze_util::MakeCanonical(abs_path.c_str());
1306 if (!resolved_path.empty()) {
1307 return resolved_path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001308 } else {
1309 // This happens during our integration tests, but thats okay, as we won't
1310 // log the invocation anyway.
Laszlo Csomorc3545392016-11-24 13:33:28 +00001311 return abs_path;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001312 }
1313 }
1314}
1315
Chloe Calvarineaa3be72016-12-13 19:48:34 +00001316int GetExitCodeForAbruptExit(const GlobalVariables &globals) {
ccalvarin1419dda2017-03-28 21:12:28 +00001317 BAZEL_LOG(INFO) << "Looking for a custom exit-code.";
1318 std::string filename = blaze_util::JoinPath(
1319 globals.options->output_base, "exit_code_to_use_on_abrupt_exit");
1320 std::string content;
1321 if (!blaze_util::ReadFile(filename, &content)) {
1322 BAZEL_LOG(INFO) << "Unable to read the custom exit-code file. "
1323 << "Exiting with an INTERNAL_ERROR.";
1324 return blaze_exit_code::INTERNAL_ERROR;
Chloe Calvarineaa3be72016-12-13 19:48:34 +00001325 }
ccalvarin1419dda2017-03-28 21:12:28 +00001326 if (!blaze_util::UnlinkPath(filename)) {
1327 BAZEL_LOG(INFO) << "Unable to delete the custom exit-code file. "
1328 << "Exiting with an INTERNAL_ERROR.";
1329 return blaze_exit_code::INTERNAL_ERROR;
1330 }
1331 int custom_exit_code;
1332 if (!blaze_util::safe_strto32(content, &custom_exit_code)) {
1333 BAZEL_LOG(INFO) << "Content of custom exit-code file not an int: "
1334 << content << "Exiting with an INTERNAL_ERROR.";
1335 return blaze_exit_code::INTERNAL_ERROR;
1336 }
1337 BAZEL_LOG(INFO) << "Read exit code " << custom_exit_code
1338 << " from custom exit-code file. Exiting accordingly.";
1339 return custom_exit_code;
Chloe Calvarineaa3be72016-12-13 19:48:34 +00001340}
1341
ccalvarin1419dda2017-03-28 21:12:28 +00001342int Main(int argc, const char *argv[], WorkspaceLayout *workspace_layout,
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001343 OptionProcessor *option_processor,
Chloe Calvarin78f1c852016-11-22 21:58:50 +00001344 std::unique_ptr<blaze_util::LogHandler> log_handler) {
1345 // Logging must be set first to assure no log statements are missed.
1346 blaze_util::SetLogHandler(std::move(log_handler));
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001347
Thiago Farina676cb9f2016-10-06 11:00:43 +00001348 globals = new GlobalVariables(option_processor);
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001349 blaze::SetupStdStreams();
Laszlo Csomorc4fb2182017-07-27 16:37:08 +02001350 if (argc == 1 && blaze::WarnIfStartedFromDesktop()) {
1351 // Only check and warn for from-desktop start if there were no args.
1352 // In this case the user probably clicked Bazel's icon (as opposed to either
1353 // starting it from a terminal, or as a subprocess with args, or on Windows
1354 // from a ".lnk" file with some args).
1355 return blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR;
1356 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001357
jmmva96369c2017-07-10 18:14:36 +02001358 // Best-effort operation to raise the resource limits from soft to hard. We
1359 // do this early during the main program instead of just before execing the
1360 // Blaze server binary, because it's easier (for testing purposes) and because
1361 // the Blaze client also benefits from this (e.g. during installation).
1362 UnlimitResources();
1363
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001364 // Must be done before command line parsing.
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001365 ComputeWorkspace(workspace_layout);
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001366
1367 // Must be done before command line parsing.
1368 // ParseOptions already populate --client_env, so detect bash before it
1369 // happens.
1370 DetectBashOrDie();
1371
Laszlo Csomorc3545392016-11-24 13:33:28 +00001372 globals->binary_path = CheckAndGetBinaryPath(argv[0]);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001373 ParseOptions(argc, argv);
Lukacs Berkibb2230f2016-04-27 14:19:25 +00001374
elenairinaabc69c32017-08-18 13:22:44 +02001375 blaze::SetDebugLog(globals->options->client_debug);
1376 debug_log("Debug logging active");
1377
Dmitry Lomov5a0d6ff2017-08-22 14:51:49 +02001378 PrepareEnvironmentForJvm();
Laszlo Csomor8a48f612016-11-17 10:18:34 +00001379 blaze::CreateSecureOutputRoot(globals->options->output_user_root);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001380
1381 const string self_path = GetSelfPath();
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001382 ComputeBaseDirectories(workspace_layout, self_path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001383
ccalvarin1419dda2017-03-28 21:12:28 +00001384 blaze_server = static_cast<BlazeServer *>(
1385 new GrpcBlazeServer(globals->options->connect_timeout_secs));
Lukacs Berki907dbbf2016-04-15 11:30:12 +00001386
Lukacs Berki415d39a2016-04-28 13:18:54 +00001387 globals->command_wait_time = blaze_server->AcquireLock();
Lukacs Berkice1445f2016-04-19 15:52:55 +00001388
Julio Merino28774852016-09-14 16:59:46 +00001389 WarnFilesystemType(globals->options->output_base);
Lukacs Berkice1445f2016-04-19 15:52:55 +00001390
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001391 ExtractData(self_path);
Lukacs Berki949c8762016-07-08 12:17:28 +00001392 VerifyJavaVersionAndSetJvm();
1393
Lukacs Berki1977d922016-05-02 09:31:37 +00001394 blaze_server->Connect();
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001395 EnsureCorrectRunningVersion(blaze_server);
1396 KillRunningServerIfDifferentStartupOptions(blaze_server);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001397
Julio Merino28774852016-09-14 16:59:46 +00001398 if (globals->options->batch) {
1399 SetScheduling(globals->options->batch_cpu_scheduling,
1400 globals->options->io_nice_level);
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001401 StartStandalone(workspace_layout, blaze_server);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001402 } else {
Julio Merinoe3e3bfa2016-12-08 22:22:12 +00001403 SendServerRequest(workspace_layout, blaze_server);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001404 }
1405 return 0;
1406}
Thiago Farina0b6963e2015-04-28 20:26:45 +00001407
ccalvarin1419dda2017-03-28 21:12:28 +00001408static void null_grpc_log_function(gpr_log_func_args *args) {}
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001409
Lukacs Berki71675a52016-11-08 09:48:27 +00001410GrpcBlazeServer::GrpcBlazeServer(int connect_timeout_secs) {
Lukacs Berki1977d922016-05-02 09:31:37 +00001411 connected_ = false;
Lukacs Berki71675a52016-11-08 09:48:27 +00001412 connect_timeout_secs_ = connect_timeout_secs;
1413
1414 gpr_set_log_function(null_grpc_log_function);
1415
Thiago Farina0bba4c92016-12-14 15:29:11 +00001416 pipe_ = blaze_util::CreatePipe();
1417 if (pipe_ == NULL) {
Laszlo Csomoref5ceef2016-11-18 11:19:02 +00001418 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "Couldn't create pipe");
Lukacs Berki6dd29092016-05-30 14:05:33 +00001419 }
1420}
1421
1422GrpcBlazeServer::~GrpcBlazeServer() {
Thiago Farina0bba4c92016-12-14 15:29:11 +00001423 delete pipe_;
1424 pipe_ = NULL;
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001425}
1426
ccalvarin1419dda2017-03-28 21:12:28 +00001427bool GrpcBlazeServer::TryConnect(command_server::CommandServer::Stub *client) {
Lukacs Berki10dd6382017-01-11 09:08:54 +00001428 grpc::ClientContext context;
ccalvarin1419dda2017-03-28 21:12:28 +00001429 context.set_deadline(std::chrono::system_clock::now() +
1430 std::chrono::seconds(connect_timeout_secs_));
Lukacs Berki10dd6382017-01-11 09:08:54 +00001431
1432 command_server::PingRequest request;
1433 command_server::PingResponse response;
1434 request.set_cookie(request_cookie_);
1435
1436 debug_log("Trying to connect to server (timeout: %d secs)...",
ccalvarin1419dda2017-03-28 21:12:28 +00001437 connect_timeout_secs_);
Lukacs Berki10dd6382017-01-11 09:08:54 +00001438 grpc::Status status = client->Ping(&context, request, &response);
1439
1440 if (!status.ok() || response.cookie() != response_cookie_) {
1441 debug_log("Connection to server failed: %s",
ccalvarin1419dda2017-03-28 21:12:28 +00001442 status.error_message().c_str());
Lukacs Berki10dd6382017-01-11 09:08:54 +00001443 return false;
1444 }
1445
1446 return true;
1447}
1448
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001449bool GrpcBlazeServer::Connect() {
Lukacs Berki1977d922016-05-02 09:31:37 +00001450 assert(!connected_);
1451
Laszlo Csomor760f7862016-12-19 15:46:47 +00001452 std::string server_dir =
1453 blaze_util::JoinPath(globals->options->output_base, "server");
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001454 std::string port;
Lukacs Berkib7caf9d2016-04-25 09:44:14 +00001455 std::string ipv4_prefix = "127.0.0.1:";
Lukacs Berkic8e74242016-04-28 08:32:04 +00001456 std::string ipv6_prefix_1 = "[0:0:0:0:0:0:0:1]:";
1457 std::string ipv6_prefix_2 = "[::1]:";
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001458
Laszlo Csomor760f7862016-12-19 15:46:47 +00001459 if (!blaze_util::ReadFile(blaze_util::JoinPath(server_dir, "command_port"),
1460 &port)) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001461 return false;
1462 }
1463
Lukacs Berkib7caf9d2016-04-25 09:44:14 +00001464 // Make sure that we are being directed to localhost
ccalvarin1419dda2017-03-28 21:12:28 +00001465 if (port.compare(0, ipv4_prefix.size(), ipv4_prefix) &&
1466 port.compare(0, ipv6_prefix_1.size(), ipv6_prefix_1) &&
1467 port.compare(0, ipv6_prefix_2.size(), ipv6_prefix_2)) {
Lukacs Berkib7caf9d2016-04-25 09:44:14 +00001468 return false;
1469 }
1470
Laszlo Csomor760f7862016-12-19 15:46:47 +00001471 if (!blaze_util::ReadFile(blaze_util::JoinPath(server_dir, "request_cookie"),
1472 &request_cookie_)) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001473 return false;
1474 }
1475
Laszlo Csomor760f7862016-12-19 15:46:47 +00001476 if (!blaze_util::ReadFile(blaze_util::JoinPath(server_dir, "response_cookie"),
Laszlo Csomor49970e02016-11-28 08:55:47 +00001477 &response_cookie_)) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001478 return false;
1479 }
1480
lberkia94aa632017-06-06 08:54:50 -04001481 pid_t server_pid = GetServerPid(server_dir);
1482 if (server_pid < 0) {
1483 return false;
1484 }
1485
mschallerfd37b512017-07-11 18:21:36 +02001486 if (!VerifyServerProcess(server_pid, globals->options->output_base)) {
lberkia94aa632017-06-06 08:54:50 -04001487 return false;
1488 }
1489
ccalvarin1419dda2017-03-28 21:12:28 +00001490 std::shared_ptr<grpc::Channel> channel(
1491 grpc::CreateChannel(port, grpc::InsecureChannelCredentials()));
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001492 std::unique_ptr<command_server::CommandServer::Stub> client(
1493 command_server::CommandServer::NewStub(channel));
1494
Lukacs Berki10dd6382017-01-11 09:08:54 +00001495 if (!TryConnect(client.get())) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001496 return false;
1497 }
1498
Lukacs Berki00cfb7d2016-04-20 09:01:52 +00001499 this->client_ = std::move(client);
Lukacs Berki1977d922016-05-02 09:31:37 +00001500 connected_ = true;
lberkia94aa632017-06-06 08:54:50 -04001501 globals->server_pid = server_pid;
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001502 return true;
1503}
1504
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001505// Cancellation works as follows:
1506//
1507// When the user presses Ctrl-C, a SIGINT is delivered to the client, which is
1508// translated into a BlazeServer::Cancel() call. Since it's not a good idea to
Lukacs Berki6dd29092016-05-30 14:05:33 +00001509// do significant work in signal handlers, all it does is write a byte to an
1510// unnamed pipe.
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001511//
Lukacs Berki6dd29092016-05-30 14:05:33 +00001512// This unnamed pipe is used to communicate with the cancel thread. Whenever
1513// something interesting happens, a byte is written into it, which is read by
1514// the cancel thread. These commands are available:
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001515//
Lukacs Berki6dd29092016-05-30 14:05:33 +00001516// - NOP
1517// - JOIN. The cancel thread needs to be terminated.
1518// - CANCEL. If the command ID is already available, a cancel request is sent.
1519// - COMMAND_ID_RECEIVED. The client learned the command ID from the server.
1520// If there is a pending cancellation request, it is acted upon.
1521//
1522// The only data the cancellation thread shares with the main thread is the
1523// file descriptor for receiving commands and command_id_, the latter of which
1524// is protected by a mutex, which mainly serves as a memory fence.
1525//
1526// The cancellation thread is joined at the end of the execution of the command.
1527// The main thread wakes it up just so that it can finish (using the JOIN
1528// action)
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001529//
1530// It's conceivable that the server is busy and thus it cannot service the
1531// cancellation request. In that case, we simply ignore the failure and the both
1532// the server and the client go on as if nothing had happened (except that this
Lukacs Berkie6a34f62016-04-25 12:16:04 +00001533// Ctrl-C still counts as a SIGINT, three of which result in a SIGKILL being
1534// delivered to the server)
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001535void GrpcBlazeServer::CancelThread() {
1536 bool running = true;
Lukacs Berki6dd29092016-05-30 14:05:33 +00001537 bool cancel = false;
1538 bool command_id_received = false;
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001539 while (running) {
Lukacs Berki6dd29092016-05-30 14:05:33 +00001540 char buf;
Laszlo Csomoref5ceef2016-11-18 11:19:02 +00001541
Laszlo Csomor508af302017-03-02 09:09:09 +00001542 int error;
1543 int bytes_read = pipe_->Receive(&buf, 1, &error);
1544 if (bytes_read < 0 && error == blaze_util::IPipe::INTERRUPTED) {
Lukacs Berki6dd29092016-05-30 14:05:33 +00001545 continue;
1546 } else if (bytes_read != 1) {
1547 pdie(blaze_exit_code::INTERNAL_ERROR,
1548 "Cannot communicate with cancel thread");
1549 }
1550
1551 switch (buf) {
1552 case CancelThreadAction::NOTHING:
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001553 break;
1554
Lukacs Berki6dd29092016-05-30 14:05:33 +00001555 case CancelThreadAction::JOIN:
1556 running = false;
1557 break;
1558
1559 case CancelThreadAction::COMMAND_ID_RECEIVED:
1560 command_id_received = true;
1561 if (cancel) {
1562 SendCancelMessage();
1563 cancel = false;
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001564 }
1565 break;
1566
Lukacs Berki6dd29092016-05-30 14:05:33 +00001567 case CancelThreadAction::CANCEL:
1568 if (command_id_received) {
1569 SendCancelMessage();
1570 } else {
1571 cancel = true;
1572 }
1573 break;
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001574 }
1575 }
1576}
1577
Lukacs Berki6dd29092016-05-30 14:05:33 +00001578void GrpcBlazeServer::SendCancelMessage() {
1579 std::unique_lock<std::mutex> lock(cancel_thread_mutex_);
1580
1581 command_server::CancelRequest request;
1582 request.set_cookie(request_cookie_);
1583 request.set_command_id(command_id_);
1584 grpc::ClientContext context;
1585 context.set_deadline(std::chrono::system_clock::now() +
Lukacs Berki3ace3002016-08-31 08:55:34 +00001586 std::chrono::seconds(10));
Lukacs Berki6dd29092016-05-30 14:05:33 +00001587 command_server::CancelResponse response;
1588 // There isn't a lot we can do if this request fails
Lukacs Berki3ace3002016-08-31 08:55:34 +00001589 grpc::Status status = client_->Cancel(&context, request, &response);
1590 if (!status.ok()) {
1591 fprintf(stderr, "\nCould not interrupt server (%s)\n\n",
1592 status.error_message().c_str());
1593 }
Lukacs Berki6dd29092016-05-30 14:05:33 +00001594}
1595
Lukacs Berki1977d922016-05-02 09:31:37 +00001596// This will wait indefinitely until the server shuts down
1597void GrpcBlazeServer::KillRunningServer() {
1598 assert(connected_);
Lukacs Berki1977d922016-05-02 09:31:37 +00001599
Lukacs Berkie6a34f62016-04-25 12:16:04 +00001600 grpc::ClientContext context;
1601 command_server::RunRequest request;
1602 command_server::RunResponse response;
1603 request.set_cookie(request_cookie_);
Julio Merino28774852016-09-14 16:59:46 +00001604 request.set_block_for_lock(globals->options->block_for_lock);
Laszlo Csomorae16e762016-11-18 10:16:08 +00001605 request.set_client_description("pid=" + blaze::GetProcessIdAsString() +
1606 " (for shutdown)");
Lukacs Berkie6a34f62016-04-25 12:16:04 +00001607 request.add_arg("shutdown");
1608 std::unique_ptr<grpc::ClientReader<command_server::RunResponse>> reader(
1609 client_->Run(&context, request));
1610
ccalvarin1419dda2017-03-28 21:12:28 +00001611 while (reader->Read(&response)) {
1612 }
Lukacs Berkie6a34f62016-04-25 12:16:04 +00001613
mschallerfd37b512017-07-11 18:21:36 +02001614 // Wait for the server process to terminate (if we know the server PID).
1615 // If it does not terminate itself gracefully within 1m, terminate it.
Lukacs Berki10dd6382017-01-11 09:08:54 +00001616 if (globals->server_pid > 0 &&
mschallerfd37b512017-07-11 18:21:36 +02001617 !AwaitServerProcessTermination(globals->server_pid,
1618 globals->options->output_base,
1619 kPostShutdownGracePeriodSeconds)) {
1620 KillServerProcess(globals->server_pid, globals->options->output_base);
Lukacs Berkiee44c382016-09-14 10:53:37 +00001621 }
Lukacs Berki1977d922016-05-02 09:31:37 +00001622
1623 connected_ = false;
Lukacs Berkie6a34f62016-04-25 12:16:04 +00001624}
1625
1626unsigned int GrpcBlazeServer::Communicate() {
Lukacs Berki1977d922016-05-02 09:31:37 +00001627 assert(connected_);
mschallerfd37b512017-07-11 18:21:36 +02001628 assert(globals->server_pid > 0);
Lukacs Berki1977d922016-05-02 09:31:37 +00001629
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001630 vector<string> arg_vector;
Julio Merino28774852016-09-14 16:59:46 +00001631 string command = globals->option_processor->GetCommand();
Googler54a3ac22017-08-29 20:19:18 +02001632 if (!command.empty()) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001633 arg_vector.push_back(command);
1634 AddLoggingArgs(&arg_vector);
1635 }
1636
lpinoc00ba522017-07-10 19:17:40 +02001637 const vector<string> command_args =
1638 globals->option_processor->GetCommandArguments();
1639 if (!command_args.empty()) {
1640 arg_vector.insert(arg_vector.end(),
1641 command_args.begin(),
1642 command_args.end());
1643 }
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001644
1645 command_server::RunRequest request;
Lukacs Berki00cfb7d2016-04-20 09:01:52 +00001646 request.set_cookie(request_cookie_);
Julio Merino28774852016-09-14 16:59:46 +00001647 request.set_block_for_lock(globals->options->block_for_lock);
Laszlo Csomorae16e762016-11-18 10:16:08 +00001648 request.set_client_description("pid=" + blaze::GetProcessIdAsString());
ccalvarin1419dda2017-03-28 21:12:28 +00001649 for (const string &arg : arg_vector) {
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001650 request.add_arg(arg);
1651 }
ccalvarin0ddda782017-04-06 21:12:11 +00001652 if (globals->options->invocation_policy != NULL &&
1653 strlen(globals->options->invocation_policy) > 0) {
1654 request.set_invocation_policy(globals->options->invocation_policy);
1655 }
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001656
ccalvarin1cbe62a2017-08-14 21:09:07 +02001657 const StartupOptions *startup_options(
1658 globals->option_processor->GetParsedStartupOptions());
1659 for (const auto &startup_option :
1660 startup_options->original_startup_options_) {
1661 command_server::StartupOption *proto_option_field =
1662 request.add_startup_options();
1663 request.add_startup_options();
1664 proto_option_field->set_source(startup_option.source);
1665 proto_option_field->set_option(startup_option.value);
1666 }
1667
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001668 grpc::ClientContext context;
1669 command_server::RunResponse response;
1670 std::unique_ptr<grpc::ClientReader<command_server::RunResponse>> reader(
Lukacs Berki00cfb7d2016-04-20 09:01:52 +00001671 client_->Run(&context, request));
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001672
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001673 std::thread cancel_thread(&GrpcBlazeServer::CancelThread, this);
1674 bool command_id_set = false;
Laurent Le Brun08849b22016-09-20 12:21:32 +00001675 bool pipe_broken = false;
Lukacs Berki381275a2017-02-13 09:44:47 +00001676 int exit_code = -1;
1677 bool finished = false;
1678 bool finished_warning_emitted = false;
mschallerfd37b512017-07-11 18:21:36 +02001679 bool termination_expected = false;
Lukacs Berki381275a2017-02-13 09:44:47 +00001680
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001681 while (reader->Read(&response)) {
Lukacs Berki381275a2017-02-13 09:44:47 +00001682 if (finished && !finished_warning_emitted) {
1683 fprintf(stderr, "\nServer returned messages after reporting exit code\n");
1684 finished_warning_emitted = true;
1685 }
1686
Lukacs Berkic55e9c72016-04-25 13:43:40 +00001687 if (response.cookie() != response_cookie_) {
1688 fprintf(stderr, "\nServer response cookie invalid, exiting\n");
1689 return blaze_exit_code::INTERNAL_ERROR;
1690 }
1691
Peter Foleyf795c752017-04-10 11:58:31 +00001692 const char *broken_pipe_name = nullptr;
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001693
Lukacs Berki381275a2017-02-13 09:44:47 +00001694 if (response.finished()) {
1695 exit_code = response.exit_code();
mschallerfd37b512017-07-11 18:21:36 +02001696 termination_expected = response.termination_expected();
Lukacs Berki381275a2017-02-13 09:44:47 +00001697 finished = true;
1698 }
1699
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001700 if (!response.standard_output().empty()) {
1701 size_t size = response.standard_output().size();
Laszlo Csomor508af302017-03-02 09:09:09 +00001702 if (blaze_util::WriteToStdOutErr(response.standard_output().c_str(), size,
1703 /* to_stdout */ true) ==
1704 blaze_util::WriteResult::BROKEN_PIPE) {
Lukacs Berki3c1e4042017-01-11 13:30:29 +00001705 broken_pipe_name = "standard output";
Laurent Le Brun08849b22016-09-20 12:21:32 +00001706 }
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001707 }
1708
Laszlo Csomor74ffaf72016-11-24 12:17:20 +00001709 if (!response.standard_error().empty()) {
1710 size_t size = response.standard_error().size();
Laszlo Csomor508af302017-03-02 09:09:09 +00001711 if (blaze_util::WriteToStdOutErr(response.standard_error().c_str(), size,
1712 /* to_stdout */ false) ==
1713 blaze_util::WriteResult::BROKEN_PIPE) {
Lukacs Berki3c1e4042017-01-11 13:30:29 +00001714 broken_pipe_name = "standard error";
Laurent Le Brun08849b22016-09-20 12:21:32 +00001715 }
1716 }
1717
Peter Foleyf795c752017-04-10 11:58:31 +00001718 if (broken_pipe_name != nullptr && !pipe_broken) {
Laurent Le Brun08849b22016-09-20 12:21:32 +00001719 pipe_broken = true;
Lukacs Berki3c1e4042017-01-11 13:30:29 +00001720 fprintf(stderr, "\nCannot write to %s; exiting...\n\n", broken_pipe_name);
Laurent Le Brun08849b22016-09-20 12:21:32 +00001721 Cancel();
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001722 }
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001723
Googler54a3ac22017-08-29 20:19:18 +02001724 if (!command_id_set && !response.command_id().empty()) {
Lukacs Berki6dd29092016-05-30 14:05:33 +00001725 std::unique_lock<std::mutex> lock(cancel_thread_mutex_);
Lukacs Berki00cfb7d2016-04-20 09:01:52 +00001726 command_id_ = response.command_id();
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001727 command_id_set = true;
Lukacs Berki6dd29092016-05-30 14:05:33 +00001728 SendAction(CancelThreadAction::COMMAND_ID_RECEIVED);
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001729 }
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001730 }
1731
mschallerfd37b512017-07-11 18:21:36 +02001732 // If the server has shut down, but does not terminate itself within a 1m
1733 // grace period, terminate it.
1734 if (termination_expected &&
1735 !AwaitServerProcessTermination(globals->server_pid,
1736 globals->options->output_base,
1737 kPostShutdownGracePeriodSeconds)) {
1738 KillServerProcess(globals->server_pid, globals->options->output_base);
1739 }
1740
Lukacs Berki6dd29092016-05-30 14:05:33 +00001741 SendAction(CancelThreadAction::JOIN);
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001742 cancel_thread.join();
1743
Lukacs Berki381275a2017-02-13 09:44:47 +00001744 grpc::Status status = reader->Finish();
1745 if (!status.ok()) {
ccalvarin1419dda2017-03-28 21:12:28 +00001746 fprintf(stderr,
1747 "\nServer terminated abruptly "
Lukacs Berki381275a2017-02-13 09:44:47 +00001748 "(error code: %d, error message: '%s', log file: '%s')\n\n",
1749 status.error_code(), status.error_message().c_str(),
1750 globals->jvm_log_file.c_str());
1751 return GetExitCodeForAbruptExit(*globals);
1752 } else if (!finished) {
ccalvarin1419dda2017-03-28 21:12:28 +00001753 fprintf(stderr,
1754 "\nServer finished RPC without an explicit exit code "
1755 "(log file: '%s')\n\n",
1756 globals->jvm_log_file.c_str());
Lukacs Berki2896dc02016-07-07 07:55:04 +00001757 return GetExitCodeForAbruptExit(*globals);
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001758 }
1759
Lukacs Berki3c1e4042017-01-11 13:30:29 +00001760 // We'll exit with exit code SIGPIPE on Unixes due to PropagateSignalOnExit()
ccalvarin1419dda2017-03-28 21:12:28 +00001761 return pipe_broken ? blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR : exit_code;
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001762}
1763
1764void GrpcBlazeServer::Disconnect() {
Lukacs Berki1977d922016-05-02 09:31:37 +00001765 assert(connected_);
1766
Lukacs Berki00cfb7d2016-04-20 09:01:52 +00001767 client_.reset();
1768 request_cookie_ = "";
1769 response_cookie_ = "";
Lukacs Berki1977d922016-05-02 09:31:37 +00001770 connected_ = false;
Lukacs Berki1b25ce22016-04-15 13:11:21 +00001771}
1772
Lukacs Berki6dd29092016-05-30 14:05:33 +00001773void GrpcBlazeServer::SendAction(CancelThreadAction action) {
1774 char msg = action;
Thiago Farina0bba4c92016-12-14 15:29:11 +00001775 if (!pipe_->Send(&msg, 1)) {
Laszlo Csomor3b89d2d2016-11-28 14:04:27 +00001776 blaze::SigPrintf(
1777 "\nCould not interrupt server (cannot write to client pipe)\n\n");
Sasha Smundak1fdd31d2016-07-25 17:54:00 +00001778 }
Lukacs Berki6dd29092016-05-30 14:05:33 +00001779}
1780
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001781void GrpcBlazeServer::Cancel() {
Lukacs Berki1977d922016-05-02 09:31:37 +00001782 assert(connected_);
Lukacs Berki6dd29092016-05-30 14:05:33 +00001783 SendAction(CancelThreadAction::CANCEL);
Lukacs Berkif1df38a2016-04-19 07:42:22 +00001784}
1785
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001786} // namespace blaze