8255899: Allow uninstallation of jpackage exe bundles

Reviewed-by: almatvee, herrick
This commit is contained in:
Alexey Semenyuk 2020-12-15 16:42:29 +00:00
parent 65756abf44
commit d53ee6219b
26 changed files with 2085 additions and 292 deletions

View File

@ -30,6 +30,7 @@
#include "UnixSysInfo.h"
#include "Package.h"
#include "Log.h"
#include "app.h"
#include "ErrorHandling.h"
@ -124,5 +125,5 @@ void launchApp() {
int main(int argc, char *argv[]) {
SysInfo::argc = argc;
SysInfo::argv = argv;
return AppLauncher::launch(std::nothrow, launchApp);
return app::launch(std::nothrow, launchApp);
}

View File

@ -24,6 +24,7 @@
*/
#include "AppLauncher.h"
#include "app.h"
#include "FileUtils.h"
#include "UnixSysInfo.h"
#include "JvmLauncher.h"
@ -74,10 +75,10 @@ int main(int argc, char *argv[]) {
// Besides we should ignore main() arguments because these are the
// arguments passed into JLI_Launch() call and not the arguments with
// which the launcher was started.
return AppLauncher::launch(std::nothrow, launchJvm);
return app::launch(std::nothrow, launchJvm);
}
SysInfo::argc = argc;
SysInfo::argv = argv;
return AppLauncher::launch(std::nothrow, initJvmLauncher);
return app::launch(std::nothrow, initJvmLauncher);
}

View File

@ -136,158 +136,3 @@ Jvm* AppLauncher::createJvmLauncher() const {
void AppLauncher::launch() const {
std::unique_ptr<Jvm>(createJvmLauncher())->launch();
}
namespace {
const std::string* theLastErrorMsg = 0;
NopLogAppender nopLogAppender;
class StandardLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
std::cerr << "[" << v.logLevel << "] "
<< v.fileName
<< ":" << v.lineNum
<< ": " << v.message
<< std::endl;
}
} standardLogAppender;
class LastErrorLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
std::cerr << AppLauncher::lastErrorMsg() << std::endl;
}
} lastErrorLogAppender;
} // namespace
LogAppender& AppLauncher::defaultLastErrorLogAppender() {
return lastErrorLogAppender;
}
std::string AppLauncher::lastErrorMsg() {
if (theLastErrorMsg) {
return *theLastErrorMsg;
}
return "";
}
bool AppLauncher::isWithLogging() {
// If JPACKAGE_DEBUG environment variable is set to "true"
// logging is enabled.
return SysInfo::getEnvVariable(
std::nothrow, _T("JPACKAGE_DEBUG")) == _T("true");
}
namespace {
class ResetLastErrorMsgAtEndOfScope {
public:
~ResetLastErrorMsgAtEndOfScope() {
JP_NO_THROW(theLastErrorMsg = 0);
}
};
class SetLoggerAtEndOfScope {
public:
SetLoggerAtEndOfScope(
std::unique_ptr<WithExtraLogAppender>& withLogAppender,
LogAppender* lastErrorLogAppender):
withLogAppender(withLogAppender),
lastErrorLogAppender(lastErrorLogAppender) {
}
~SetLoggerAtEndOfScope() {
JP_TRY;
std::unique_ptr<WithExtraLogAppender> other(
new WithExtraLogAppender(*lastErrorLogAppender));
withLogAppender.swap(other);
JP_CATCH_ALL;
}
private:
std::unique_ptr<WithExtraLogAppender>& withLogAppender;
LogAppender* lastErrorLogAppender;
};
} // namespace
int AppLauncher::launch(const std::nothrow_t&,
LauncherFunc func, LogAppender* lastErrorLogAppender) {
if (isWithLogging()) {
Logger::defaultLogger().setAppender(standardLogAppender);
} else {
Logger::defaultLogger().setAppender(nopLogAppender);
}
LOG_TRACE_FUNCTION();
if (!lastErrorLogAppender) {
lastErrorLogAppender = &defaultLastErrorLogAppender();
}
std::unique_ptr<WithExtraLogAppender> withLogAppender;
std::string errorMsg;
const ResetLastErrorMsgAtEndOfScope resetLastErrorMsg;
JP_TRY;
// This will temporary change log appenders of the default logger
// to save log messages in the default and additional log appenders.
// Log appenders config of the default logger will be restored to
// the original state at function exit automatically.
const SetLoggerAtEndOfScope setLogger(withLogAppender, lastErrorLogAppender);
func();
return 0;
// The point of all these redefines is to save the last raw error message in
// 'AppLauncher::theLastErrorMsg' variable.
// By default error messages are saved in exception instances with the details
// of error origin (source file, function name, line number).
// We don't want these details in user error messages. However we still want to
// save full information about the last error in the default log appender.
#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e) \
do { \
errorMsg = (tstrings::any() << e.what()).str(); \
theLastErrorMsg = &errorMsg; \
reportError(JP_SOURCE_CODE_POS, e); \
} while(0)
#define JP_HANDLE_UNKNOWN_ERROR \
do { \
errorMsg = "Unknown error"; \
theLastErrorMsg = &errorMsg; \
reportUnknownError(JP_SOURCE_CODE_POS); \
} while(0)
#define JP_CATCH_EXCEPTIONS \
catch (const JpErrorBase& e) { \
errorMsg = (tstrings::any() << e.rawMessage()).str(); \
theLastErrorMsg = &errorMsg; \
try { \
throw; \
} catch (const std::runtime_error& e) { \
reportError(JP_SOURCE_CODE_POS, e); \
} \
} catch (const std::runtime_error& e) { \
errorMsg = lastCRTError(); \
theLastErrorMsg = &errorMsg; \
reportError(JP_SOURCE_CODE_POS, e); \
} \
JP_CATCH_UNKNOWN_EXCEPTION
JP_CATCH_ALL;
#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e) JP_REPORT_ERROR(e)
#define JP_HANDLE_UNKNOWN_ERROR JP_REPORT_UNKNOWN_ERROR
#define JP_CATCH_EXCEPTIONS JP_DEFAULT_CATCH_EXCEPTIONS
return 1;
}

View File

