blob: cde7523ff5c4d4db1f54dde6f21a529e4c1dd3e5 [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
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000015#include "src/main/cpp/blaze_util.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016
17#include <errno.h>
18#include <fcntl.h>
19#include <limits.h>
20#include <pwd.h>
21#include <stdarg.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
Thiago Farinafdc7f982015-04-27 23:01:34 +000025#include <sys/file.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026#include <sys/ioctl.h>
27#include <sys/socket.h>
28#include <sys/stat.h>
Thiago Farinafdc7f982015-04-27 23:01:34 +000029#include <sys/types.h>
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030#include <unistd.h>
Thiago Farinafdc7f982015-04-27 23:01:34 +000031
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032#include <sstream>
33
Dmitry Lomov78c0cc72015-08-11 16:44:21 +000034#include "src/main/cpp/blaze_util_platform.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000035#include "src/main/cpp/util/errors.h"
Thiago Farina7f9357f2015-04-23 13:57:43 +000036#include "src/main/cpp/util/exit_code.h"
Han-Wen Nienhuys36fbe632015-04-21 13:58:08 +000037#include "src/main/cpp/util/file.h"
38#include "src/main/cpp/util/numbers.h"
39#include "src/main/cpp/util/strings.h"
Thiago Farinafdc7f982015-04-27 23:01:34 +000040#include "src/main/cpp/util/port.h"
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041
Thiago Farina241f46c2015-04-13 14:33:30 +000042using blaze_util::die;
43using blaze_util::pdie;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044using std::vector;
45
46namespace blaze {
47
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048string GetUserName() {
49 const char *user = getenv("USER");
50 if (user && user[0] != '\0') return user;
51 errno = 0;
52 passwd *pwent = getpwuid(getuid()); // NOLINT (single-threaded)
53 if (pwent == NULL || pwent->pw_name == NULL) {
54 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
55 "$USER is not set, and unable to look up name of current user");
56 }
57 return pwent->pw_name;
58}
59
60// Returns the given path in absolute form. Does not change paths that are
61// already absolute.
62//
63// If called from working directory "/bar":
64// MakeAbsolute("foo") --> "/bar/foo"
65// MakeAbsolute("/foo") ---> "/foo"
Thiago Farina2fd78902015-05-18 11:37:59 +000066string MakeAbsolute(const string &path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067 // Check if path is already absolute.
68 if (path.empty() || path[0] == '/') {
69 return path;
70 }
71
72 char cwdbuf[PATH_MAX];
73 if (getcwd(cwdbuf, sizeof cwdbuf) == NULL) {
74 pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "getcwd() failed");
75 }
76
77 // Determine whether the cwd ends with "/" or not.
78 string separator = (cwdbuf[strlen(cwdbuf) - 1] == '/') ? "" : "/";
79 return cwdbuf + separator + path;
80}
81
Han-Wen Nienhuysf4a3d4f2015-06-01 13:22:10 +000082// Runs "stat" on `path`. Returns -1 and sets errno if stat fails or
83// `path` isn't a directory. If check_perms is true, this will also
84// make sure that `path` is owned by the current user and has `mode`
85// permissions (observing the umask). It attempts to run chmod to
86// correct the mode if necessary. If `path` is a symlink, this will
87// check ownership of the link, not the underlying directory.
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +000088static int GetDirectoryStat(const string& path, mode_t mode, bool check_perms) {
89 struct stat filestat = {};
90 if (stat(path.c_str(), &filestat) == -1) {
91 return -1;
92 }
93
94 if (!S_ISDIR(filestat.st_mode)) {
95 errno = ENOTDIR;
96 return -1;
97 }
98
99 if (check_perms) {
100 // If this is a symlink, run checks on the link. (If we did lstat above
101 // then it would return false for ISDIR).
102 struct stat linkstat = {};
103 if (lstat(path.c_str(), &linkstat) != 0) {
104 return -1;
105 }
106 if (linkstat.st_uid != geteuid()) {
107 // The directory isn't owned by me.
108 errno = EACCES;
109 return -1;
110 }
Han-Wen Nienhuysf4a3d4f2015-06-01 13:22:10 +0000111
112 mode_t mask = umask(022);
113 umask(mask);
114 mode = (mode & ~mask);
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000115 if ((filestat.st_mode & 0777) != mode
116 && chmod(path.c_str(), mode) == -1) {
117 // errno set by chmod.
118 return -1;
119 }
120 }
121 return 0;
122}
123
124static int MakeDirectories(const string& path, mode_t mode, bool childmost) {
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000125 if (path.empty() || path == "/") {
126 errno = EACCES;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127 return -1;
128 }
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000129
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000130 int retval = GetDirectoryStat(path, mode, childmost);
131 if (retval == 0) {
132 return 0;
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000133 }
134
135 if (errno == ENOENT) {
136 // Path does not exist, attempt to create its parents, then it.
137 string parent = blaze_util::Dirname(path);
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000138 if (MakeDirectories(parent, mode, false) == -1) {
139 // errno set by stat.
140 return -1;
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000141 }
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000142
143 if (mkdir(path.c_str(), mode) == -1) {
144 if (errno == EEXIST) {
145 if (childmost) {
146 // If there are multiple bazel calls at the same time then the
147 // directory could be created between the MakeDirectories and mkdir
148 // calls. This is okay, but we still have to check the permissions.
149 return GetDirectoryStat(path, mode, childmost);
150 } else {
151 // If this isn't the childmost directory, we don't care what the
152 // permissions were. If it's not even a directory then that error will
153 // get caught when we attempt to create the next directory down the
154 // chain.
155 return 0;
156 }
157 }
158 // errno set by mkdir.
159 return -1;
160 }
161 return 0;
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000162 }
163
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000164 return retval;
Kristina Chodorow237ba6c2015-04-07 16:41:40 +0000165}
166
167// mkdir -p path. Returns 0 if the path was created or already exists and could
168// be chmod-ed to exactly the given permissions. If final part of the path is a
169// symlink, this ensures that the destination of the symlink has the desired
170// permissions. It also checks that the directory or symlink is owned by us.
171// On failure, this returns -1 and sets errno.
Kristina Chodorow6d7b7ff2015-05-13 16:59:49 +0000172int MakeDirectories(const string& path, mode_t mode) {
173 return MakeDirectories(path, mode, true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100174}
175
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000176// Replaces 'contents' with contents of 'fd' file descriptor.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177// Returns false on error.
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000178bool ReadFileDescriptor(int fd, string *content) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100179 content->clear();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100180 char buf[4096];
181 // OPT: This loop generates one spurious read on regular files.
182 while (int r = read(fd, buf, sizeof buf)) {
183 if (r == -1) {
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000184 if (errno == EINTR || errno == EAGAIN) continue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 return false;
186 }
187 content->append(buf, r);
188 }
189 close(fd);
190 return true;
191}
192
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000193// Replaces 'content' with contents of file 'filename'.
194// Returns false on error.
195bool ReadFile(const string &filename, string *content) {
196 int fd = open(filename.c_str(), O_RDONLY);
197 if (fd == -1) return false;
198 return ReadFileDescriptor(fd, content);
199}
200
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100201// Writes 'content' into file 'filename', and makes it executable.
202// Returns false on failure, sets errno.
203bool WriteFile(const string &content, const string &filename) {
204 unlink(filename.c_str());
205 int fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0755); // chmod +x
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000206 if (fd == -1) {
207 return false;
208 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 int r = write(fd, content.data(), content.size());
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000210 if (r == -1) {
211 return false;
212 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 int saved_errno = errno;
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000214 if (close(fd)) {
215 return false; // Can fail on NFS.
216 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100217 errno = saved_errno; // Caller should see errno from write().
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000218 return static_cast<uint>(r) == content.size();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100219}
220
221// Returns true iff both stdout and stderr are connected to a
222// terminal, and it can support color and cursor movement
223// (this is computed heuristically based on the values of
224// environment variables).
225bool IsStandardTerminal() {
226 string term = getenv("TERM") == nullptr ? "" : getenv("TERM");
227 string emacs = getenv("EMACS") == nullptr ? "" : getenv("EMACS");
228 if (term == "" || term == "dumb" || term == "emacs" || term == "xterm-mono" ||
229 term == "symbolics" || term == "9term" || emacs == "t") {
230 return false;
231 }
232 return isatty(STDOUT_FILENO) && isatty(STDERR_FILENO);
233}
234
235// Returns the number of columns of the terminal to which stdout is
236// connected, or $COLUMNS (default 80) if there is no such terminal.
237int GetTerminalColumns() {
238 struct winsize ws;
239 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
240 return ws.ws_col;
241 }
242 const char* columns_env = getenv("COLUMNS");
243 if (columns_env != NULL && columns_env[0] != '\0') {
244 char* endptr;
245 int columns = blaze_util::strto32(columns_env, &endptr, 10);
246 if (*endptr == '\0') { // $COLUMNS is a valid number
247 return columns;
248 }
249 }
250 return 80; // default if not a terminal.
251}
252
Thiago Farina2fd78902015-05-18 11:37:59 +0000253const char* GetUnaryOption(const char *arg,
254 const char *next_arg,
255 const char *key) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100256 const char *value = blaze_util::var_strprefix(arg, key);
257 if (value == NULL) {
258 return NULL;
259 } else if (value[0] == '=') {
260 return value + 1;
261 } else if (value[0]) {
262 return NULL; // trailing garbage in key name
263 }
264
265 return next_arg;
266}
267
268bool GetNullaryOption(const char *arg, const char *key) {
269 const char *value = blaze_util::var_strprefix(arg, key);
270 if (value == NULL) {
271 return false;
272 } else if (value[0] == '=') {
273 die(blaze_exit_code::BAD_ARGV,
274 "In argument '%s': option '%s' does not take a value.", arg, key);
275 } else if (value[0]) {
276 return false; // trailing garbage in key name
277 }
278
279 return true;
280}
281
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282bool VerboseLogging() {
283 return getenv("VERBOSE_BLAZE_CLIENT") != NULL;
284}
285
286// Read the Jvm version from a file descriptor. The read fd
287// should contains a similar output as the java -version output.
288string ReadJvmVersion(int fd) {
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000289 string version_string;
290 if (ReadFileDescriptor(fd, &version_string)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 // try to look out for 'version "'
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000292 static const string version_pattern = "version \"";
293 size_t found = version_string.find(version_pattern);
294 if (found != string::npos) {
295 found += version_pattern.size();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100296 // If we found "version \"", process until next '"'
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000297 size_t end = version_string.find("\"", found);
298 if (end == string::npos) { // consider end of string as a '"'
299 end = version_string.size();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100300 }
Damien Martin-Guillereza73ab642015-02-10 14:53:25 +0000301 return version_string.substr(found, end - found);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100302 }
303 }
304 return "";
305}
306
Thiago Farina2fd78902015-05-18 11:37:59 +0000307string GetJvmVersion(const string &java_exe) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100308 vector<string> args;
309 args.push_back("java");
310 args.push_back("-version");
311
312 int fds[2];
313 if (pipe(fds)) {
314 pdie(blaze_exit_code::INTERNAL_ERROR, "pipe creation failed");
315 }
316
317 int child = fork();
318 if (child == -1) {
319 pdie(blaze_exit_code::INTERNAL_ERROR, "fork() failed");
320 } else if (child > 0) { // we're the parent
321 close(fds[1]); // parent keeps only the reading side
322 return ReadJvmVersion(fds[0]);
323 } else {
324 close(fds[0]); // child keeps only the writing side
325 // Redirect output to the writing side of the dup.
326 dup2(fds[1], STDOUT_FILENO);
327 dup2(fds[1], STDERR_FILENO);
328 // Execute java -version
329 ExecuteProgram(java_exe, args);
330 pdie(blaze_exit_code::INTERNAL_ERROR, "Failed to run java -version");
331 }
Ulf Adams523bff52015-07-24 12:17:18 +0000332 // The if never falls through here.
333 return NULL;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100334}
335
Thiago Farina2fd78902015-05-18 11:37:59 +0000336bool CheckJavaVersionIsAtLeast(const string &jvm_version,
337 const string &version_spec) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100338 vector<string> jvm_version_vect = blaze_util::Split(jvm_version, '.');
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000339 int jvm_version_size = static_cast<int>(jvm_version_vect.size());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100340 vector<string> version_spec_vect = blaze_util::Split(version_spec, '.');
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000341 int version_spec_size = static_cast<int>(version_spec_vect.size());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100342 int i;
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000343 for (i = 0; i < jvm_version_size && i < version_spec_size; i++) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100344 int jvm = blaze_util::strto32(jvm_version_vect[i].c_str(), NULL, 10);
345 int spec = blaze_util::strto32(version_spec_vect[i].c_str(), NULL, 10);
346 if (jvm > spec) {
347 return true;
348 } else if (jvm < spec) {
349 return false;
350 }
351 }
Kristina Chodorow5529c1a2015-09-09 14:14:46 +0000352 if (i < version_spec_size) {
353 for (; i < version_spec_size; i++) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 if (version_spec_vect[i] != "0") {
355 return false;
356 }
357 }
358 }
359 return true;
360}
361
362} // namespace blaze