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