@ -30,7 +30,6 @@
#include "tstrings.h"
class Jvm;
class LogAppender;
class AppLauncher {
public:
@ -65,17 +64,6 @@ public:
void launch() const;
static LogAppender& defaultLastErrorLogAppender();
static bool isWithLogging();
typedef void (*LauncherFunc) ();
static int launch(const std::nothrow_t&, LauncherFunc func,
LogAppender* lastErrorLogAppender = 0);
static std::string lastErrorMsg();
private:
tstring_array args;
tstring launcherPath;

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <memory>
#include "app.h"
#include "Log.h"
#include "SysInfo.h"
#include "ErrorHandling.h"
namespace {
const std::string* theLastErrorMsg = 0;
NopLogAppender nopLogAppender;
class StandardLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
std::cerr << "[" << v.logLevel << "] "
<< v.fileName
<< ":" << v.lineNum
<< ": " << v.message
<< std::endl;
}
} standardLogAppender;
class LastErrorLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
std::cerr << app::lastErrorMsg() << std::endl;
}
} lastErrorLogAppender;
class ResetLastErrorMsgAtEndOfScope {
public:
~ResetLastErrorMsgAtEndOfScope() {
JP_NO_THROW(theLastErrorMsg = 0);
}
};
class SetLoggerAtEndOfScope {
public:
SetLoggerAtEndOfScope(
std::unique_ptr<WithExtraLogAppender>& withLogAppender,
LogAppender* lastErrorLogAppender):
withLogAppender(withLogAppender),
lastErrorLogAppender(lastErrorLogAppender) {
}
~SetLoggerAtEndOfScope() {
JP_TRY;
std::unique_ptr<WithExtraLogAppender> other(
new WithExtraLogAppender(*lastErrorLogAppender));
withLogAppender.swap(other);
JP_CATCH_ALL;
}
private:
std::unique_ptr<WithExtraLogAppender>& withLogAppender;
LogAppender* lastErrorLogAppender;
};
} // namespace
namespace app {
LogAppender& defaultLastErrorLogAppender() {
return lastErrorLogAppender;
}
std::string lastErrorMsg() {
if (theLastErrorMsg) {
return *theLastErrorMsg;
}
return "";
}
bool isWithLogging() {
// If JPACKAGE_DEBUG environment variable is set to "true"
// logging is enabled.
return SysInfo::getEnvVariable(
std::nothrow, _T("JPACKAGE_DEBUG")) == _T("true");
}
int launch(const std::nothrow_t&,
LauncherFunc func, LogAppender* lastErrorLogAppender) {
if (isWithLogging()) {
Logger::defaultLogger().setAppender(standardLogAppender);
} else {
Logger::defaultLogger().setAppender(nopLogAppender);
}
LOG_TRACE_FUNCTION();
if (!lastErrorLogAppender) {
lastErrorLogAppender = &defaultLastErrorLogAppender();
}
std::unique_ptr<WithExtraLogAppender> withLogAppender;
std::string errorMsg;
const ResetLastErrorMsgAtEndOfScope resetLastErrorMsg;
JP_TRY;
// This will temporary change log appenders of the default logger
// to save log messages in the default and additional log appenders.
// Log appenders config of the default logger will be restored to
// the original state at function exit automatically.
const SetLoggerAtEndOfScope setLogger(withLogAppender, lastErrorLogAppender);
func();
return 0;
// The point of all these redefines is to save the last raw error message in
// 'theLastErrorMsg' variable.
// By default error messages are saved in exception instances with the details
// of error origin (source file, function name, line number).
// We don't want these details in user error messages. However we still want to
// save full information about the last error in the default log appender.
#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e) \
do { \
errorMsg = (tstrings::any() << e.what()).str(); \
theLastErrorMsg = &errorMsg; \
reportError(JP_SOURCE_CODE_POS, e); \
} while(0)
#define JP_HANDLE_UNKNOWN_ERROR \
do { \
errorMsg = "Unknown error"; \
theLastErrorMsg = &errorMsg; \
reportUnknownError(JP_SOURCE_CODE_POS); \
} while(0)
#define JP_CATCH_EXCEPTIONS \
catch (const JpErrorBase& e) { \
errorMsg = (tstrings::any() << e.rawMessage()).str(); \
theLastErrorMsg = &errorMsg; \
try { \
throw; \
} catch (const std::runtime_error& e) { \
reportError(JP_SOURCE_CODE_POS, e); \
} \
} catch (const std::runtime_error& e) { \
errorMsg = lastCRTError(); \
theLastErrorMsg = &errorMsg; \
reportError(JP_SOURCE_CODE_POS, e); \
} \
JP_CATCH_UNKNOWN_EXCEPTION
JP_CATCH_ALL;
#undef JP_HANDLE_ERROR
#undef JP_HANDLE_UNKNOWN_ERROR
#undef JP_CATCH_EXCEPTIONS
#define JP_HANDLE_ERROR(e) JP_REPORT_ERROR(e)
#define JP_HANDLE_UNKNOWN_ERROR JP_REPORT_UNKNOWN_ERROR
#define JP_CATCH_EXCEPTIONS JP_DEFAULT_CATCH_EXCEPTIONS
return 1;
}
} // namespace app

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef app_h
#define app_h
#include "tstrings.h"
class LogAppender;
namespace app {
LogAppender& defaultLastErrorLogAppender();
bool isWithLogging();
typedef void (*LauncherFunc) ();
int launch(const std::nothrow_t&, LauncherFunc func,
LogAppender* lastErrorLogAppender = 0);
std::string lastErrorMsg();
} // namespace app
#endif // app_h

View File

@ -287,5 +287,36 @@ std::wstring toUtf16(const std::string& utf8str) {
return fromMultiByte(utf8str, CP_UTF8);
}
// converts utf16-encoded string to Windows encoded string (WIDECHAR or ACP)
tstring toWinString(const std::wstring& utf16) {
#if defined(_UNICODE) || defined(UNICODE)
return utf16;
#else
return toMultiByte(utf16, CP_ACP);
#endif
}
// converts utf8-encoded string to Windows encoded string (WIDECHAR or ACP)
tstring toWinString(const std::string& utf8) {
return toWinString(tstrings::toUtf16(utf8));
}
std::string winStringToUtf8(const std::wstring& winStr) {
return toUtf8(winStr);
}
std::string winStringToUtf8(const std::string& winStr) {
return toUtf8(fromMultiByte(winStr, CP_ACP));
}
std::wstring winStringToUtf16(const std::wstring& winStr) {
return winStr;
}
std::wstring winStringToUtf16(const std::string& winStr) {
return fromMultiByte(winStr, CP_ACP);
}
} // namespace tstrings
#endif // ifdef TSTRINGS_WITH_WCHAR

View File

