Home | History | Annotate | Download | only in src
      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, &currentVersion)) {
    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