blob: ae8af0d424cf83eb48e3572d13c82b7ac3ba6d11 [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.
26
27#include <assert.h>
28#include <ctype.h>
29#include <dirent.h>
30#include <errno.h>
31#include <fcntl.h>
32#include <limits.h>
33#include <poll.h>
34#include <sched.h>
35#include <signal.h>
36#include <stdarg.h>
Thiago Farina8a67da42015-05-05 18:04:50 +000037#include <stdint.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <sys/resource.h>
42#include <sys/select.h>
43#include <sys/socket.h>
44#include <sys/stat.h>
45#include <sys/statvfs.h>
46#include <sys/time.h>
47#include <sys/un.h>
48#include <time.h>
49#include <unistd.h>
50#include <utime.h>
51#include <algorithm>
52#include <set>
53#include <string>
54#include <utility>
55#include <vector>
56
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000057#include "src/main/cpp/blaze_startup_options.h"
58#include "src/main/cpp/blaze_util.h"
59#include "src/main/cpp/blaze_util_platform.h"
60#include "src/main/cpp/option_processor.h"
61#include "src/main/cpp/util/errors.h"
Thiago Farina7f9357f2015-04-23 13:57:43 +000062#include "src/main/cpp/util/exit_code.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000063#include "src/main/cpp/util/file.h"
64#include "src/main/cpp/util/md5.h"
65#include "src/main/cpp/util/numbers.h"
66#include "src/main/cpp/util/port.h"
67#include "src/main/cpp/util/strings.h"
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +000068#include "third_party/ijar/zip.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069
Thiago Farina7390ddb2015-04-09 13:53:53 +000070using blaze_util::Md5Digest;
Thiago Farina241f46c2015-04-13 14:33:30 +000071using blaze_util::die;
72using blaze_util::pdie;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010073using std::set;
74using std::vector;
75
76// This should already be defined in sched.h, but it's not.
77#ifndef SCHED_BATCH
78#define SCHED_BATCH 3
79#endif
80
81namespace blaze {
82
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083////////////////////////////////////////////////////////////////////////
84// Global Variables
85
86// The reason for a blaze server restart.
87// Keep in sync with logging.proto
88enum RestartReason {
89 NO_RESTART = 0,
90 NO_DAEMON,
91 NEW_VERSION,
92 NEW_OPTIONS
93};
94
95struct GlobalVariables {
96 // Used to make concurrent invocations of this program safe.
97 string lockfile; // = <output_base>/lock
98 int lockfd;
99
100 string jvm_log_file; // = <output_base>/server/jvm.out
101
102 string cwd;
103
104 // The nearest enclosing workspace directory, starting from cwd.
105 // If not under a workspace directory, this is equal to cwd.
106 string workspace;
107
108 // Option processor responsible for parsing RC files and converting them into
109 // the argument list passed on to the server.
110 OptionProcessor option_processor;
111
112 pid_t server_pid;
113
114 volatile sig_atomic_t sigint_count;
115
116 // The number of the last received signal that should cause the client
117 // to shutdown. This is saved so that the client's WTERMSIG can be set
118 // correctly. (Currently only SIGPIPE uses this mechanism.)
119 volatile sig_atomic_t received_signal;
120
121 // Contains the relative paths of all the files in the attached zip, and is
122 // populated during GetInstallDir().
123 vector<string> extracted_binaries;
124
125 // Parsed startup options
126 BlazeStartupOptions options;
127
128 // The time in ms the launcher spends before sending the request to the Blaze
Thiago Farina8a67da42015-05-05 18:04:50 +0000129 uint64_t startup_time;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130
131 // The time spent on extracting the new blaze version
132 // This is part of startup_time
Thiago Farina8a67da42015-05-05 18:04:50 +0000133 uint64_t extract_data_time;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100134
135 // The time in ms if a command had to wait on a busy Blaze server process
136 // This is part of startup_time
Thiago Farina8a67da42015-05-05 18:04:50 +0000137 uint64_t command_wait_time;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100138
139 RestartReason restart_reason;
140
141 // Absolute path of the blaze binary
142 string binary_path;
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000143
144 // MD5 hash of the Blaze binary (includes deploy.jar, extracted binaries, and
145 // anything else that ends up under the install_base).
146 string install_md5;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100147};
148
149static GlobalVariables *globals;
150
Thiago Farina0b6963e2015-04-28 20:26:45 +0000151static void InitGlobals() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100152 globals = new GlobalVariables;
Doug Rabsond655f2a2015-08-13 14:41:50 +0000153 globals->server_pid = -1;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100154 globals->sigint_count = 0;
Doug Rabson709bc612015-08-25 14:12:00 +0000155 globals->received_signal = 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 globals->startup_time = 0;
157 globals->extract_data_time = 0;
158 globals->command_wait_time = 0;
159 globals->restart_reason = NO_RESTART;
160}
161
162////////////////////////////////////////////////////////////////////////
163// Logic
164
165
166// Returns the canonical form of the base dir given a root and a hashable
167// string. The resulting dir is composed of the root + md5(hashable)
168static string GetHashedBaseDir(const string &root,
169 const string &hashable) {
Thiago Farina7390ddb2015-04-09 13:53:53 +0000170 unsigned char buf[Md5Digest::kDigestLength];
171 Md5Digest digest;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 digest.Update(hashable.data(), hashable.size());
173 digest.Finish(buf);
174 return root + "/" + digest.String();
175}
176
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000177// A devtools_ijar::ZipExtractorProcessor to extract the InstallKeyFile
178class GetInstallKeyFileProcessor : public devtools_ijar::ZipExtractorProcessor {
179 public:
Thiago Farina9cb32752015-06-03 15:34:19 +0000180 explicit GetInstallKeyFileProcessor(string *install_base_key)
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000181 : install_base_key_(install_base_key) {}
182
183 virtual bool Accept(const char *filename, const devtools_ijar::u4 attr) {
184 globals->extracted_binaries.push_back(filename);
185 return strcmp(filename, "install_base_key") == 0;
186 }
187
188 virtual void Process(const char *filename, const devtools_ijar::u4 attr,
189 const devtools_ijar::u1 *data, const size_t size) {
190 string str(reinterpret_cast<const char *>(data), size);
191 blaze_util::StripWhitespace(&str);
Lukacs Berki58c29ae2015-10-16 14:48:33 +0000192 if (str.size() != 32) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000193 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Lukacs Berki58c29ae2015-10-16 14:48:33 +0000194 "\nFailed to extract install_base_key: file size mismatch "
195 "(should be 32, is %zd)", str.size());
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000196 }
197 *install_base_key_ = str;
198 }
199
200 private:
201 string *install_base_key_;
202};
203
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100204// Returns the install base (the root concatenated with the contents of the file
205// 'install_base_key' contained as a ZIP entry in the Blaze binary); as a side
206// effect, it also populates the extracted_binaries global variable.
207static string GetInstallBase(const string &root, const string &self_path) {
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000208 GetInstallKeyFileProcessor processor(&globals->install_md5);
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000209 std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
210 devtools_ijar::ZipExtractor::Create(self_path.c_str(), &processor));
211 if (extractor.get() == NULL) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100212 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000213 "\nFailed to open %s as a zip file: (%d) %s",
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000214 globals->options.GetProductName().c_str(), errno, strerror(errno));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100215 }
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000216 if (extractor->ProcessAll() < 0) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100217 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000218 "\nFailed to extract install_base_key: %s", extractor->GetError());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100219 }
220
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000221 if (globals->install_md5.empty()) {
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000222 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
223 "\nFailed to find install_base_key's in zip file");
224 }
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000225 return root + "/" + globals->install_md5;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226}
227
228// Escapes colons by replacing them with '_C' and underscores by replacing them
229// with '_U'. E.g. "name:foo_bar" becomes "name_Cfoo_Ubar"
230static string EscapeForOptionSource(const string& input) {
231 string result = input;
232 blaze_util::Replace("_", "_U", &result);
233 blaze_util::Replace(":", "_C", &result);
234 return result;
235}
236
237// Returns the JVM command argument array.
238static vector<string> GetArgumentArray() {
239 vector<string> result;
240
241 // e.g. A Blaze server process running in ~/src/build_root (where there's a
242 // ~/src/build_root/WORKSPACE file) will appear in ps(1) as "blaze(src)".
243 string workspace =
244 blaze_util::Basename(blaze_util::Dirname(globals->workspace));
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000245 string product = globals->options.GetProductName();
246 blaze_util::ToLower(&product);
247 result.push_back(product + "(" + workspace + ")");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100248 if (globals->options.batch) {
249 result.push_back("-client");
250 result.push_back("-Xms256m");
251 result.push_back("-XX:NewRatio=4");
252 } else {
253 result.push_back("-server");
254 }
255
256 result.push_back("-XX:+HeapDumpOnOutOfMemoryError");
257 string heap_crash_path = globals->options.output_base;
258 result.push_back("-XX:HeapDumpPath=" + heap_crash_path);
259
260 result.push_back("-Xverify:none");
261
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000262 vector<string> user_options;
263
Janak Ramakrishnan533657e2015-11-13 23:34:14 +0000264 if (globals->options.preserve_spaces_in_host_jvm_args) {
265 user_options.insert(user_options.begin(),
266 globals->options.host_jvm_args.begin(),
267 globals->options.host_jvm_args.end());
268 } else {
269 for (const auto &arg : globals->options.host_jvm_args) {
270 // int num_segments =
271 blaze_util::SplitQuotedStringUsing(arg, ' ', &user_options);
272 // TODO(janakr): Enable this warning when users have been migrated.
273 // if (num_segments > 1) {
274 // fprintf(stderr, "WARNING: You are passing multiple jvm options"
275 // " under a single --host_jvm_args option: %s. This will stop
276 // working "
277 // "soon. Instead, pass each option under its own
278 // --host_jvm_args "
279 // "option.\n", arg);
280 //
281 }
282 }
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000283
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100284 // Add JVM arguments particular to building blaze64 and particular JVM
285 // versions.
286 string error;
287 blaze_exit_code::ExitCode jvm_args_exit_code =
288 globals->options.AddJVMArguments(globals->options.GetHostJavabase(),
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000289 &result, user_options, &error);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100290 if (jvm_args_exit_code != blaze_exit_code::SUCCESS) {
291 die(jvm_args_exit_code, "%s", error.c_str());
292 }
293
294 // We put all directories on the java.library.path that contain .so files.
295 string java_library_path = "-Djava.library.path=";
296 string real_install_dir = blaze_util::JoinPath(globals->options.install_base,
297 "_embedded_binaries");
298 bool first = true;
299 for (const auto& it : globals->extracted_binaries) {
Thiago Farina01f36002015-04-08 15:59:08 +0000300 if (IsSharedLibrary(it)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 if (!first) {
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000302 java_library_path += blaze::ListSeparator();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100303 }
304 first = false;
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000305 java_library_path += blaze::ConvertPath(
306 blaze_util::JoinPath(real_install_dir, blaze_util::Dirname(it)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 }
308 }
309 result.push_back(java_library_path);
310
311 // Force use of latin1 for file names.
312 result.push_back("-Dfile.encoding=ISO-8859-1");
313
314 if (globals->options.host_jvm_debug) {
315 fprintf(stderr,
316 "Running host JVM under debugger (listening on TCP port 5005).\n");
317 // Start JVM so that it listens for a connection from a
318 // JDWP-compliant debugger:
319 result.push_back("-Xdebug");
320 result.push_back("-Xrunjdwp:transport=dt_socket,server=y,address=5005");
321 }
Janak Ramakrishnande735c02015-06-02 16:38:57 +0000322 result.insert(result.end(), user_options.begin(), user_options.end());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100323
324 result.push_back("-jar");
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000325 result.push_back(blaze::ConvertPath(
326 blaze_util::JoinPath(real_install_dir, globals->extracted_binaries[0])));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100327
328 if (!globals->options.batch) {
329 result.push_back("--max_idle_secs");
Googler9588b812015-07-23 11:49:37 +0000330 result.push_back(ToString(globals->options.max_idle_secs));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100331 } else {
Googlerc8c64e72015-03-23 23:22:18 +0000332 // --batch must come first in the arguments to Java main() because
333 // the code expects it to be at args[0] if it's been set.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100334 result.push_back("--batch");
335 }
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000336 result.push_back("--install_base=" +
337 blaze::ConvertPath(globals->options.install_base));
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +0000338 result.push_back("--install_md5=" + globals->install_md5);
Dmitry Lomov78c0cc72015-08-11 16:44:21 +0000339 result.push_back("--output_base=" +
340 blaze::ConvertPath(globals->options.output_base));
341 result.push_back("--workspace_directory=" +
342 blaze::ConvertPath(globals->workspace));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100343 if (!globals->options.skyframe.empty()) {
344 result.push_back("--skyframe=" + globals->options.skyframe);
345 }
Marian Lobur6dcdd602015-04-09 09:28:40 +0000346 if (globals->options.blaze_cpu) {
347 result.push_back("--blaze_cpu=true");
348 }
349
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350 if (globals->options.allow_configurable_attributes) {
351 result.push_back("--allow_configurable_attributes");
352 }
353 if (globals->options.watchfs) {
354 result.push_back("--watchfs");
355 }
356 if (globals->options.fatal_event_bus_exceptions) {
357 result.push_back("--fatal_event_bus_exceptions");
358 } else {
359 result.push_back("--nofatal_event_bus_exceptions");
360 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100361
362 // This is only for Blaze reporting purposes; the real interpretation of the
363 // jvm flags occurs when we set up the java command line.
364 if (globals->options.host_jvm_debug) {
365 result.push_back("--host_jvm_debug");
366 }
367 if (!globals->options.host_jvm_profile.empty()) {
368 result.push_back("--host_jvm_profile=" + globals->options.host_jvm_profile);
369 }
Janak Ramakrishnan533657e2015-11-13 23:34:14 +0000370 if (globals->options.preserve_spaces_in_host_jvm_args) {
371 result.push_back("--experimental_preserve_spaces_in_host_jvm_args");
372 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100373 if (!globals->options.host_jvm_args.empty()) {
Janak Ramakrishnan533657e2015-11-13 23:34:14 +0000374 for (const auto &arg : globals->options.host_jvm_args) {
375 result.push_back("--host_jvm_args=" + arg);
376 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100377 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000378
379 if (globals->options.invocation_policy != NULL &&
380 strlen(globals->options.invocation_policy) > 0) {
381 result.push_back(string("--invocation_policy=") +
382 globals->options.invocation_policy);
383 }
384
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 globals->options.AddExtraOptions(&result);
386
387 // The option sources are transmitted in the following format:
388 // --option_sources=option1:source1:option2:source2:...
389 string option_sources = "--option_sources=";
390 first = true;
391 for (const auto& it : globals->options.option_sources) {
392 if (!first) {
393 option_sources += ":";
394 }
395
396 first = false;
397 option_sources += EscapeForOptionSource(it.first) + ":" +
398 EscapeForOptionSource(it.second);
399 }
400
401 result.push_back(option_sources);
402 return result;
403}
404
405// Add commom command options for logging to the given argument array.
406static void AddLoggingArgs(vector<string>* args) {
Googler9588b812015-07-23 11:49:37 +0000407 args->push_back("--startup_time=" + ToString(globals->startup_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100408 if (globals->command_wait_time != 0) {
409 args->push_back("--command_wait_time=" +
Googler9588b812015-07-23 11:49:37 +0000410 ToString(globals->command_wait_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100411 }
412 if (globals->extract_data_time != 0) {
413 args->push_back("--extract_data_time=" +
Googler9588b812015-07-23 11:49:37 +0000414 ToString(globals->extract_data_time));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100415 }
416 if (globals->restart_reason != NO_RESTART) {
417 const char *reasons[] = {
418 "no_restart", "no_daemon", "new_version", "new_options"
419 };
420 args->push_back(
421 string("--restart_reason=") + reasons[globals->restart_reason]);
422 }
423 args->push_back(
424 string("--binary_path=") + globals->binary_path);
425}
426
427
428// Join the elements of the specified array with NUL's (\0's), akin to the
429// format of /proc/$PID/cmdline.
Thiago Farina0b6963e2015-04-28 20:26:45 +0000430static string GetArgumentString(const vector<string>& argument_array) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100431 string result;
432 blaze_util::JoinStrings(argument_array, '\0', &result);
433 return result;
434}
435
436// Causes the current process to become a daemon (i.e. a child of
437// init, detached from the terminal, in its own session.) We don't
438// change cwd, though.
439static void Daemonize(int socket) {
440 // Don't call die() or exit() in this function; we're already in a
441 // child process so it won't work as expected. Just don't do
442 // anything that can possibly fail. :)
443
444 signal(SIGHUP, SIG_IGN);
445 if (fork() > 0) {
446 // This second fork is required iff there's any chance cmd will
447 // open an specific tty explicitly, e.g., open("/dev/tty23"). If
448 // not, this fork can be removed.
449 _exit(blaze_exit_code::SUCCESS);
450 }
451
452 setsid();
453
454 close(0);
455 close(1);
456 close(2);
457 close(socket);
458
459 open("/dev/null", O_RDONLY); // stdin
460 // stdout:
461 if (open(globals->jvm_log_file.c_str(),
462 O_WRONLY | O_CREAT | O_TRUNC, 0666) == -1) {
463 // In a daemon, no-one can hear you scream.
464 open("/dev/null", O_WRONLY);
465 }
466 dup(STDOUT_FILENO); // stderr (2>&1)
467
468 // Keep server from inheriting a useless fd.
469 // The file lock was already lost at fork().
470 close(globals->lockfd);
471}
472
473// Do a chdir into the workspace, and die if it fails.
474static void GoToWorkspace() {
475 if (BlazeStartupOptions::InWorkspace(globals->workspace) &&
476 chdir(globals->workspace.c_str()) != 0) {
477 pdie(blaze_exit_code::INTERNAL_ERROR,
478 "chdir() into %s failed", globals->workspace.c_str());
479 }
480}
481
482// Check the java version if a java version specification is bundled. On
483// success,
484// return the executable path of the java command.
485static string VerifyJavaVersionAndGetJvm() {
486 string exe = globals->options.GetJvm();
487
488 string version_spec_file = blaze_util::JoinPath(
489 blaze_util::JoinPath(globals->options.install_base, "_embedded_binaries"),
490 "java.version");
491 string version_spec = "";
492 if (ReadFile(version_spec_file, &version_spec)) {
493 blaze_util::StripWhitespace(&version_spec);
494 // A version specification is given, get version of java.
495 string jvm_version = GetJvmVersion(exe);
496
497 // Compare that jvm_version is found and at least the one specified.
498 if (jvm_version.size() == 0) {
499 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
500 "Java version not detected while at least %s is needed.\n"
501 "Please set JAVA_HOME.", version_spec.c_str());
502 } else if (!CheckJavaVersionIsAtLeast(jvm_version, version_spec)) {
503 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
504 "Java version is %s while at least %s is needed.\n"
505 "Please set JAVA_HOME.",
506 jvm_version.c_str(), version_spec.c_str());
507 }
508 }
509
510 return exe;
511}
512
513// Starts the Blaze server. Returns a readable fd connected to the server.
514// This is currently used only to detect liveness.
515static int StartServer(int socket) {
516 vector<string> jvm_args_vector = GetArgumentArray();
517 string argument_string = GetArgumentString(jvm_args_vector);
518
519 // Write the cmdline argument string to the server dir. If we get to this
520 // point, there is no server running, so we don't overwrite the cmdline file
521 // for the existing server. If might be that the server dies and the cmdline
522 // file stays there, but that is not a problem, since we always check the
523 // server, too.
524 WriteFile(argument_string, globals->options.output_base + "/server/cmdline");
525
526 // unless we restarted for a new-version, mark this as initial start
527 if (globals->restart_reason == NO_RESTART) {
528 globals->restart_reason = NO_DAEMON;
529 }
530
531 // Computing this path may report a fatal error, so do it before forking.
532 string exe = VerifyJavaVersionAndGetJvm();
533
534 // Go to the workspace before we daemonize, so
535 // we can still print errors to the terminal.
536 GoToWorkspace();
537
538 int fds[2];
539 if (pipe(fds)) {
540 pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed");
541 }
542 int child = fork();
543 if (child == -1) {
544 pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed");
545 } else if (child > 0) { // we're the parent
546 close(fds[1]); // parent keeps only the reading side
547 return fds[0];
548 } else {
549 close(fds[0]); // child keeps only the writing side
550 }
551
552 Daemonize(socket);
553 ExecuteProgram(exe, jvm_args_vector);
554 pdie(blaze_exit_code::INTERNAL_ERROR, "execv of '%s' failed", exe.c_str());
Ulf Adams523bff52015-07-24 12:17:18 +0000555 return -1;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100556}
557
558static bool KillRunningServerIfAny();
559
560// Replace this process with blaze in standalone/batch mode.
561// The batch mode blaze process handles the command and exits.
562//
563// This function passes the commands array to the blaze process.
564// This array should start with a command ("build", "info", etc.).
565static void StartStandalone() {
566 KillRunningServerIfAny();
567
568 // Wall clock time since process startup.
569 globals->startup_time = ProcessClock() / 1000000LL;
570
571 if (VerboseLogging()) {
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000572 fprintf(stderr, "Starting %s in batch mode.\n",
573 globals->options.GetProductName().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100574 }
575 string command = globals->option_processor.GetCommand();
576 vector<string> command_arguments;
577 globals->option_processor.GetCommandArguments(&command_arguments);
578
579 if (!command_arguments.empty() && command == "shutdown") {
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000580 string product = globals->options.GetProductName();
581 blaze_util::ToLower(&product);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100582 fprintf(stderr,
583 "WARNING: Running command \"shutdown\" in batch mode. Batch mode "
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000584 "is triggered\nwhen not running %s within a workspace. If you "
585 "intend to shutdown an\nexisting %s server, run \"%s "
586 "shutdown\" from the directory where\nit was started.\n",
587 globals->options.GetProductName().c_str(),
588 globals->options.GetProductName().c_str(), product.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100589 }
590 vector<string> jvm_args_vector = GetArgumentArray();
591 if (command != "") {
592 jvm_args_vector.push_back(command);
593 AddLoggingArgs(&jvm_args_vector);
594 }
595
596 jvm_args_vector.insert(jvm_args_vector.end(),
597 command_arguments.begin(),
598 command_arguments.end());
599
600 GoToWorkspace();
601
602 string exe = VerifyJavaVersionAndGetJvm();
603 ExecuteProgram(exe, jvm_args_vector);
604 pdie(blaze_exit_code::INTERNAL_ERROR, "execv of '%s' failed", exe.c_str());
605}
606
607// Like connect(2), but uses the AF_UNIX address denoted by socket_file,
608// resolving symbolic links. (The server may make "socket_file" a
609// symlink, to avoid ENAMETOOLONG, in which case the client must
610// resolve it in userspace before connecting.)
611static int Connect(int socket, const string &socket_file) {
612 struct sockaddr_un addr;
613 addr.sun_family = AF_UNIX;
614
615 char *resolved_path = realpath(socket_file.c_str(), NULL);
616 if (resolved_path != NULL) {
617 strncpy(addr.sun_path, resolved_path, sizeof addr.sun_path);
618 addr.sun_path[sizeof addr.sun_path - 1] = '\0';
619 free(resolved_path);
620 sockaddr *paddr = reinterpret_cast<sockaddr *>(&addr);
621 return connect(socket, paddr, sizeof addr);
622 } else if (errno == ENOENT) { // No socket means no server to connect to
623 errno = ECONNREFUSED;
624 return -1;
625 } else {
626 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
627 "realpath('%s') failed", socket_file.c_str());
Ulf Adams523bff52015-07-24 12:17:18 +0000628 return -1;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100629 }
630}
631
632// Write the contents of file_name to stream.
633static void WriteFileToStreamOrDie(FILE *stream, const char *file_name) {
634 FILE *fp = fopen(file_name, "r");
635 if (fp == NULL) {
636 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
637 "opening %s failed", file_name);
638 }
639 char buffer[255];
640 int num_read;
641 while ((num_read = fread(buffer, 1, sizeof buffer, fp)) > 0) {
642 if (ferror(fp)) {
643 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
644 "failed to read from '%s'", file_name);
645 }
646 fwrite(buffer, 1, num_read, stream);
647 }
648 fclose(fp);
649}
650
Doug Rabsond655f2a2015-08-13 14:41:50 +0000651// After connecting to the Blaze server, initialize server_pid.
652static void GetServerPid(int s, const string &pid_file) {
653 globals->server_pid = GetPeerProcessId(s);
654 if (globals->server_pid == -1) {
655 // Note: there is no race here on startup since the server creates
656 // the pid file strictly before it binds the socket.
657 char buf[16];
658 auto len = readlink(pid_file.c_str(), buf, sizeof(buf) - 1);
659 if (len > 0) {
660 buf[len] = '\0';
661 globals->server_pid = atoi(buf);
662 } else {
663 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
664 "can't get server pid from connection");
665 }
666 }
667}
668
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100669// Connects to the Blaze server, returning the socket, or -1 if no
670// server is running and !start. If start, attempts to start a new
671// server, and exits on failure.
672static int ConnectToServer(bool start) {
673 int s = socket(PF_UNIX, SOCK_STREAM, 0);
674 if (s == -1) {
675 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
676 "can't create AF_UNIX socket");
677 }
678
679 string server_dir = globals->options.output_base + "/server";
680
681 // The server dir has the socket, so we don't allow access by other
682 // users.
683 if (MakeDirectories(server_dir, 0700) == -1) {
684 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
685 "server directory '%s' could not be created", server_dir.c_str());
686 }
687
688 string socket_file = server_dir + "/server.socket";
Doug Rabsond655f2a2015-08-13 14:41:50 +0000689 string pid_file = server_dir + "/server.pid";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100690
Doug Rabsond655f2a2015-08-13 14:41:50 +0000691 globals->server_pid = 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100692 if (Connect(s, socket_file) == 0) {
Doug Rabsond655f2a2015-08-13 14:41:50 +0000693 GetServerPid(s, pid_file);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100694 return s;
695 }
Ulf Adams523bff52015-07-24 12:17:18 +0000696 if (start) {
697 SetScheduling(globals->options.batch_cpu_scheduling,
698 globals->options.io_nice_level);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100699
700 int fd = StartServer(s);
701 if (fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL))) {
702 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
703 "Failed: fcntl to enable O_NONBLOCK on pipe");
704 }
705 // Give the server one minute to start up.
706 for (int ii = 0; ii < 600; ++ii) { // 60s; enough time to connect
707 // with debugger
708 if (Connect(s, socket_file) == 0) {
709 if (ii) {
710 fputc('\n', stderr);
711 fflush(stderr);
712 }
Doug Rabsond655f2a2015-08-13 14:41:50 +0000713 GetServerPid(s, pid_file);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100714 return s;
715 }
716 fputc('.', stderr);
717 fflush(stderr);
718 poll(NULL, 0, 100); // sleep 100ms. (usleep(3) is obsolete.)
719 char c;
720 if (read(fd, &c, 1) != -1 || errno != EAGAIN) {
721 fprintf(stderr, "\nunexpected pipe read status: %s\n"
722 "Server presumed dead. Now printing '%s':\n",
723 strerror(errno), globals->jvm_log_file.c_str());
724 WriteFileToStreamOrDie(stderr, globals->jvm_log_file.c_str());
725 exit(blaze_exit_code::INTERNAL_ERROR);
726 }
727 }
728 die(blaze_exit_code::INTERNAL_ERROR,
729 "\nError: couldn't connect to server at '%s' after 60 seconds.",
730 socket_file.c_str());
Ulf Adams523bff52015-07-24 12:17:18 +0000731 // The if never falls through here.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100732 }
Ulf Adams523bff52015-07-24 12:17:18 +0000733 return -1;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100734}
735
Eric Fellheimer4fed6092015-10-22 19:31:05 +0000736// Poll until the given process denoted by pid goes away. Return false if this
737// does not occur within wait_time_secs.
738static bool WaitForServerDeath(pid_t pid, int wait_time_secs) {
739 for (int ii = 0; ii < wait_time_secs * 10; ++ii) {
740 if (kill(pid, 0) == -1) {
741 if (errno == ESRCH) {
742 return true;
743 }
744 pdie(blaze_exit_code::INTERNAL_ERROR, "could not be killed");
745 }
746 poll(NULL, 0, 100); // sleep 100ms. (usleep(3) is obsolete.)
747 }
748 return false;
749}
750
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100751// Kills the specified running Blaze server.
752static void KillRunningServer(pid_t server_pid) {
Doug Rabsond655f2a2015-08-13 14:41:50 +0000753 if (server_pid == -1) return;
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000754 fprintf(stderr, "Sending SIGTERM to previous %s server (pid=%d)... ",
755 globals->options.GetProductName().c_str(), server_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100756 fflush(stderr);
Eric Fellheimer4fed6092015-10-22 19:31:05 +0000757 kill(server_pid, SIGTERM);
758 if (WaitForServerDeath(server_pid, 10)) {
759 fprintf(stderr, "done.\n");
760 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100761 }
762
763 // If the previous attempt did not suceeded, kill the whole group.
764 fprintf(stderr,
Kristina Chodorow11d40d22015-03-17 18:26:59 +0000765 "Sending SIGKILL to previous %s server process group (pid=%d)... ",
766 globals->options.GetProductName().c_str(), server_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100767 fflush(stderr);
768 killpg(server_pid, SIGKILL);
Eric Fellheimer4fed6092015-10-22 19:31:05 +0000769 if (WaitForServerDeath(server_pid, 10)) {
770 fprintf(stderr, "killed.\n");
771 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100772 }
Eric Fellheimer4fed6092015-10-22 19:31:05 +0000773 // Process did not go away 10s after SIGKILL. Stuck in state 'Z' or 'D'?
774 pdie(blaze_exit_code::INTERNAL_ERROR, "SIGKILL unsuccessful after 10s");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100775}
776
777
778// Kills the running Blaze server, if any. Finds the pid from the socket.
779static bool KillRunningServerIfAny() {
780 int socket = ConnectToServer(false);
781 if (socket != -1) {
Doug Rabsond655f2a2015-08-13 14:41:50 +0000782 KillRunningServer(globals->server_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100783 return true;
784 }
785 return false;
786}
787
788
789// Calls fsync() on the file (or directory) specified in 'file_path'.
790// pdie()'s if syncing fails.
791static void SyncFile(const char *file_path) {
792 // fsync always fails on Cygwin with "Permission denied" for some reason.
793#ifndef __CYGWIN__
794 int fd = open(file_path, O_RDONLY);
795 if (fd < 0) {
796 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
797 "failed to open '%s' for syncing", file_path);
798 }
799 if (fsync(fd) < 0) {
800 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
801 "failed to sync '%s'", file_path);
802 }
803 close(fd);
804#endif
805}
806
807// Walks the temporary directory recursively and collects full file paths.
808static void CollectExtractedFiles(const string &dir_path, vector<string> &files) {
809 DIR *dir;
810 struct dirent *ent;
811
812 if ((dir = opendir(dir_path.c_str())) == NULL) {
813 die(blaze_exit_code::INTERNAL_ERROR, "opendir failed");
814 }
815
816 while ((ent = readdir(dir)) != NULL) {
817 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
818 continue;
819 }
820
821 string filename(blaze_util::JoinPath(dir_path, ent->d_name));
822 bool is_directory;
823 if (ent->d_type == DT_UNKNOWN) {
824 struct stat buf;
825 if (lstat(filename.c_str(), &buf) == -1) {
826 die(blaze_exit_code::INTERNAL_ERROR, "stat failed");
827 }
828 is_directory = S_ISDIR(buf.st_mode);
829 } else {
830 is_directory = (ent->d_type == DT_DIR);
831 }
832
833 if (is_directory) {
834 CollectExtractedFiles(filename, files);
835 } else {
836 files.push_back(filename);
837 }
838 }
839
840 closedir(dir);
841}
842
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000843// A devtools_ijar::ZipExtractorProcessor to extract the files from the blaze
844// zip.
845class ExtractBlazeZipProcessor : public devtools_ijar::ZipExtractorProcessor {
846 public:
Thiago Farina9cb32752015-06-03 15:34:19 +0000847 explicit ExtractBlazeZipProcessor(const string &embedded_binaries)
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000848 : embedded_binaries_(embedded_binaries) {}
849
850 virtual bool Accept(const char *filename, const devtools_ijar::u4 attr) {
851 return !devtools_ijar::zipattr_is_dir(attr);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100852 }
853
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000854 virtual void Process(const char *filename, const devtools_ijar::u4 attr,
855 const devtools_ijar::u1 *data, const size_t size) {
856 string path = blaze_util::JoinPath(embedded_binaries_, filename);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100857 if (MakeDirectories(blaze_util::Dirname(path), 0777) == -1) {
858 pdie(blaze_exit_code::INTERNAL_ERROR,
859 "couldn't create '%s'", path.c_str());
860 }
Damien Martin-Guillereze20e5442015-03-26 09:04:23 +0000861 int fd = open(path.c_str(), O_CREAT | O_WRONLY, 0755);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100862 if (fd < 0) {
863 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
864 "\nFailed to open extraction file: %s", strerror(errno));
865 }
866
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000867 if (write(fd, data, size) != size) {
868 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
869 "\nError writing zipped file to %s", path.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100870 }
871 if (close(fd) != 0) {
872 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
873 "\nCould not close file %s", path.c_str());
874 }
875 }
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000876
877 private:
878 const string embedded_binaries_;
879};
880
881// Actually extracts the embedded data files into the tree whose root
882// is 'embedded_binaries'.
883static void ActuallyExtractData(const string &argv0,
884 const string &embedded_binaries) {
885 ExtractBlazeZipProcessor processor(embedded_binaries);
886 if (MakeDirectories(embedded_binaries, 0777) == -1) {
887 pdie(blaze_exit_code::INTERNAL_ERROR, "couldn't create '%s'",
888 embedded_binaries.c_str());
889 }
890
891 fprintf(stderr, "Extracting %s installation...\n",
892 globals->options.GetProductName().c_str());
893 std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
894 devtools_ijar::ZipExtractor::Create(argv0.c_str(), &processor));
895 if (extractor.get() == NULL) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100896 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
Damien Martin-Guillerezeb6e9032015-06-01 14:45:21 +0000897 "\nFailed to open %s as a zip file: (%d) %s",
898 globals->options.GetProductName().c_str(), errno, strerror(errno));
899 }
900 if (extractor->ProcessAll() < 0) {
901 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
902 "\nFailed to extract %s as a zip file: %s",
903 globals->options.GetProductName().c_str(), extractor->GetError());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100904 }
905
906 const time_t TEN_YEARS_IN_SEC = 3600 * 24 * 365 * 10;
907 time_t future_time = time(NULL) + TEN_YEARS_IN_SEC;
908
909 // Set the timestamps of the extracted files to the future and make sure (or
910 // at least as sure as we can...) that the files we have written are actually
911 // on the disk.
912
913 vector<string> extracted_files;
914 CollectExtractedFiles(embedded_binaries, extracted_files);
915
916 set<string> synced_directories;
917 for (vector<string>::iterator it = extracted_files.begin(); it != extracted_files.end(); it++) {
918
919 const char *extracted_path = it->c_str();
920
921 // Set the time to a distantly futuristic value so we can observe tampering.
922 // Note that keeping the default timestamp set by unzip (1970-01-01) and using
923 // that to detect tampering is not enough, because we also need the timestamp
924 // to change between Blaze releases so that the metadata cache knows that
925 // the files may have changed. This is important for actions that use
926 // embedded binaries as artifacts.
927 struct utimbuf times = { future_time, future_time };
928 if (utime(extracted_path, &times) == -1) {
929 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
930 "failed to set timestamp on '%s'", extracted_path);
931 }
932
933 SyncFile(extracted_path);
934
935 string directory = blaze_util::Dirname(extracted_path);
936
937 // Now walk up until embedded_binaries and sync every directory in between.
938 // synced_directories is used to avoid syncing the same directory twice.
939 // The !directory.empty() and directory != "/" conditions are not strictly
940 // needed, but it makes this loop more robust, because otherwise, if due to
941 // some glitch, directory was not under embedded_binaries, it would get
942 // into an infinite loop.
943 while (directory != embedded_binaries &&
944 synced_directories.count(directory) == 0 &&
945 !directory.empty() &&
946 directory != "/") {
947 SyncFile(directory.c_str());
948 synced_directories.insert(directory);
949 directory = blaze_util::Dirname(directory);
950 }
951 }
952
953 SyncFile(embedded_binaries.c_str());
954}
955
956// Installs Blaze by extracting the embedded data files, iff necessary.
957// The MD5-named install_base directory on disk is trusted; we assume
958// no-one has modified the extracted files beneath this directory once
959// it is in place. Concurrency during extraction is handled by
960// extracting in a tmp dir and then renaming it into place where it
961// becomes visible automically at the new path.
962// Populates globals->extracted_binaries with their extracted locations.
963static void ExtractData(const string &self_path) {
964 // If the install dir doesn't exist, create it, if it does, we know it's good.
965 struct stat buf;
966 if (stat(globals->options.install_base.c_str(), &buf) == -1) {
Thiago Farina8a67da42015-05-05 18:04:50 +0000967 uint64_t st = MonotonicClock();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100968 // Work in a temp dir to avoid races.
969 string tmp_install = globals->options.install_base + ".tmp." +
Googler9588b812015-07-23 11:49:37 +0000970 ToString(getpid());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100971 string tmp_binaries = tmp_install + "/_embedded_binaries";
972 ActuallyExtractData(self_path, tmp_binaries);
973
Thiago Farina8a67da42015-05-05 18:04:50 +0000974 uint64_t et = MonotonicClock();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100975 globals->extract_data_time = (et - st) / 1000000LL;
976
977 // Now rename the completed installation to its final name. If this
978 // fails due to an ENOTEMPTY then we assume another good
979 // installation snuck in before us.
980 if (rename(tmp_install.c_str(), globals->options.install_base.c_str()) == -1
981 && errno != ENOTEMPTY) {
982 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
983 "install base directory '%s' could not be renamed into place",
984 tmp_install.c_str());
985 }
986 } else {
987 if (!S_ISDIR(buf.st_mode)) {
988 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
989 "Error: Install base directory '%s' could not be created. "
990 "It exists but is not a directory.",
991 globals->options.install_base.c_str());
992 }
993
994 const time_t time_now = time(NULL);
995 string real_install_dir = blaze_util::JoinPath(
996 globals->options.install_base,
997 "_embedded_binaries");
998 for (const auto& it : globals->extracted_binaries) {
999 string path = blaze_util::JoinPath(real_install_dir, it);
1000 // Check that the file exists and is readable.
1001 if (stat(path.c_str(), &buf) == -1) {
1002 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1003 "Error: corrupt installation: file '%s' missing."
1004 " Please remove '%s' and try again.",
1005 path.c_str(), globals->options.install_base.c_str());
1006 }
1007 // Check that the timestamp is in the future. A past timestamp would indicate
1008 // that the file has been tampered with. See ActuallyExtractData().
Damien Martin-Guillerez12997672015-09-03 21:54:08 +00001009 if (!S_ISDIR(buf.st_mode) && buf.st_mtime <= time_now) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001010 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1011 "Error: corrupt installation: file '%s' "
1012 "modified. Please remove '%s' and try again.",
1013 path.c_str(), globals->options.install_base.c_str());
1014 }
1015 }
1016 }
1017}
1018
1019// Returns true if the server needs to be restarted to accommodate changes
1020// between the two argument lists.
1021static bool ServerNeedsToBeKilled(const vector<string>& args1,
1022 const vector<string>& args2) {
1023 // We need not worry about one side missing an argument and the other side
1024 // having the default value, since this command line is already the
1025 // canonicalized one that always contains every switch (with default values
1026 // if it was not present on the real command line). Same applies for argument
1027 // ordering.
1028 if (args1.size() != args2.size()) {
1029 return true;
1030 }
1031
1032 for (int i = 0; i < args1.size(); i++) {
1033 string option_sources = "--option_sources=";
1034 if (args1[i].substr(0, option_sources.size()) == option_sources &&
1035 args2[i].substr(0, option_sources.size()) == option_sources) {
1036 continue;
1037 }
1038
1039 if (args1[i] !=args2[i]) {
1040 return true;
1041 }
1042
1043 if (args1[i] == "--max_idle_secs") {
1044 // Skip the argument of --max_idle_secs.
1045 i++;
1046 }
1047 }
1048
1049 return false;
1050}
1051
1052// Kills the running Blaze server, if any, if the startup options do not match.
1053static void KillRunningServerIfDifferentStartupOptions() {
1054 int socket = ConnectToServer(false);
1055
1056 if (socket == -1) {
1057 return;
1058 }
1059
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001060 close(socket);
1061 string cmdline_path = globals->options.output_base + "/server/cmdline";
1062 string joined_arguments;
1063
1064 // No, /proc/$PID/cmdline does not work, because it is limited to 4K. Even
1065 // worse, its behavior differs slightly between kernels (in some, when longer
1066 // command lines are truncated, the last 4 bytes are replaced with
1067 // "..." + NUL.
1068 ReadFile(cmdline_path, &joined_arguments);
1069 vector<string> arguments = blaze_util::Split(joined_arguments, '\0');
1070
1071 // These strings contain null-separated command line arguments. If they are
1072 // the same, the server can stay alive, otherwise, it needs shuffle off this
1073 // mortal coil.
1074 if (ServerNeedsToBeKilled(arguments, GetArgumentArray())) {
1075 globals->restart_reason = NEW_OPTIONS;
1076 fprintf(stderr,
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001077 "WARNING: Running %s server needs to be killed, because the "
1078 "startup options are different.\n",
1079 globals->options.GetProductName().c_str());
Doug Rabsond655f2a2015-08-13 14:41:50 +00001080 KillRunningServer(globals->server_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001081 }
1082}
1083
1084
1085// Kills the old running server if it is not the same version as us,
1086// dealing with various combinations of installation scheme
1087// (installation symlink and older MD5_MANIFEST contents).
1088// This function requires that the installation be complete, and the
1089// server lock acquired.
1090static void EnsureCorrectRunningVersion() {
1091 // Read the previous installation's semaphore symlink in output_base. If the
1092 // target dirs don't match, or if the symlink was not present, then kill any
1093 // running servers. Lastly, symlink to our installation so others know which
1094 // installation is running.
1095 string installation_path = globals->options.output_base + "/install";
1096 char prev_installation[PATH_MAX + 1] = ""; // NULs the whole array
1097 if (readlink(installation_path.c_str(),
1098 prev_installation, PATH_MAX) == -1 ||
1099 prev_installation != globals->options.install_base) {
1100 if (KillRunningServerIfAny()) {
1101 globals->restart_reason = NEW_VERSION;
1102 }
1103 unlink(installation_path.c_str());
1104 if (symlink(globals->options.install_base.c_str(),
1105 installation_path.c_str())) {
1106 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1107 "failed to create installation symlink '%s'",
1108 installation_path.c_str());
1109 }
1110 const time_t time_now = time(NULL);
1111 struct utimbuf times = { time_now, time_now };
1112 if (utime(globals->options.install_base.c_str(), &times) == -1) {
1113 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1114 "failed to set timestamp on '%s'",
1115 globals->options.install_base.c_str());
1116 }
1117 }
1118}
1119
1120
1121// A signal-safe version of fprintf(stderr, ...).
1122//
1123// WARNING: any output from the blaze client may be interleaved
1124// with output from the blaze server. In --curses mode,
1125// the Blaze server often erases the previous line of output.
1126// So, be sure to end each such message with TWO newlines,
1127// otherwise it may be erased by the next message from the
1128// Blaze server.
1129// Also, it's a good idea to start each message with a newline,
1130// in case the Blaze server has written a partial line.
1131static void sigprintf(const char *format, ...) {
1132 char buf[1024];
1133 va_list ap;
1134 va_start(ap, format);
1135 int r = vsnprintf(buf, sizeof buf, format, ap);
1136 va_end(ap);
1137 write(STDERR_FILENO, buf, r);
1138}
1139
1140
1141// Signal handler.
1142static void handler(int signum) {
1143 // A defensive measure:
1144 if (kill(globals->server_pid, 0) == -1 && errno == ESRCH) {
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001145 sigprintf("\n%s server has died; client exiting.\n\n",
1146 globals->options.GetProductName().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001147 _exit(1);
1148 }
1149
1150 switch (signum) {
1151 case SIGINT:
1152 if (++globals->sigint_count >= 3) {
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001153 sigprintf("\n%s caught third interrupt signal; killed.\n\n",
1154 globals->options.GetProductName().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001155 kill(globals->server_pid, SIGKILL);
1156 _exit(1);
1157 }
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001158 sigprintf("\n%s caught interrupt signal; shutting down.\n\n",
1159 globals->options.GetProductName().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001160 kill(globals->server_pid, SIGINT);
1161 break;
1162 case SIGTERM:
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001163 sigprintf("\n%s caught terminate signal; shutting down.\n\n",
1164 globals->options.GetProductName().c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001165 kill(globals->server_pid, SIGINT);
1166 break;
1167 case SIGPIPE:
1168 // Don't bother the user with a message in this case; they're
1169 // probably using head(1) or more(1).
1170 kill(globals->server_pid, SIGINT);
1171 signal(SIGPIPE, SIG_IGN); // ignore subsequent SIGPIPE signals
1172 globals->received_signal = SIGPIPE;
1173 break;
1174 case SIGQUIT:
1175 sigprintf("\nSending SIGQUIT to JVM process %d (see %s).\n\n",
1176 globals->server_pid,
1177 globals->jvm_log_file.c_str());
1178 kill(globals->server_pid, SIGQUIT);
1179 break;
1180 }
1181}
1182
1183
1184// Reads a single char from the specified stream.
1185static char read_server_char(FILE *fp) {
1186 int c = getc(fp);
1187 if (c == EOF) {
1188 // e.g. external SIGKILL of server, misplaced System.exit() in the server,
1189 // or a JVM crash. Print out the jvm.out file in case there's something
1190 // useful.
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001191 fprintf(stderr, "Error: unexpected EOF from %s server.\n"
1192 "Contents of '%s':\n", globals->options.GetProductName().c_str(),
1193 globals->jvm_log_file.c_str());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001194 WriteFileToStreamOrDie(stderr, globals->jvm_log_file.c_str());
1195 exit(blaze_exit_code::INTERNAL_ERROR);
1196 }
1197 return static_cast<char>(c);
1198}
1199
1200// Constructs the command line for a server request,
1201static string BuildServerRequest() {
1202 vector<string> arg_vector;
1203 string command = globals->option_processor.GetCommand();
1204 if (command != "") {
1205 arg_vector.push_back(command);
1206 AddLoggingArgs(&arg_vector);
1207 }
1208
1209 globals->option_processor.GetCommandArguments(&arg_vector);
1210
1211 string request("blaze");
1212 for (vector<string>::iterator it = arg_vector.begin();
1213 it != arg_vector.end(); it++) {
1214 request.push_back('\0');
1215 request.append(*it);
1216 }
1217 return request;
1218}
1219
1220// Performs all I/O for a single client request to the server, and
1221// shuts down the client (by exit or signal).
1222static void SendServerRequest(void) ATTRIBUTE_NORETURN;
1223static void SendServerRequest(void) {
1224 int socket = -1;
1225 while (true) {
1226 socket = ConnectToServer(true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001227 // Check for deleted server cwd:
1228 string server_cwd = GetProcessCWD(globals->server_pid);
Lukacs Berki4be230a2015-10-15 13:43:03 +00001229 // TODO(bazel-team): Is this check even necessary? If someone deletes or
1230 // moves the server directory, the client cannot connect to the server
1231 // anymore. IOW, the client finds the server based on the output base,
1232 // so if a server is found, it should be by definition at the correct output
1233 // base.
1234 //
1235 // If server_cwd is empty, GetProcessCWD failed. This notably occurs when
1236 // running under Docker because then readlink(/proc/[pid]/cwd) returns
1237 // EPERM.
1238 // Docker issue #6687 (https://github.com/docker/docker/issues/6687) fixed
1239 // this, but one still needs the --cap-add SYS_PTRACE command line flag, at
1240 // least according to the discussion on Docker issue #6800
1241 // (https://github.com/docker/docker/issues/6687), and even then, it's a
1242 // non-default Docker flag. Given that this occurs only in very weird
1243 // cases, it's better to assume that everything is alright if we can't get
1244 // the cwd.
1245
1246 if (!server_cwd.empty() &&
1247 (server_cwd != globals->workspace || // changed
1248 server_cwd.find(" (deleted)") != string::npos)) { // deleted.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001249 // There's a distant possibility that the two paths look the same yet are
1250 // actually different because the two processes have different mount
1251 // tables.
1252 if (VerboseLogging()) {
1253 fprintf(stderr, "Server's cwd moved or deleted (%s).\n",
1254 server_cwd.c_str());
1255 }
1256 close(socket);
1257 KillRunningServer(globals->server_pid);
1258 } else {
1259 break;
1260 }
1261 }
1262
1263 FILE *fp = fdopen(socket, "r"); // use buffering for reads--it's faster
1264
1265 if (VerboseLogging()) {
1266 fprintf(stderr, "Connected (server pid=%d).\n", globals->server_pid);
1267 }
1268
1269 // Wall clock time since process startup.
1270 globals->startup_time = ProcessClock() / 1000000LL;
1271 const string request = BuildServerRequest();
1272
1273 // Unblock all signals.
1274 sigset_t sigset;
1275 sigemptyset(&sigset);
1276 sigprocmask(SIG_SETMASK, &sigset, NULL);
1277
1278 signal(SIGINT, handler);
1279 signal(SIGTERM, handler);
1280 signal(SIGPIPE, handler);
1281 signal(SIGQUIT, handler);
1282
1283 // Send request and shutdown the write half of the connection:
1284 // (Request is written in a single chunk.)
1285 if (write(socket, request.data(), request.size()) != request.size()) {
1286 pdie(blaze_exit_code::INTERNAL_ERROR, "write() to server failed");
1287 }
1288 // In this (totally bizarre) protocol, this is the
1289 // client's way of saying "um, that's the end of the request".
1290 if (shutdown(socket, SHUT_WR) == -1) {
1291 pdie(blaze_exit_code::INTERNAL_ERROR, "shutdown(WR) failed");
1292 }
1293
1294 // Wait until we receive some response from the server.
1295 // (We do this by calling select() with a timeout.)
1296 // If we don't receive a response within 3 seconds, print a message,
1297 // so that the user has some idea what is going on.
1298 while (true) {
1299 fd_set fdset;
1300 FD_ZERO(&fdset);
1301 FD_SET(socket, &fdset);
1302 struct timeval timeout;
1303 timeout.tv_sec = 3;
1304 timeout.tv_usec = 0;
1305 int result = select(socket + 1, &fdset, NULL, &fdset, &timeout);
1306 if (result > 0) {
1307 // Data is ready on socket. Go ahead and read it.
1308 break;
1309 } else if (result == 0) {
1310 // Timeout. Print a message, then go ahead and read from
1311 // the socket (the read will usually block).
1312 fprintf(stderr,
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001313 "INFO: Waiting for response from %s server (pid %d)...\n",
1314 globals->options.GetProductName().c_str(), globals->server_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001315 break;
1316 } else { // result < 0
1317 // Error. For EINTR we try again, all other errors are fatal.
1318 if (errno != EINTR) {
1319 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1320 "select() on server socket failed");
1321 }
1322 }
1323 }
1324
1325 // Read and demux the response. This protocol is awful.
1326 for (;;) {
1327 // Read one line:
1328 char at = read_server_char(fp);
1329 assert(at == '@');
1330 (void) at; // avoid warning about unused variable
1331 char tag = read_server_char(fp);
1332 assert(tag == '1' || tag == '2' || tag == '3');
1333 char at_or_newline = read_server_char(fp);
1334 bool second_at = at_or_newline == '@';
1335 if (second_at) {
1336 at_or_newline = read_server_char(fp);
1337 }
1338 assert(at_or_newline == '\n');
1339
1340 if (tag == '3') {
1341 // In this (totally bizarre) protocol, this is the
1342 // server's way of saying "um, that's the end of the response".
1343 break;
1344 }
1345 FILE *stream = tag == '1' ? stdout : stderr;
1346 for (;;) {
1347 char c = read_server_char(fp);
1348 if (c == '\n') {
1349 if (!second_at) fputc(c, stream);
1350 fflush(stream);
1351 break;
1352 } else {
1353 fputc(c, stream);
1354 }
1355 }
1356 }
1357
1358 char line[255];
1359 if (fgets(line, sizeof line, fp) == NULL ||
1360 !isdigit(line[0])) {
1361 die(blaze_exit_code::INTERNAL_ERROR,
1362 "Error: can't read exit code from server.");
1363 }
1364 int exit_code;
1365 blaze_util::safe_strto32(line, &exit_code);
1366
1367 close(socket); // might fail EINTR, just ignore.
1368
1369 if (globals->received_signal) { // Kill ourselves with the same signal, so
1370 // that callers see the right WTERMSIG value.
1371 signal(globals->received_signal, SIG_DFL);
1372 raise(globals->received_signal);
1373 exit(1); // (in case raise didn't kill us for some reason)
1374 }
1375
1376 exit(exit_code);
1377}
1378
1379// Parse the options, storing parsed values in globals.
1380// Returns the index of the first non-option argument.
1381static void ParseOptions(int argc, const char *argv[]) {
1382 string error;
1383 blaze_exit_code::ExitCode parse_exit_code =
1384 globals->option_processor.ParseOptions(argc, argv, globals->workspace,
1385 globals->cwd, &error);
1386 if (parse_exit_code != blaze_exit_code::SUCCESS) {
1387 die(parse_exit_code, "%s", error.c_str());
1388 }
1389 globals->options = globals->option_processor.GetParsedStartupOptions();
1390}
1391
1392// Returns the canonical form of a path.
1393static string MakeCanonical(const char *path) {
1394 char *resolved_path = realpath(path, NULL);
1395 if (resolved_path == NULL) {
1396 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1397 "realpath('%s') failed", path);
1398 }
1399
1400 string ret = resolved_path;
1401 free(resolved_path);
1402 return ret;
1403}
1404
1405// Compute the globals globals->cwd and globals->workspace.
1406static void ComputeWorkspace() {
1407 char cwdbuf[PATH_MAX];
1408 if (getcwd(cwdbuf, sizeof cwdbuf) == NULL) {
1409 pdie(blaze_exit_code::INTERNAL_ERROR, "getcwd() failed");
1410 }
1411 globals->cwd = MakeCanonical(cwdbuf);
1412 globals->workspace = BlazeStartupOptions::GetWorkspace(globals->cwd);
1413}
1414
1415// Figure out the base directories based on embedded data, username, cwd, etc.
1416// Sets globals->options.install_base, globals->options.output_base,
1417// globals->lock_file, globals->jvm_log_file.
Thiago Farina2fd78902015-05-18 11:37:59 +00001418static void ComputeBaseDirectories(const string &self_path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001419 // Only start a server when in a workspace because otherwise we won't do more
1420 // than emit a help message.
1421 if (!BlazeStartupOptions::InWorkspace(globals->workspace)) {
1422 globals->options.batch = true;
1423 }
1424
1425 // The default install_base is <output_user_root>/install/<md5(blaze)>
1426 // but if an install_base is specified on the command line, we use that as
1427 // the base instead.
1428 if (globals->options.install_base.empty()) {
1429 string install_user_root = globals->options.output_user_root + "/install";
1430 globals->options.install_base =
1431 GetInstallBase(install_user_root, self_path);
1432 } else {
Eric Fellheimer4c5eb0f2015-08-12 15:02:24 +00001433 // We call GetInstallBase anyway to populate extracted_binaries and
1434 // install_md5.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001435 GetInstallBase("", self_path);
1436 }
1437
1438 if (globals->options.output_base.empty()) {
1439 globals->options.output_base = GetHashedBaseDir(
1440 globals->options.output_user_root, globals->workspace);
1441 }
1442
1443 struct stat buf;
1444 if (stat(globals->options.output_base.c_str(), &buf) == -1) {
1445 if (MakeDirectories(globals->options.output_base, 0777) == -1) {
1446 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1447 "Output base directory '%s' could not be created",
1448 globals->options.output_base.c_str());
1449 }
1450 } else {
1451 if (!S_ISDIR(buf.st_mode)) {
1452 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1453 "Error: Output base directory '%s' could not be created. "
1454 "It exists but is not a directory.",
1455 globals->options.output_base.c_str());
1456 }
1457 }
1458 if (access(globals->options.output_base.c_str(), R_OK | W_OK | X_OK) != 0) {
1459 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1460 "Error: Output base directory '%s' must be readable and writable.",
1461 globals->options.output_base.c_str());
1462 }
1463
1464 globals->options.output_base =
1465 MakeCanonical(globals->options.output_base.c_str());
1466 globals->lockfile = globals->options.output_base + "/lock";
1467 globals->jvm_log_file = globals->options.output_base + "/server/jvm.out";
1468}
1469
1470static void CheckEnvironment() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001471 if (getenv("LD_ASSUME_KERNEL") != NULL) {
1472 // Fix for bug: if ulimit -s and LD_ASSUME_KERNEL are both
1473 // specified, the JVM fails to create threads. See thread_stack_regtest.
1474 // This is also provoked by LD_LIBRARY_PATH=/usr/lib/debug,
1475 // or anything else that causes the JVM to use LinuxThreads.
1476 fprintf(stderr, "Warning: ignoring LD_ASSUME_KERNEL in environment.\n");
1477 unsetenv("LD_ASSUME_KERNEL");
1478 }
1479
1480 if (getenv("LD_PRELOAD") != NULL) {
1481 fprintf(stderr, "Warning: ignoring LD_PRELOAD in environment.\n");
1482 unsetenv("LD_PRELOAD");
1483 }
1484
1485 if (getenv("_JAVA_OPTIONS") != NULL) {
1486 // This would override --host_jvm_args
1487 fprintf(stderr, "Warning: ignoring _JAVA_OPTIONS in environment.\n");
1488 unsetenv("_JAVA_OPTIONS");
1489 }
1490
Thiago Farinadfe43a22015-04-07 13:48:49 +00001491 if (getenv("TEST_TMPDIR") != NULL) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001492 fprintf(stderr, "INFO: $TEST_TMPDIR defined: output root default is "
1493 "'%s'.\n", globals->options.output_root.c_str());
1494 }
1495
1496 // TODO(bazel-team): We've also seen a failure during loading (creating
1497 // threads?) when ulimit -Hs 8192. Characterize that and check for it here.
1498
1499 // Make the JVM use ISO-8859-1 for parsing its command line because "blaze
1500 // run" doesn't handle non-ASCII command line arguments. This is apparently
1501 // the most reliable way to select the platform default encoding.
1502 setenv("LANG", "en_US.ISO-8859-1", 1);
1503 setenv("LANGUAGE", "en_US.ISO-8859-1", 1);
1504 setenv("LC_ALL", "en_US.ISO-8859-1", 1);
1505 setenv("LC_CTYPE", "en_US.ISO-8859-1", 1);
1506}
1507
1508// Create the lockfile and take an exclusive lock on a region within it. This
1509// lock is inherited with the file descriptor across execve(), but not fork().
1510// So in the batch case, the JVM holds the lock until exit; otherwise, this
1511// program holds it until exit.
1512static void AcquireLock() {
1513 globals->lockfd = open(globals->lockfile.c_str(), O_CREAT|O_RDWR, 0644);
1514 if (globals->lockfd < 0) {
1515 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1516 "cannot open lockfile '%s' for writing", globals->lockfile.c_str());
1517 }
1518
1519 struct flock lock;
1520 lock.l_type = F_WRLCK;
1521 lock.l_whence = SEEK_SET;
1522 lock.l_start = 0;
1523 // This doesn't really matter now, but allows us to subdivide the lock
1524 // later if that becomes meaningful. (Ranges beyond EOF can be locked.)
1525 lock.l_len = 4096;
1526
1527 // Try to take the lock, without blocking.
1528 if (fcntl(globals->lockfd, F_SETLK, &lock) == -1) {
1529 if (errno != EACCES && errno != EAGAIN) {
1530 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1531 "unexpected result from F_SETLK");
1532 }
1533
1534 // We didn't get the lock. Find out who has it.
1535 struct flock probe = lock;
1536 probe.l_pid = 0;
1537 if (fcntl(globals->lockfd, F_GETLK, &probe) == -1) {
1538 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1539 "unexpected result from F_GETLK");
1540 }
1541 if (!globals->options.block_for_lock) {
1542 die(blaze_exit_code::BAD_ARGV,
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001543 "Another %s command is running (pid=%d). Exiting immediately.",
1544 globals->options.GetProductName().c_str(), probe.l_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001545 }
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001546 fprintf(stderr, "Another %s command is running (pid = %d). "
1547 "Waiting for it to complete...",
1548 globals->options.GetProductName().c_str(), probe.l_pid);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001549 fflush(stderr);
1550
1551 // Take a clock sample for that start of the waiting time
Thiago Farina8a67da42015-05-05 18:04:50 +00001552 uint64_t st = MonotonicClock();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001553 // Try to take the lock again (blocking).
1554 int r;
1555 do {
1556 r = fcntl(globals->lockfd, F_SETLKW, &lock);
1557 } while (r == -1 && errno == EINTR);
1558 fprintf(stderr, "\n");
1559 if (r == -1) {
1560 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1561 "couldn't acquire file lock");
1562 }
1563 // Take another clock sample, calculate elapsed
Thiago Farina8a67da42015-05-05 18:04:50 +00001564 uint64_t et = MonotonicClock();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001565 globals->command_wait_time = (et - st) / 1000000LL;
1566 }
1567
1568 // Identify ourselves in the lockfile.
1569 ftruncate(globals->lockfd, 0);
1570 const char *tty = ttyname(STDIN_FILENO); // NOLINT (single-threaded)
Kristina Chodorow11d40d22015-03-17 18:26:59 +00001571 string msg = "owner=" + globals->options.GetProductName() + " launcher\npid="
Googler9588b812015-07-23 11:49:37 +00001572 + ToString(getpid()) + "\ntty=" + (tty ? tty : "") + "\n";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001573 // Don't bother checking for error, since it's unlikely and unimportant.
1574 // The contents are currently meant only for debugging.
1575 write(globals->lockfd, msg.data(), msg.size());
1576}
1577
Thiago Farina0b6963e2015-04-28 20:26:45 +00001578static void SetupStreams() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001579 // Line-buffer stderr, since we always flush at the end of a server
1580 // message. This saves lots of single-char calls to write(2).
1581 // This doesn't work if any writes to stderr have already occurred!
1582 setlinebuf(stderr);
1583
1584 // Ensure we have three open fds. Otherwise we can end up with
1585 // bizarre things like stdout going to the lock file, etc.
1586 if (fcntl(0, F_GETFL) == -1) open("/dev/null", O_RDONLY);
1587 if (fcntl(1, F_GETFL) == -1) open("/dev/null", O_WRONLY);
1588 if (fcntl(2, F_GETFL) == -1) open("/dev/null", O_WRONLY);
1589}
1590
1591// Set an 8MB stack for Blaze. When the stack max is unbounded, it changes the
1592// layout in the JVM's address space, and we are unable to instantiate the
1593// default 3000MB heap.
1594static void EnsureFiniteStackLimit() {
1595 struct rlimit limit;
1596 const int default_stack = 8 * 1024 * 1024; // 8MB.
1597 if (getrlimit(RLIMIT_STACK, &limit)) {
1598 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "getrlimit() failed");
1599 }
1600
1601 if (default_stack < limit.rlim_cur) {
1602 limit.rlim_cur = default_stack;
1603 if (setrlimit(RLIMIT_STACK, &limit)) {
1604 perror("setrlimit() failed: If the stack limit is too high, "
1605 "this can cause the JVM to be unable to allocate enough "
1606 "contiguous address space for its heap");
1607 }
1608 }
1609}
1610
1611static void CheckBinaryPath(const string& argv0) {
1612 if (argv0[0] == '/') {
1613 globals->binary_path = argv0;
1614 } else {
1615 string abs_path = globals->cwd + '/' + argv0;
1616 char *resolved_path = realpath(abs_path.c_str(), NULL);
1617 if (resolved_path) {
1618 globals->binary_path = resolved_path;
1619 free(resolved_path);
1620 } else {
1621 // This happens during our integration tests, but thats okay, as we won't
1622 // log the invocation anyway.
1623 globals->binary_path = abs_path;
1624 }
1625 }
1626}
1627
1628// Create the user's directory where we keep state, installations etc.
1629// Typically, this happens inside a temp directory, so we have to be
1630// careful about symlink attacks.
1631static void CreateSecureOutputRoot() {
1632 const char* root = globals->options.output_user_root.c_str();
1633 struct stat fileinfo = {};
1634
Kristina Chodorow46af79d2015-03-20 22:35:37 +00001635 if (MakeDirectories(root, 0755) == -1) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001636 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "mkdir('%s')", root);
1637 }
1638
1639 // The path already exists.
1640 // Check ownership and mode, and verify that it is a directory.
1641
1642 if (lstat(root, &fileinfo) < 0) {
1643 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "lstat('%s')", root);
1644 }
1645
1646 if (fileinfo.st_uid != geteuid()) {
1647 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not owned by me",
1648 root);
1649 }
1650
1651 if ((fileinfo.st_mode & 022) != 0) {
1652 int new_mode = fileinfo.st_mode & (~022);
1653 if (chmod(root, new_mode) < 0) {
1654 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
1655 "'%s' has mode %o, chmod to %o failed", root,
1656 fileinfo.st_mode & 07777, new_mode);
1657 }
1658 }
1659
1660 if (stat(root, &fileinfo) < 0) {
1661 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "stat('%s')", root);
1662 }
1663
1664 if (!S_ISDIR(fileinfo.st_mode)) {
1665 die(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "'%s' is not a directory",
1666 root);
1667 }
1668}
1669
1670// TODO(bazel-team): Execute the server as a child process and write its exit
1671// code to a file. In case the server becomes unresonsive or terminates
1672// unexpectedly (in a way that isn't already handled), we can observe the file,
1673// if it exists. (If it doesn't, then we know something went horribly wrong.)
1674int main(int argc, const char *argv[]) {
1675 InitGlobals();
1676 SetupStreams();
1677
1678 // Must be done before command line parsing.
1679 ComputeWorkspace();
1680 CheckBinaryPath(argv[0]);
1681 ParseOptions(argc, argv);
1682 string error;
1683 blaze_exit_code::ExitCode reexec_options_exit_code =
1684 globals->options.CheckForReExecuteOptions(argc, argv, &error);
1685 if (reexec_options_exit_code != blaze_exit_code::SUCCESS) {
1686 die(reexec_options_exit_code, "%s", error.c_str());
1687 }
1688 CheckEnvironment();
1689 CreateSecureOutputRoot();
1690
1691 const string self_path = GetSelfPath();
1692 ComputeBaseDirectories(self_path);
1693
1694 AcquireLock();
1695
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001696 WarnFilesystemType(globals->options.output_base);
1697 EnsureFiniteStackLimit();
1698
1699 ExtractData(self_path);
1700 EnsureCorrectRunningVersion();
1701 KillRunningServerIfDifferentStartupOptions();
1702
1703 if (globals->options.batch) {
1704 SetScheduling(globals->options.batch_cpu_scheduling,
1705 globals->options.io_nice_level);
1706 StartStandalone();
1707 } else {
1708 SendServerRequest();
1709 }
1710 return 0;
1711}
Thiago Farina0b6963e2015-04-28 20:26:45 +00001712
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001713} // namespace blaze
1714
1715int main(int argc, const char *argv[]) {
1716 return blaze::main(argc, argv);
1717}