blob: 5b76f9343029710071055934e6f42924517d68f8 [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.
#include "base/system_util.h"
#ifdef OS_WIN
#include <Windows.h>
#include <LMCons.h>
#include <Sddl.h>
#include <ShlObj.h>
#else // OS_WIN
#include <pwd.h>
#include <sys/mman.h>
#include <unistd.h>
#ifdef OS_MACOSX
#include <sys/stat.h>
#include <sys/sysctl.h>
#endif // OS_MACOSX
#endif // OS_WIN
#ifdef OS_MACOSX
#include <cerrno>
#endif // OS_MACOSX
#include <cstdlib>
#include <cstring>
#ifdef OS_WIN
#include <memory>
#endif // OS_WIN
#include <sstream>
#include <string>
#include "base/const.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/mac_util.h"
#include "base/number_util.h"
#include "base/singleton.h"
#include "base/util.h"
#include "base/win_util.h"
#ifdef OS_ANDROID
// HACK to avoid a bug in sysconf in android.
#include "android/jni/sysconf.h"
#define sysconf mysysconf
#include "base/android_util.h"
#endif // OS_ANDROID
#ifdef OS_WIN
using std::unique_ptr;
#endif // OS_WIN
namespace mozc {
namespace {
class UserProfileDirectoryImpl {
public:
UserProfileDirectoryImpl();
string get() {
#ifdef __native_client__
// Copies string here to prevent Copy-On-Write issues in multi-thread
// environment.
// TODO(hsumita): Remove this hack if not necessary.
return string(dir_.data(), dir_.size());
#else
return dir_;
#endif // __native_client__
}
void set(const string &dir) { dir_ = dir; }
private:
string dir_;
};
#ifdef OS_WIN
// TODO(yukawa): Use API wrapper so that unit test can emulate any case.
class LocalAppDataDirectoryCache {
public:
LocalAppDataDirectoryCache() : result_(E_FAIL) {
result_ = SafeTryGetLocalAppData(&path_);
}
HRESULT result() const {
return result_;
}
const bool succeeded() const {
return SUCCEEDED(result_);
}
const string &path() const {
return path_;
}
private:
// b/5707813 implies that TryGetLocalAppData causes an exception and makes
// Singleton<LocalAppDataDirectoryCache> invalid state which results in an
// infinite spin loop in CallOnce. To prevent this, the constructor of
// LocalAppDataDirectoryCache must be exception free.
// Note that __try and __except does not guarantees that any destruction
// of internal C++ objects when a non-C++ exception occurs except that
// /EHa compiler option is specified.
// Since Mozc uses /EHs option in common.gypi, we must admit potential
// memory leakes when any non-C++ exception occues in TryGetLocalAppData.
// See http://msdn.microsoft.com/en-us/library/1deeycx5.aspx
static HRESULT __declspec(nothrow) SafeTryGetLocalAppData(string *dir) {
__try {
return TryGetLocalAppData(dir);
} __except(EXCEPTION_EXECUTE_HANDLER) {
return E_UNEXPECTED;
}
}
static HRESULT TryGetLocalAppData(string *dir) {
if (dir == nullptr) {
return E_FAIL;
}
dir->clear();
bool in_app_container = false;
if (!WinUtil::IsProcessInAppContainer(::GetCurrentProcess(),
&in_app_container)) {
return E_FAIL;
}
if (in_app_container) {
return TryGetLocalAppDataForAppContainer(dir);
}
if (SystemUtil::IsVistaOrLater()) {
return TryGetLocalAppDataLow(dir);
}
// Windows XP: use "%USERPROFILE%\Local Settings\Application Data"
// Retrieve the directory "%USERPROFILE%\Local Settings\Application Data",
// which is a user directory which serves a data repository for local
// applications, to avoid user profiles from being roamed by indexers.
wchar_t config[MAX_PATH] = {};
const HRESULT result = ::SHGetFolderPathW(
nullptr, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, &config[0]);
if (FAILED(result)) {
return result;
}
string buffer;
if (Util::WideToUTF8(&config[0], &buffer) == 0) {
return E_FAIL;
}
*dir = buffer;
return S_OK;
}
static HRESULT TryGetLocalAppDataForAppContainer(string *dir) {
// User profiles for processes running under AppContainer seem to be as
// follows, while the scheme is not officially documented.
// "%LOCALAPPDATA%\Packages\<package sid>\..."
// Note: You can also obtain this path by GetAppContainerFolderPath API.
// http://msdn.microsoft.com/en-us/library/windows/desktop/hh448543.aspx
// Anyway, here we use heuristics to obtain "LocalLow" folder path.
// TODO(yukawa): Establish more reliable way to obtain the path.
wchar_t config[MAX_PATH] = {};
const HRESULT result = ::SHGetFolderPathW(
nullptr, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, &config[0]);
if (FAILED(result)) {
return result;
}
wstring path = config;
const wstring::size_type local_pos = path.find(L"\\Packages\\");
if (local_pos == wstring::npos) {
return E_FAIL;
}
path.erase(local_pos);
path += L"Low";
if (Util::WideToUTF8(path, dir) == 0) {
return E_FAIL;
}
return S_OK;
}
static HRESULT TryGetLocalAppDataLow(string *dir) {
if (dir == nullptr) {
return E_FAIL;
}
dir->clear();
if (!SystemUtil::IsVistaOrLater()) {
return E_NOTIMPL;
}
// Windows Vista: use LocalLow
// Call SHGetKnownFolderPath dynamically.
// http://msdn.microsoft.com/en-us/library/bb762188(VS.85).aspx
// http://msdn.microsoft.com/en-us/library/bb762584(VS.85).aspx
// GUID: {A520A1A4-1780-4FF6-BD18-167343C5AF16}
const HMODULE hLib = WinUtil::LoadSystemLibrary(L"shell32.dll");
if (hLib == nullptr) {
return E_NOTIMPL;
}
typedef HRESULT (WINAPI *FPSHGetKnownFolderPath)(
const GUID &, DWORD, HANDLE, PWSTR *);
FPSHGetKnownFolderPath func = reinterpret_cast<FPSHGetKnownFolderPath>
(::GetProcAddress(hLib, "SHGetKnownFolderPath"));
if (func == nullptr) {
::FreeLibrary(hLib);
return E_NOTIMPL;
}
wchar_t *task_mem_buffer = nullptr;
const HRESULT result =
(*func)(FOLDERID_LocalAppDataLow, 0, nullptr, &task_mem_buffer);
if (FAILED(result)) {
if (task_mem_buffer != nullptr) {
::CoTaskMemFree(task_mem_buffer);
}
::FreeLibrary(hLib);
return result;
}
if (task_mem_buffer == nullptr) {
::FreeLibrary(hLib);
return E_UNEXPECTED;
}
wstring wpath = task_mem_buffer;
::CoTaskMemFree(task_mem_buffer);
string path;
if (Util::WideToUTF8(wpath, &path) == 0) {
::FreeLibrary(hLib);
return E_UNEXPECTED;
}
*dir = path;
::FreeLibrary(hLib);
return S_OK;
}
HRESULT result_;
string path_;
};
#endif // OS_WIN
UserProfileDirectoryImpl::UserProfileDirectoryImpl() {
#ifdef MOZC_USE_PEPPER_FILE_IO
// In NaCl, we can't call FileUtil::CreateDirectory() nor
// FileUtil::DirectoryExists(). So we just set dir_ here.
dir_ = "/";
return;
#else // MOZC_USE_PEPPER_FILE_IO
string dir;
#ifdef OS_WIN
DCHECK(SUCCEEDED(Singleton<LocalAppDataDirectoryCache>::get()->result()));
dir = Singleton<LocalAppDataDirectoryCache>::get()->path();
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
dir = FileUtil::JoinPath(dir, kCompanyNameInEnglish);
FileUtil::CreateDirectory(dir);
#endif // GOOGLE_JAPANESE_INPUT_BUILD
dir = FileUtil::JoinPath(dir, kProductNameInEnglish);
#elif defined(OS_MACOSX)
dir = MacUtil::GetApplicationSupportDirectory();
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
dir = FileUtil::JoinPath(dir, "Google");
// The permission of ~/Library/Application Support/Google seems to be 0755.
// TODO(komatsu): nice to make a wrapper function.
::mkdir(dir.c_str(), 0755);
dir = FileUtil::JoinPath(dir, "JapaneseInput");
#else // GOOGLE_JAPANESE_INPUT_BUILD
dir = FileUtil::JoinPath(dir, "Mozc");
#endif // GOOGLE_JAPANESE_INPUT_BUILD
#elif defined(OS_ANDROID)
// For android, we just use pre-defined directory, which is under the package
// directory, asssuming each device has single user.
dir = FileUtil::JoinPath("/data/data", kMozcAndroidPackage);
dir = FileUtil::JoinPath(dir, ".mozc");
#else // !OS_WIN && !OS_MACOSX && !OS_ANDROID
char buf[1024];
struct passwd pw, *ppw;
const uid_t uid = geteuid();
CHECK_EQ(0, getpwuid_r(uid, &pw, buf, sizeof(buf), &ppw))
<< "Can't get passwd entry for uid " << uid << ".";
CHECK_LT(0, strlen(pw.pw_dir))
<< "Home directory for uid " << uid << " is not set.";
dir = FileUtil::JoinPath(pw.pw_dir, ".mozc");
#endif // !OS_WIN && !OS_MACOSX && !OS_ANDROID
FileUtil::CreateDirectory(dir);
if (!FileUtil::DirectoryExists(dir)) {
LOG(ERROR) << "Failed to create directory: " << dir;
}
// set User profile directory
dir_ = dir;
#endif // MOZC_USE_PEPPER_FILE_IO
}
} // namespace
string SystemUtil::GetUserProfileDirectory() {
return Singleton<UserProfileDirectoryImpl>::get()->get();
}
string SystemUtil::GetLoggingDirectory() {
#ifdef OS_MACOSX
string dir = MacUtil::GetLoggingDirectory();
FileUtil::CreateDirectory(dir);
return dir;
#else // OS_MACOSX
return GetUserProfileDirectory();
#endif // OS_MACOSX
}
void SystemUtil::SetUserProfileDirectory(const string &path) {
Singleton<UserProfileDirectoryImpl>::get()->set(path);
}
#ifdef OS_WIN
namespace {
// TODO(yukawa): Use API wrapper so that unit test can emulate any case.
class ProgramFilesX86Cache {
public:
ProgramFilesX86Cache() : result_(E_FAIL) {
result_ = SafeTryProgramFilesPath(&path_);
}
const bool succeeded() const {
return SUCCEEDED(result_);
}
const HRESULT result() const {
return result_;
}
const string &path() const {
return path_;
}
private:
// b/5707813 implies that the Shell API causes an exception in some cases.
// In order to avoid potential infinite loops in CallOnce. the constructor of
// ProgramFilesX86Cache must be exception free.
// Note that __try and __except does not guarantees that any destruction
// of internal C++ objects when a non-C++ exception occurs except that
// /EHa compiler option is specified.
// Since Mozc uses /EHs option in common.gypi, we must admit potential
// memory leakes when any non-C++ exception occues in TryProgramFilesPath.
// See http://msdn.microsoft.com/en-us/library/1deeycx5.aspx
static HRESULT __declspec(nothrow) SafeTryProgramFilesPath(string *path) {
__try {
return TryProgramFilesPath(path);
} __except(EXCEPTION_EXECUTE_HANDLER) {
return E_UNEXPECTED;
}
}
static HRESULT TryProgramFilesPath(string *path) {
if (path == nullptr) {
return E_FAIL;
}
path->clear();
wchar_t program_files_path_buffer[MAX_PATH] = {};
#if defined(_M_X64)
// In 64-bit processes (such as Text Input Prosessor DLL for 64-bit apps),
// CSIDL_PROGRAM_FILES points 64-bit Program Files directory. In this case,
// we should use CSIDL_PROGRAM_FILESX86 to find server, renderer, and other
// binaries' path.
const HRESULT result = ::SHGetFolderPathW(
nullptr, CSIDL_PROGRAM_FILESX86, nullptr,
SHGFP_TYPE_CURRENT, program_files_path_buffer);
#elif defined(_M_IX86)
// In 32-bit processes (such as server, renderer, and other binaries),
// CSIDL_PROGRAM_FILES always points 32-bit Program Files directory
// even if they are running in 64-bit Windows.
const HRESULT result = ::SHGetFolderPathW(
nullptr, CSIDL_PROGRAM_FILES, nullptr,
SHGFP_TYPE_CURRENT, program_files_path_buffer);
#else // !_M_X64 && !_M_IX86
#error "Unsupported CPU architecture"
#endif // _M_X64, _M_IX86, and others
if (FAILED(result)) {
return result;
}
string program_files;
if (Util::WideToUTF8(program_files_path_buffer, &program_files) == 0) {
return E_FAIL;
}
*path = program_files;
return S_OK;
}
HRESULT result_;
string path_;
};
} // namespace
#endif // OS_WIN
string SystemUtil::GetServerDirectory() {
#ifdef OS_WIN
DCHECK(SUCCEEDED(Singleton<ProgramFilesX86Cache>::get()->result()));
#if defined(GOOGLE_JAPANESE_INPUT_BUILD)
return FileUtil::JoinPath(
FileUtil::JoinPath(Singleton<ProgramFilesX86Cache>::get()->path(),
kCompanyNameInEnglish),
kProductNameInEnglish);
#else // GOOGLE_JAPANESE_INPUT_BUILD
return FileUtil::JoinPath(Singleton<ProgramFilesX86Cache>::get()->path(),
kProductNameInEnglish);
#endif // GOOGLE_JAPANESE_INPUT_BUILD
#elif defined(OS_MACOSX)
return MacUtil::GetServerDirectory();
#elif defined(OS_LINUX)
#if defined(MOZC_SERVER_DIRECTORY)
return MOZC_SERVER_DIRECTORY;
#else
return "/usr/lib/mozc";
#endif // MOZC_SERVER_DIRECTORY
#endif // OS_WIN, OS_MACOSX, OS_LINUX
}
string SystemUtil::GetServerPath() {
const string server_path = GetServerDirectory();
// if server path is empty, return empty path
if (server_path.empty()) {
return "";
}
return FileUtil::JoinPath(server_path, kMozcServerName);
}
string SystemUtil::GetRendererPath() {
const string server_path = GetServerDirectory();
// if server path is empty, return empty path
if (server_path.empty()) {
return "";
}
return FileUtil::JoinPath(server_path, kMozcRenderer);
}
string SystemUtil::GetToolPath() {
const string server_path = GetServerDirectory();
// if server path is empty, return empty path
if (server_path.empty()) {
return "";
}
return FileUtil::JoinPath(server_path, kMozcTool);
}
string SystemUtil::GetDocumentDirectory() {
#if defined(OS_MACOSX)
return GetServerDirectory();
#elif defined(MOZC_DOCUMENT_DIRECTORY)
return MOZC_DOCUMENT_DIRECTORY;
#else
return FileUtil::JoinPath(GetServerDirectory(), "documents");
#endif // OS_MACOSX
}
string SystemUtil::GetCrashReportDirectory() {
const char kCrashReportDirectory[] = "CrashReports";
return FileUtil::JoinPath(SystemUtil::GetUserProfileDirectory(),
kCrashReportDirectory);
}
string SystemUtil::GetUserNameAsString() {
#ifdef MOZC_USE_PEPPER_FILE_IO
LOG(ERROR) << "SystemUtil::GetUserNameAsString() is not implemented in NaCl.";
return "username";
#elif defined(OS_WIN) // MOZC_USE_PEPPER_FILE_IO
wchar_t wusername[UNLEN + 1];
DWORD name_size = UNLEN + 1;
// Call the same name Windows API. (include Advapi32.lib).
// TODO(komatsu, yukawa): Add error handling.
// TODO(komatsu, yukawa): Consider the case where the current thread is
// or will be impersonated.
const BOOL result = ::GetUserName(wusername, &name_size);
DCHECK_NE(FALSE, result);
string username;
Util::WideToUTF8(&wusername[0], &username);
return username;
#elif defined(OS_ANDROID) // OS_WIN
// Android doesn't seem to support getpwuid_r.
struct passwd *ppw = getpwuid(geteuid());
CHECK(ppw != NULL);
return ppw->pw_name;
#else // OS_ANDROID
// OS_MACOSX or OS_LINUX
struct passwd pw, *ppw;
char buf[1024];
CHECK_EQ(0, getpwuid_r(geteuid(), &pw, buf, sizeof(buf), &ppw));
return pw.pw_name;
#endif // !MOZC_USE_PEPPER_FILE_IO && !OS_WIN && !OS_ANDROID
}
#ifdef OS_WIN
namespace {
class UserSidImpl {
public:
UserSidImpl();
const string &get() { return sid_; }
private:
string sid_;
};
UserSidImpl::UserSidImpl() {
HANDLE htoken = nullptr;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &htoken)) {
sid_ = SystemUtil::GetUserNameAsString();
LOG(ERROR) << "OpenProcessToken failed: " << ::GetLastError();
return;
}
DWORD length = 0;
::GetTokenInformation(htoken, TokenUser, nullptr, 0, &length);
unique_ptr<char[]> buf(new char[length]);
PTOKEN_USER p_user = reinterpret_cast<PTOKEN_USER>(buf.get());
if (length == 0 ||
!::GetTokenInformation(htoken, TokenUser, p_user, length, &length)) {
::CloseHandle(htoken);
sid_ = SystemUtil::GetUserNameAsString();
LOG(ERROR) << "OpenTokenInformation failed: " << ::GetLastError();
return;
}
LPTSTR p_sid_user_name;
if (!::ConvertSidToStringSidW(p_user->User.Sid, &p_sid_user_name)) {
::CloseHandle(htoken);
sid_ = SystemUtil::GetUserNameAsString();
LOG(ERROR) << "ConvertSidToStringSidW failed: " << ::GetLastError();
return;
}
Util::WideToUTF8(p_sid_user_name, &sid_);
::LocalFree(p_sid_user_name);
::CloseHandle(htoken);
}
} // namespace
#endif // OS_WIN
string SystemUtil::GetUserSidAsString() {
#ifdef OS_WIN
return Singleton<UserSidImpl>::get()->get();
#else // OS_WIN
return GetUserNameAsString();
#endif // OS_WIN
}
#ifdef OS_WIN
namespace {
string GetObjectNameAsString(HANDLE handle) {
if (handle == nullptr) {
LOG(ERROR) << "Unknown handle";
return "";
}
DWORD size = 0;
if (::GetUserObjectInformationA(handle, UOI_NAME, nullptr, 0, &size) ||
ERROR_INSUFFICIENT_BUFFER != ::GetLastError()) {
LOG(ERROR) << "GetUserObjectInformationA() failed: "
<< ::GetLastError();
return "";
}
if (size == 0) {
LOG(ERROR) << "buffer size is 0";
return "";
}
unique_ptr<char[]> buf(new char[size]);
DWORD return_size = 0;
if (!::GetUserObjectInformationA(handle, UOI_NAME, buf.get(),
size, &return_size)) {
LOG(ERROR) << "::GetUserObjectInformationA() failed: "
<< ::GetLastError();
return "";
}
if (return_size <= 1) {
LOG(ERROR) << "result buffer size is too small";
return "";
}
char *result = buf.get();
result[return_size - 1] = '\0'; // just make sure NULL terminated
return result;
}
bool GetCurrentSessionId(uint32 *session_id) {
DCHECK(session_id);
*session_id = 0;
DWORD id = 0;
if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &id)) {
LOG(ERROR) << "cannot get session id: " << ::GetLastError();
return false;
}
static_assert(sizeof(DWORD) == sizeof(uint32),
"DWORD and uint32 must be equivalent");
*session_id = static_cast<uint32>(id);
return true;
}
// Here we use the input desktop instead of the desktop associated with the
// current thread. One reason behind this is that some applications such as
// Adobe Reader XI use multiple desktops in a process. Basically the input
// desktop is most appropriate and important desktop for our use case.
// See http://blogs.adobe.com/asset/2012/10/new-security-capabilities-in-adobe-reader-and-acrobat-xi-now-available.html
string GetInputDesktopName() {
const HDESK desktop_handle =
::OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
if (desktop_handle == nullptr) {
return "";
}
const string desktop_name = GetObjectNameAsString(desktop_handle);
::CloseDesktop(desktop_handle);
return desktop_name;
}
string GetProcessWindowStationName() {
// We must not close the returned value of GetProcessWindowStation().
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683225.aspx
const HWINSTA window_station = ::GetProcessWindowStation();
if (window_station == nullptr) {
return "";
}
return GetObjectNameAsString(window_station);
}
string GetSessionIdString() {
uint32 session_id = 0;
if (!GetCurrentSessionId(&session_id)) {
return "";
}
return NumberUtil::SimpleItoa(session_id);
}
} // namespace
#endif // OS_WIN
string SystemUtil::GetDesktopNameAsString() {
#ifdef OS_LINUX
const char *display = getenv("DISPLAY");
if (display == NULL) {
return "";
}
return display;
#elif defined(OS_MACOSX)
return "";
#elif defined(OS_WIN)
const string &session_id = GetSessionIdString();
if (session_id.empty()) {
DLOG(ERROR) << "Failed to retrieve session id";
return "";
}
const string &window_station_name = GetProcessWindowStationName();
if (window_station_name.empty()) {
DLOG(ERROR) << "Failed to retrieve window station name";
return "";
}
const string &desktop_name = GetInputDesktopName();
if (desktop_name.empty()) {
DLOG(ERROR) << "Failed to retrieve desktop name";
return "";
}
return (session_id + "." + window_station_name + "." + desktop_name);
#endif // OS_LINUX, OS_MACOSX, OS_WIN
}
#ifdef OS_WIN
namespace {
// TODO(yukawa): Use API wrapper so that unit test can emulate any case.
template<DWORD MajorVersion, DWORD MinorVersion>
class IsWindowsVerXOrLaterCache {
public:
IsWindowsVerXOrLaterCache()
: succeeded_(false),
is_ver_x_or_later_(true) {
// Examine if this system is greater than or equal to WinNT ver. X
{
OSVERSIONINFOEX osvi = {};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = MajorVersion;
osvi.dwMinorVersion = MinorVersion;
DWORDLONG conditional = 0;
VER_SET_CONDITION(conditional, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(conditional, VER_MINORVERSION, VER_GREATER_EQUAL);
const BOOL result = ::VerifyVersionInfo(
&osvi, VER_MAJORVERSION | VER_MINORVERSION, conditional);
if (result != FALSE) {
succeeded_ = true;
is_ver_x_or_later_ = true;
return;
}
}
// Examine if this system is less than WinNT ver. X
{
OSVERSIONINFOEX osvi = {};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = MajorVersion;
osvi.dwMinorVersion = MinorVersion;
DWORDLONG conditional = 0;
VER_SET_CONDITION(conditional, VER_MAJORVERSION, VER_LESS);
VER_SET_CONDITION(conditional, VER_MINORVERSION, VER_LESS);
const BOOL result = ::VerifyVersionInfo(
&osvi, VER_MAJORVERSION | VER_MINORVERSION, conditional);
if (result != FALSE) {
succeeded_ = true;
is_ver_x_or_later_ = false;
return;
}
}
// Unexpected situation.
succeeded_ = false;
is_ver_x_or_later_ = false;
}
const bool succeeded() const {
return succeeded_;
}
const bool is_ver_x_or_later() const {
return is_ver_x_or_later_;
}
private:
bool succeeded_;
bool is_ver_x_or_later_;
};
typedef IsWindowsVerXOrLaterCache<6, 0> IsWindowsVistaOrLaterCache;
typedef IsWindowsVerXOrLaterCache<6, 1> IsWindows7OrLaterCache;
typedef IsWindowsVerXOrLaterCache<6, 2> IsWindows8OrLaterCache;
typedef IsWindowsVerXOrLaterCache<6, 3> IsWindows8_1OrLaterCache;
// TODO(yukawa): Use API wrapper so that unit test can emulate any case.
class SystemDirectoryCache {
public:
SystemDirectoryCache() : system_dir_(nullptr) {
const UINT copied_len_wo_null_if_success =
::GetSystemDirectory(path_buffer_, arraysize(path_buffer_));
if (copied_len_wo_null_if_success >= arraysize(path_buffer_)) {
// Function failed.
return;
}
DCHECK_EQ(L'\0', path_buffer_[copied_len_wo_null_if_success]);
system_dir_ = path_buffer_;
}
const bool succeeded() const {
return system_dir_ != nullptr;
}
const wchar_t *system_dir() const {
return system_dir_;
}
private:
wchar_t path_buffer_[MAX_PATH];
wchar_t *system_dir_;
};
} // namespace
// TODO(team): Support other platforms.
bool SystemUtil::EnsureVitalImmutableDataIsAvailable() {
if (!Singleton<IsWindowsVistaOrLaterCache>::get()->succeeded()) {
return false;
}
if (!Singleton<IsWindows7OrLaterCache>::get()->succeeded()) {
return false;
}
if (!Singleton<IsWindows8OrLaterCache>::get()->succeeded()) {
return false;
}
if (!Singleton<IsWindows8_1OrLaterCache>::get()->succeeded()) {
return false;
}
if (!Singleton<SystemDirectoryCache>::get()->succeeded()) {
return false;
}
if (!Singleton<ProgramFilesX86Cache>::get()->succeeded()) {
return false;
}
if (!Singleton<LocalAppDataDirectoryCache>::get()->succeeded()) {
return false;
}
return true;
}
#endif // OS_WIN
void SystemUtil::CommandLineRotateArguments(int argc, char ***argv) {
char *arg = **argv;
memmove(*argv, *argv + 1, (argc - 1) * sizeof(**argv));
(*argv)[argc - 1] = arg;
}
bool SystemUtil::CommandLineGetFlag(int argc,
char **argv,
string *key,
string *value,
int *used_args) {
key->clear();
value->clear();
*used_args = 0;
if (argc < 1) {
return false;
}
*used_args = 1;
const char *start = argv[0];
if (start[0] != '-') {
return false;
}
++start;
if (start[0] == '-') ++start;
const string arg = start;
const size_t n = arg.find("=");
if (n != string::npos) {
*key = arg.substr(0, n);
*value = arg.substr(n + 1, arg.size() - n);
return true;
}
key->assign(arg);
value->clear();
if (argc == 1) {
return true;
}
start = argv[1];
if (start[0] == '-') {
return true;
}
*used_args = 2;
value->assign(start);
return true;
}
bool SystemUtil::IsPlatformSupported() {
#if defined(OS_MACOSX)
// TODO(yukawa): support Mac.
return true;
#elif defined(OS_LINUX)
// TODO(yukawa): support Linux.
return true;
#elif defined(OS_WIN)
// Sometimes we suffer from version lie of GetVersion(Ex) such as
// http://b/2430094
// This is why we use VerifyVersionInfo here instead of GetVersion(Ex).
// You can find a table of version number for each version of Windows in
// the following page.
// http://msdn.microsoft.com/en-us/library/ms724833.aspx
{
// Windows 7 <= |OSVERSION|: supported
OSVERSIONINFOEX osvi = {};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 1;
DWORDLONG conditional = 0;
VER_SET_CONDITION(conditional, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(conditional, VER_MINORVERSION, VER_GREATER_EQUAL);
const DWORD typemask = VER_MAJORVERSION | VER_MINORVERSION;
if (::VerifyVersionInfo(&osvi, typemask, conditional) != 0) {
return true; // supported
}
}
{
// Windows Vista SP2 <= |OSVERSION| < Windows 7: supported
OSVERSIONINFOEX osvi = {};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 0;
osvi.wServicePackMajor = 2;
DWORDLONG conditional = 0;
VER_SET_CONDITION(conditional, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(conditional, VER_MINORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(conditional, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
const DWORD typemask = VER_MAJORVERSION | VER_MINORVERSION |
VER_SERVICEPACKMAJOR;
if (::VerifyVersionInfo(&osvi, typemask, conditional) != 0) {
return true; // supported
}
}
// |OSVERSION| < Windows Vista SP2: not supported
return false; // not support
#else // !OS_LINUX && !OS_MACOSX && !OS_WIN
#error "Unsupported platform".
#endif // OS_LINUX, OS_MACOSX, OS_WIN
}
bool SystemUtil::IsVistaOrLater() {
#ifdef OS_WIN
DCHECK(Singleton<IsWindowsVistaOrLaterCache>::get()->succeeded());
return Singleton<IsWindowsVistaOrLaterCache>::get()->is_ver_x_or_later();
#else
return false;
#endif // OS_WIN
}
bool SystemUtil::IsWindows7OrLater() {
#ifdef OS_WIN
DCHECK(Singleton<IsWindows7OrLaterCache>::get()->succeeded());
return Singleton<IsWindows7OrLaterCache>::get()->is_ver_x_or_later();
#else
return false;
#endif // OS_WIN
}
bool SystemUtil::IsWindows8OrLater() {
#ifdef OS_WIN
DCHECK(Singleton<IsWindows8OrLaterCache>::get()->succeeded());
return Singleton<IsWindows8OrLaterCache>::get()->is_ver_x_or_later();
#else
return false;
#endif // OS_WIN
}
bool SystemUtil::IsWindows8_1OrLater() {
#ifdef OS_WIN
DCHECK(Singleton<IsWindows8_1OrLaterCache>::get()->succeeded());
return Singleton<IsWindows8_1OrLaterCache>::get()->is_ver_x_or_later();
#else
return false;
#endif // OS_WIN
}
namespace {
volatile mozc::SystemUtil::IsWindowsX64Mode g_is_windows_x64_mode =
mozc::SystemUtil::IS_WINDOWS_X64_DEFAULT_MODE;
} // namespace
bool SystemUtil::IsWindowsX64() {
switch (g_is_windows_x64_mode) {
case IS_WINDOWS_X64_EMULATE_32BIT_MACHINE:
return false;
case IS_WINDOWS_X64_EMULATE_64BIT_MACHINE:
return true;
case IS_WINDOWS_X64_DEFAULT_MODE:
// handled below.
break;
default:
// Should never reach here.
DLOG(FATAL) << "Unexpected mode specified. mode = "
<< g_is_windows_x64_mode;
// handled below.
break;
}
#ifdef OS_WIN
SYSTEM_INFO system_info = {};
// This function never fails.
::GetNativeSystemInfo(&system_info);
return (system_info.wProcessorArchitecture ==
PROCESSOR_ARCHITECTURE_AMD64);
#else
return false;
#endif // OS_WIN
}
void SystemUtil::SetIsWindowsX64ModeForTest(IsWindowsX64Mode mode) {
g_is_windows_x64_mode = mode;
switch (g_is_windows_x64_mode) {
case IS_WINDOWS_X64_EMULATE_32BIT_MACHINE:
case IS_WINDOWS_X64_EMULATE_64BIT_MACHINE:
case IS_WINDOWS_X64_DEFAULT_MODE:
// Known mode. OK.
break;
default:
DLOG(FATAL) << "Unexpected mode specified. mode = "
<< g_is_windows_x64_mode;
break;
}
}
#ifdef OS_WIN
const wchar_t *SystemUtil::GetSystemDir() {
DCHECK(Singleton<SystemDirectoryCache>::get()->succeeded());
return Singleton<SystemDirectoryCache>::get()->system_dir();
}
bool SystemUtil::GetFileVersion(const wstring &file_fullpath, int *major,
int *minor, int *build, int *revision) {
DCHECK(major);
DCHECK(minor);
DCHECK(build);
DCHECK(revision);
string path;
Util::WideToUTF8(file_fullpath.c_str(), &path);
// Accoding to KB826496, we should check file existence.
// http://support.microsoft.com/kb/826496
if (!FileUtil::FileExists(path)) {
LOG(ERROR) << "file not found";
return false;
}
DWORD handle = 0;
const DWORD version_size =
::GetFileVersionInfoSizeW(file_fullpath.c_str(), &handle);
if (version_size == 0) {
LOG(ERROR) << "GetFileVersionInfoSizeW failed."
<< " error = " << ::GetLastError();
return false;
}
unique_ptr<BYTE[]> version_buffer(new BYTE[version_size]);
if (!::GetFileVersionInfoW(file_fullpath.c_str(), 0,
version_size, version_buffer.get())) {
LOG(ERROR) << "GetFileVersionInfo failed."
<< " error = " << ::GetLastError();
return false;
}
VS_FIXEDFILEINFO *fixed_fileinfo = nullptr;
UINT length = 0;
if (!::VerQueryValueW(version_buffer.get(), L"\\",
reinterpret_cast<LPVOID *>(&fixed_fileinfo),
&length)) {
LOG(ERROR) << "VerQueryValue failed."
<< " error = " << ::GetLastError();
return false;
}
*major = HIWORD(fixed_fileinfo->dwFileVersionMS);
*minor = LOWORD(fixed_fileinfo->dwFileVersionMS);
*build = HIWORD(fixed_fileinfo->dwFileVersionLS);
*revision = LOWORD(fixed_fileinfo->dwFileVersionLS);
return true;
}
string SystemUtil::GetFileVersionString(const wstring &file_fullpath) {
int major, minor, build, revision;
if (!GetFileVersion(file_fullpath, &major, &minor, &build, &revision)) {
return "";
}
stringstream buf;
buf << major << "." << minor << "." << build << "." << revision;
return buf.str();
}
string SystemUtil::GetMSCTFAsmCacheReadyEventName() {
const string &session_id = GetSessionIdString();
if (session_id.empty()) {
DLOG(ERROR) << "Failed to retrieve session id";
return "";
}
const string &desktop_name = GetInputDesktopName();
if (desktop_name.empty()) {
DLOG(ERROR) << "Failed to retrieve desktop name";
return "";
}
// Compose "Local\MSCTF.AsmCacheReady.<desktop name><session #>".
return ("Local\\MSCTF.AsmCacheReady." + desktop_name + session_id);
}
#endif // OS_WIN
// TODO(toshiyuki): move this to the initialization module and calculate
// version only when initializing.
string SystemUtil::GetOSVersionString() {
#ifdef OS_WIN
string ret = "Windows";
OSVERSIONINFOEX osvi = { 0 };
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if (GetVersionEx(reinterpret_cast<OSVERSIONINFO *>(&osvi))) {
ret += ".";
ret += NumberUtil::SimpleItoa(static_cast<uint32>(osvi.dwMajorVersion));
ret += ".";
ret += NumberUtil::SimpleItoa(static_cast<uint32>(osvi.dwMinorVersion));
ret += "." + NumberUtil::SimpleItoa(osvi.wServicePackMajor);
ret += "." + NumberUtil::SimpleItoa(osvi.wServicePackMinor);
} else {
LOG(WARNING) << "GetVersionEx failed";
}
return ret;
#elif defined(OS_MACOSX)
const string ret = "MacOSX " + MacUtil::GetOSVersionString();
// TODO(toshiyuki): get more specific info
return ret;
#elif defined(OS_LINUX)
const string ret = "Linux";
return ret;
#else // !OS_WIN && !OS_MACOSX && !OS_LINUX
const string ret = "Unknown";
return ret;
#endif // OS_WIN, OS_MACOSX, OS_LINUX
}
bool SystemUtil::MacOSVersionIsGreaterOrEqual(int32 major,
int32 minor,
int32 fix) {
#ifdef OS_MACOSX
return MacUtil::OSVersionIsGreaterOrEqual(major, minor, fix);
#else
return false;
#endif // OS_MACOSX
}
void SystemUtil::DisableIME() {
#ifdef OS_WIN
// turn off IME:
// AFAIK disabling TSF under Vista and later OS is almost impossible
// so that all we have to do is to prevent from calling
// ITfThreadMgr::Activate and ITfThreadMgrEx::ActivateEx in this thread.
::ImmDisableTextFrameService(-1);
::ImmDisableIME(-1);
#endif // OS_WIN
}
uint64 SystemUtil::GetTotalPhysicalMemory() {
#if defined(OS_WIN)
MEMORYSTATUSEX memory_status = { sizeof(MEMORYSTATUSEX) };
if (!::GlobalMemoryStatusEx(&memory_status)) {
return 0;
}
return memory_status.ullTotalPhys;
#elif defined(OS_MACOSX)
int mib[] = { CTL_HW, HW_MEMSIZE };
uint64 total_memory = 0;
size_t size = sizeof(total_memory);
const int error =
sysctl(mib, arraysize(mib), &total_memory, &size, NULL, 0);
if (error == -1) {
const int error = errno;
LOG(ERROR) << "sysctl with hw.memsize failed. "
<< "errno: " << error;
return 0;
}
return total_memory;
#elif defined(OS_LINUX)
#if defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
const long page_size = sysconf(_SC_PAGESIZE);
const long number_of_phyisical_pages = sysconf(_SC_PHYS_PAGES);
if (number_of_phyisical_pages < 0) {
// likely to be overflowed.
LOG(FATAL) << number_of_phyisical_pages << ", " << page_size;
return 0;
}
return static_cast<uint64>(number_of_phyisical_pages) * page_size;
#else // defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
return 0;
#endif // defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
#else // !(defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX))
#error "unknown platform"
#endif // OS_WIN, OS_MACOSX, OS_LINUX
}
bool SystemUtil::IsLittleEndian() {
#ifndef OS_WIN
union {
unsigned char c[4];
unsigned int i;
} u;
static_assert(sizeof(u.c) == sizeof(u.i),
"Expecting (unsigned) int is 32-bit integer.");
static_assert(sizeof(u) == sizeof(u.i),
"Checking alignment.");
u.i = 0x12345678U;
return u.c[0] == 0x78U;
#else // OS_WIN
return true;
#endif // OS_WIN
}
int SystemUtil::MaybeMLock(const void *addr, size_t len) {
// TODO(yukawa): Integrate mozc_cache service.
#if defined(OS_WIN) || defined(OS_ANDROID) || defined(__native_client__)
return -1;
#else // defined(OS_WIN) || defined(OS_ANDROID) ||
// defined(__native_client__)
return mlock(addr, len);
#endif // defined(OS_WIN) || defined(OS_ANDROID) ||
// defined(__native_client__)
}
int SystemUtil::MaybeMUnlock(const void *addr, size_t len) {
#if defined(OS_WIN) || defined(OS_ANDROID) || defined(__native_client__)
return -1;
#else // defined(OS_WIN) || defined(OS_ANDROID) ||
// defined(__native_client__)
return munlock(addr, len);
#endif // defined(OS_WIN) || defined(OS_ANDROID) ||
// defined(__native_client__)
}
} // namespace mozc