This is an automated email from the ASF dual-hosted git repository. skygo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans-native-launchers.git
commit 1d019a29da279c15b4c3adf98ee2e56b6380c0de Author: Jaroslav Tulach <jaroslav.tul...@oracle.com> AuthorDate: Sun May 6 07:27:25 2018 +0200 Moving the platform modules into their own subdirectory to lower the clutter in the root --- .dep.inc | 5 + Makefile | 107 +++++++ argnames.h | 41 +++ jvmlauncher.cpp | 455 +++++++++++++++++++++++++++ jvmlauncher.h | 123 ++++++++ nbexec.cpp | 63 ++++ nbexec.exe.manifest | 51 +++ nbexec.rc | 26 ++ nbexec_exe.rc | 29 ++ nbexecexe.cpp | 32 ++ nbexecloader.h | 66 ++++ nbproject/configurations.xml | 210 +++++++++++++ nbproject/project.properties | 17 + nbproject/project.xml | 49 +++ platformlauncher.cpp | 724 +++++++++++++++++++++++++++++++++++++++++++ platformlauncher.h | 111 +++++++ utilsfuncs.cpp | 449 +++++++++++++++++++++++++++ utilsfuncs.h | 52 ++++ version.h | 28 ++ version.rc | 63 ++++ 20 files changed, 2701 insertions(+) diff --git a/.dep.inc b/.dep.inc new file mode 100755 index 0000000..4560e55 --- /dev/null +++ b/.dep.inc @@ -0,0 +1,5 @@ +# This code depends on make tool being used +DEPFILES=$(wildcard $(addsuffix .d, ${OBJECTFILES})) +ifneq (${DEPFILES},) +include ${DEPFILES} +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee8f7c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,107 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Main targets can be executed directly, and they are: +# +# build build a specific configuration +# clean remove built files from a configuration +# clobber remove all built files +# all build all configurations +# help print help mesage +# +# Targets .build-impl, .clean-impl, .clobber-impl, .all-impl, and +# .help-impl are implemented in nbproject/makefile-impl.mk. +# +# NOCDDL + +# Environment +MKDIR=mkdir +CP=cp +CCADMIN=CCadmin +RANLIB=ranlib + + + +# build +build: .build-post-$(CONF) + +.build-pre: +# Add your pre 'build' code here... + +.build-post-nbexec: .build-impl nbexecexe.cpp nbexecloader.h utilsfuncs.cpp nbexec_exe.rc + windres.exe -Ocoff nbexec_exe.rc nbexec_exe.res + g++ -s -mno-cygwin -Wl,--nxcompat -Wl,--dynamicbase -Wl,--no-seh -DNBEXEC_DLL=\"nbexec.dll\" nbexecexe.cpp utilsfuncs.cpp nbexec_exe.res -o nbexec.exe + cp nbexec.exe ../../../nbbuild/netbeans/platform/lib/ + cp nbexec.dll ../../../nbbuild/netbeans/platform/lib/ + +.build-post-nbexec64: .build-impl nbexecexe.cpp nbexecloader.h utilsfuncs.cpp nbexec_exe.rc + x86_64-w64-mingw32-windres.exe -Ocoff nbexec_exe.rc nbexec_exe64.res + x86_64-w64-mingw32-g++.exe -m64 -s -mno-cygwin -Wl,--nxcompat -Wl,--dynamicbase -DNBEXEC_DLL=\"nbexec64.dll\" -static-libgcc -static-libstdc++ nbexecexe.cpp utilsfuncs.cpp nbexec_exe64.res -o nbexec64.exe + cp nbexec64.exe ../../../nbbuild/netbeans/platform/lib/ + cp nbexec64.dll ../../../nbbuild/netbeans/platform/lib/ + + + +# clean +clean: .clean-post-$(CONF) + +.clean-pre: +# Add your pre 'clean' code here... + +.clean-post-nbexec: .clean-impl + rm -f nbexec_exe32.res nbexec32.exe + +.clean-post-nbexec64: .clean-impl + rm -f nbexec_exe64.res nbexec64.exe + + + +# clobber +clobber: .clobber-post + +.clobber-pre: +# Add your pre 'clobber' code here... + +.clobber-post: .clobber-impl +# Add your post 'clobber' code here... + + + +# all +all: .all-post + +.all-pre: +# Add your pre 'all' code here... + +.all-post: .all-impl +# Add your post 'all' code here... + + + +# help +help: .help-post + +.help-pre: +# Add your pre 'help' code here... + +.help-post: .help-impl +# Add your post 'help' code here... + + + +# include project implementation makefile +include nbproject/Makefile-impl.mk diff --git a/argnames.h b/argnames.h new file mode 100644 index 0000000..c4bdaba --- /dev/null +++ b/argnames.h @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _ARGNAMES_H +#define _ARGNAMES_H + +#define ARG_NAME_SEPAR_PROC "--fork-java" +#define ARG_NAME_CONSOLE "--console" +#define ARG_NAME_LAUNCHER_LOG "--trace" +#define ARG_NAME_LA_START_APP "--la_start_app" +#define ARG_NAME_LA_START_AU "--la_start_au" +#define ARG_NAME_LA_PPID "--la_ppid" +#define ARG_NAME_USER_DIR "--userdir" +#define ARG_DEFAULT_USER_DIR_ROOT "--default_userdir_root" +#define ARG_NAME_CACHE_DIR "--cachedir" +#define ARG_NAME_CLUSTERS "--clusters" +#define ARG_NAME_BOOTCLASS "--bootclass" +#define ARG_NAME_JDKHOME "--jdkhome" +#define ARG_NAME_CP_PREPEND "--cp:p" +#define ARG_NAME_CP_APPEND "--cp:a" +#define ARG_NAME_NOSPLASH "--nosplash" + + +#endif /* _ARGNAMES_H */ + diff --git a/jvmlauncher.cpp b/jvmlauncher.cpp new file mode 100644 index 0000000..7e297f1 --- /dev/null +++ b/jvmlauncher.cpp @@ -0,0 +1,455 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#include "jvmlauncher.h" +#include <cassert> +#include <fstream> + +using namespace std; + +const char *JvmLauncher::JDK_KEY = "Software\\JavaSoft\\Java Development Kit"; +const char *JvmLauncher::JRE_KEY = "Software\\JavaSoft\\Java Runtime Environment"; +const char *JvmLauncher::JDK_POST9_KEY = "Software\\JavaSoft\\JDK"; +const char *JvmLauncher::JRE_POST9_KEY = "Software\\JavaSoft\\JRE"; +const char *JvmLauncher::CUR_VERSION_NAME = "CurrentVersion"; +const char *JvmLauncher::JAVA_HOME_NAME = "JavaHome"; +const char *JvmLauncher::JAVA_BIN_DIR = "\\bin"; +const char *JvmLauncher::JAVA_EXE_FILE = "\\bin\\java.exe"; +const char *JvmLauncher::JAVAW_EXE_FILE = "\\bin\\javaw.exe"; +const char *JvmLauncher::JAVA_CLIENT_DLL_FILE = "\\bin\\client\\jvm.dll"; +const char *JvmLauncher::JAVA_SERVER_DLL_FILE = "\\bin\\server\\jvm.dll"; +const char *JvmLauncher::JAVA_JRE_PREFIX = "\\jre"; +const char *JvmLauncher::JNI_CREATEVM_FUNC = "JNI_CreateJavaVM"; + +extern void exitHook(int status); + +JvmLauncher::JvmLauncher() + : suppressConsole(false) { +} + +JvmLauncher::JvmLauncher(const JvmLauncher& orig) { +} + +JvmLauncher::~JvmLauncher() { +} + +bool JvmLauncher::checkJava(const char *path, const char *prefix) { + assert(path); + assert(prefix); + logMsg("checkJava(%s)", path); + javaPath = path; + if (*javaPath.rbegin() == '\\') { + javaPath.erase(javaPath.length() - 1, 1); + } + javaExePath = javaPath + prefix + JAVA_EXE_FILE; + javawExePath = javaPath + prefix + JAVAW_EXE_FILE; + javaClientDllPath = javaPath + prefix + JAVA_CLIENT_DLL_FILE; + javaServerDllPath = javaPath + prefix + JAVA_SERVER_DLL_FILE; + if (!fileExists(javaClientDllPath.c_str())) { + javaClientDllPath = ""; + } + if (!fileExists(javaServerDllPath.c_str())) { + javaServerDllPath = ""; + } + javaBinPath = javaPath + prefix + JAVA_BIN_DIR; + if (fileExists(javaExePath.c_str()) || !javaClientDllPath.empty() || !javaServerDllPath.empty()) { + if (!fileExists(javawExePath.c_str())) { + logMsg("javaw.exe not exists, forcing java.exe"); + javawExePath = javaExePath; + } + return true; + } + + javaPath.clear(); + javaBinPath.clear(); + javaExePath.clear(); + javawExePath.clear(); + javaClientDllPath.clear(); + javaServerDllPath.clear(); + return false; +} + +bool JvmLauncher::initialize(const char *javaPathOrMinVersion) { + logMsg("JvmLauncher::initialize()\n\tjavaPathOrMinVersion: %s", javaPathOrMinVersion); + assert(javaPathOrMinVersion); + if (isVersionString(javaPathOrMinVersion)) { + return findJava(javaPathOrMinVersion); + } else { + return (checkJava(javaPathOrMinVersion, JAVA_JRE_PREFIX) || checkJava(javaPathOrMinVersion, "")); + } +} + +bool JvmLauncher::getJavaPath(string &path) { + logMsg("JvmLauncher::getJavaPath()"); + path = javaPath; + return !javaPath.empty(); +} + +bool JvmLauncher::start(const char *mainClassName, const list<string> &args, const list<string> &options, bool &separateProcess, DWORD *retCode) { + assert(mainClassName); + logMsg("JvmLauncher::start()\n\tmainClassName: %s\n\tseparateProcess: %s", + mainClassName, separateProcess ? "true" : "false"); + logMsg(" args:"); + for (list<string>::const_iterator it = args.begin(); it != args.end(); ++it) { + logMsg("\t%s", it->c_str()); + } + logMsg(" options:"); + for (list<string>::const_iterator it = options.begin(); it != options.end(); ++it) { + logMsg("\t%s", it->c_str()); + } + + if (!javaExePath.empty() && javaClientDllPath.empty() && javaServerDllPath.empty()) { + logMsg("Found only java.exe at %s. No DLLs. Falling back to java.exe\n", javaExePath.c_str()); + separateProcess = true; + } else { + if (javaExePath.empty() || (javaClientDllPath.empty() && javaServerDllPath.empty())) { + if (!initialize("")) { + return false; + } + } + } + + if (!separateProcess) { + // both client/server found, check option which should be used + if (!javaClientDllPath.empty() && !javaServerDllPath.empty()) { + javaDllPath = findClientOption(options) ? javaClientDllPath : javaServerDllPath; + } else { + javaDllPath = javaClientDllPath.empty() ? javaServerDllPath : javaClientDllPath; + } + + // it is necessary to absolutize dll path because current dir has to be + // temporarily changed for dll loading + char absoluteJavaDllPath[MAX_PATH] = ""; + strncpy(absoluteJavaDllPath, javaDllPath.c_str(), MAX_PATH); + normalizePath(absoluteJavaDllPath, MAX_PATH); + javaDllPath = absoluteJavaDllPath; + + logMsg("Java DLL path: %s", javaDllPath.c_str()); + if (!canLoadJavaDll()) { + logMsg("Falling back to running Java in a separate process; DLL cannot be loaded (64-bit DLL?)."); + separateProcess = true; + } + } + + return separateProcess ? startOutProcJvm(mainClassName, args, options, retCode) + : startInProcJvm(mainClassName, args, options); +} + +bool JvmLauncher::findClientOption(const list<string> &options) { + for (list<string>::const_iterator it = options.begin(); it != options.end(); ++it) { + if (*it == "-client") { + return true; + } + } + return false; +} + +bool JvmLauncher::canLoadJavaDll() { + // be prepared for stupid placement of msvcr71.dll in java installation + // (in java 1.6/1.7 jvm.dll is dynamically linked to msvcr71.dll which si placed + // in bin directory) + PrepareDllPath prepare(javaBinPath.c_str()); + HMODULE hDll = LoadLibrary(javaDllPath.c_str()); + if (hDll) { + FreeLibrary(hDll); + return true; + } + logErr(true, false, "Cannot load %s.", javaDllPath.c_str()); + return false; +} + +bool JvmLauncher::isVersionString(const char *str) { + char *end = 0; + strtod(str, &end); + return *end == '\0'; +} + +bool JvmLauncher::startInProcJvm(const char *mainClassName, const std::list<std::string> &args, const std::list<std::string> &options) { + class Jvm { + public: + + Jvm(JvmLauncher *jvmLauncher) + : hDll(0) + , hSplash(0) + , jvm(0) + , env(0) + , jvmOptions(0) + , jvmLauncher(jvmLauncher) + { + } + + ~Jvm() { + if (env && env->ExceptionOccurred()) { + env->ExceptionDescribe(); + } + + if (jvm) { + logMsg("Destroying JVM"); + jvm->DestroyJavaVM(); + } + + if (jvmOptions) { + delete[] jvmOptions; + } + + if (hDll) { + FreeLibrary(hDll); + } + if (hSplash) { + FreeLibrary(hSplash); + } + } + + bool init(const list<string> &options) { + logMsg("JvmLauncher::Jvm::init()"); + logMsg("LoadLibrary(\"%s\")", jvmLauncher->javaDllPath.c_str()); + { + PrepareDllPath prepare(jvmLauncher->javaBinPath.c_str()); + hDll = LoadLibrary(jvmLauncher->javaDllPath.c_str()); + if (!hDll) { + logErr(true, true, "Cannot load %s.", jvmLauncher->javaDllPath.c_str()); + return false; + } + + string pref = jvmLauncher->javaBinPath; + pref += "\\splashscreen.dll"; + const string splash = pref; + logMsg("Trying to load %s", splash.c_str()); + hSplash = LoadLibrary(splash.c_str()); + logMsg("Splash loaded as %d", hSplash); + } + + CreateJavaVM createJavaVM = (CreateJavaVM) GetProcAddress(hDll, JNI_CREATEVM_FUNC); + if (!createJavaVM) { + logErr(true, true, "GetProcAddress for %s failed.", JNI_CREATEVM_FUNC); + return false; + } + + logMsg("JVM options:"); + jvmOptions = new JavaVMOption[options.size() + 1]; + int i = 0; + for (list<string>::const_iterator it = options.begin(); it != options.end(); ++it, ++i) { + const string &option = *it; + logMsg("\t%s", option.c_str()); + if (option.find("-splash:") == 0 && hSplash > 0) { + const string splash = option.substr(8); + logMsg("splash at %s", splash.c_str()); + + SplashInit splashInit = (SplashInit)GetProcAddress(hSplash, "SplashInit"); + SplashLoadFile splashLoadFile = (SplashLoadFile)GetProcAddress(hSplash, "SplashLoadFile"); + + logMsg("splash init %d and load %d", splashInit, splashLoadFile); + if (splashInit && splashLoadFile) { + splashInit(); + splashLoadFile(splash.c_str()); + } + } + jvmOptions[i].optionString = (char *) option.c_str(); + jvmOptions[i].extraInfo = 0; + } + JavaVMInitArgs jvmArgs; + jvmOptions[options.size()].optionString = (char *) "exit"; + jvmOptions[options.size()].extraInfo = (void *) &exitHook; + + jvmArgs.options = jvmOptions; + jvmArgs.nOptions = options.size() + 1; + jvmArgs.version = JNI_VERSION_1_4; + jvmArgs.ignoreUnrecognized = JNI_TRUE; + + logMsg("Creating JVM..."); + if (createJavaVM(&jvm, &env, &jvmArgs) < 0) { + logErr(false, true, "JVM creation failed"); + return false; + } + logMsg("JVM created."); + return true; + } + typedef jint (CALLBACK *CreateJavaVM)(JavaVM **jvm, JNIEnv **env, void *args); + typedef void (CALLBACK *SplashInit)(); + typedef int (CALLBACK *SplashLoadFile)(const char* file); + + HMODULE hDll; + HMODULE hSplash; + JavaVM *jvm; + JNIEnv *env; + JavaVMOption *jvmOptions; + JvmLauncher *jvmLauncher; + }; + + Jvm jvm(this); + if (!jvm.init(options)) { + return false; + } + + jclass mainClass = jvm.env->FindClass(mainClassName); + if (!mainClass) { + logErr(false, true, "Cannot find class %s.", mainClassName); + return false; + } + + jmethodID mainMethod = jvm.env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V"); + if (!mainMethod) { + logErr(false, true, "Cannot get main method."); + return false; + } + + jclass jclassString = jvm.env->FindClass("java/lang/String"); + if (!jclassString) { + logErr(false, true, "Cannot find java/lang/String class"); + return false; + } + + jstring jstringArg = jvm.env->NewStringUTF(""); + if (!jstringArg) { + logErr(false, true, "NewStringUTF() failed"); + return false; + } + + jobjectArray mainArgs = jvm.env->NewObjectArray(args.size(), jclassString, jstringArg); + if (!mainArgs) { + logErr(false, true, "NewObjectArray() failed"); + return false; + } + int i = 0; + for (list<string>::const_iterator it = args.begin(); it != args.end(); ++it, ++i) { + const string &arg = *it; + const int len = 32*1024; + char utf8[len] = ""; + if (convertAnsiToUtf8(arg.c_str(), utf8, len)) + logMsg("Conversion to UTF8 failed"); + jstring jstringArg = jvm.env->NewStringUTF(utf8); + if (!jstringArg) { + logErr(false, true, "NewStringUTF() failed"); + return false; + } + jvm.env->SetObjectArrayElement(mainArgs, i, jstringArg); + } + + jvm.env->CallStaticVoidMethod(mainClass, mainMethod, mainArgs); + return true; +} + + +bool JvmLauncher::startOutProcJvm(const char *mainClassName, const std::list<std::string> &args, const std::list<std::string> &options, DWORD *retCode) { + string cmdLine = '\"' + (suppressConsole ? javawExePath : javaExePath) + '\"'; + cmdLine.reserve(32*1024); + for (list<string>::const_iterator it = options.begin(); it != options.end(); ++it) { + cmdLine += " \""; + cmdLine += *it; + cmdLine += "\""; + } + + // mainClass and args + cmdLine += ' '; + cmdLine += mainClassName; + for (list<string>::const_iterator it = args.begin(); it != args.end(); ++it) { + if (javaClientDllPath.empty() && *it == "-client") { + logMsg("Removing -client option, client java dll not found."); + // remove client parameter, no client java found + continue; + } + cmdLine += " \""; + cmdLine += *it; + cmdLine += "\""; + } + + logMsg("Command line:\n%s", cmdLine.c_str()); + if (cmdLine.size() >= 32*1024) { + logErr(false, true, "Command line is too long. Length: %u. Maximum length: %u.", cmdLine.c_str(), 32*1024); + return false; + } + + STARTUPINFO si = {0}; + si.cb = sizeof (STARTUPINFO); + PROCESS_INFORMATION pi = {0}; + + char cmdLineStr[32*1024] = ""; + strcpy(cmdLineStr, cmdLine.c_str()); + if (!CreateProcess(NULL, cmdLineStr, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { + logErr(true, true, "Failed to create process"); + return false; + } + + disableFolderVirtualization(pi.hProcess); + ResumeThread(pi.hThread); + WaitForSingleObject(pi.hProcess, INFINITE); + if (retCode) { + GetExitCodeProcess(pi.hProcess, retCode); + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; +} + +bool JvmLauncher::findJava(const char *minJavaVersion) { + // scan for registry for jdk/jre version 9 + if (findJava(JDK_POST9_KEY, "", minJavaVersion)) { + return true; + } + if (findJava(JRE_POST9_KEY, "", minJavaVersion)) { + return true; + } + if (findJava(JDK_KEY, JAVA_JRE_PREFIX, minJavaVersion)) { + return true; + } + if (findJava(JRE_KEY, "", minJavaVersion)) { + return true; + } + javaPath = ""; + javaExePath = ""; + javaClientDllPath = ""; + javaServerDllPath = ""; + javaBinPath = ""; + return false; +} + +bool JvmLauncher::findJava(const char *javaKey, const char *prefix, const char *minJavaVersion) { + logMsg("JvmLauncher::findJava()\n\tjavaKey: %s\n\tprefix: %s\n\tminJavaVersion: %s", javaKey, prefix, minJavaVersion); + string value; + bool result = false; + if (getStringFromRegistry(HKEY_LOCAL_MACHINE, javaKey, CUR_VERSION_NAME, value)) { + if (value >= minJavaVersion) { + string path; + if (getStringFromRegistry(HKEY_LOCAL_MACHINE, (string(javaKey) + "\\" + value).c_str(), JAVA_HOME_NAME, path)) { + if (*path.rbegin() == '\\') { + path.erase(path.length() - 1, 1); + } + result = checkJava(path.c_str(), prefix); + } + } + } + if(!result && isWow64()) { + if (getStringFromRegistry64bit(HKEY_LOCAL_MACHINE, javaKey, CUR_VERSION_NAME, value)) { + if (value >= minJavaVersion) { + string path; + if (getStringFromRegistry64bit(HKEY_LOCAL_MACHINE, (string(javaKey) + "\\" + value).c_str(), JAVA_HOME_NAME, path)) { + if (*path.rbegin() == '\\') { + path.erase(path.length() - 1, 1); + } + result = checkJava(path.c_str(), prefix); + } + } + } + } + // probably also need to check 32bit registry when launcher becomes 64-bit but is not the case now. + return result; +} diff --git a/jvmlauncher.h b/jvmlauncher.h new file mode 100644 index 0000000..01f4e34 --- /dev/null +++ b/jvmlauncher.h @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#ifndef _JVMLAUNCHER_H +#define _JVMLAUNCHER_H + +#include <windows.h> +#include <string> +#include <list> +#include "jni.h" +#include "utilsfuncs.h" + +class JvmLauncher { + static const int MAX_ARGS_LEN = 32*1024; + + static const char *JDK_KEY; + static const char *JRE_KEY; + // registry key change starting with version 9 + static const char *JDK_POST9_KEY; + static const char *JRE_POST9_KEY; + static const char *CUR_VERSION_NAME; + static const char *JAVA_HOME_NAME; + static const char *JAVA_BIN_DIR; + static const char *JAVA_EXE_FILE; + static const char *JAVAW_EXE_FILE; + static const char *JAVA_CLIENT_DLL_FILE; + static const char *JAVA_SERVER_DLL_FILE; + static const char *JAVA_JRE_PREFIX; + static const char *JNI_CREATEVM_FUNC; + +public: + JvmLauncher(); + virtual ~JvmLauncher(); + + bool initialize(const char *javaPathOrMinVersion); + bool getJavaPath(std::string &path); + bool start(const char *mainClassName, const std::list<std::string> &args, const std::list<std::string> &options, bool &separateProcess, DWORD *retCode); + + void setSuppressConsole(bool val) { + suppressConsole = val; + } + +private: + JvmLauncher(const JvmLauncher& orig); + + bool checkJava(const char *javaPath, const char *prefix); + bool findJava(const char *minJavaVersion); + bool findJava(const char *javaKey, const char *prefix, const char *minJavaVersion); + bool startOutProcJvm(const char *mainClassName, const std::list<std::string> &args, const std::list<std::string> &options, DWORD *retCode); + bool startInProcJvm(const char *mainClassName, const std::list<std::string> &args, const std::list<std::string> &options); + bool isVersionString(const char *str); + bool canLoadJavaDll(); + bool findClientOption(const std::list<std::string> &options); + +private: + bool suppressConsole; + std::string javaExePath; + std::string javawExePath; + std::string javaDllPath; + std::string javaClientDllPath; + std::string javaServerDllPath; + std::string javaPath; + std::string javaBinPath; + + class PrepareDllPath { + public: + PrepareDllPath(const char *dllDirectory) + : setDllDirectory(0) { + logMsg("PrepareDllPath: %s", dllDirectory); + oldCurDir[0] = '\0'; + + // SetDllDirectory is present since XP SP1, so we have to load it dynamically + HINSTANCE hKernel32 = GetModuleHandle("kernel32"); + if (!hKernel32) { + logErr(true, false, "Cannot load kernel32."); + return; + } + + LPFNSDD setDllDirectory = (LPFNSDD)GetProcAddress(hKernel32, "SetDllDirectoryA"); + if (setDllDirectory) { + setDllDirectory(dllDirectory); + } else { + logErr(true, false, "Cannot find SetDllDirectoryA"); + } + GetCurrentDirectory(MAX_PATH, oldCurDir); + SetCurrentDirectory(dllDirectory); + } + ~PrepareDllPath() { + if (setDllDirectory) { + setDllDirectory(NULL); + } + if (oldCurDir[0]) { + SetCurrentDirectory(oldCurDir); + } + } + private: + typedef BOOL (WINAPI *LPFNSDD)(LPCTSTR lpPathname); + LPFNSDD setDllDirectory; + char oldCurDir[MAX_PATH]; + }; +}; + +#endif /* _JVMLAUNCHER_H */ + diff --git a/nbexec.cpp b/nbexec.cpp new file mode 100644 index 0000000..2e63d02 --- /dev/null +++ b/nbexec.cpp @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#include "platformlauncher.h" +#include "utilsfuncs.h" + +PlatformLauncher launcher; + +extern "C" BOOL APIENTRY DllMain(HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) { + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + launcher.onExit(); + break; + } + return TRUE; +} + +void exitHook(int status) { + logMsg("Exit hook called with status %d", status); + launcher.onExit(); +} + +#define NBEXEC_EXPORT extern "C" __declspec(dllexport) + +NBEXEC_EXPORT int startPlatform(int argc, char *argv[], const char *helpMsg) { + DWORD retCode = 0; + launcher.appendToHelp(helpMsg); + launcher.setSuppressConsole(!isConsoleAttached()); + if (!launcher.start(argv, argc, &retCode)) { + return -1; + } + return retCode; +} + + diff --git a/nbexec.exe.manifest b/nbexec.exe.manifest new file mode 100644 index 0000000..da15223 --- /dev/null +++ b/nbexec.exe.manifest @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity version="9.0.0.0" + processorArchitecture="X86" + name="nbexec.exe" + type="win32"/> + +<description>nbexec Process.</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> +<!-- Identify the application security requirements. --> +<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel + level="asInvoker" + uiAccess="false"/> + </requestedPrivileges> + </security> +</trustInfo> +</assembly> diff --git a/nbexec.rc b/nbexec.rc new file mode 100644 index 0000000..b0843af --- /dev/null +++ b/nbexec.rc @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <winuser.h> + +#define FNAME "nbexec.dll" +#define FILETYPE_ID 0x2L + +#include "version.rc" + diff --git a/nbexec_exe.rc b/nbexec_exe.rc new file mode 100644 index 0000000..598b6c0 --- /dev/null +++ b/nbexec_exe.rc @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <winuser.h> + +#define FNAME "nbexec.exe" +#define FILETYPE_ID 0x1L + +#include "version.rc" + + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "nbexec.exe.manifest" + diff --git a/nbexecexe.cpp b/nbexecexe.cpp new file mode 100644 index 0000000..181a365 --- /dev/null +++ b/nbexecexe.cpp @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#include <windows.h> +#include "nbexecloader.h" + +int main(int argc, char *argv[]) { + checkLoggingArg(argc, argv, true); + NBExecLoader loader; + + // NBEXEC_DLL specified in preprocessor definitions + return loader.start(NBEXEC_DLL, argc - 1, argv + 1); +} diff --git a/nbexecloader.h b/nbexecloader.h new file mode 100644 index 0000000..221a557 --- /dev/null +++ b/nbexecloader.h @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef _NBEXECLOADER_H +#define _NBEXECLOADER_H + +#include "utilsfuncs.h" + +#define HELP_MSG \ +"\ + --console suppress supppress console output\n\ + --console new open new console for output\n\ +\n" + +class NBExecLoader { + typedef int (*StartPlatform)(int argc, char *argv[], const char *help); + +public: + NBExecLoader() + : hLib(0) { + } + ~NBExecLoader() { + if (hLib) { + FreeLibrary(hLib); + } + } + int start(const char *path, int argc, char *argv[]) { + if (!hLib) { + hLib = LoadLibrary(path); + if (!hLib) { + logErr(true, true, "Cannot load \"%s\".", path); + return -1; + } + } + + StartPlatform startPlatform = (StartPlatform) GetProcAddress(hLib, "startPlatform"); + if (!startPlatform) { + logErr(true, true, "Cannot start platform, failed to find startPlatform() in %s", path); + return -1; + } + logMsg("Starting platform...\n"); + return startPlatform(argc, argv, HELP_MSG); + } + +private: + HMODULE hLib; +}; + +#endif /* _NBEXECLOADER_H */ + diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml new file mode 100644 index 0000000..0121be8 --- /dev/null +++ b/nbproject/configurations.xml @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> +<configurationDescriptor version="100"> + <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT"> + <logicalFolder name="HeaderFiles" + displayName="Header Files" + projectFiles="true"> + <itemPath>argnames.h</itemPath> + <itemPath>jvmlauncher.h</itemPath> + <itemPath>platformlauncher.h</itemPath> + <itemPath>utilsfuncs.h</itemPath> + <itemPath>version.h</itemPath> + </logicalFolder> + <logicalFolder name="ResourceFiles" + displayName="Resource Files" + projectFiles="true"> + <itemPath>nbexec.exe.manifest</itemPath> + <itemPath>nbexec.rc</itemPath> + <itemPath>nbexec_exe.rc</itemPath> + <itemPath>version.rc</itemPath> + </logicalFolder> + <logicalFolder name="SourceFiles" + displayName="Source Files" + projectFiles="true"> + <itemPath>jvmlauncher.cpp</itemPath> + <itemPath>nbexec.cpp</itemPath> + <itemPath>nbexecexe.cpp</itemPath> + <itemPath>nbexecloader.h</itemPath> + <itemPath>platformlauncher.cpp</itemPath> + <itemPath>utilsfuncs.cpp</itemPath> + </logicalFolder> + <logicalFolder name="ExternalFiles" + displayName="Important Files" + projectFiles="false"> + <itemPath>Makefile</itemPath> + </logicalFolder> + <logicalFolder name="ExternalFiles" + displayName="Important Files" + projectFiles="false"> + <itemPath>Makefile</itemPath> + </logicalFolder> + </logicalFolder> + <projectmakefile>Makefile</projectmakefile> + <confs> + <conf name="nbexec" type="2"> + <toolsSet> + <compilerSet>Cygwin|Cygwin</compilerSet> + <dependencyChecking>true</dependencyChecking> + <rebuildPropChanged>false</rebuildPropChanged> + </toolsSet> + <compileType> + <cTool> + <developmentMode>5</developmentMode> + <warningLevel>2</warningLevel> + </cTool> + <ccTool> + <developmentMode>5</developmentMode> + <stripSymbols>true</stripSymbols> + <architecture>1</architecture> + <incDir> + <pElem>C:/Program Files/Java/jdk1.8.0_77/include</pElem> + <pElem>C:/Program Files/Java/jdk1.8.0_77/include/win32</pElem> + </incDir> + <commandLine>-mno-cygwin</commandLine> + <preprocessorList> + <Elem>NBEXEC_DLL="nbexec.dll"</Elem> + </preprocessorList> + </ccTool> + <linkerTool> + <output>nbexec.dll</output> + <additionalDep>${OBJECTDIR}/nbexec.res</additionalDep> + <linkerLibItems> + <linkerOptionItem>${OBJECTDIR}/nbexec.res</linkerOptionItem> + </linkerLibItems> + <commandLine>-Wl,--nxcompat -Wl,--dynamicbase -Wl,--no-seh</commandLine> + </linkerTool> + </compileType> + <item path="argnames.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="jvmlauncher.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="jvmlauncher.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="nbexec.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="nbexec.exe.manifest" ex="false" tool="3" flavor2="0"> + </item> + <item path="nbexec.rc" ex="false" tool="3" flavor2="0"> + <customTool> + <customToolCommandline>windres.exe -Ocoff nbexec.rc ${OBJECTDIR}/nbexec.res</customToolCommandline> + <customToolDescription>Compiling Resource files...</customToolDescription> + <customToolOutputs>${OBJECTDIR}/nbexec.res</customToolOutputs> + <customToolAdditionalDep>version.h</customToolAdditionalDep> + </customTool> + </item> + <item path="nbexec_exe.rc" ex="false" tool="3" flavor2="0"> + <customTool> + <customToolDescription></customToolDescription> + </customTool> + </item> + <item path="nbexecexe.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="nbexecloader.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="platformlauncher.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="platformlauncher.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="utilsfuncs.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="utilsfuncs.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="version.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="version.rc" ex="false" tool="3" flavor2="0"> + </item> + </conf> + <conf name="nbexec64" type="2"> + <toolsSet> + <compilerSet>Cygwin64|Cygwin</compilerSet> + <dependencyChecking>true</dependencyChecking> + <rebuildPropChanged>false</rebuildPropChanged> + </toolsSet> + <compileType> + <cTool> + <developmentMode>5</developmentMode> + </cTool> + <ccTool> + <developmentMode>5</developmentMode> + <stripSymbols>true</stripSymbols> + <architecture>2</architecture> + <incDir> + <pElem>C:/Program Files/Java/jdk1.8.0_77/include</pElem> + <pElem>C:/Program Files/Java/jdk1.8.0_77/include/win32</pElem> + </incDir> + <commandLine>-mno-cygwin -static-libgcc -static-libstdc++</commandLine> + <preprocessorList> + <Elem>NBEXEC_DLL="nbexec64.dll"</Elem> + </preprocessorList> + </ccTool> + <linkerTool> + <output>nbexec64.dll</output> + <additionalDep>${OBJECTDIR}/nbexec64.res</additionalDep> + <linkerLibItems> + <linkerOptionItem>${OBJECTDIR}/nbexec64.res</linkerOptionItem> + </linkerLibItems> + <commandLine>-Wl,--nxcompat -Wl,--dynamicbase</commandLine> + </linkerTool> + </compileType> + <item path="argnames.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="jvmlauncher.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="jvmlauncher.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="nbexec.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="nbexec.exe.manifest" ex="false" tool="3" flavor2="0"> + </item> + <item path="nbexec.rc" ex="false" tool="3" flavor2="0"> + <customTool> + <customToolCommandline>x86_64-w64-mingw32-windres.exe -Ocoff nbexec.rc ${OBJECTDIR}/nbexec64.res</customToolCommandline> + <customToolDescription>Compiling Resource files...</customToolDescription> + <customToolOutputs>${OBJECTDIR}/nbexec64.res</customToolOutputs> + <customToolAdditionalDep>version.h</customToolAdditionalDep> + </customTool> + </item> + <item path="nbexec_exe.rc" ex="false" tool="3" flavor2="0"> + <customTool> + <customToolDescription></customToolDescription> + </customTool> + </item> + <item path="nbexecexe.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="nbexecloader.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="platformlauncher.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="platformlauncher.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="utilsfuncs.cpp" ex="false" tool="1" flavor2="0"> + </item> + <item path="utilsfuncs.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="version.h" ex="false" tool="3" flavor2="0"> + </item> + <item path="version.rc" ex="false" tool="3" flavor2="0"> + </item> + </conf> + </confs> +</configurationDescriptor> diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..2456923 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..99f9274 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> +<project xmlns="http://www.netbeans.org/ns/project/1"> + <type>org.netbeans.modules.cnd.makeproject</type> + <configuration> + <data xmlns="http://www.netbeans.org/ns/make-project/1"> + <name>Platform Launcher Win</name> + <make-project-type>0</make-project-type> + <c-extensions/> + <cpp-extensions>cpp</cpp-extensions> + <header-extensions>h</header-extensions> + <sourceEncoding>UTF-8</sourceEncoding> + <make-dep-projects/> + <sourceRootList/> + <confList> + <confElem> + <name>nbexec</name> + <type>2</type> + </confElem> + <confElem> + <name>nbexec64</name> + <type>2</type> + </confElem> + </confList> + <formatting> + <project-formatting-style>false</project-formatting-style> + </formatting> + </data> + </configuration> +</project> diff --git a/platformlauncher.cpp b/platformlauncher.cpp new file mode 100644 index 0000000..b29b309 --- /dev/null +++ b/platformlauncher.cpp @@ -0,0 +1,724 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#include "utilsfuncs.h" +#include "platformlauncher.h" +#include "argnames.h" + +using namespace std; + +const char *PlatformLauncher::HELP_MSG = +"\nUsage: launcher {options} arguments\n\ +\n\ +General options:\n\ + --help show this help\n\ + --jdkhome <path> path to JDK\n\ + -J<jvm_option> pass <jvm_option> to JVM\n\ +\n\ + --cp:p <classpath> prepend <classpath> to classpath\n\ + --cp:a <classpath> append <classpath> to classpath\n\ +\n\ + --fork-java run java in separate process\n\ + --trace <path> path for launcher log (for trouble shooting)\n\ +\n"; + +const char *PlatformLauncher::REQ_JAVA_VERSION = "1.8"; + +const char *PlatformLauncher::OPT_JDK_HOME = "-Djdk.home="; +const char *PlatformLauncher::OPT_NB_PLATFORM_HOME = "-Dnetbeans.home="; +const char *PlatformLauncher::OPT_NB_CLUSTERS = "-Dnetbeans.dirs="; +const char *PlatformLauncher::OPT_NB_USERDIR = "-Dnetbeans.user="; +const char *PlatformLauncher::OPT_DEFAULT_USERDIR_ROOT = "-Dnetbeans.default_userdir_root="; +const char *PlatformLauncher::OPT_HEAP_DUMP = "-XX:+HeapDumpOnOutOfMemoryError"; +const char *PlatformLauncher::OPT_HEAP_DUMP_PATH = "-XX:HeapDumpPath="; +const char *PlatformLauncher::OPT_KEEP_WORKING_SET_ON_MINIMIZE = "-Dsun.awt.keepWorkingSetOnMinimize=true"; +const char *PlatformLauncher::OPT_CLASS_PATH = "-Djava.class.path="; +const char *PlatformLauncher::OPT_SPLASH = "-splash:"; +const char *PlatformLauncher::OPT_SPLASH_PATH = "\\var\\cache\\splash.png"; + +const char *PlatformLauncher::HEAP_DUMP_PATH = "\\var\\log\\heapdump.hprof"; +const char *PlatformLauncher::RESTART_FILE_PATH = "\\var\\restart"; + +const char *PlatformLauncher::UPDATER_MAIN_CLASS = "org/netbeans/updater/UpdaterFrame"; +const char *PlatformLauncher::IDE_MAIN_CLASS = "org/netbeans/Main"; + +PlatformLauncher::PlatformLauncher() + : separateProcess(false) + , suppressConsole(false) + , heapDumpPathOptFound(false) + , nosplash(false) + , exiting(false) { +} + +PlatformLauncher::PlatformLauncher(const PlatformLauncher& orig) { +} + +PlatformLauncher::~PlatformLauncher() { +} + +bool PlatformLauncher::start(char* argv[], int argc, DWORD *retCode) { + if (!checkLoggingArg(argc, argv, false) || !initPlatformDir() || !parseArgs(argc, argv)) { + return false; + } + disableFolderVirtualization(GetCurrentProcess()); + + if (jdkhome.empty()) { + if (!jvmLauncher.initialize(REQ_JAVA_VERSION)) { + logErr(false, true, "Cannot find Java %s or higher.", REQ_JAVA_VERSION); + return false; + } + } + jvmLauncher.getJavaPath(jdkhome); + + deleteNewClustersFile(); + prepareOptions(); + + if (nextAction.empty()) { + if (shouldAutoUpdateClusters(true)) { + // run updater + if (!run(true, retCode)) { + return false; + } + } + + while (true) { + // run app + if (!run(false, retCode)) { + return false; + } + + if (shouldAutoUpdateClusters(false)) { + // run updater + if (!run(true, retCode)) { + return false; + } + } else if (!restartRequested()) { + break; + } + } + } else { + if (nextAction == ARG_NAME_LA_START_APP) { + return run(false, retCode); + } else if (nextAction == ARG_NAME_LA_START_AU) { + if (shouldAutoUpdateClusters(false)) { + return run(true, retCode); + } + } else { + logErr(false, true, "We should not get here."); + return false; + } + } + + return true; +} + +bool PlatformLauncher::run(bool updater, DWORD *retCode) { + logMsg(updater ? "Starting updater..." : "Starting application..."); + constructClassPath(updater); + const char *mainClass; + if (updater) { + mainClass = UPDATER_MAIN_CLASS; + nextAction = ARG_NAME_LA_START_APP; + } else { + DeleteFile((userDir + RESTART_FILE_PATH).c_str()); + mainClass = bootclass.empty() ? IDE_MAIN_CLASS : bootclass.c_str(); + nextAction = ARG_NAME_LA_START_AU; + } + + string option = OPT_NB_CLUSTERS; + option += auClusters.empty() ? clusters : auClusters; + javaOptions.push_back(option); + + option = OPT_CLASS_PATH; + option += classPath; + javaOptions.push_back(option); + + jvmLauncher.setSuppressConsole(suppressConsole); + bool rc = jvmLauncher.start(mainClass, progArgs, javaOptions, separateProcess, retCode); + if (!separateProcess) { + exit(0); + } + + javaOptions.pop_back(); + javaOptions.pop_back(); + return rc; +} + + + +bool PlatformLauncher::initPlatformDir() { + char path[MAX_PATH] = ""; + getCurrentModulePath(path, MAX_PATH); + logMsg("Module: %s", path); + char *bslash = strrchr(path, '\\'); + if (!bslash) { + return false; + } + *bslash = '\0'; + bslash = strrchr(path, '\\'); + if (!bslash) { + return false; + } + *bslash = '\0'; + clusters = platformDir = path; + logMsg("Platform dir: %s", platformDir.c_str()); + return true; +} + +bool PlatformLauncher::parseArgs(int argc, char *argv[]) { +#define CHECK_ARG \ + if (i+1 == argc) {\ + logErr(false, true, "Argument is missing for \"%s\" option.", argv[i]);\ + return false;\ + } + + logMsg("Parsing arguments:"); + for (int i = 0; i < argc; i++) { + logMsg("\t%s", argv[i]); + } + + for (int i = 0; i < argc; i++) { + if (strcmp(ARG_NAME_SEPAR_PROC, argv[i]) == 0) { + separateProcess = true; + logMsg("Run Java in separater process"); + } else if (strcmp(ARG_NAME_LAUNCHER_LOG, argv[i]) == 0) { + CHECK_ARG; + i++; + } else if (strcmp(ARG_NAME_LA_START_APP, argv[i]) == 0 + || strcmp(ARG_NAME_LA_START_AU, argv[i]) == 0) { + nextAction = argv[i]; + logMsg("Next launcher action: %s", nextAction.c_str()); + } else if (strcmp(ARG_NAME_LA_PPID, argv[i]) == 0) { + CHECK_ARG; + suppressConsole = false; + parentProcID = argv[++i]; + logMsg("Parent process ID found: %s", parentProcID.c_str()); + } else if (strcmp(ARG_NAME_USER_DIR, argv[i]) == 0) { + CHECK_ARG; + char tmp[MAX_PATH + 1] = {0}; + strncpy(tmp, argv[++i], MAX_PATH); + if (strcmp(tmp, "memory") != 0 && !normalizePath(tmp, MAX_PATH)) { + logErr(false, true, "User directory path \"%s\" is not valid.", argv[i]); + return false; + } + userDir = tmp; + logMsg("User dir: %s", userDir.c_str()); + } else if (strcmp(ARG_DEFAULT_USER_DIR_ROOT, argv[i]) == 0) { + CHECK_ARG; + char tmp[MAX_PATH + 1] = {0}; + strncpy(tmp, argv[++i], MAX_PATH); + if (strcmp(tmp, "memory") != 0 && !normalizePath(tmp, MAX_PATH)) { + logErr(false, true, "Default User directory path \"%s\" is not valid.", argv[i]); + return false; + } + defaultUserDirRoot = tmp; + logMsg("Default Userdir root: %s", defaultUserDirRoot.c_str()); + } else if (strcmp(ARG_NAME_CLUSTERS, argv[i]) == 0) { + CHECK_ARG; + clusters = argv[++i]; + } else if (strcmp(ARG_NAME_BOOTCLASS, argv[i]) == 0) { + CHECK_ARG; + bootclass = argv[++i]; + } else if (strcmp(ARG_NAME_JDKHOME, argv[i]) == 0) { + CHECK_ARG; + if (jdkhome.empty()) { + jdkhome = argv[++i]; + if (!jvmLauncher.initialize(jdkhome.c_str())) { + logMsg("Cannot locate java installation in specified jdkhome: %s", jdkhome.c_str()); + string errMsg = "Cannot locate java installation in specified jdkhome:\n"; + errMsg += jdkhome; + errMsg += "\nDo you want to try to use default version?"; + jdkhome = ""; + if (::MessageBox(NULL, errMsg.c_str(), "Invalid jdkhome specified", MB_ICONQUESTION | MB_YESNO) == IDNO) { + return false; + } + } + } else { + i++; + } + } else if (strcmp(ARG_NAME_CP_PREPEND, argv[i]) == 0 + || strcmp(ARG_NAME_CP_PREPEND + 1, argv[i]) == 0) { + CHECK_ARG; + cpBefore += argv[++i]; + } else if (strcmp(ARG_NAME_CP_APPEND, argv[i]) == 0 + || strcmp(ARG_NAME_CP_APPEND + 1, argv[i]) == 0 + || strncmp(ARG_NAME_CP_APPEND + 1, argv[i], 3) == 0 + || strncmp(ARG_NAME_CP_APPEND, argv[i], 4) == 0) { + CHECK_ARG; + cpAfter += argv[++i]; + } else if (strncmp("-J", argv[i], 2) == 0) { + javaOptions.push_back(argv[i] + 2); + if (strncmp(argv[i] + 2, OPT_HEAP_DUMP_PATH, strlen(OPT_HEAP_DUMP_PATH)) == 0) { + heapDumpPathOptFound = true; + } + } else { + if (strcmp(argv[i], "-h") == 0 + || strcmp(argv[i], "-help") == 0 + || strcmp(argv[i], "--help") == 0 + || strcmp(argv[i], "/?") == 0) { + printToConsole(HELP_MSG); + if (!appendHelp.empty()) { + printToConsole(appendHelp.c_str()); + } + } else if (strcmp(ARG_NAME_NOSPLASH, argv[i]) == 0) { + nosplash = true; + } + progArgs.push_back(argv[i]); + } + } + return true; +} + +bool PlatformLauncher::processAutoUpdateCL() { + logMsg("processAutoUpdateCL()..."); + if (userDir.empty()) { + logMsg("\tuserdir empty, quiting"); + return false; + } + string listPath = userDir; + listPath += "\\update\\download\\netbeans.dirs"; + + WIN32_FIND_DATA fd = {0}; + HANDLE hFind = 0; + hFind = FindFirstFile(listPath.c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) { + logMsg("File \"%s\" does not exist", listPath.c_str()); + return false; + } + FindClose(hFind); + + FILE *file = fopen(listPath.c_str(), "r"); + if (!file) { + logErr(true, false, "Cannot open file %s", listPath.c_str()); + return false; + } + + int len = fd.nFileSizeLow + 1; + char *str = new char[len]; + if (!fgets(str, len, file)) { + fclose(file); + delete[] str; + logErr(true, false, "Cannot read from file %s", listPath.c_str()); + return false; + } + len = strlen(str) - 1; + if (str[len] == '\n') { + str[len] = '\0'; + } + + auClusters = str; + fclose(file); + delete[] str; + return true; +} + +void PlatformLauncher::deleteNewClustersFile() { + logMsg("deleteNewClustersFile()..."); + if (userDir.empty()) { + logMsg("\tuserdir empty, quiting"); + return; + } + string listPath = userDir; + listPath += "\\update\\download\\netbeans.dirs"; + + if (fileExists(listPath.c_str())) { + DeleteFileA(listPath.c_str()); + logMsg("%s file deleted.", listPath.c_str()); + } +} + +// check if new updater exists, if exists install it (replace old one) and remove ...\new_updater directory +bool PlatformLauncher::checkForNewUpdater(const char *basePath) { + logMsg("checkForNewUpdater() at %s", basePath); + BOOL removeDir = false; + string srcPath = basePath; + srcPath += "\\update\\new_updater\\updater.jar"; + WIN32_FIND_DATA fd = {0}; + HANDLE hFind = FindFirstFile(srcPath.c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) { + logMsg("New updater found: %s", srcPath.c_str()); + FindClose(hFind); + string destPath = basePath; + destPath += "\\modules\\ext\\updater.jar"; + createPath(destPath.c_str()); + + int i = 0; + while (true) { + if (MoveFileEx(srcPath.c_str(), destPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + break; + } + if (exiting || ++i > 10) { + logErr(true, false, "Failed to move \"%s\" to \"%s\"", srcPath.c_str(), destPath.c_str()); + return false; + } + logErr(true, false, "Failed to move \"%s\" to \"%s\", trying to wait", srcPath.c_str(), destPath.c_str()); + Sleep(100); + } + logMsg("New updater successfully moved from \"%s\" to \"%s\"", srcPath.c_str(), destPath.c_str()); + removeDir = true; + } else { + logMsg("No new updater at %s", srcPath.c_str()); + } + string locPath = basePath; + locPath += "\\update\\new_updater\\updater_*.jar"; + hFind = FindFirstFile(locPath.c_str(), &fd); + while (hFind != INVALID_HANDLE_VALUE) { + string destPath = basePath; + string name = fd.cFileName; + logMsg("New updater localization found: %s", name.c_str()); + destPath += "\\modules\\ext\\locale\\"; + destPath += name; + + string fromPath = basePath; + fromPath += "\\update\\new_updater\\"; + fromPath += name; + + createPath(destPath.c_str()); + + int i = 0; + while (true) { + if (MoveFileEx(fromPath.c_str(), destPath.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + break; + } + if (exiting || ++i > 10) { + logErr(true, false, "Failed to move \"%s\" to \"%s\"", fromPath.c_str(), destPath.c_str()); + return false; + } + logErr(true, false, "Failed to move \"%s\" to \"%s\", trying to wait", fromPath.c_str(), destPath.c_str()); + Sleep(100); + } + logMsg("New updater successfully moved from \"%s\" to \"%s\"", fromPath.c_str(), destPath.c_str()); + removeDir = true; + + if (!FindNextFile(hFind, &fd)) { + break; + } + } + FindClose(hFind); + + if (removeDir) { + srcPath.erase(srcPath.rfind('\\')); + logMsg("Removing directory \"%s\"", srcPath.c_str()); + if (!RemoveDirectory(srcPath.c_str())) { + logErr(true, false, "Failed to remove directory \"%s\"", srcPath.c_str()); + } + } + return true; +} + +bool PlatformLauncher::shouldAutoUpdate(bool firstStart, const char *basePath) { + // The logic is following: + // if there is an NBM for installation then run updater + // unless it is not a first start and we asked to install later (on next start) + + // then also check if last run left list of modules to disable/uninstall and + // did not mark them to be deactivated later (on next start) + string path = basePath; + path += "\\update\\download\\*.nbm"; + logMsg("Checking for updates: %s", path.c_str()); + WIN32_FIND_DATA fd; + HANDLE hFindNbms = FindFirstFile(path.c_str(), &fd); + if (hFindNbms != INVALID_HANDLE_VALUE) { + logMsg("Some updates found at %s", path.c_str()); + FindClose(hFindNbms); + } else { + //also check for OSGi jars if *.nbm not found + path = basePath; + path += "\\update\\download\\*.jar"; + hFindNbms = FindFirstFile(path.c_str(), &fd); + if (hFindNbms != INVALID_HANDLE_VALUE) { + logMsg("Some OSGi updates found at %s", path.c_str()); + FindClose(hFindNbms); + } + } + + path = basePath; + path += "\\update\\download\\install_later.xml"; + HANDLE hFind = FindFirstFile(path.c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) { + logMsg("install_later.xml found: %s", path.c_str()); + FindClose(hFind); + } + + if (hFindNbms != INVALID_HANDLE_VALUE && (firstStart || hFind == INVALID_HANDLE_VALUE)) { + return true; + } + + path = basePath; + path += "\\update\\deactivate\\deactivate_later.txt"; + hFind = FindFirstFile(path.c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) { + logMsg("deactivate_later.txt found: %s", path.c_str()); + FindClose(hFind); + } + + if (firstStart || hFind == INVALID_HANDLE_VALUE) { + path = basePath; + path += "\\update\\deactivate\\to_disable.txt"; + hFind = FindFirstFile(path.c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) { + logMsg("to_disable.txt found: %s", path.c_str()); + FindClose(hFind); + return true; + } + + path = basePath; + path += "\\update\\deactivate\\to_uninstall.txt"; + hFind = FindFirstFile(path.c_str(), &fd); + if (hFind != INVALID_HANDLE_VALUE) { + logMsg("to_uninstall.txt found: %s", path.c_str()); + FindClose(hFind); + return true; + } + } + + return false; +} + +bool PlatformLauncher::shouldAutoUpdateClusters(bool firstStart) { + bool runUpdater = false; + string cl = processAutoUpdateCL() ? auClusters : clusters; + checkForNewUpdater(platformDir.c_str()); + runUpdater = shouldAutoUpdate(firstStart, platformDir.c_str()); + + const char delim = ';'; + string::size_type start = cl.find_first_not_of(delim, 0); + string::size_type end = cl.find_first_of(delim, start); + while (string::npos != end || string::npos != start) { + string cluster = cl.substr(start, end - start); + checkForNewUpdater(cluster.c_str()); + if (!runUpdater) { + runUpdater = shouldAutoUpdate(firstStart, cluster.c_str()); + } + start = cl.find_first_not_of(delim, end); + end = cl.find_first_of(delim, start); + } + + checkForNewUpdater(userDir.c_str()); + if (!runUpdater) { + runUpdater = shouldAutoUpdate(firstStart, userDir.c_str()); + } + return runUpdater; +} + +void PlatformLauncher::prepareOptions() { + string option = OPT_JDK_HOME; + option += jdkhome; + javaOptions.push_back(option); + + if (!nosplash) { + string splashPath = userDir; + splashPath += OPT_SPLASH_PATH; + if (fileExists(splashPath.c_str())) { + javaOptions.push_back(OPT_SPLASH + splashPath); + } + } + + option = OPT_NB_PLATFORM_HOME; + option += platformDir; + javaOptions.push_back(option); + + option = OPT_NB_USERDIR; + option += userDir; + javaOptions.push_back(option); + + option = OPT_DEFAULT_USERDIR_ROOT; + option += defaultUserDirRoot; + javaOptions.push_back(option); + + option = OPT_HEAP_DUMP; + javaOptions.push_back(option); + + if (!heapDumpPathOptFound) { + option = OPT_HEAP_DUMP_PATH; + option += userDir; + option += HEAP_DUMP_PATH; + javaOptions.push_back(option); + // rename old heap dump to .old + string heapdumpfile = userDir + HEAP_DUMP_PATH; + if (fileExists(heapdumpfile.c_str())) { + string heapdumpfileold = heapdumpfile + ".old"; + if (fileExists(heapdumpfileold.c_str())) { + DeleteFileA(heapdumpfileold.c_str()); + } + MoveFile (heapdumpfile.c_str(), heapdumpfileold.c_str()); + } + } + + option = OPT_KEEP_WORKING_SET_ON_MINIMIZE; + javaOptions.push_back(option); +} + +string & PlatformLauncher::constructClassPath(bool runUpdater) { + logMsg("constructClassPath()"); + addedToCP.clear(); + classPath = cpBefore; + + addJarsToClassPathFrom(userDir.c_str()); + addJarsToClassPathFrom(platformDir.c_str()); + + if (runUpdater) { + const char *baseUpdaterPath = userDir.c_str(); + string updaterPath = userDir + "\\modules\\ext\\updater.jar"; + + // if user updater does not exist, use updater from platform + if (!fileExists(updaterPath.c_str())) { + baseUpdaterPath = platformDir.c_str(); + updaterPath = platformDir + "\\modules\\ext\\updater.jar"; + } + + addToClassPath(updaterPath.c_str(), false); + addFilesToClassPath(baseUpdaterPath, "\\modules\\ext\\locale", "updater_*.jar"); + } + + addToClassPath((jdkhome + "\\lib\\dt.jar").c_str(), true); + addToClassPath((jdkhome + "\\lib\\tools.jar").c_str(), true); + + if (!cpAfter.empty()) { + addToClassPath(cpAfter.c_str(), false); + } + logMsg("ClassPath: %s", classPath.c_str()); + return classPath; +} + +void PlatformLauncher::addJarsToClassPathFrom(const char *dir) { + addFilesToClassPath(dir, "lib\\patches", "*.jar"); + addFilesToClassPath(dir, "lib\\patches", "*.zip"); + + addFilesToClassPath(dir, "lib", "*.jar"); + addFilesToClassPath(dir, "lib", "*.zip"); + + addFilesToClassPath(dir, "lib\\locale", "*.jar"); + addFilesToClassPath(dir, "lib\\locale", "*.zip"); +} + +void PlatformLauncher::addFilesToClassPath(const char *dir, const char *subdir, const char *pattern) { + logMsg("addFilesToClassPath()\n\tdir: %s\n\tsubdir: %s\n\tpattern: %s", dir, subdir, pattern); + string path = dir; + path += '\\'; + path += subdir; + path += '\\'; + + WIN32_FIND_DATA fd = {0}; + string patternPath = path + pattern; + HANDLE hFind = FindFirstFile(patternPath.c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) { + logMsg("Nothing found (%s)", patternPath.c_str()); + return; + } + do { + string name = subdir; + name += fd.cFileName; + string fullName = path + fd.cFileName; + if (addedToCP.insert(name).second) { + addToClassPath(fullName.c_str()); + } else { + logMsg("\"%s\" already added, skipping \"%s\"", name.c_str(), fullName.c_str()); + } + } while (FindNextFile(hFind, &fd)); + FindClose(hFind); +} + +void PlatformLauncher::addToClassPath(const char *path, bool onlyIfExists) { + logMsg("addToClassPath()\n\tpath: %s\n\tonlyIfExists: %s", path, onlyIfExists ? "true" : "false"); + if (onlyIfExists && !fileExists(path)) { + return; + } + + if (!classPath.empty()) { + classPath += ';'; + } + classPath += path; +} + +void PlatformLauncher::appendToHelp(const char *msg) { + if (msg) { + appendHelp = msg; + } +} + +bool PlatformLauncher::restartRequested() { + return fileExists((userDir + RESTART_FILE_PATH).c_str()); +} + +void PlatformLauncher::onExit() { + logMsg("onExit()"); + + if (exiting) { + logMsg("Already exiting, no need to schedule restart"); + return; + } + + exiting = true; + + if (separateProcess) { + logMsg("JVM in separate process, no need to restart"); + return; + } + + bool restart = (nextAction == ARG_NAME_LA_START_APP || (nextAction == ARG_NAME_LA_START_AU && shouldAutoUpdateClusters(false))); + if (!restart && restartRequested()) { + restart = true; + nextAction = ARG_NAME_LA_START_APP; + } + + if (restart) { + string cmdLine = GetCommandLine(); + logMsg("Old command line: %s", cmdLine.c_str()); + string::size_type bslashPos = cmdLine.find_last_of('\\'); + string::size_type pos = cmdLine.find(ARG_NAME_LA_START_APP); + if ((bslashPos == string::npos || bslashPos < pos) && pos != string::npos) { + cmdLine.erase(pos, strlen(ARG_NAME_LA_START_APP)); + } + pos = cmdLine.find(ARG_NAME_LA_START_AU); + if ((bslashPos == string::npos || bslashPos < pos) && pos != string::npos) { + cmdLine.erase(pos, strlen(ARG_NAME_LA_START_AU)); + } + + if (*cmdLine.rbegin() != ' ') { + cmdLine += ' '; + } + if (!parentProcID.empty() && cmdLine.find(ARG_NAME_LA_PPID) == string::npos) { + cmdLine += ARG_NAME_LA_PPID; + cmdLine += ' '; + cmdLine += parentProcID; + } + + if (*cmdLine.rbegin() != ' ') { + cmdLine += ' '; + } + cmdLine += nextAction; + + logMsg("New command line: %s", cmdLine.c_str()); + char cmdLineStr[32 * 1024] = ""; + strcpy(cmdLineStr, cmdLine.c_str()); + STARTUPINFO si = {0}; + PROCESS_INFORMATION pi = {0}; + si.cb = sizeof(STARTUPINFO); + if (!CreateProcess(NULL, cmdLineStr, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { + logErr(true, true, "Failed to create process."); + return; + } + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } +} diff --git a/platformlauncher.h b/platformlauncher.h new file mode 100644 index 0000000..07fe4d8 --- /dev/null +++ b/platformlauncher.h @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#ifndef _PLATFORMLAUNCHER_H +#define _PLATFORMLAUNCHER_H + +#include "jvmlauncher.h" +#include <string> +#include <list> +#include <set> + +class PlatformLauncher { + static const char *REQ_JAVA_VERSION; + static const char *HELP_MSG; + + static const char *HEAP_DUMP_PATH; + static const char *RESTART_FILE_PATH; + + static const char *OPT_JDK_HOME; + static const char *OPT_NB_PLATFORM_HOME; + static const char *OPT_NB_CLUSTERS; + static const char *OPT_NB_USERDIR; + static const char *OPT_DEFAULT_USERDIR_ROOT; + static const char *OPT_HEAP_DUMP; + static const char *OPT_HEAP_DUMP_PATH; + static const char *OPT_KEEP_WORKING_SET_ON_MINIMIZE; + static const char *OPT_CLASS_PATH; + static const char *OPT_SPLASH; + static const char *OPT_SPLASH_PATH; + + static const char *UPDATER_MAIN_CLASS; + static const char *IDE_MAIN_CLASS; + + +public: + PlatformLauncher(); + virtual ~PlatformLauncher(); + + bool start(char* argv[], int argc, DWORD *retCode); + void appendToHelp(const char *msg); + void onExit(); + + void setSuppressConsole(bool val) { + suppressConsole = val; + } + +private: + PlatformLauncher(const PlatformLauncher& orig); + bool parseArgs(int argc, char *argv[]); + bool initPlatformDir(); + bool processAutoUpdateCL(); + void deleteNewClustersFile(); + bool checkForNewUpdater(const char *basePath); + bool shouldAutoUpdate(bool firstStart, const char *basePath); + bool shouldAutoUpdateClusters(bool firstStart); + void prepareOptions(); + std::string & constructClassPath(bool runUpdater); + void addFilesToClassPath(const char *dir, const char *subdir, const char *pattern); + void addToClassPath(const char *path, bool onlyIfExists = false); + void addJarsToClassPathFrom(const char *dir); + bool run(bool updater, DWORD *retCode); + bool restartRequested(); + +private: + bool separateProcess; + bool suppressConsole; + bool heapDumpPathOptFound; + bool nosplash; + bool exiting; + std::string platformDir; + std::string userDir; + std::string defaultUserDirRoot; + std::string clusters; + std::string bootclass; + std::string jdkhome; + std::string cpBefore; + std::string cpAfter; + std::string auClusters; + std::string nextAction; + std::string parentProcID; + + std::list<std::string> javaOptions; + std::list<std::string> launcherOptions; + std::list<std::string> progArgs; + JvmLauncher jvmLauncher; + std::set<std::string> addedToCP; + std::string classPath; + std::string appendHelp; +}; + +#endif /* _PLATFORMLAUNCHER_H */ + diff --git a/utilsfuncs.cpp b/utilsfuncs.cpp new file mode 100644 index 0000000..2902b1e --- /dev/null +++ b/utilsfuncs.cpp @@ -0,0 +1,449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#ifndef KEY_WOW64_64KEY +#define KEY_WOW64_64KEY 0x0100 +#endif + +#include "utilsfuncs.h" +#include "argnames.h" +#include <tlhelp32.h> +#include <windows.h> + +using namespace std; + +bool disableFolderVirtualization(HANDLE hProcess) { + OSVERSIONINFO osvi = {0}; + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if (GetVersionEx(&osvi) && osvi.dwMajorVersion == 6) // check it is Win VISTA + { + HANDLE hToken; + if (OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)) { + DWORD tokenInfoVal = 0; + if (!SetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS) 24, &tokenInfoVal, sizeof (DWORD))) { + // invalid token information class (24) is OK, it means there is no folder virtualization on current system + if (GetLastError() != ERROR_INVALID_PARAMETER) { + logErr(true, true, "Failed to set token information."); + return false; + } + } + CloseHandle(hToken); + } else { + logErr(true, true, "Failed to open process token."); + return false; + } + } + return true; +} + +bool getStringFromRegistry(HKEY rootKey, const char *keyName, const char *valueName, string &value) { + return getStringFromRegistryEx(rootKey, keyName, valueName, value, false); +} + +bool getStringFromRegistry64bit(HKEY rootKey, const char *keyName, const char *valueName, string &value) { + return getStringFromRegistryEx(rootKey, keyName, valueName, value, true); +} + + + +bool getStringFromRegistryEx(HKEY rootKey, const char *keyName, const char *valueName, string &value, bool read64bit) { + logMsg("getStringFromRegistry()\n\tkeyName: %s\n\tvalueName: %s", keyName, valueName); + HKEY hKey = 0; + if (RegOpenKeyEx(rootKey, keyName, 0, KEY_READ | (read64bit ? KEY_WOW64_64KEY : 0), &hKey) == ERROR_SUCCESS) { + DWORD valSize = 4096; + DWORD type = 0; + char val[4096] = ""; + if (RegQueryValueEx(hKey, valueName, 0, &type, (BYTE *) val, &valSize) == ERROR_SUCCESS + && type == REG_SZ) { + logMsg("%s: %s", valueName, val); + RegCloseKey(hKey); + value = val; + return true; + } else { + logErr(true, false, "RegQueryValueEx() failed."); + } + RegCloseKey(hKey); + } else { + logErr(true, false, "RegOpenKeyEx() failed."); + } + return false; +} + +bool getDwordFromRegistry(HKEY rootKey, const char *keyName, const char *valueName, DWORD &value) { + logMsg("getDwordFromRegistry()\n\tkeyName: %s\n\tvalueName: %s", keyName, valueName); + HKEY hKey = 0; + if (RegOpenKeyEx(rootKey, keyName, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + DWORD valSize = sizeof(DWORD); + DWORD type = 0; + if (RegQueryValueEx(hKey, valueName, 0, &type, (BYTE *) &value, &valSize) == ERROR_SUCCESS + && type == REG_DWORD) { + logMsg("%s: %u", valueName, value); + RegCloseKey(hKey); + return true; + } else { + logErr(true, false, "RegQueryValueEx() failed."); + } + RegCloseKey(hKey); + } else { + logErr(true, false, "RegOpenKeyEx() failed."); + } + return false; +} + +bool dirExists(const char *path) { + WIN32_FIND_DATA fd = {0}; + HANDLE hFind = 0; + hFind = FindFirstFile(path, &fd); + if (hFind == INVALID_HANDLE_VALUE) { + logMsg("Dir \"%s\" does not exist", path); + return false; + } + logMsg("Dir \"%s\" exists", path); + FindClose(hFind); + return (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool fileExists(const char *path) { + WIN32_FIND_DATA fd = {0}; + HANDLE hFind = 0; + hFind = FindFirstFile(path, &fd); + if (hFind == INVALID_HANDLE_VALUE) { + logMsg("File \"%s\" does not exist", path); + return false; + } + + logMsg("File \"%s\" exists", path); + FindClose(hFind); + return true; +} + +bool normalizePath(char *path, int len) { + char tmp[MAX_PATH] = ""; + int i = 0; + while (path[i] && i < MAX_PATH - 1) { + tmp[i] = path[i] == '/' ? '\\' : path[i]; + i++; + } + tmp[i] = '\0'; + return _fullpath(path, tmp, len) != NULL; +} + +bool createPath(const char *path) { + logMsg("Creating directory \"%s\"", path); + char dir[MAX_PATH] = ""; + const char *sep = strchr(path, '\\'); + while (sep) { + strncpy(dir, path, sep - path); + if (!CreateDirectory(dir, 0) && GetLastError() != ERROR_ALREADY_EXISTS) { + logErr(true, false, "Failed to create directory %s", dir); + return false; + } + sep = strchr(sep + 1, '\\'); + } + return true; +} + + +char * getCurrentModulePath(char *path, int pathLen) { + MEMORY_BASIC_INFORMATION mbi; + static int dummy; + VirtualQuery(&dummy, &mbi, sizeof (mbi)); + HMODULE hModule = (HMODULE) mbi.AllocationBase; + GetModuleFileName(hModule, path, pathLen); + return path; +} + +char * skipWhitespaces(char *str) { + while (*str != '\0' && (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r')) { + str++; + } + return str; +} + +char * trimWhitespaces(char *str) { + char *end = str + strlen(str) - 1; + while (end >= str && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) { + *end = '\0'; + end--; + } + return end; +} + +char* getSysError(char *str, int strSize) { + int err = GetLastError(); + LPTSTR lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & lpMsgBuf, + 0, + NULL + ); + LPTSTR tmp = strchr(lpMsgBuf, '\r'); + if (tmp != NULL) { + *tmp = '\0'; + } + + _snprintf(str, strSize, " %s (%u)", lpMsgBuf, err); + LocalFree(lpMsgBuf); + return str; +} + +string gLogFileName; + +void logV(bool appendSysError, bool showMsgBox, const char *format, va_list args) { + char msg[4096] = ""; + vsnprintf(msg, 4096, format, args); + + if (appendSysError) { + char sysErr[512] = ""; + getSysError(sysErr, 512); + strncat(msg, sysErr, 4096 - strlen(msg)); + } + + if (!gLogFileName.empty()) { + FILE *file = fopen(gLogFileName.c_str(), "a"); + if (file) { + fprintf(file, "%s\n", msg); + fclose(file); + } + } + + if (showMsgBox) { + ::MessageBox(NULL, msg, "Error", MB_OK | MB_ICONSTOP); + } +} + +void logErr(bool appendSysError, bool showMsgBox, const char *format, ...) { + va_list args; + va_start(args, format); + logV(appendSysError, showMsgBox, format, args); +} + +void logMsg(const char *format, ...) { + va_list args; + va_start(args, format); + logV(false, false, format, args); +} + +bool restarting(int argc, char *argv[]) { + for (int i = 0; i < argc; i++) { + if (strcmp(ARG_NAME_LA_START_APP, argv[i]) == 0 || strcmp(ARG_NAME_LA_START_AU, argv[i]) == 0) { + return true; + } + } + return false; +} + +bool checkLoggingArg(int argc, char *argv[], bool delFile) { + for (int i = 0; i < argc; i++) { + if (strcmp(ARG_NAME_LAUNCHER_LOG, argv[i]) == 0) { + if (i + 1 == argc) { + logErr(false, true, "Argument is missing for \"%s\" option.", argv[i]); + return false; + } + gLogFileName = argv[++i]; + // if we are restarting, keep log file + if (delFile && !restarting(argc, argv)) { + DeleteFile(gLogFileName.c_str()); + } + break; + } + } + return true; +} + +bool setupProcess(int &argc, char *argv[], DWORD &parentProcID, const char *attachMsg) { +#define CHECK_ARG \ + if (i+1 == argc) {\ + logErr(false, true, "Argument is missing for \"%s\" option.", argv[i]);\ + return false;\ + } + + parentProcID = 0; + DWORD cmdLineArgPPID = 0; + for (int i = 0; i < argc; i++) { + if (strcmp(ARG_NAME_CONSOLE, argv[i]) == 0) { + CHECK_ARG; + if (strcmp("new", argv[i + 1]) == 0){ + AllocConsole(); + } else if (strcmp("suppress", argv[i + 1]) == 0) { + // nothing, no console should be attached + } else { + logErr(false, true, "Invalid argument for \"%s\" option.", argv[i]); + return false; + } + // remove options + for (int k = i + 2; k < argc; k++) { + argv[k-2] = argv[k]; + } + argc -= 2; + return true; + } else if (strcmp(ARG_NAME_LA_PPID, argv[i]) == 0) { + CHECK_ARG; + char *end = 0; + cmdLineArgPPID = strtoul(argv[++i], &end, 10); + if (cmdLineArgPPID == 0 && *end != '\0') { + logErr(false, true, "Invalid parameter for option %s", ARG_NAME_LA_PPID); + return false; + } + logMsg("Command line arg PPID: %u", cmdLineArgPPID); + break; + } + } +#undef CHECK_ARG + + // default, attach to parent process console if exists + // AttachConsole exists since WinXP, so be nice and do it dynamically + typedef BOOL (WINAPI *LPFAC)(DWORD dwProcessId); + HINSTANCE hKernel32 = GetModuleHandle("kernel32"); + if (hKernel32) { + LPFAC attachConsole = (LPFAC) GetProcAddress(hKernel32, "AttachConsole"); + if (attachConsole) { + if (cmdLineArgPPID) { + if (!attachConsole(cmdLineArgPPID)) { + logErr(true, false, "AttachConsole of PPID: %u failed.", cmdLineArgPPID); + } + } else { + if (!attachConsole((DWORD) -1)) { + logErr(true, false, "AttachConsole of PP failed."); + } else { + getParentProcessID(parentProcID); + if (attachMsg) { + printToConsole(attachMsg); + } + } + } + } else { + logErr(true, false, "GetProcAddress() for AttachConsole failed."); + } + } + return true; +} + +bool isConsoleAttached() { + typedef HWND (WINAPI *GetConsoleWindowT)(); + HINSTANCE hKernel32 = GetModuleHandle("kernel32"); + if (hKernel32) { + GetConsoleWindowT getConsoleWindow = (GetConsoleWindowT) GetProcAddress(hKernel32, "GetConsoleWindow"); + if (getConsoleWindow) { + if (getConsoleWindow() != NULL) { + logMsg("Console is attached."); + return true; + } + } else { + logErr(true, false, "GetProcAddress() for GetConsoleWindow failed."); + } + } + return false; +} + +bool printToConsole(const char *msg) { + FILE *console = fopen("CON", "a"); + if (!console) { + return false; + } + fprintf(console, "%s", msg); + fclose(console); + return false; +} + +bool getParentProcessID(DWORD &id) { + typedef HANDLE (WINAPI * CreateToolhelp32SnapshotT)(DWORD, DWORD); + typedef BOOL (WINAPI * Process32FirstT)(HANDLE, LPPROCESSENTRY32); + typedef BOOL (WINAPI * Process32NextT)(HANDLE, LPPROCESSENTRY32); + + HINSTANCE hKernel32 = GetModuleHandle("kernel32"); + if (!hKernel32) { + return false; + } + + CreateToolhelp32SnapshotT createToolhelp32Snapshot = (CreateToolhelp32SnapshotT) GetProcAddress(hKernel32, "CreateToolhelp32Snapshot"); + Process32FirstT process32First = (Process32FirstT) GetProcAddress(hKernel32, "Process32First"); + Process32NextT process32Next = (Process32NextT) GetProcAddress(hKernel32, "Process32Next"); + + if (createToolhelp32Snapshot == NULL || process32First == NULL || process32Next == NULL) { + logErr(true, false, "Failed to obtain Toolhelp32 functions."); + return false; + } + + HANDLE hSnapshot = createToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) { + logErr(true, false, "Failed to obtain process snapshot."); + return false; + } + + PROCESSENTRY32 entry = {0}; + entry.dwSize = sizeof (PROCESSENTRY32); + if (!process32First(hSnapshot, &entry)) { + CloseHandle(hSnapshot); + return false; + } + + DWORD curID = GetCurrentProcessId(); + logMsg("Current process ID: %u", curID); + + do { + if (entry.th32ProcessID == curID) { + id = entry.th32ParentProcessID; + logMsg("Parent process ID: %u", id); + CloseHandle(hSnapshot); + return true; + } + } while (process32Next(hSnapshot, &entry)); + + CloseHandle(hSnapshot); + return false; +} + +bool isWow64() +{ + BOOL IsWow64 = FALSE; + typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); + LPFN_ISWOW64PROCESS fnIsWow64Process; + + fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); + + if (NULL != fnIsWow64Process) + { + if (!fnIsWow64Process(GetCurrentProcess(),&IsWow64)) + { + // handle error + } + } + return IsWow64; +} + +int convertAnsiToUtf8(const char *ansi, char *utf8, int utf8Len) { + const int len = 32*1024; + WCHAR tmp[len] = L""; + if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansi, -1, tmp, len) == 0) + return -1; + if (WideCharToMultiByte(CP_UTF8, 0, tmp, -1, utf8, utf8Len, NULL, NULL) == 0) + return -1; + return 0; +} + diff --git a/utilsfuncs.h b/utilsfuncs.h new file mode 100644 index 0000000..6cf172a --- /dev/null +++ b/utilsfuncs.h @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + /* + * Author: Tomas Holy + */ + +#ifndef _UTILSFUNCS_H +#define _UTILSFUNCS_H + +#include <windows.h> +#include <string> + +bool isWow64(); +bool disableFolderVirtualization(HANDLE hProcess); +bool getStringFromRegistry(HKEY rootKey, const char *keyName, const char *valueName, std::string &value); +bool getStringFromRegistryEx(HKEY rootKey, const char *keyName, const char *valueName, std::string &value,bool read64bit); +bool getStringFromRegistry64bit(HKEY rootKey, const char *keyName, const char *valueName, std::string &value); +bool getDwordFromRegistry(HKEY rootKey, const char *keyName, const char *valueName, DWORD &value); +bool dirExists(const char *path); +bool fileExists(const char *path); +bool normalizePath(char *path, int len); +bool createPath(const char *path); +char * getCurrentModulePath(char *path, int pathLen); +char * skipWhitespaces(char *str); +char * trimWhitespaces(char *str); +void logMsg(const char *format, ...); +void logErr(bool appendSysError, bool showMsgBox, const char *format, ...); +bool checkLoggingArg(int argc, char *argv[], bool delFile); +bool setupProcess(int &argc, char *argv[], DWORD &parentProcID, const char *attachMsg = 0); +bool printToConsole(const char *msg); +bool getParentProcessID(DWORD &id); +bool isConsoleAttached(); +int convertAnsiToUtf8(const char *ansi, char *utf8, int utf8Len); + +#endif /* _UTILSFUNCS_H */ + diff --git a/version.h b/version.h new file mode 100644 index 0000000..b423ffc --- /dev/null +++ b/version.h @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#define COMPANY "Oracle Corporation" +#define COMPONENT "NetBeans Platform Launcher" +#define VER "9.0.0.0" +#define FVER 9,0,0,0 +#define BUILD_ID "04012017" +#define INTERNAL_NAME "nbexec" +#define COPYRIGHT "\xA9 2007, 2017 Oracle and/or its affiliates. All rights reserved." +#define NAME "NetBeans Platform Launcher" + diff --git a/version.rc b/version.rc new file mode 100644 index 0000000..7a516a4 --- /dev/null +++ b/version.rc @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <winuser.h> +#include <winver.h> +#include "version.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FVER + PRODUCTVERSION FVER + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + // FILEOS 0x4 is Win32, 0x40004 is Win32 NT only + FILEOS 0x4L + // FILETYPE should be 0x1 for .exe and 0x2 for .dll + FILETYPE FILETYPE_ID + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "CompanyName", COMPANY "\0" + VALUE "FileDescription", COMPONENT "\0" + VALUE "FileVersion", VER "\0" + VALUE "Full Version", BUILD_ID "\0" + VALUE "InternalName", INTERNAL_NAME "\0" + VALUE "LegalCopyright", COPYRIGHT "\0" + VALUE "OriginalFilename", FNAME "\0" + VALUE "ProductName", NAME "\0" + VALUE "ProductVersion", VER "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists