blob: 5ea2a98d90d2493508da18128f437c0fe51b02d5 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifdef OS_WIN
#include "server/cache_service_manager.h"
#include <windows.h>
#include <shlwapi.h> // for SHLoadIndirectString
#include <strsafe.h>
#include <wincrypt.h>
#include <memory>
#include <string>
#include "base/const.h"
#include "base/file_util.h"
#include "base/scoped_handle.h"
#include "base/system_util.h"
#include "base/util.h"
#include "server/mozc_cache_service_resource.h"
#include "server/win32_service_state.pb.h"
using std::unique_ptr;
namespace mozc {
namespace {
const uint64 kMinimumRequiredMemorySizeForInstall = 384 * 1024 * 1024;
class ScopedSCHandle {
public:
ScopedSCHandle()
: handle_(NULL) {}
explicit ScopedSCHandle(SC_HANDLE handle)
: handle_(handle) {}
~ScopedSCHandle() {
if (NULL != handle_) {
::CloseServiceHandle(handle_);
}
}
SC_HANDLE get() const { return handle_; }
void swap(ScopedSCHandle & other) {
SC_HANDLE tmp = handle_;
handle_ = other.handle_;
other.handle_ = tmp;
}
private:
SC_HANDLE handle_;
};
// Returns a redirector for the specified string resource for Vista or later.
// Returns an empty string if any error occurs.
// See http://msdn.microsoft.com/en-us/library/dd374120.aspx for details.
wstring GetRegistryStringRedirector(int resource_id) {
const wchar_t kRegistryStringRedirectionPattern[] = L"@%s,-%d";
const wstring &service_path = CacheServiceManager::GetUnquotedServicePath();
wchar_t buffer[MAX_PATH] = { L'\0' };
HRESULT hr = ::StringCchPrintf(
buffer, arraysize(buffer), kRegistryStringRedirectionPattern,
CacheServiceManager::GetUnquotedServicePath().c_str(), resource_id);
if (hr != S_OK) {
return L"";
}
const wstring redirector(buffer);
// If this program is running on Windows Vista or later,
// just returns the redirector.
return redirector;
}
wstring GetDisplayName() {
return GetRegistryStringRedirector(IDS_DISPLAYNAME);
}
wstring GetDescription() {
return GetRegistryStringRedirector(IDS_DESCRIPTION);
}
// This function serializes a given message (any type of protobuf message)
// as a wstring encoded by base64.
// Returns true if succeeds.
bool SerializeToBase64WString(const ::google::protobuf::Message &message,
wstring *dest) {
if (dest == NULL) {
return false;
}
const int serialized_len = message.ByteSize();
unique_ptr<BYTE[]> serialized(new BYTE[serialized_len]);
if (!message.SerializeToArray(serialized.get(), serialized_len)) {
LOG(ERROR) << "SerializeAsArray failed";
return false;
}
DWORD base64_string_len = 0;
BOOL result = ::CryptBinaryToString(serialized.get(), serialized_len,
CRYPT_STRING_BASE64, NULL,
&base64_string_len);
if (result == FALSE) {
return false;
}
unique_ptr<wchar_t[]> base64_string(new wchar_t[base64_string_len]);
result = ::CryptBinaryToString(serialized.get(), serialized_len,
CRYPT_STRING_BASE64, base64_string.get(),
&base64_string_len);
if (result == FALSE) {
return false;
}
dest->assign(base64_string.get(), base64_string_len);
return true;
}
// This function deserializes a message (any type of protobuf message)
// from the given wstring. This wstring should be generated by
// SerializeToBase64WString.
// Returns true if succeeds
bool DeserializeFromBase64WString(const wstring &src,
::google::protobuf::Message *message) {
if (message == NULL) {
return false;
}
DWORD buffer_len = 0;
BOOL result = ::CryptStringToBinary(src.c_str(),
src.size(),
CRYPT_STRING_BASE64,
NULL,
&buffer_len,
NULL,
NULL);
if (result == FALSE) {
return false;
}
unique_ptr<BYTE[]> buffer(new BYTE[buffer_len]);
result = ::CryptStringToBinary(src.c_str(),
src.size(),
CRYPT_STRING_BASE64,
buffer.get(),
&buffer_len,
NULL,
NULL);
if (result == FALSE) {
return false;
}
if (!message->ParseFromArray(buffer.get(), buffer_len)) {
LOG(ERROR) << "ParseFromArray failed";
return false;
}
return true;
}
// A helper function to retrieve a service handle of the cache service
// with specified access rights.
// Returns true if one of the following conditions is satisfied.
// - A valid service handle is retrieved. In this case, |handle| owns the
// retrieved handle.
// - It turns out w/o any error that the cache service is not installed.
// In this case, |handle| owns a NULL handle.
// Otherwise, returns false.
bool GetCacheService(DWORD service_controler_rights, DWORD service_rights,
ScopedSCHandle *handle) {
if (NULL == handle) {
return false;
}
ScopedSCHandle sc_handle(::OpenSCManager(NULL, NULL,
service_controler_rights));
if (NULL == sc_handle.get()) {
LOG(ERROR) << "OpenSCManager failed: " << ::GetLastError();
return false;
}
ScopedSCHandle service_handle(::OpenService(
sc_handle.get(), CacheServiceManager::GetServiceName(), service_rights));
const int error = ::GetLastError();
if (NULL == service_handle.get() && ERROR_SERVICE_DOES_NOT_EXIST != error) {
LOG(ERROR) << "OpenService failed: " << error;
return false;
}
// |service_handle| is null if and only if the cache service is not
// installed.
service_handle.swap(*handle);
return true;
}
bool IsServiceRunning(const ScopedSCHandle &service_handle) {
if (NULL == service_handle.get()) {
return false;
}
SERVICE_STATUS service_status = {};
if (!::QueryServiceStatus(service_handle.get(), &service_status)) {
LOG(ERROR) << "QueryServiceStatus failed: " << ::GetLastError();
return false;
}
return service_status.dwCurrentState == SERVICE_RUNNING;
}
bool StartServiceInternal(const ScopedSCHandle &service_handle,
const vector<wstring> &arguments) {
if (arguments.size() <= 0) {
if (!::StartService(service_handle.get(), 0, NULL)) {
LOG(ERROR) << "StartService failed: " << ::GetLastError();
return false;
}
return true;
}
unique_ptr<const wchar_t*[]> args(new const wchar_t*[arguments.size()]);
for (size_t i = 0; i < arguments.size(); ++i) {
args[i] = arguments[i].c_str();
}
if (!::StartService(service_handle.get(), arguments.size(), args.get())) {
LOG(ERROR) << "StartService failed: " << ::GetLastError();
return false;
}
return true;
}
bool StopService(const ScopedSCHandle &service_handle) {
SERVICE_STATUS status;
if (!::ControlService(service_handle.get(), SERVICE_CONTROL_STOP, &status)) {
LOG(ERROR) << "ControlService failed: " << ::GetLastError();
return false;
}
return true;
}
bool SetServiceDescription(const ScopedSCHandle &service_handle,
const wstring &description) {
// +1 for '\0'
const size_t buffer_length = description.size() + 1;
unique_ptr<wchar_t[]> buffer(new wchar_t[buffer_length]);
description._Copy_s(buffer.get(), buffer_length, description.size());
buffer[buffer_length - 1] = L'\0';
SERVICE_DESCRIPTION desc = {};
desc.lpDescription = buffer.get();
if (!::ChangeServiceConfig2(service_handle.get(),
SERVICE_CONFIG_DESCRIPTION, &desc)) {
LOG(ERROR) << "ChangeServiceConfig2 failed: " << ::GetLastError();
return false;
}
return true;
}
// Set *nice-to-have* features for the cache service.
// We do not care about any failure because it is not critical for the
// functionality of the cache service itself.
void SetAdvancedConfig(const ScopedSCHandle &service_handle) {
// Windows Vista or later has some nice features as described in the
// following documents.
// http://msdn.microsoft.com/en-us/magazine/cc164252.aspx
// Enable "Delayed Auto-start".
{
SERVICE_DELAYED_AUTO_START_INFO info = {};
info.fDelayedAutostart = TRUE;
if (!::ChangeServiceConfig2(service_handle.get(),
SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
&info)) {
LOG(ERROR) << "ChangeServiceConfig2 failed: " << ::GetLastError();
}
}
// http://blogs.technet.com/richard_macdonald/archive/2007/06/27/1375523.aspx
// See also http://b/2470180
// Only SE_INC_BASE_PRIORITY_NAME and SE_CHANGE_NOTIFY are needed.
// According to MSDN Library, we need not explicitly specify the later.
// See http://msdn.microsoft.com/en-us/library/ms685976.aspx for details.
{
SERVICE_REQUIRED_PRIVILEGES_INFO privileges_info = {};
privileges_info.pmszRequiredPrivileges = SE_INC_BASE_PRIORITY_NAME
TEXT("\0");
if (!::ChangeServiceConfig2(service_handle.get(),
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,
&privileges_info)) {
LOG(ERROR) << "ChangeServiceConfig2 failed: " << ::GetLastError();
}
}
// Remove write privileges from the cache service.
// See http://msdn.microsoft.com/en-us/library/ms685987.aspx for details.
// This may restrict glog functions such as LOG(ERROR).
// TODO(yukawa): Output logging messages as debug strings, or output them
// to the Win32 event log.
{
SERVICE_SID_INFO sid_info = {};
sid_info.dwServiceSidType = SERVICE_SID_TYPE_RESTRICTED;
if (!::ChangeServiceConfig2(service_handle.get(),
SERVICE_CONFIG_SERVICE_SID_INFO,
&sid_info)) {
LOG(ERROR) << "ChangeServiceConfig2 failed: " << ::GetLastError();
}
}
}
// This function updates the following settings of the cache service as
// specified.
// - Display name
// - Desctiption
// - Load type (regardless of the amount of the system memory)
// This function also starts or stops the cache service.
// Win32ServiceState::IsInstalled() will be ignored. You cannot use this
// function to install nor uninstall the cache service.
bool RestoreStateInternal(const cache_service::Win32ServiceState &state) {
ScopedSCHandle service_handle;
const DWORD kSCRights = SC_MANAGER_CONNECT;
const DWORD kServiceRights =
GENERIC_READ | GENERIC_WRITE | SERVICE_START | SERVICE_STOP;
if (!GetCacheService(kSCRights, kServiceRights, &service_handle)
|| (NULL == service_handle.get())) {
return false;
}
if (!::ChangeServiceConfig(service_handle.get(), SERVICE_NO_CHANGE,
static_cast<DWORD>(state.load_type()),
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL,
NULL, GetDisplayName().c_str())) {
LOG(ERROR) << "ChangeServiceConfig failed: " << ::GetLastError();
return false;
}
if (!SetServiceDescription(service_handle, GetDescription())) {
return false;
}
// We ignore any failure of advanced configurations because they are
// *nice-to-have* features.
SetAdvancedConfig(service_handle);
const bool now_running = IsServiceRunning(service_handle);
// If the service was runnning and is not running now, start it unless the
// load type is not DISABLED.
if (state.running() && !now_running &&
(state.load_type() != cache_service::Win32ServiceState::DISABLED)) {
vector<wstring> arguments(state.arguments_size());
arguments.resize(state.arguments_size());
for (size_t i = 0; i < state.arguments_size(); ++i) {
if (Util::UTF8ToWide(state.arguments(i).c_str(), &arguments[i]) <= 0) {
return false;
}
}
return StartServiceInternal(service_handle, arguments);
// If the service is now runnning and was not running, stop it.
} else if (!state.running() && now_running) {
return StopService(service_handle);
// Nothing to do.
} else {
return true;
}
assert(0);
return false;
}
// Returns true if a byte array which contains valid QUERY_SERVICE_CONFIG
// is successfully retrieved. Some members of QUERY_SERVICE_CONFIG points
// memory addresses inside the retrieved byte array.
bool GetServiceConfig(const ScopedSCHandle &service_handle,
unique_ptr<char[]> *result) {
if (NULL == result) {
return false;
}
if (NULL == service_handle.get()) {
return false;
}
DWORD size = 0;
if (!::QueryServiceConfig(service_handle.get(), NULL, 0, &size) &&
::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
LOG(ERROR) << "QueryServiceConfig failed: " << ::GetLastError();
return false;
}
if (size == 0) {
LOG(ERROR) << "buffer size is 0";
return false;
}
unique_ptr<char[]> buf(new char[size]);
LPQUERY_SERVICE_CONFIG service_config =
reinterpret_cast<LPQUERY_SERVICE_CONFIG>(buf.get());
if (!::QueryServiceConfig(service_handle.get(),
service_config, size, &size)) {
LOG(ERROR) << "QueryServiceConfig failed: " << ::GetLastError();
return false;
}
buf.swap(*result);
return true;
}
} // anonymous namespace
bool CacheServiceManager::IsInstalled() {
ScopedSCHandle service_handle;
if (!GetCacheService(SC_MANAGER_CONNECT | GENERIC_READ, SERVICE_QUERY_STATUS,
&service_handle)
|| (NULL == service_handle.get())) {
return false;
}
return true;
}
bool CacheServiceManager::IsRunning() {
ScopedSCHandle service_handle;
if (!GetCacheService(SC_MANAGER_CONNECT | GENERIC_READ, SERVICE_QUERY_STATUS,
&service_handle)
|| (NULL == service_handle.get())) {
return false;
}
return IsServiceRunning(service_handle);
}
bool CacheServiceManager::IsEnabled() {
ScopedSCHandle service_handle;
if (!GetCacheService(SC_MANAGER_CONNECT | GENERIC_READ, SERVICE_QUERY_CONFIG,
&service_handle)
|| (NULL == service_handle.get())) {
return false;
}
// This byte array will contain both QUERY_SERVICE_CONFIG and some
// string buffers which are reffered by corresponding members of
// QUERY_SERVICE_CONFIG. We have to keep the array until we complete
// all tasks which use the contents of QUERY_SERVICE_CONFIG.
unique_ptr<char[]> buffer;
if (!GetServiceConfig(service_handle, &buffer)) {
return false;
}
const LPQUERY_SERVICE_CONFIG service_config =
reinterpret_cast<const LPQUERY_SERVICE_CONFIG>(buffer.get());
return service_config->dwStartType == SERVICE_AUTO_START;
}
const wchar_t *CacheServiceManager::GetServiceName() {
return kMozcCacheServiceName;
}
wstring CacheServiceManager::GetUnquotedServicePath() {
const string lock_service_path =
FileUtil::JoinPath(SystemUtil::GetServerDirectory(),
kMozcCacheServiceExeName);
wstring wlock_service_path;
if (Util::UTF8ToWide(lock_service_path.c_str(), &wlock_service_path) <= 0) {
return L"";
}
return wlock_service_path;
}
wstring CacheServiceManager::GetQuotedServicePath() {
return L"\"" + GetUnquotedServicePath() + L"\"";
}
bool CacheServiceManager::EnableAutostart() {
if (!IsInstalled()) {
return false;
}
const bool enough = CacheServiceManager::HasEnoughMemory();
// Fortunately, the expected service settings can be descried by
// Win32ServiceState so we can use RestoreStateInternal to change the
// settings and start the service (if needed).
// If the system does not have enough memory, disable the service
// to reduce the performance impact on the boot-time.
cache_service::Win32ServiceState state;
state.set_version(1);
state.set_installed(true);
state.set_load_type(enough ? cache_service::Win32ServiceState::AUTO_START
: cache_service::Win32ServiceState::DISABLED);
state.set_running(enough);
const bool result = RestoreStateInternal(state);
return enough ? result : false;
}
bool CacheServiceManager::DisableService() {
if (!IsInstalled()) {
return false;
}
// Fortunately, the expected service settings can be descried by
// Win32ServiceState so we can use RestoreStateInternal to change the
// settings and stop the service (if needed).
cache_service::Win32ServiceState state;
state.set_version(1);
state.set_installed(true);
state.set_load_type(cache_service::Win32ServiceState::DISABLED);
state.set_running(false);
return RestoreStateInternal(state);
}
bool CacheServiceManager::RestartService() {
ScopedSCHandle service_handle;
if (!GetCacheService(SC_MANAGER_CONNECT,
SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS,
&service_handle)
|| (NULL == service_handle.get())) {
return false;
}
SERVICE_STATUS status;
if (!::ControlService(service_handle.get(), SERVICE_CONTROL_STOP, &status)) {
LOG(ERROR) << "ControlService failed: " << ::GetLastError();
}
const int kNumTrial = 10;
for (int i = 0; i < kNumTrial; ++i) {
SERVICE_STATUS service_status = {};
if (!::QueryServiceStatus(service_handle.get(), &service_status)) {
LOG(ERROR) << "QueryServiceStatus failed: " << ::GetLastError();
return false;
}
if (service_status.dwCurrentState != SERVICE_RUNNING) {
break;
}
::Sleep(200); // wait for 0.2sec
}
return StartServiceInternal(service_handle, vector<wstring>());
}
bool CacheServiceManager::HasEnoughMemory() {
return SystemUtil::GetTotalPhysicalMemory() >=
kMinimumRequiredMemorySizeForInstall;
}
bool CacheServiceManager::BackupStateAsString(wstring *result) {
if (result == NULL) {
return false;
}
result->clear();
cache_service::Win32ServiceState state;
state.set_version(1);
ScopedSCHandle service_handle;
if (!GetCacheService(SC_MANAGER_CONNECT | GENERIC_READ,
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG,
&service_handle)) {
// If it turns out w/o any error that the cache service is not installed,
// |GetCacheService| returns true. We can conclude any other error
// occurred.
return false;
}
// If the cachse service is actually installed, |service_handle| should have
// non-null value.
state.set_installed(NULL != service_handle.get());
if (!state.installed()) {
// The cache service is not installed.
// Use default settings with setting |installed| flag to false.
state.set_load_type(cache_service::Win32ServiceState::AUTO_START);
state.set_running(true);
} else {
// This byte array will contain both QUERY_SERVICE_CONFIG and some
// string buffers which are reffered by corresponding members of
// QUERY_SERVICE_CONFIG. We have to keep the array until we complete
// all tasks which use the contents of QUERY_SERVICE_CONFIG.
unique_ptr<char[]> buffer;
if (!GetServiceConfig(service_handle, &buffer)) {
return false;
}
const LPQUERY_SERVICE_CONFIG service_config =
reinterpret_cast<const LPQUERY_SERVICE_CONFIG>(buffer.get());
// retrieves the server load type.
state.set_load_type(
static_cast<cache_service::Win32ServiceState::LoadType>(
service_config->dwStartType));
// retrieves the server running status.
state.set_running(IsServiceRunning(service_handle));
}
if (!SerializeToBase64WString(state, result)) {
return false;
}
return true;
}
bool CacheServiceManager::RestoreStateFromString(const wstring &serialized) {
cache_service::Win32ServiceState state;
if (!DeserializeFromBase64WString(serialized, &state)) {
return false;
}
return RestoreStateInternal(state);
}
bool CacheServiceManager::EnsureServiceStopped() {
ScopedSCHandle service_handle;
const DWORD kSCRights = SC_MANAGER_CONNECT;
const DWORD kServiceRights = GENERIC_READ | SERVICE_STOP;
if (!GetCacheService(kSCRights, kServiceRights, &service_handle)) {
return false;
}
if (NULL == service_handle.get()) {
return true;
}
if (!IsServiceRunning(service_handle)) {
return true;
}
if (!StopService(service_handle)) {
return false;
}
return IsServiceRunning(service_handle);
}
} // namespace mozc
#endif // OS_WIN