@ -143,6 +143,16 @@ namespace tstrings {
// conversion to the active code page
std::string toACP(const std::wstring& utf16str);
// conversion from windows-encoding string (WIDECHAR or ACP) to utf8/utf16
std::string winStringToUtf8(const std::wstring& winStr);
std::string winStringToUtf8(const std::string& winStr);
std::wstring winStringToUtf16(const std::wstring& winStr);
std::wstring winStringToUtf16(const std::string& winStr);
// conversion from utf8/utf16 to windows-encoding string (WIDECHAR or ACP)
tstring toWinString(const std::wstring& utf16);
tstring toWinString(const std::string& utf8);
// conversion to Utf8
std::string toUtf8(const std::wstring& utf16str);
@ -153,6 +163,10 @@ namespace tstrings {
return toUtf16(utf8str);
}
inline tstring fromUtf16(const std::wstring& utf16str) {
return utf16str;
}
#else
inline std::string fromUtf8(const std::string& utf8str) {
return utf8str;

View File

@ -31,6 +31,7 @@
#include "JvmLauncher.h"
#include "Log.h"
#include "Dll.h"
#include "WinApp.h"
#include "Toolbox.h"
#include "FileUtils.h"
#include "UniqueHandle.h"
@ -170,113 +171,13 @@ void launchApp() {
#ifndef JP_LAUNCHERW
int __cdecl wmain() {
return AppLauncher::launch(std::nothrow, launchApp);
return app::launch(std::nothrow, launchApp);
}
#else
namespace {
class LastErrorGuiLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
JP_TRY;
const std::wstring msg = (tstrings::any()
<< AppLauncher::lastErrorMsg()).wstr();
MessageBox(0, msg.c_str(),
FileUtils::basename(SysInfo::getProcessModulePath()).c_str(),
MB_ICONERROR | MB_OK);
JP_CATCH_ALL;
}
};
class Console {
public:
Console() {
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
// Failed to connect to parent's console. Create our own.
if (!AllocConsole()) {
// We already have a console, no need to redirect std I/O.
return;
}
}
stdoutChannel = std::unique_ptr<Channel>(new Channel(stdout));
stderrChannel = std::unique_ptr<Channel>(new Channel(stderr));
}
struct FileCloser {
typedef FILE* pointer;
void operator()(pointer h) {
::fclose(h);
}
};
typedef std::unique_ptr<
FileCloser::pointer,
FileCloser
> UniqueFILEHandle;
private:
class Channel {
public:
Channel(FILE* stdFILEHandle): stdFILEHandle(stdFILEHandle) {
const char* stdFileName = "CONOUT$";
const char* openMode = "w";
if (stdFILEHandle == stdin) {
stdFileName = "CONIN$";
openMode = "r";
}
FILE* fp = 0;
freopen_s(&fp, stdFileName, openMode, stdFILEHandle);
fileHandle = UniqueFILEHandle(fp);
std::ios_base::sync_with_stdio();
}
virtual ~Channel() {
JP_TRY;
FILE* fp = 0;
fileHandle = UniqueFILEHandle(fp);
std::ios_base::sync_with_stdio();
JP_CATCH_ALL;
}
private:
UniqueFILEHandle fileHandle;
FILE *stdFILEHandle;
};
std::unique_ptr<Channel> stdoutChannel;
std::unique_ptr<Channel> stderrChannel;
};
void launchAppW() {
std::unique_ptr<Console> console;
if (AppLauncher::isWithLogging()) {
console = std::unique_ptr<Console>(new Console());
}
launchApp();
}
} // namespace
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
LastErrorGuiLogAppender lastErrorLogAppender;
TeeLogAppender logAppender(&AppLauncher::defaultLastErrorLogAppender(),
&lastErrorLogAppender);
return AppLauncher::launch(std::nothrow, launchAppW, &logAppender);
return app::wlaunch(std::nothrow, launchApp);
}
#endif

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef Flag_h
#define Flag_h
template <class T, class T2=int, int Id=0>
class Flag {
public:
explicit Flag(T2 v): val(v) {}
bool operator == (const Flag& other) const {
return val == other.val;
}
bool operator != (const Flag& other) const {
return ! *this == other;
}
T2 value() const {
return val;
}
private:
T2 val;
};
#endif // #ifndef Flag_h

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <algorithm>
#include <Objbase.h>
#include "Guid.h"
#include "ErrorHandling.h"
#pragma comment(lib, "ole32")
Guid::Guid(const std::string& str) {
*this = Guid(std::wstring(str.begin(), str.end()));
}
namespace {
void initGuid(const std::wstring& str, GUID& v) {
if (S_OK != IIDFromString(str.c_str(), &v)) {
JP_THROW(tstrings::any() << "IIDFromString(" << str << ") failed");
}
}
} //namespace
Guid::Guid(const std::wstring& str) {
enum { StdGuildLength = 38 };
if ((str.size() == StdGuildLength && str.front() == L'{' && str.back() == L'}')) {
initGuid(str, value);
return;
}
enum { BracketCount = 2 };
enum { DashCount = 4 };
std::wstring buf(str);
if (str.size() >= (StdGuildLength - (BracketCount + DashCount))) {
if (str.front() != L'{' && str.back() != L'}') {
buf = L"{" + str + L"}";
}
if (str.find(L'-') == std::wstring::npos) {
const size_t positions[] = { 9, 14, 19, 24 };
for (int i = 0; i < DashCount; ++i) {
buf.insert(positions[i], 1, L'-');
}
}
if (buf.size() != StdGuildLength) {
// Still no good, drop all tweaks.
// Let parsing function fail on the original string.
buf = str;
}
}
initGuid(buf, value);
}
Guid::Guid(const GUID& v): value(v) {
}
Guid::Guid() {
memset(&value, 0, sizeof(value));
}
bool Guid::operator < (const Guid& other) const {
return toString() < other.toString();
}
bool Guid::operator == (const Guid& other) const {
return IsEqualGUID(value, other.value) != FALSE;
}
tstring Guid::toString(int flags) const {
wchar_t buffer[128];
const int chars = StringFromGUID2(value, buffer, _countof(buffer));
if (chars < 3 /* strlen("{}") + 1 */) {
JP_THROW("StringFromGUID2() failed");
}
tstring reply(tstrings::fromUtf16(buffer));
if (flags & NoCurlyBrackets) {
reply = reply.substr(1, reply.size() - 2);
}
if (flags & NoDashes) {
// Drop all '-'.
reply = tstring(reply.begin(), std::remove(reply.begin(), reply.end(), _T('-')));
}
if (flags & LowerCase) {
reply = tstrings::toLower(reply);
}
return reply;
}
Guid Guid::generate() {
GUID guid = { 0 };
if (S_OK != CoCreateGuid(&guid)) {
JP_THROW("CoCreateGuid() failed");
}
return Guid(guid);
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef Guid_h
#define Guid_h
#include <windows.h>
#include "tstrings.h"
class Guid {
public:
Guid(const std::string& str);
Guid(const std::wstring& str);
Guid(const GUID& v);
Guid();
// Comparison for equality is the only comparison operation that make
// sense for GUIDs. However in order to use STL algorithms with
// Guid class need to define less operator.
bool operator < (const Guid& other) const;
bool operator == (const Guid& other) const;
bool operator != (const Guid& other) const {
return ! (*this == other);
}
enum StringifyFlags {
WithCurlyBrackets = 0x0,
WithDashes = 0x0,
UpperCase = 0x0,
StringifyDefaults = WithCurlyBrackets | UpperCase | WithDashes,
NoCurlyBrackets = 0x1,
NoDashes = 0x2,
LowerCase = 0x4,
};
tstring toString(int flags=StringifyDefaults) const;
/**
* Returns string GUID representation of this instance compatible with
* Windows MSI API.
*/
tstring toMsiString() const {
return toString(UpperCase | WithCurlyBrackets | WithDashes);
}
static Guid generate();
private:
GUID value;
};
#endif // #ifndef Guid_h

View File

@ -0,0 +1,282 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "MsiDb.h"
#include "FileUtils.h"
#include "WinFileUtils.h"
#include "Log.h"
#pragma comment(lib, "msi.lib")
namespace msi {
void closeDatabaseView(MSIHANDLE hView) {
if (hView) {
const auto status = MsiViewClose(hView);
if (status != ERROR_SUCCESS) {
LOG_WARNING(tstrings::any() << "MsiViewClose("
<< hView << ") failed with error=" << status);
return;
}
closeMSIHANDLE(hView);
}
}
namespace {
UniqueMSIHANDLE openDatabase(const tstring& msiPath) {
MSIHANDLE h = 0;
const UINT status = MsiOpenDatabase(msiPath.c_str(),
MSIDBOPEN_READONLY, &h);
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any()
<< "MsiOpenDatabase(" << msiPath
<< ", MSIDBOPEN_READONLY) failed", status));
}
return UniqueMSIHANDLE(h);
}
} // namespace
Database::Database(const Guid& productCode):
msiPath(getProductInfo(productCode, INSTALLPROPERTY_LOCALPACKAGE)),
dbHandle(openDatabase(msiPath)) {
}
Database::Database(const tstring& msiPath): msiPath(msiPath),
dbHandle(openDatabase(msiPath)) {
}
tstring Database::getProperty(const tstring& name) const {
// Query value of a property with the given name from 'Property' MSI table.
const tstring sqlQuery = (tstrings::any()
<< "SELECT Value FROM Property WHERE Property = '"
<< name << "'").tstr();
DatabaseView view(*this, sqlQuery);
const DatabaseRecord record(view);
// Data is stored in a record object. SQL query is constructed in a way
// this record object contains a single field.
// Verify record contains exactly one field.
if (record.getFieldCount() != 1) {
JP_THROW(Error(
tstrings::any() << "record.getFieldCount(" << msiPath
<< ", " << sqlQuery
<< ") returned unexpected value",
ERROR_SUCCESS));
}
// Field identifier. They start with 1, not from 0.
const unsigned field = 1;
return record.getString(field);
}
tstring Database::getProperty(const std::nothrow_t&, const tstring& name) const {
try {
return getProperty(name);
} catch (const NoMoreItemsError&) {
}
JP_CATCH_EXCEPTIONS;
return tstring();
}
DatabaseRecord::DatabaseRecord(unsigned fieldCount) {
MSIHANDLE h = MsiCreateRecord(fieldCount);
if (!h) {
JP_THROW(msi::Error(tstrings::any() << "MsiCreateRecord("
<< fieldCount << ") failed", ERROR_FUNCTION_FAILED));
}
handle = UniqueMSIHANDLE(h);
}
DatabaseRecord& DatabaseRecord::operator=(const DatabaseRecord& other) {
DatabaseRecord tmp(other);
std::swap(handle, tmp.handle);
return *this;
}
DatabaseRecord& DatabaseRecord::fetch(DatabaseView& view) {
*this = view.fetch();
return *this;
}
DatabaseRecord& DatabaseRecord::tryFetch(DatabaseView& view) {
*this = view.tryFetch();
return *this;
}
DatabaseRecord& DatabaseRecord::setString(unsigned idx, const tstring& v) {
const UINT status = MsiRecordSetString(handle.get(), idx, v.c_str());
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any() << "MsiRecordSetString(" << idx
<< ", " << v << ") failed", status));
}
return *this;
}
DatabaseRecord& DatabaseRecord::setInteger(unsigned idx, int v) {
const UINT status = MsiRecordSetInteger(handle.get(), idx, v);
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any() << "MsiRecordSetInteger(" << idx
<< ", " << v << ") failed", status));
}
return *this;
}
DatabaseRecord& DatabaseRecord::setStreamFromFile(unsigned idx,
const tstring& v) {
const UINT status = MsiRecordSetStream(handle.get(), idx, v.c_str());
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any() << "MsiRecordSetStream(" << idx
<< ", " << v << ") failed", status));
}
return *this;
}
unsigned DatabaseRecord::getFieldCount() const {
const unsigned reply = MsiRecordGetFieldCount(handle.get());
if (int(reply) <= 0) {
JP_THROW(Error(std::string("MsiRecordGetFieldCount() failed"),
ERROR_FUNCTION_FAILED));
}
return reply;
}
int DatabaseRecord::getInteger(unsigned idx) const {
int const reply = MsiRecordGetInteger(handle.get(), idx);
if (reply == MSI_NULL_INTEGER) {
JP_THROW(Error(tstrings::any() << "MsiRecordGetInteger(" << idx
<< ") failed", ERROR_FUNCTION_FAILED));
}
return reply;
}
void DatabaseRecord::saveStreamToFile(unsigned idx,
const tstring& path) const {
enum { ReadStreamBufferBytes = 1024 * 1024 };
FileUtils::FileWriter writer(path);
std::vector<char> buffer(ReadStreamBufferBytes);
DWORD bytes;
do {
bytes = ReadStreamBufferBytes;
const UINT status = MsiRecordReadStream(handle.get(), UINT(idx),
buffer.data(), &bytes);
if (status != ERROR_SUCCESS) {
JP_THROW(Error(std::string("MsiRecordReadStream() failed"),
status));
}
writer.write(buffer.data(), bytes);
} while(bytes == ReadStreamBufferBytes);
writer.finalize();
}
DatabaseView::DatabaseView(const Database& db, const tstring& sqlQuery,
const DatabaseRecord& queryParam): db(db), sqlQuery(sqlQuery) {
MSIHANDLE h = 0;
// Create SQL query.
for (const UINT status = MsiDatabaseOpenView(db.dbHandle.get(),
sqlQuery.c_str(), &h); status != ERROR_SUCCESS; ) {
JP_THROW(Error(tstrings::any() << "MsiDatabaseOpenView("
<< sqlQuery << ") failed", status));
}
UniqueMSIHANDLE tmp(h);
// Run SQL query.
for (const UINT status = MsiViewExecute(h, queryParam.handle.get());
status != ERROR_SUCCESS; ) {
JP_THROW(Error(tstrings::any() << "MsiViewExecute("
<< sqlQuery << ") failed", status));
}
// MsiViewClose should be called only after
// successful MsiViewExecute() call.
handle = UniqueDbView(h);
tmp.release();
}
DatabaseRecord DatabaseView::fetch() {
DatabaseRecord reply = tryFetch();
if (reply.empty()) {
JP_THROW(NoMoreItemsError(tstrings::any() << "No more items in ["
<< sqlQuery << "] query"));
}
return reply;
}
DatabaseRecord DatabaseView::tryFetch() {
MSIHANDLE h = 0;
// Fetch data from executed SQL query.
// Data is stored in a record object.
for (const UINT status = MsiViewFetch(handle.get(), &h);
status != ERROR_SUCCESS; ) {
if (status == ERROR_NO_MORE_ITEMS) {
return DatabaseRecord();
}
JP_THROW(Error(tstrings::any() << "MsiViewFetch(" << sqlQuery
<< ") failed", status));
}
DatabaseRecord reply;
reply.handle = UniqueMSIHANDLE(h);
return reply;
}
DatabaseView& DatabaseView::modify(const DatabaseRecord& record,
MSIMODIFY mode) {
const UINT status = MsiViewModify(handle.get(), mode, record.handle.get());
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any() << "MsiViewModify(mode=" << mode
<< ") failed", status));
}
return *this;
}
} // namespace msi

