1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifdef _WIN32 18 19 // Indicate we want at least all Windows Server 2003 (5.2) APIs. 20 // Note: default is set by system/core/include/arch/windows/AndroidConfig.h to 0x0500 21 // which is Win2K. However our minimum SDK tools requirement is Win XP (0x0501). 22 // However we do need 0x0502 to get access to the WOW-64-32 constants for the 23 // registry, except we'll need to be careful since they are not available on XP. 24 #undef _WIN32_WINNT 25 #define _WIN32_WINNT 0x0502 26 // Indicate we want at least all IE 5 shell APIs 27 #define _WIN32_IE 0x0500 28 29 #include "find_java.h" 30 #include <shlobj.h> 31 #include <ctype.h> 32 33 // Define some types missing in MingW 34 #ifndef LSTATUS 35 typedef LONG LSTATUS; 36 #endif 37 38 // Check to see if the application is running in 32-bit or 64-bit mode. In other words, this will 39 // return false if you run a 32-bit build even on a 64-bit machine. 40 static bool isApplication64() { 41 SYSTEM_INFO sysInfo; 42 GetSystemInfo(&sysInfo); 43 44 // Note: The constant name here is a bit misleading, as it actually covers all 64-bit processors 45 // and not just AMD. 46 // See also: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724958(v=vs.85).aspx 47 return (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64); 48 } 49 50 // Extract the first thing that looks like (digit.digit+). 51 // Note: this will break when java reports a version with major > 9. 52 // However it will reasonably cope with "1.10", if that ever happens. 53 static bool extractJavaVersion(const char *start, 54 int length, 55 CString *outVersionStr, 56 int *outVersionInt) { 57 const char *end = start + length; 58 for (const char *c = start; c < end - 2; c++) { 59 if (isdigit(c[0]) && 60 c[1] == '.' && 61 isdigit(c[2])) { 62 const char *e = c+2; 63 while (isdigit(e[1])) { 64 e++; 65 } 66 if (outVersionStr != NULL) { 67 outVersionStr->set(c, e - c + 1); 68 } 69 if (outVersionInt != NULL) { 70 // add major * 1000, currently only 1 digit 71 int value = (*c - '0') * 1000; 72 // add minor 73 for (int m = 1; *e != '.'; e--, m *= 10) { 74 value += (*e - '0') * m; 75 } 76 *outVersionInt = value; 77 } 78 return true; 79 } 80 } 81 return false; 82 } 83 84 // Check if the passed in path is a path to a JDK 85 static bool isJdkPath(const CPath &path) { 86 87 // If the files "bin/java.exe" and "lib/tools.jar" exist, we're probably a JDK. 88 CPath pathBin(path); 89 pathBin.addPath("bin"); 90 pathBin.addPath("java.exe"); 91 92 CPath pathTools(path); 93 pathTools.addPath("lib"); 94 pathTools.addPath("tools.jar"); 95 96 return (pathBin.fileExists() && pathTools.fileExists()); 97 } 98 99 // Check whether we can find $PATH/java.exe. 100 // inOutPath should be the directory where we're looking at. 101 // In output, it will be the java path we tested. 102 // Returns the java version integer found (e.g. 1006 for 1.6). 103 // Return 0 in case of error. 104 static int checkPath(CPath *inOutPath) { 105 inOutPath->addPath("java.exe"); 106 107 int result = 0; 108 if (inOutPath->fileExists()) { 109 // Run java -version 110 // Reject the version if it's not at least our current minimum. 111 if (!getJavaVersion(*inOutPath, NULL /*versionStr*/, &result)) { 112 result = 0; 113 } 114 } 115 116 return result; 117 } 118 119 // Check whether we can find $PATH/bin/java.exe 120 // Returns the Java version found (e.g. 1006 for 1.6) or 0 in case of error. 121 static int checkBinPath(CPath *inOutPath) { 122 inOutPath->addPath("bin"); 123 return checkPath(inOutPath); 124 } 125 126 // Test for the existence of java.exe in a custom path 127 int findJavaInPath(const CPath &path, CPath *outJavaPath, bool isJdk, int minVersion) { 128 SetLastError(0); 129 130 int version = 0; 131 CPath temp(path); 132 if (!isJdk) { 133 version = checkPath(&temp); 134 } 135 else { 136 if (isJdkPath(temp)) { 137 version = checkBinPath(&temp); 138 } 139 } 140 141 if (version >= minVersion) { 142 if (gIsDebug) { 143 fprintf(stderr, "Java %d found in path: %s\n", version, temp.cstr()); 144 } 145 *outJavaPath = temp; 146 return version; 147 } 148 return 0; 149 } 150 151 // Search java.exe in the environment 152 int findJavaInEnvPath(CPath *outJavaPath, bool isJdk, int minVersion) { 153 SetLastError(0); 154 155 const char* envPath = getenv("JAVA_HOME"); 156 if (envPath != NULL) { 157 CPath p(envPath); 158 159 if (!isJdk || isJdkPath(p)) { 160 int v = checkBinPath(&p); 161 if (v >= minVersion) { 162 if (gIsDebug) { 163 fprintf(stderr, "Java %d found via JAVA_HOME: %s\n", v, p.cstr()); 164 } 165 *outJavaPath = p; 166 // As an optimization for runtime, if we find a suitable java 167 // version in JAVA_HOME we won't waste time looking at the PATH. 168 return v; 169 } 170 } 171 } 172 173 int currVersion = 0; 174 envPath = getenv("PATH"); 175 if (!envPath) return currVersion; 176 177 // Otherwise look at the entries in the current path. 178 // If we find more than one, keep the one with the highest version. 179 180 CArray<CString> *paths = CString(envPath).split(';'); 181 for(int i = 0; i < paths->size(); i++) { 182 CPath p((*paths)[i].cstr()); 183 184 if (isJdk && !isJdkPath(p)) { 185 continue; 186 } 187 188 int v = checkPath(&p); 189 if (v >= minVersion && v > currVersion) { 190 if (gIsDebug) { 191 fprintf(stderr, "Java %d found via env PATH: %s\n", v, p.cstr()); 192 } 193 194 currVersion = v; 195 *outJavaPath = p; 196 } 197 } 198 199 delete paths; 200 return currVersion; 201 } 202 203 // -------------- 204 205 static bool getRegValue(const char *keyPath, 206 const char *keyName, 207 REGSAM access, 208 CString *outValue) { 209 HKEY key; 210 LSTATUS status = RegOpenKeyExA( 211 HKEY_LOCAL_MACHINE, // hKey 212 keyPath, // lpSubKey 213 0, // ulOptions 214 KEY_READ | access, // samDesired, 215 &key); // phkResult 216 if (status == ERROR_SUCCESS) { 217 218 LSTATUS ret = ERROR_MORE_DATA; 219 DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough 220 char* buffer = (char*) malloc(size); 221 222 while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) { 223 ret = RegQueryValueExA( 224 key, // hKey 225 keyName, // lpValueName 226 NULL, // lpReserved 227 NULL, // lpType 228 (LPBYTE) buffer, // lpData 229 &size); // lpcbData 230 231 if (ret == ERROR_MORE_DATA) { 232 size *= 2; 233 buffer = (char*) realloc(buffer, size); 234 } else { 235 buffer[size] = 0; 236 } 237 } 238 239 if (ret != ERROR_MORE_DATA) outValue->set(buffer); 240 241 free(buffer); 242 RegCloseKey(key); 243 244 return (ret != ERROR_MORE_DATA); 245 } 246 247 return false; 248 } 249 250 // Explore the registry to find a suitable version of Java. 251 // Returns an int which is the version of Java found (e.g. 1006 for 1.6) and the 252 // matching path in outJavaPath. 253 // Returns 0 if nothing suitable was found. 254 static int exploreJavaRegistry(const char *entry, REGSAM access, int minVersion, 255 CPath *outJavaPath) { 256 257 // Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion] 258 CPath rootKey("SOFTWARE\\JavaSoft\\"); 259 rootKey.addPath(entry); 260 261 int versionInt = 0; 262 CString currentVersion; 263 CPath subKey(rootKey); 264 if (getRegValue(subKey.cstr(), "CurrentVersion", access, ¤tVersion)) { 265 // CurrentVersion should be something like "1.7". 266 // We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome] 267 subKey.addPath(currentVersion); 268 CPath javaHome; 269 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) { 270 versionInt = checkBinPath(&javaHome); 271 if (versionInt >= 0) { 272 if (gIsDebug) { 273 fprintf(stderr, 274 "Java %d found via registry: %s\n", 275 versionInt, javaHome.cstr()); 276 } 277 *outJavaPath = javaHome; 278 } 279 if (versionInt >= minVersion) { 280 // Heuristic: if the current version is good enough, stop here 281 return versionInt; 282 } 283 } 284 } 285 286 // Try again, but this time look at all the versions available 287 HKEY javaHomeKey; 288 LSTATUS status = RegOpenKeyExA( 289 HKEY_LOCAL_MACHINE, // hKey 290 "SOFTWARE\\JavaSoft", // lpSubKey 291 0, // ulOptions 292 KEY_READ | access, // samDesired 293 &javaHomeKey); // phkResult 294 if (status == ERROR_SUCCESS) { 295 char name[256]; 296 DWORD index = 0; 297 CPath javaHome; 298 for (LONG result = ERROR_SUCCESS; result == ERROR_SUCCESS; index++) { 299 DWORD nameLen = 255; 300 name[nameLen] = 0; 301 result = RegEnumKeyExA( 302 javaHomeKey, // hKey 303 index, // dwIndex 304 name, // lpName 305 &nameLen, // lpcName 306 NULL, // lpReserved 307 NULL, // lpClass 308 NULL, // lpcClass, 309 NULL); // lpftLastWriteTime 310 if (result == ERROR_SUCCESS && nameLen < 256) { 311 name[nameLen] = 0; 312 CPath subKey(rootKey); 313 subKey.addPath(name); 314 315 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) { 316 int v = checkBinPath(&javaHome); 317 if (v > versionInt) { 318 if (gIsDebug) { 319 fprintf(stderr, 320 "Java %d found via registry: %s\n", 321 versionInt, javaHome.cstr()); 322 } 323 *outJavaPath = javaHome; 324 versionInt = v; 325 } 326 } 327 } 328 } 329 330 RegCloseKey(javaHomeKey); 331 } 332 333 return 0; 334 } 335 336 static bool getMaxJavaInRegistry(const char *entry, REGSAM access, CPath *outJavaPath, int *inOutVersion) { 337 CPath path; 338 int version = exploreJavaRegistry(entry, access, *inOutVersion, &path); 339 if (version > *inOutVersion) { 340 *outJavaPath = path; 341 *inOutVersion = version; 342 return true; 343 } 344 return false; 345 } 346 347 int findJavaInRegistry(CPath *outJavaPath, bool isJdk, int minVersion) { 348 // Check the JRE first, then the JDK. 349 int version = minVersion - 1; // Inner methods check if they're greater than this version. 350 bool result = false; 351 result |= (!isJdk && getMaxJavaInRegistry("Java Runtime Environment", 0, outJavaPath, &version)); 352 result |= getMaxJavaInRegistry("Java Development Kit", 0, outJavaPath, &version); 353 354 // Even if we're 64-bit, try again but check the 32-bit registry, looking for 32-bit java. 355 if (isApplication64()) { 356 result |= (!isJdk && 357 getMaxJavaInRegistry("Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath, &version)); 358 result |= getMaxJavaInRegistry("Java Development Kit", KEY_WOW64_32KEY, outJavaPath, &version); 359 } 360 361 return result ? version : 0; 362 } 363 364 // -------------- 365 366 static bool checkProgramFiles(CPath *outJavaPath, int *inOutVersion, bool isJdk, 367 bool force32bit) { 368 369 char programFilesPath[MAX_PATH + 1]; 370 int nFolder = force32bit ? CSIDL_PROGRAM_FILESX86 : CSIDL_PROGRAM_FILES; 371 HRESULT result = SHGetFolderPathA( 372 NULL, // hwndOwner 373 nFolder, 374 NULL, // hToken 375 SHGFP_TYPE_CURRENT, // dwFlags 376 programFilesPath); // pszPath 377 if (FAILED(result)) return false; 378 379 CPath path(programFilesPath); 380 path.addPath("Java"); 381 382 // Do we have a C:\Program Files\Java directory? 383 if (!path.dirExists()) return false; 384 385 CPath glob(path); 386 glob.addPath("j*"); 387 388 bool found = false; 389 WIN32_FIND_DATAA findData; 390 HANDLE findH = FindFirstFileA(glob.cstr(), &findData); 391 if (findH == INVALID_HANDLE_VALUE) return false; 392 do { 393 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { 394 CPath temp(path); 395 temp.addPath(findData.cFileName); 396 // Check C:\Program Files\Java\j*\bin\java.exe 397 398 if (!isJdk || isJdkPath(temp)) { 399 int v = checkBinPath(&temp); 400 if (v > *inOutVersion) { 401 found = true; 402 *inOutVersion = v; 403 *outJavaPath = temp; 404 } 405 } 406 } 407 } while (!found && FindNextFileA(findH, &findData) != 0); 408 FindClose(findH); 409 410 return found; 411 } 412 413 int findJavaInProgramFiles(CPath *outJavaPath, bool isJdk, int minVersion) { 414 415 // Check the C:\Program Files directory 416 bool result = false; 417 int version = minVersion - 1; // Inner methods check if they're greater than this version. 418 result |= checkProgramFiles(outJavaPath, &version, isJdk, false); 419 420 // Even if we're 64-bit, try again but check the C:\Program Files (x86) directory, looking for 421 // 32-bit java. 422 if (isApplication64()) { 423 result |= checkProgramFiles(outJavaPath, &version, isJdk, true); 424 } 425 426 return result ? version : 0; 427 } 428 429 // -------------- 430 431 // Tries to invoke the java.exe at the given path and extract it's 432 // version number. 433 // - outVersionStr: if not null, will capture version as a string (e.g. "1.6") 434 // - outVersionInt: if not null, will capture version as an int (major * 1000 + minor, e.g. 1006). 435 bool getJavaVersion(CPath &javaPath, CString *outVersionStr, int *outVersionInt) { 436 bool result = false; 437 438 // Run "java -version", which outputs something like to *STDERR*: 439 // 440 // java version "1.6.0_29" 441 // Java(TM) SE Runtime Environment (build 1.6.0_29-b11) 442 // Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing) 443 // 444 // We want to capture the first line, and more exactly the "1.6" part. 445 446 447 CString cmd; 448 cmd.setf("\"%s\" -version", javaPath.cstr()); 449 450 SECURITY_ATTRIBUTES saAttr; 451 STARTUPINFO startup; 452 PROCESS_INFORMATION pinfo; 453 454 // Want to inherit pipe handle 455 ZeroMemory(&saAttr, sizeof(saAttr)); 456 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 457 saAttr.bInheritHandle = TRUE; 458 saAttr.lpSecurityDescriptor = NULL; 459 460 // Create pipe for stdout 461 HANDLE stdoutPipeRd, stdoutPipeWt; 462 if (!CreatePipe( 463 &stdoutPipeRd, // hReadPipe, 464 &stdoutPipeWt, // hWritePipe, 465 &saAttr, // lpPipeAttributes, 466 0)) { // nSize (0=default buffer size) 467 if (gIsConsole || gIsDebug) displayLastError("CreatePipe failed: "); 468 return false; 469 } 470 if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) { 471 if (gIsConsole || gIsDebug) displayLastError("SetHandleInformation failed: "); 472 return false; 473 } 474 475 ZeroMemory(&pinfo, sizeof(pinfo)); 476 477 ZeroMemory(&startup, sizeof(startup)); 478 startup.cb = sizeof(startup); 479 startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; 480 startup.wShowWindow = SW_HIDE|SW_MINIMIZE; 481 // Capture both stderr and stdout 482 startup.hStdError = stdoutPipeWt; 483 startup.hStdOutput = stdoutPipeWt; 484 startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 485 486 BOOL ok = CreateProcessA( 487 NULL, // program path 488 (LPSTR) cmd.cstr(), // command-line 489 NULL, // process handle is not inheritable 490 NULL, // thread handle is not inheritable 491 TRUE, // yes, inherit some handles 492 0, // process creation flags 493 NULL, // use parent's environment block 494 NULL, // use parent's starting directory 495 &startup, // startup info, i.e. std handles 496 &pinfo); 497 498 if ((gIsConsole || gIsDebug) && !ok) displayLastError("CreateProcess failed: "); 499 500 // Close the write-end of the output pipe (we're only reading from it) 501 CloseHandle(stdoutPipeWt); 502 503 // Read from the output pipe. We don't need to read everything, 504 // the first line should be 'Java version "1.2.3_45"\r\n' 505 // so reading about 32 chars is all we need. 506 char first32[32 + 1]; 507 int index = 0; 508 first32[0] = 0; 509 510 if (ok) { 511 512 #define SIZE 1024 513 char buffer[SIZE]; 514 DWORD sizeRead = 0; 515 516 while (ok) { 517 // Keep reading in the same buffer location 518 ok = ReadFile(stdoutPipeRd, // hFile 519 buffer, // lpBuffer 520 SIZE, // DWORD buffer size to read 521 &sizeRead, // DWORD buffer size read 522 NULL); // overlapped 523 if (!ok || sizeRead == 0 || sizeRead > SIZE) break; 524 525 // Copy up to the first 32 characters 526 if (index < 32) { 527 DWORD n = 32 - index; 528 if (n > sizeRead) n = sizeRead; 529 // copy as lowercase to simplify checks later 530 for (char *b = buffer; n > 0; n--, b++, index++) { 531 char c = *b; 532 if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; 533 first32[index] = c; 534 } 535 first32[index] = 0; 536 } 537 } 538 539 WaitForSingleObject(pinfo.hProcess, INFINITE); 540 541 DWORD exitCode; 542 if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) { 543 // this should not return STILL_ACTIVE (259) 544 result = exitCode == 0; 545 } 546 547 CloseHandle(pinfo.hProcess); 548 CloseHandle(pinfo.hThread); 549 } 550 CloseHandle(stdoutPipeRd); 551 552 if (result && index > 0) { 553 // Look for a few keywords in the output however we don't 554 // care about specific ordering or case-senstiviness. 555 // We only captures roughtly the first line in lower case. 556 char *j = strstr(first32, "java"); 557 if (!j) { 558 j = strstr(first32, "openjdk"); 559 } 560 char *v = strstr(first32, "version"); 561 if ((gIsConsole || gIsDebug) && (!j || !v)) { 562 fprintf(stderr, "Error: keywords 'java|openjdk version' not found in '%s'\n", first32); 563 } 564 if (j != NULL && v != NULL) { 565 result = extractJavaVersion(first32, index, outVersionStr, outVersionInt); 566 } 567 } 568 569 return result; 570 } 571 572 573 #endif /* _WIN32 */ 574