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