View File

@ -0,0 +1,194 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef MsiDb_h
#define MsiDb_h
#include <windows.h>
#include <msiquery.h>
#include "MsiUtils.h"
class Guid;
/**
* Helpers to interact with MSI through database interface.
*/
namespace msi {
void closeDatabaseView(MSIHANDLE h);
struct MsiDbViewDeleter {
typedef MSIHANDLE pointer;
void operator()(MSIHANDLE h) {
closeDatabaseView(h);
}
};
} // namespace msi
typedef std::unique_ptr<MSIHANDLE, msi::MsiDbViewDeleter> UniqueDbView;
namespace msi {
class CA;
class DatabaseView;
class DatabaseRecord;
/**
* Opens product's database to query properties.
* The database is opened in R/O mode, i.e. it is safe to call this method
* even if there is active install/uninstall session. Unlike MsiOpenProduct(),
* it never fails with 1618 ('Another installation is
* already in progress') error code.
*
* Database can be opened from product code GUID, path to msi package or from
* custom action.
*
* If opened from CA the database is opened in R/W mode, however only adding
* new temporary records is supported. It is forbidden to change data in
* existing records.
*/
class Database {
public:
/**
* Opens msi database from the given product code GUID.
* Throws exception if fails.
*/
explicit Database(const Guid& productCode);
/**
* Opens msi database from the given path to .msi file.
* Throws exception if fails.
*/
explicit Database(const tstring& msiPath);
/**
* Opens msi database from the given custom action.
* Throws exception if fails.
*/
explicit Database(const CA& ca);
/**
* Returns value of property with the given name.
* Throws NoMoreItemsError if property with the given name doesn't exist
* or Error if some error occurred.
*/
tstring getProperty(const tstring& name) const;
/**
* Returns value of property with the given name.
* Returns empty string if property with the given name doesn't exist or
* if some error occurred.
*/
tstring getProperty(const std::nothrow_t&, const tstring& name) const;
friend class DatabaseView;
private:
Database(const Database&);
Database& operator=(const Database&);
private:
const tstring msiPath;
UniqueMSIHANDLE dbHandle;
};
typedef std::unique_ptr<Database> DatabasePtr;
class DatabaseRecord {
public:
DatabaseRecord(): handle(MSIHANDLE(0)) {
}
DatabaseRecord(const DatabaseRecord& other): handle(MSIHANDLE(0)) {
handle.swap(other.handle);
}
DatabaseRecord& operator=(const DatabaseRecord& other);
friend class DatabaseView;
explicit DatabaseRecord(unsigned fieldCount);
explicit DatabaseRecord(DatabaseView& view) {
fetch(view);
}
DatabaseRecord& fetch(DatabaseView& view);
DatabaseRecord& tryFetch(DatabaseView& view);
DatabaseRecord& setString(unsigned idx, const tstring& v);
DatabaseRecord& setInteger(unsigned idx, int v);
DatabaseRecord& setStreamFromFile(unsigned idx, const tstring& v);
unsigned getFieldCount() const;
tstring getString(unsigned idx) const;
int getInteger(unsigned idx) const;
void saveStreamToFile(unsigned idx, const tstring& path) const;
bool empty() const {
return 0 == handle.get();
}
MSIHANDLE getHandle() const {
return handle.get();
}
private:
mutable UniqueMSIHANDLE handle;
};
class DatabaseView {
public:
DatabaseView(const Database& db, const tstring& sqlQuery,
const DatabaseRecord& queryParam=DatabaseRecord());
DatabaseRecord fetch();
DatabaseRecord tryFetch();
DatabaseView& modify(const DatabaseRecord& record, MSIMODIFY mode);
private:
tstring sqlQuery;
const Database& db;
UniqueDbView handle;
};
} // namespace msi
#endif // #ifndef MsiDb_h

