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