Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 15 | #include "src/main/cpp/blaze_util.h" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 16 | |
| 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 Farina | fdc7f98 | 2015-04-27 23:01:34 +0000 | [diff] [blame] | 25 | #include <sys/file.h> |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 26 | #include <sys/ioctl.h> |
| 27 | #include <sys/socket.h> |
| 28 | #include <sys/stat.h> |
Thiago Farina | fdc7f98 | 2015-04-27 23:01:34 +0000 | [diff] [blame] | 29 | #include <sys/types.h> |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 30 | #include <unistd.h> |
Thiago Farina | fdc7f98 | 2015-04-27 23:01:34 +0000 | [diff] [blame] | 31 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 32 | #include <sstream> |
| 33 | |
Dmitry Lomov | 78c0cc7 | 2015-08-11 16:44:21 +0000 | [diff] [blame] | 34 | #include "src/main/cpp/blaze_util_platform.h" |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 35 | #include "src/main/cpp/util/errors.h" |
Thiago Farina | 7f9357f | 2015-04-23 13:57:43 +0000 | [diff] [blame] | 36 | #include "src/main/cpp/util/exit_code.h" |
Han-Wen Nienhuys | 36fbe63 | 2015-04-21 13:58:08 +0000 | [diff] [blame] | 37 | #include "src/main/cpp/util/file.h" |
| 38 | #include "src/main/cpp/util/numbers.h" |
| 39 | #include "src/main/cpp/util/strings.h" |
Thiago Farina | fdc7f98 | 2015-04-27 23:01:34 +0000 | [diff] [blame] | 40 | #include "src/main/cpp/util/port.h" |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 41 | |
Thiago Farina | 241f46c | 2015-04-13 14:33:30 +0000 | [diff] [blame] | 42 | using blaze_util::die; |
| 43 | using blaze_util::pdie; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 44 | using std::vector; |
| 45 | |
| 46 | namespace blaze { |
| 47 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 48 | string 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 Farina | 2fd7890 | 2015-05-18 11:37:59 +0000 | [diff] [blame] | 66 | string MakeAbsolute(const string &path) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 67 | // 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 Nienhuys | f4a3d4f | 2015-06-01 13:22:10 +0000 | [diff] [blame] | 82 | // 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 Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 88 | static 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 Nienhuys | f4a3d4f | 2015-06-01 13:22:10 +0000 | [diff] [blame] | 111 | |
| 112 | mode_t mask = umask(022); |
| 113 | umask(mask); |
| 114 | mode = (mode & ~mask); |
Kristina Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 115 | 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 | |
| 124 | static int MakeDirectories(const string& path, mode_t mode, bool childmost) { |
Kristina Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 125 | if (path.empty() || path == "/") { |
| 126 | errno = EACCES; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 127 | return -1; |
| 128 | } |
Kristina Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 129 | |
Kristina Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 130 | int retval = GetDirectoryStat(path, mode, childmost); |
| 131 | if (retval == 0) { |
| 132 | return 0; |
Kristina Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 133 | } |
| 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 Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 138 | if (MakeDirectories(parent, mode, false) == -1) { |
| 139 | // errno set by stat. |
| 140 | return -1; |
Kristina Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 141 | } |
Kristina Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 142 | |
| 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 Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 162 | } |
| 163 | |
Kristina Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 164 | return retval; |
Kristina Chodorow | 237ba6c | 2015-04-07 16:41:40 +0000 | [diff] [blame] | 165 | } |
| 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 Chodorow | 6d7b7ff | 2015-05-13 16:59:49 +0000 | [diff] [blame] | 172 | int MakeDirectories(const string& path, mode_t mode) { |
| 173 | return MakeDirectories(path, mode, true); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 174 | } |
| 175 | |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 176 | // Replaces 'contents' with contents of 'fd' file descriptor. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 177 | // Returns false on error. |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 178 | bool ReadFileDescriptor(int fd, string *content) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 179 | content->clear(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 180 | 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-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 184 | if (errno == EINTR || errno == EAGAIN) continue; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 185 | return false; |
| 186 | } |
| 187 | content->append(buf, r); |
| 188 | } |
| 189 | close(fd); |
| 190 | return true; |
| 191 | } |
| 192 | |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 193 | // Replaces 'content' with contents of file 'filename'. |
| 194 | // Returns false on error. |
| 195 | bool 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 201 | // Writes 'content' into file 'filename', and makes it executable. |
| 202 | // Returns false on failure, sets errno. |
| 203 | bool 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 Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 206 | if (fd == -1) { |
| 207 | return false; |
| 208 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 209 | int r = write(fd, content.data(), content.size()); |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 210 | if (r == -1) { |
| 211 | return false; |
| 212 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 213 | int saved_errno = errno; |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 214 | if (close(fd)) { |
| 215 | return false; // Can fail on NFS. |
| 216 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 217 | errno = saved_errno; // Caller should see errno from write(). |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 218 | return static_cast<uint>(r) == content.size(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 219 | } |
| 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). |
| 225 | bool 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. |
| 237 | int 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 Farina | 2fd7890 | 2015-05-18 11:37:59 +0000 | [diff] [blame] | 253 | const char* GetUnaryOption(const char *arg, |
| 254 | const char *next_arg, |
| 255 | const char *key) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 256 | 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 | |
| 268 | bool 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 282 | bool 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. |
| 288 | string ReadJvmVersion(int fd) { |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 289 | string version_string; |
| 290 | if (ReadFileDescriptor(fd, &version_string)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 291 | // try to look out for 'version "' |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 292 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 296 | // If we found "version \"", process until next '"' |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 297 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 300 | } |
Damien Martin-Guillerez | a73ab64 | 2015-02-10 14:53:25 +0000 | [diff] [blame] | 301 | return version_string.substr(found, end - found); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 302 | } |
| 303 | } |
| 304 | return ""; |
| 305 | } |
| 306 | |
Thiago Farina | 2fd7890 | 2015-05-18 11:37:59 +0000 | [diff] [blame] | 307 | string GetJvmVersion(const string &java_exe) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 308 | 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 Adams | 523bff5 | 2015-07-24 12:17:18 +0000 | [diff] [blame] | 332 | // The if never falls through here. |
| 333 | return NULL; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 334 | } |
| 335 | |
Thiago Farina | 2fd7890 | 2015-05-18 11:37:59 +0000 | [diff] [blame] | 336 | bool CheckJavaVersionIsAtLeast(const string &jvm_version, |
| 337 | const string &version_spec) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 338 | vector<string> jvm_version_vect = blaze_util::Split(jvm_version, '.'); |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 339 | int jvm_version_size = static_cast<int>(jvm_version_vect.size()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 340 | vector<string> version_spec_vect = blaze_util::Split(version_spec, '.'); |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 341 | int version_spec_size = static_cast<int>(version_spec_vect.size()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 342 | int i; |
Kristina Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 343 | for (i = 0; i < jvm_version_size && i < version_spec_size; i++) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 344 | 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 Chodorow | 5529c1a | 2015-09-09 14:14:46 +0000 | [diff] [blame] | 352 | if (i < version_spec_size) { |
| 353 | for (; i < version_spec_size; i++) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 354 | if (version_spec_vect[i] != "0") { |
| 355 | return false; |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | return true; |
| 360 | } |
| 361 | |
| 362 | } // namespace blaze |