View File

@ -0,0 +1,420 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "MsiUtils.h"
#include "MsiDb.h"
#include "Resources.h"
#include "Dll.h"
#include "UniqueHandle.h"
#include "FileUtils.h"
#include "WinErrorHandling.h"
#pragma comment(lib, "msi.lib")
namespace msi {
namespace {
template <class Func, class Arg1Type, class Arg2Type>
tstring getProperty(Func func, const LPCSTR funcName, Arg1Type arg1,
Arg2Type arg2) {
std::vector<TCHAR> buf(20);
DWORD size = static_cast<DWORD>(buf.size());
UINT status = ERROR_MORE_DATA;
while (ERROR_MORE_DATA ==
(status = func(arg1, arg2, &*buf.begin(), &size))) {
buf.resize(buf.size() * 2);
size = static_cast<DWORD>(buf.size());
}
if (status != ERROR_SUCCESS) {
JP_THROW(Error(tstrings::any() << funcName << "(" << arg1
<< ", " << arg2 << ") failed", status));
}
return tstring(buf.begin(), buf.begin() + size);
}
template <class Func, class Arg1Type, class Arg2Type>
tstring getProperty(const std::nothrow_t&, Func func, const LPCSTR funcName,
Arg1Type arg1, Arg2Type arg2) {
try {
return getProperty(func, funcName, arg1, arg2);
} catch (const std::exception&) {
}
return tstring();
}
tstring escapePropertyValue(const tstring& value) {
// Escape quotes as described in
// http://msdn.microsoft.com/en-us/library/aa367988.aspx
tstring reply = tstrings::replace(value, _T("\""), _T("\"\""));
if (reply.empty()) {
// MSDN: To clear a public property by using the command line,
// set its value to an empty string.
reply = _T("\"\"");
}
if (reply.find_first_of(_T(" \t")) != tstring::npos) {
reply = _T('"') + reply + _T('"');
}
return reply;
}
template <class It>
tstring stringifyProperties(It b, It e) {
tostringstream buf;
for (; b != e; ++b) {
const tstring value = escapePropertyValue(b->second);
buf << _T(" ") << b->first << _T("=") << value;
}
tstring reply = tstrings::trim(buf.str());
return reply;
}
class CallbackTrigger {
CallbackTrigger(const CallbackTrigger&);
CallbackTrigger& operator=(const CallbackTrigger&);
enum { MESSAGE_FILTER = 0xffffffff };
static int WINAPI adapter(LPVOID ctx, UINT type, LPCWSTR msg) {
Callback* callback = reinterpret_cast<Callback*>(ctx);
if (!callback) {
return 0;
}
JP_TRY;
// MSDN: Handling Progress Messages Using MsiSetExternalUI
// http://msdn.microsoft.com/en-us/library/aa368786(v=vs.85).aspx
const INSTALLMESSAGE mt = (INSTALLMESSAGE)(0xFF000000 & type);
const UINT flags = 0x00FFFFFF & type;
if (msg) {
callback->notify(mt, flags, tstrings::toWinString(msg));
}
JP_CATCH_ALL;
return 0;
}
public:
explicit CallbackTrigger(Callback& cb) {
MsiSetExternalUIW(adapter, DWORD(MESSAGE_FILTER), &cb);
}
~CallbackTrigger() {
// Not restoring the original callback.
// Just because the original message filter is unknown.
MsiSetExternalUIW(0, 0, 0);
}
};
class LogFileTrigger {
LogFileTrigger(const LogFileTrigger&);
LogFileTrigger& operator=(const LogFileTrigger&);
public:
explicit LogFileTrigger(const tstring& path) {
if (path.empty()) {
MsiEnableLog(0, NULL, 0);
} else {
MsiEnableLog(INSTALLLOGMODE_VERBOSE, path.c_str(), 0);
}
}
~LogFileTrigger() {
// Disable log
MsiEnableLog(0, NULL, 0);
}
};
struct SuppressUI: public OverrideUI {
SuppressUI(): OverrideUI(withoutUI()) {
}
};
class StateImpl: public ActionData::State {
const OverrideUI overrideUi;
LogFileTrigger logGuard;
std::unique_ptr<CallbackTrigger> callbackGuard;
public:
explicit StateImpl(const ActionData& data): overrideUi(data.uiMode),
logGuard(data.logFile) {
if (data.callback) {
callbackGuard = std::unique_ptr<CallbackTrigger>(
new CallbackTrigger(*data.callback));
}
}
};
} // namespace
void closeMSIHANDLE(MSIHANDLE h) {
if (h) {
const auto status = MsiCloseHandle(h);
if (status != ERROR_SUCCESS) {
LOG_WARNING(tstrings::any() << "MsiCloseHandle("
<< h << ") failed with error=" << status);
}
}
}
// DatabaseRecord::getString() should live in MsiDb.cpp.
// However it can't access handy msi::getProperty() from that location.
tstring DatabaseRecord::getString(unsigned idx) const {
return ::msi::getProperty(MsiRecordGetString, "MsiRecordGetString",
handle.get(), UINT(idx));
}
tstring getProductInfo(const Guid& productCode, const tstring& prop) {
const tstring id = productCode.toMsiString();
return getProperty(MsiGetProductInfo, "MsiGetProductInfo", id.c_str(),
prop.c_str());
}
tstring getProductInfo(const std::nothrow_t&, const Guid& productCode,
const tstring& prop) {
const tstring id = productCode.toMsiString();
return getProperty(std::nothrow, MsiGetProductInfo, "MsiGetProductInfo",
id.c_str(), prop.c_str());
}
tstring getPropertyFromCustomAction(MSIHANDLE h, const tstring& prop) {
return getProperty(MsiGetProperty, "MsiGetProperty", h, prop.c_str());
}
tstring getPropertyFromCustomAction(const std::nothrow_t&, MSIHANDLE h,
const tstring& prop) {
return getProperty(std::nothrow, MsiGetProperty,"MsiGetProperty", h,
prop.c_str());
}
namespace {
std::string makeMessage(const std::string& msg, UINT errorCode) {
std::ostringstream err;
err << "MSI error [" << errorCode << "]";
const std::wstring msimsg_dll = tstrings::winStringToUtf16(FileUtils::combinePath(
SysInfo::getSystem32Dir(), _T("msimsg.dll")));
// Convert MSI Error Code to Description String
// http://msdn.microsoft.com/en-us/library/aa370315(v=vs.85).aspx
Dll::Handle lib(LoadLibraryExW(msimsg_dll.c_str(), NULL,
LOAD_LIBRARY_AS_DATAFILE));
if (!lib.get()) {
JP_THROW(SysError(tstrings::any() << "LoadLibraryExW(" <<
msimsg_dll << ") failed", LoadLibraryExW));
} else {
tstring descr;
try {
descr = StringResource(errorCode, lib.get()).string();
} catch (const std::exception &) {
descr = _T("No description");
}
err << "(" << descr << ")";
}
return joinErrorMessages(msg, err.str());
}
} // namespace
Error::Error(const tstrings::any& msg, UINT ec): std::runtime_error(
makeMessage(msg.str(), ec)), errorCode(ec) {
}
Error::Error(const std::string& msg, UINT ec): std::runtime_error(
makeMessage(msg, ec)), errorCode(ec) {
}
tstring ActionData::getCmdLineArgs() const {
tstring raw = tstrings::trim(rawCmdLineArgs);
tstring strProperties = stringifyProperties(props.begin(), props.end());
if (!raw.empty() && !strProperties.empty()) {
raw += _T(' ');
}
return raw + strProperties;
}
std::unique_ptr<ActionData::State> ActionData::createState() const {
return std::unique_ptr<ActionData::State>(new StateImpl(*this));
}
namespace {
bool isMsiStatusSuccess(const UINT status) {
switch (status) {
case ERROR_SUCCESS:
case ERROR_SUCCESS_REBOOT_INITIATED:
case ERROR_SUCCESS_REBOOT_REQUIRED:
return true;
default:
break;
}
return false;
}
ActionStatus handleMsiStatus(tstrings::any& logMsg, const UINT status) {
if (!isMsiStatusSuccess(status)) {
logMsg << "failed [" << status << "]";
return ActionStatus(status, logMsg.str());
}
logMsg << "succeeded";
if (status != ERROR_SUCCESS) {
logMsg << " [" << status << "]";
}
LOG_INFO(logMsg);
return ActionStatus(status);
}
} // namespace
ActionStatus::operator bool() const {
return isMsiStatusSuccess(value);
}
void ActionStatus::throwIt() const {
JP_THROW(Error(comment, value));
}
namespace {
template <class T>
ActionStatus msiAction(const T& obj, INSTALLSTATE state,
const tstring& cmdLineArgs) {
const tstring id = obj.getProductCode().toMsiString();
const int level = INSTALLLEVEL_MAXIMUM;
const UINT status = MsiConfigureProductEx(id.c_str(), level, state,
cmdLineArgs.c_str());
tstrings::any logMsg;
logMsg << "MsiConfigureProductEx("
<< id
<< ", " << level
<< ", " << state
<< ", " << cmdLineArgs
<< ") ";
return handleMsiStatus(logMsg, status);
}
} // namespace
template <>
ActionStatus action<uninstall>::execute(const uninstall& obj,
const tstring& cmdLineArgs) {
return msiAction(obj, INSTALLSTATE_ABSENT, cmdLineArgs);
}
template <>
ActionStatus action<update>::execute(const update& obj,
const tstring& cmdLineArgs) {
return msiAction(obj, INSTALLSTATE_LOCAL, cmdLineArgs);
}
template <>
ActionStatus action<install>::execute(const install& obj,
const tstring& cmdLineArgs) {
const tstring& msiPath = obj.getMsiPath();
const UINT status = MsiInstallProduct(msiPath.c_str(),
cmdLineArgs.c_str());
tstrings::any logMsg;
logMsg << "MsiInstallProduct(" << msiPath << ", " << cmdLineArgs << ") ";
return handleMsiStatus(logMsg, status);
}
uninstall::uninstall() {
// Uninstall default behavior is to never reboot.
setProperty(_T("REBOOT"), _T("ReallySuppress"));
}
bool waitForInstallationCompletion(DWORD timeoutMS)
{
// "_MSIExecute" mutex is used by the MSI installer service to prevent multiple installations at the same time
// http://msdn.microsoft.com/en-us/library/aa372909(VS.85).aspx
LPCTSTR mutexName = _T("Global\\_MSIExecute");
UniqueHandle h(OpenMutex(SYNCHRONIZE, FALSE, mutexName));
if (h.get() != NULL) {
DWORD res = WaitForSingleObject(h.get(), timeoutMS);
// log only if timeout != 0
if (timeoutMS != 0) {
LOG_INFO(tstrings::any() << "finish waiting for mutex: " << res);
}
if (res == WAIT_TIMEOUT) {
return false;
}
}
return true;
}
bool isProductInstalled(const Guid& productCode) {
// Query any property. If product exists, query should succeed.
try {
getProductInfo(productCode, INSTALLPROPERTY_VERSIONSTRING);
} catch (const Error& e) {
switch (e.getReason()) {
case ERROR_UNKNOWN_PRODUCT:
// if the application being queried is advertised and not installed.
case ERROR_UNKNOWN_PROPERTY:
return false;
}
}
return true;
}
} // namespace msi

