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