View File

@ -0,0 +1,337 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef MsiUtils_h
#define MsiUtils_h
#include <windows.h>
#include <Msi.h>
#include <iterator>
#include <stdexcept>
#include <new>
#include <map>
#include <memory>
#include "ErrorHandling.h"
#include "Toolbox.h"
#include "Guid.h"
#include "Flag.h"
#include "Log.h"
namespace msi {
void closeMSIHANDLE(MSIHANDLE h);
struct MsiHandleDeleter {
typedef MSIHANDLE pointer;
void operator()(MSIHANDLE h) {
closeMSIHANDLE(h);
}
};
} // namespace msi
typedef std::unique_ptr<MSIHANDLE, msi::MsiHandleDeleter> UniqueMSIHANDLE;
namespace msi {
tstring getProductInfo(const Guid& productCode, const tstring& prop);
tstring getProductInfo(const std::nothrow_t&, const Guid& productCode,
const tstring& prop);
tstring getPropertyFromCustomAction(MSIHANDLE h, const tstring& prop);
tstring getPropertyFromCustomAction(const std::nothrow_t&, MSIHANDLE h,
const tstring& prop);
inline tstring getPropertyFromDeferredCustomAction(MSIHANDLE h) {
return getPropertyFromCustomAction(h, _T("CustomActionData"));
}
inline tstring getPropertyFromDeferredCustomAction(const std::nothrow_t&,
MSIHANDLE h) {
return getPropertyFromCustomAction(std::nothrow, h,
_T("CustomActionData"));
}
// UI level flags
class Tag {};
typedef Flag<Tag, INSTALLUILEVEL> UiModeFlag;
inline UiModeFlag defaultUI() {
return UiModeFlag(INSTALLUILEVEL_DEFAULT);
}
inline UiModeFlag withoutUI() {
return UiModeFlag(INSTALLUILEVEL_NONE);
}
// UI level control
struct OverrideUI {
explicit OverrideUI(const UiModeFlag& uiMode):
origMsiUiLevel(MsiSetInternalUI(uiMode.value(), 0)) {
}
~OverrideUI() {
MsiSetInternalUI(origMsiUiLevel, 0);
}
private:
const INSTALLUILEVEL origMsiUiLevel;
};
struct SuppressUI: public OverrideUI {
SuppressUI(): OverrideUI(withoutUI()) {
}
};
// MSI Properties (KEY=VALUE)
typedef std::pair<tstring, tstring> Property;
typedef std::vector<Property> Properties;
// Callback for MSI functions
class Callback {
public:
virtual ~Callback() {}
virtual void notify(INSTALLMESSAGE msgType, UINT flags,
const tstring& msg) = 0;
};
// MSI Error
class Error : public std::runtime_error {
public:
Error(const tstrings::any& msg, UINT errorCode);
Error(const std::string& msg, UINT errorCode);
UINT getReason() const {
return errorCode;
}
private:
UINT errorCode;
};
// "No more items" exception
class NoMoreItemsError : public Error {
public:
NoMoreItemsError(const tstrings::any& msg)
: Error(msg, ERROR_NO_MORE_ITEMS)
{}
};
struct ActionData {
typedef std::map<tstring, tstring> PropertyMap;
PropertyMap props;
tstring rawCmdLineArgs;
UiModeFlag uiMode;
Callback* callback;
tstring logFile;
struct State {
virtual ~State() {}
};
std::unique_ptr<State> createState() const;
tstring getCmdLineArgs() const;
ActionData(): uiMode(withoutUI()), callback(0) {
}
};
// MSI function execution status.
class ActionStatus {
public:
ActionStatus(UINT value=ERROR_SUCCESS, const std::string& comment=""):
value(value), comment(comment) {
}
explicit operator bool() const;
UINT getValue() const {
return value;
}
// Unconditionally converts this instance into msi::Error instance and
// throws it.
void throwIt() const;
const std::string& getComment() const {
return comment;
}
private:
std::string comment;
UINT value;
};
// Some MSI action.
template <class T>
class action {
public:
T& setProperty(const Property& prop) {
data.props[prop.first] = prop.second;
return *static_cast<T*>(this);
}
T& setProperty(const tstring& name, const tstring& value) {
return setProperty(Property(name, value));
}
template <class It>
T& setProperties(It b, It e) {
std::copy(b, e, std::inserter(data.props, data.props.end()));
return *static_cast<T*>(this);
}
T& setRawCmdLineArgs(const tstring& value) {
data.rawCmdLineArgs = value;
return *static_cast<T*>(this);
}
T& setUiMode(const UiModeFlag& flag) {
data.uiMode = flag;
return *static_cast<T*>(this);
}
T& setLogFile(const tstring& path=tstring()) {
data.logFile = path;
return *static_cast<T*>(this);
}
T& setCallback(Callback* cb) {
data.callback = cb;
return *static_cast<T*>(this);
}
tstring getCmdLineArgs() const {
return data.getCmdLineArgs();
}
void operator () () const {
std::unique_ptr<ActionData::State> state(data.createState());
const ActionStatus status = execute(*static_cast<const T*>(this),
data.getCmdLineArgs());
if (!status) {
status.throwIt();
}
}
ActionStatus operator () (const std::nothrow_t&) const {
JP_TRY;
std::unique_ptr<ActionData::State> state(data.createState());
const ActionStatus status = execute(*static_cast<const T*>(this),
data.getCmdLineArgs());
if (!status) {
LOG_ERROR(status.getComment());
}
return status;
JP_CATCH_ALL;
return ActionStatus(ERROR_INTERNAL_ERROR, "Unknown error");
}
private:
static ActionStatus execute(const T& obj, const tstring& cmdLineArgs);
ActionData data;
};
// Function object to uninstall product with the given GUID
class uninstall: public action<uninstall> {
Guid productCode;
public:
uninstall();
uninstall& setProductCode(const Guid& pc) {
productCode = pc;
return *this;
}
const Guid& getProductCode() const {
return productCode;
}
};
// Function object to update installed product with the given GUID
class update: public action<update> {
Guid productCode;
public:
update& setProductCode(const Guid& pc) {
productCode = pc;
return *this;
}
const Guid& getProductCode() const {
return productCode;
}
};
// Function object to install package from the given msi file
class install: public action<install> {
tstring msiPath;
public:
install& setMsiPath(const tstring& path) {
msiPath = path;
return *this;
}
const tstring& getMsiPath() const {
return msiPath;
}
};
// Checks if there is some installation is in progress and waits until it completes.
// returns true if there is no installation is in progress or the installation is completed.
// returns false if timeout exceeded.
// If timeout == 0, just checks that Windows Installer service is free.
bool waitForInstallationCompletion(DWORD timeoutMS);
// Checks if there is some installation is in progress.
inline bool isInstallationInProgress() {
return !waitForInstallationCompletion(0);
}
/**
* Returns true if product with the given product code is installed.
*/
bool isProductInstalled(const Guid& productCode);
} // namespace msi
#endif // #ifndef MsiUtils_h

View File

@ -148,3 +148,21 @@ Resource::ByteArray Resource::binary() const {
LPBYTE resPtr = (LPBYTE)getPtr(size);
return ByteArray(resPtr, resPtr+size);
}
tstring StringResource::string() const
{
DWORD size = 0;
// string are stored as UNICODE
LPWSTR resPtr = reinterpret_cast<LPWSTR>(impl.getPtr(size));
// size is in bytes;
return tstrings::fromUtf16(std::wstring(resPtr, size / sizeof(wchar_t)));
}
tstring StringResource::string(const std::nothrow_t &, const tstring &defValue) const throw()
{
JP_TRY;
return string();
JP_CATCH_ALL;
return defValue;
}

View File

@ -29,6 +29,8 @@
#include "WinSysInfo.h"
class StringResource;
/**
* Classes for resource loading.
* Common use cases:
@ -37,6 +39,17 @@
* if (res.available()) {
* res.saveToFile(_T("c:\\temp\\my_resource.bin"));
* }
*
* - get string resource:
* 1) if the resource is not available, exception is thrown:
* tstring str = StringResource(MAKEINTRESOURCE(resID)).string();
*
* 2) nothrow method (returns default value if the resource is not available):
* a) returns empty string on error:
* tstring str = StringResource(MAKEINTRESOURCE(resID)).string(std::nothrow);
*
* b) returns provided default value on error:
* tstring str = StringResource(MAKEINTRESOURCE(resID)).string(std::nothrow, _T("defaultValue"));
*/
class Resource {
@ -62,6 +75,8 @@ public:
// returns the resource as byte array
ByteArray binary() const;
friend class StringResource;
private:
std::wstring nameStr;
LPCWSTR namePtr; // can be integer value or point to nameStr.c_str()
@ -82,4 +97,40 @@ private:
Resource& operator = (const Resource&);
};
// Note: string resources are returned utf16 or utf8 encoded.
// To get Windows-encoded string (utf16/ACP) use tstrings::toWinString().
class StringResource {
public:
// string resource is always identified by integer id
StringResource(UINT resourceId, HINSTANCE moduleHandle = SysInfo::getCurrentModuleHandle())
: impl(resourceId, RT_STRING, moduleHandle) {}
// returns the resource as string
tstring string() const;
// nothrow version (logs error)
tstring string(const std::nothrow_t &, const tstring &defValue = tstring()) const throw();
bool available() const throw() {
return impl.available();
}
unsigned size() const {
return impl.size();
}
static tstring load(UINT resourceId,
HINSTANCE moduleHandle = SysInfo::getCurrentModuleHandle()) {
return StringResource(resourceId, moduleHandle).string();
}
static tstring load(const std::nothrow_t &, UINT resourceId,
HINSTANCE moduleHandle = SysInfo::getCurrentModuleHandle()) throw () {
return StringResource(resourceId, moduleHandle).string(std::nothrow);
}
private:
Resource impl;
};
#endif // RESOURCES_H

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <memory>
#include "WinApp.h"
#include "Log.h"
#include "SysInfo.h"
#include "FileUtils.h"
#include "ErrorHandling.h"
// MessageBox
#pragma comment(lib, "user32")
namespace {
class LastErrorGuiLogAppender : public LogAppender {
public:
virtual void append(const LogEvent& v) {
JP_TRY;
const std::wstring msg = (tstrings::any()
<< app::lastErrorMsg()).wstr();
MessageBox(0, msg.c_str(),
FileUtils::basename(SysInfo::getProcessModulePath()).c_str(),
MB_ICONERROR | MB_OK);
JP_CATCH_ALL;
}
};
class Console {
public:
Console() {
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
// Failed to connect to parent's console. Create our own.
if (!AllocConsole()) {
// We already have a console, no need to redirect std I/O.
return;
}
}
stdoutChannel = std::unique_ptr<Channel>(new Channel(stdout));
stderrChannel = std::unique_ptr<Channel>(new Channel(stderr));
}
struct FileCloser {
typedef FILE* pointer;
void operator()(pointer h) {
::fclose(h);
}
};
typedef std::unique_ptr<
FileCloser::pointer,
FileCloser
> UniqueFILEHandle;
private:
class Channel {
public:
Channel(FILE* stdFILEHandle): stdFILEHandle(stdFILEHandle) {
const char* stdFileName = "CONOUT$";
const char* openMode = "w";
if (stdFILEHandle == stdin) {
stdFileName = "CONIN$";
openMode = "r";
}
FILE* fp = 0;
freopen_s(&fp, stdFileName, openMode, stdFILEHandle);
fileHandle = UniqueFILEHandle(fp);
std::ios_base::sync_with_stdio();
}
virtual ~Channel() {
JP_TRY;
FILE* fp = 0;
fileHandle = UniqueFILEHandle(fp);
std::ios_base::sync_with_stdio();
JP_CATCH_ALL;
}
private:
UniqueFILEHandle fileHandle;
FILE *stdFILEHandle;
};
std::unique_ptr<Channel> stdoutChannel;
std::unique_ptr<Channel> stderrChannel;
};
} // namespace
namespace app {
int wlaunch(const std::nothrow_t&, LauncherFunc func) {
std::unique_ptr<Console> console;
JP_TRY;
if (app::isWithLogging()) {
console = std::unique_ptr<Console>(new Console());
}
JP_CATCH_ALL;
LastErrorGuiLogAppender lastErrorLogAppender;
TeeLogAppender logAppender(&app::defaultLastErrorLogAppender(),
&lastErrorLogAppender);
return app::launch(std::nothrow, func, &logAppender);
}
} // namespace app

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef WinApp_h
#define WinApp_h
#include "app.h"
namespace app {
int wlaunch(const std::nothrow_t&, LauncherFunc func);
} // namespace app
#endif // WinApp_h

View File

@ -26,6 +26,7 @@
#ifndef JNIUTILS_H
#define JNIUTILS_H
#include <memory>
#include "jni.h"
#include "tstrings.h"

View File

@ -93,12 +93,12 @@ public:
ResourceEditor& id(LPCWSTR v);
/**
* Relaces resource configured in the given binary with the given data stream.
* Replaces resource configured in the given binary with the given data stream.
*/
ResourceEditor& apply(const FileLock& dstBinary, std::istream& srcStream, std::streamsize size=0);
/**
* Relaces resource configured in the given binary with contents of
* Replaces resource configured in the given binary with contents of
* the given binary file.
*/
ResourceEditor& apply(const FileLock& dstBinary, const std::wstring& srcFile);

View File

@ -196,12 +196,14 @@ void VersionInfo::fillBuffer(std::ostream& buf) const {
writeWORD(buf, 0); // wValueLength
writeWORD(buf, 1); // wType
const std::wstring strLangId = (std::wstringstream()
std::wstringstream strLangIdBuf;
strLangIdBuf
<< std::uppercase
<< std::hex
<< std::setw(8)
<< std::setfill(L'0')
<< engLangId).str();
<< engLangId;
const std::wstring strLangId = strLangIdBuf.str();
write(buf, strLangId); // szKey
add32bitPadding(buf); // Padding

View File

@ -28,6 +28,7 @@
#include "IconSwap.h"
#include "VersionInfo.h"
#include "JniUtils.h"
#include "MsiDb.h"
#ifdef __cplusplus
extern "C" {
@ -140,9 +141,17 @@ extern "C" {
const std::wstring msiPath = jni::toUnicodeString(pEnv, jmsiPath);
// Put msi file in resources.
const ResourceEditor::FileLock lock(reinterpret_cast<HANDLE>(jResourceLock));
ResourceEditor().id(L"msi").type(RT_RCDATA).apply(lock, msiPath);
// Get product code of the msi being embedded
const Guid productCode = Guid(msi::Database(msiPath).getProperty(L"ProductCode"));
// Save product code in resources
std::istringstream in(tstrings::toUtf8(productCode.toString()));
ResourceEditor().id(L"product_code").type(RT_RCDATA).apply(lock, in);
return 0;
JP_CATCH_ALL;

View File

@ -26,17 +26,34 @@
#include <algorithm>
#include <windows.h>
#include "WinApp.h"
#include "Guid.h"
#include "SysInfo.h"
#include "MsiUtils.h"
#include "FileUtils.h"
#include "WinFileUtils.h"
#include "Executor.h"
#include "Resources.h"
#include "WinErrorHandling.h"
int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int nShowCmd)
{
JP_TRY;
namespace {
int exitCode = -1;
void launchApp() {
const auto cmdline = SysInfo::getCommandArgs();
if (std::find(cmdline.begin(), cmdline.end(), L"uninstall") != cmdline.end()) {
// This is uninstall request.
// Get product code of the product to uninstall.
const auto productCodeUtf8 = Resource(L"product_code", RT_RCDATA).binary();
const Guid productCode = Guid(std::string(
(const char*)productCodeUtf8.data(), productCodeUtf8.size()));
// Uninstall product.
msi::uninstall().setProductCode(productCode)();
exitCode = 0;
return;
}
// Create temporary directory where to extract msi file.
const auto tempMsiDir = FileUtils::createTempDirectory();
@ -60,9 +77,12 @@ int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int nShowCmd)
});
// Install msi file.
return msiExecutor.execAndWaitForExit();
JP_CATCH_ALL;
return -1;
exitCode = msiExecutor.execAndWaitForExit();
}
} // namespace
int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int nShowCmd) {
app::wlaunch(std::nothrow, launchApp);
return exitCode;
}

View File

@ -114,14 +114,18 @@ public class WindowsHelper {
}
static PackageHandlers createExePackageHandlers() {
PackageHandlers exe = new PackageHandlers();
// can't have install handler without also having uninstall handler
// so following is commented out for now
// exe.installHandler = cmd -> {
// cmd.verifyIsOfType(PackageType.WIN_EXE);
// new Executor().setExecutable(cmd.outputBundle()).execute();
// };
BiConsumer<JPackageCommand, Boolean> installExe = (cmd, install) -> {
cmd.verifyIsOfType(PackageType.WIN_EXE);
Executor exec = new Executor().setExecutable(cmd.outputBundle());
if (!install) {
exec.addArgument("uninstall");
}
runMsiexecWithRetries(exec);
};
PackageHandlers exe = new PackageHandlers();
exe.installHandler = cmd -> installExe.accept(cmd, true);
exe.uninstallHandler = cmd -> installExe.accept(cmd, false);
return exe;
}