blob: e1e1bc2e4fae2c6fee4c7eb832fc2767af69bc55 [file] [log] [blame]
// Copyright 2007-2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
//
// Contains the logic to encapsulate access to the application data
// stored in the registry.
#include "omaha/worker/application_manager.h"
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <vector>
#include "base/scoped_ptr.h"
#include "omaha/common/scoped_ptr_address.h"
#include "omaha/common/time.h"
#include "omaha/common/utils.h"
#include "omaha/common/vistautil.h"
#include "omaha/goopdate/config_manager.h"
#include "omaha/goopdate/const_goopdate.h"
#include "omaha/goopdate/goopdate_utils.h"
#include "omaha/worker/application_usage_data.h"
#include "omaha/worker/application_data.h"
#include "omaha/worker/job.h"
namespace omaha {
namespace {
// Returns the number of days haven been passed since the given time.
// The parameter time is in the same format as C time() returns.
int GetNumberOfDaysSince(int time) {
ASSERT1(time >= 0);
const int now = Time64ToInt32(GetCurrent100NSTime());
ASSERT1(now >= time);
if (now < time) {
// In case the client computer clock is adjusted in between.
return 0;
}
return (now - time) / kSecondsPerDay;
}
// Determines if an application is registered with Google Update.
class IsAppRegisteredFunc
: public std::unary_function<const CString&, HRESULT> {
public:
explicit IsAppRegisteredFunc(const CString& guid)
: is_registered_(false),
guid_(guid) {}
bool is_registered() const { return is_registered_; }
HRESULT operator() (const CString& guid) {
if (guid.CompareNoCase(guid_) == 0) {
is_registered_ = true;
}
return S_OK;
}
private:
CString guid_;
bool is_registered_;
};
// Accumulates ProductData.
class CollectProductsFunc
: public std::unary_function<const CString&, HRESULT> {
public:
CollectProductsFunc(ProductDataVector* products,
bool is_machine,
bool collect_registered_products)
: products_(products),
is_machine_(is_machine),
collect_registered_products_(collect_registered_products) {
ASSERT1(products);
}
// Ignores errors and accumulates as many applications as possible.
HRESULT operator() (const CString& guid) {
AppManager app_manager(is_machine_);
ProductData product_data;
if (SUCCEEDED(app_manager.ReadProductDataFromStore(StringToGuid(guid),
&product_data))) {
ASSERT(!collect_registered_products_ ||
!product_data.app_data().is_uninstalled(),
(_T("Should not be finding uninstalled apps while looking for ")
_T("registered apps; may be enumerating the wrong key.")));
if (collect_registered_products_ ||
product_data.app_data().is_uninstalled()) {
CORE_LOG(L3, (_T("[Found %s product][%s]"),
collect_registered_products_ ? _T("registered") :
_T("uninstalled"),
guid));
products_->push_back(product_data);
}
}
return S_OK;
}
private:
bool collect_registered_products_;
bool is_machine_;
ProductDataVector* products_;
};
// Accumulates AppData for components of a product.
class CollectComponentsFunc
: public std::unary_function<const CString&, HRESULT> {
public:
CollectComponentsFunc(ProductData* product_data,
bool is_machine)
: product_data_(product_data),
is_machine_(is_machine) {
ASSERT1(product_data);
}
// Ignores errors and accumulates as many components as possible.
HRESULT operator() (const CString& guid) {
AppManager app_manager(is_machine_);
AppData component_data;
if (SUCCEEDED(app_manager.ReadAppDataFromStore(
product_data_->app_data().app_guid(),
StringToGuid(guid),
&component_data))) {
product_data_->AddComponent(component_data);
}
return S_OK;
}
private:
ProductData* product_data_;
bool is_machine_;
};
// Enumerates all sub keys of the key and calls the functor for each of them.
template <typename T>
HRESULT EnumerateSubKeys(const TCHAR* key_name, T* functor) {
RegKey client_key;
HRESULT hr = client_key.Open(key_name, KEY_READ);
if (FAILED(hr)) {
return hr;
}
int num_sub_keys = client_key.GetSubkeyCount();
for (int i = 0; i < num_sub_keys; ++i) {
CString sub_key_name;
hr = client_key.GetSubkeyNameAt(i, &sub_key_name);
if (SUCCEEDED(hr)) {
(*functor)(sub_key_name);
}
}
return S_OK;
}
} // namespace
AppManager::AppManager(bool is_machine)
: is_machine_(is_machine) {
CORE_LOG(L3, (_T("[AppManager::AppManager][is_machine=%d]"), is_machine));
}
bool AppManager::IsProductRegistered(const GUID& app_guid) const {
IsAppRegisteredFunc func(GuidToString(app_guid));
HRESULT hr = EnumerateSubKeys(
ConfigManager::Instance()->registry_clients(is_machine_),
&func);
if (FAILED(hr)) {
return false;
}
return func.is_registered();
}
// TODO(omaha): Consider making AppManager a namespace.
void AppManager::ConvertCommandLineToProductData(const CommandLineArgs& args,
ProductDataVector* products) {
ASSERT1(products);
// TODO(omaha): Need to update this to read the bundle info to build up
// multiple AppData objects (and also to read components) and add unit test.
for (size_t i = 0; i < args.extra.apps.size(); ++i) {
const CommandLineAppArgs& extra_arg = args.extra.apps[i];
const GUID& app_guid = extra_arg.app_guid;
AppData app_data(app_guid, is_machine_);
app_data.set_language(args.extra.language);
app_data.set_ap(extra_arg.ap);
app_data.set_tt_token(extra_arg.tt_token);
app_data.set_iid(args.extra.installation_id);
app_data.set_brand_code(args.extra.brand_code);
app_data.set_client_id(args.extra.client_id);
app_data.set_referral_id(args.extra.referral_id);
// install_time_diff_sec is set based on the current state of the system.
if (IsProductRegistered(app_guid)) {
app_data.set_install_time_diff_sec(GetInstallTimeDiffSec(app_guid));
} else {
// The product is not already installed. We differentiate this from no
// install time being present (i.e. the app was installed before
// installtime was implemented) by setting the diff to -1 days.
// This makes an assumption about the XML parser but works for now.
const int kNewInstallValue = -1 * kSecondsPerDay;
app_data.set_install_time_diff_sec(kNewInstallValue);
}
// Do not set is_oem_install because it is not based on the command line.
app_data.set_is_eula_accepted(!args.is_eula_required_set);
app_data.set_display_name(extra_arg.app_name);
app_data.set_browser_type(args.extra.browser_type);
app_data.set_install_source(args.install_source);
app_data.set_usage_stats_enable(args.extra.usage_stats_enable);
app_data.set_encoded_installer_data(extra_arg.encoded_installer_data);
app_data.set_install_data_index(extra_arg.install_data_index);
ProductData product_data(app_data);
products->push_back(product_data);
}
ASSERT1(products->size() == args.extra.apps.size());
}
HRESULT AppManager::GetRegisteredProducts(ProductDataVector* products) const {
ASSERT1(products);
CollectProductsFunc func(products, is_machine_, true);
return EnumerateSubKeys(
ConfigManager::Instance()->registry_clients(is_machine_),
&func);
}
HRESULT AppManager::GetUnRegisteredProducts(ProductDataVector* products) const {
ASSERT1(products);
CollectProductsFunc func(products, is_machine_, false);
return EnumerateSubKeys(
ConfigManager::Instance()->registry_client_state(is_machine_),
&func);
}
CString AppManager::GetProductClientKeyName(const GUID& app_guid) {
return goopdate_utils::GetAppClientsKey(is_machine_, GuidToString(app_guid));
}
CString AppManager::GetProductClientComponentsKeyName(const GUID& app_guid) {
return AppendRegKeyPath(GetProductClientKeyName(app_guid),
kComponentsRegKeyName);
}
CString AppManager::GetProductClientStateComponentsKeyName(
const GUID& app_guid) {
return AppendRegKeyPath(GetProductClientStateKeyName(app_guid),
kComponentsRegKeyName);
}
CString AppManager::GetComponentClientKeyName(const GUID& parent_app_guid,
const GUID& app_guid) {
return AppendRegKeyPath(GetProductClientComponentsKeyName(parent_app_guid),
GuidToString(app_guid));
}
CString AppManager::GetProductClientStateKeyName(const GUID& app_guid) {
return goopdate_utils::GetAppClientStateKey(is_machine_,
GuidToString(app_guid));
}
CString AppManager::GetComponentClientStateKeyName(const GUID& parent_app_guid,
const GUID& app_guid) {
return AppendRegKeyPath(
GetProductClientStateComponentsKeyName(parent_app_guid),
GuidToString(app_guid));
}
CString AppManager::GetProductClientStateMediumKeyName(const GUID& app_guid) {
ASSERT1(is_machine_);
return goopdate_utils::GetAppClientStateMediumKey(is_machine_,
GuidToString(app_guid));
}
CString AppManager::GetClientKeyName(const GUID& parent_app_guid,
const GUID& app_guid) {
if (::IsEqualGUID(parent_app_guid, GUID_NULL)) {
return GetProductClientKeyName(app_guid);
} else {
return GetComponentClientKeyName(parent_app_guid, app_guid);
}
}
CString AppManager::GetClientStateKeyName(const GUID& parent_app_guid,
const GUID& app_guid) {
if (::IsEqualGUID(parent_app_guid, GUID_NULL)) {
return GetProductClientStateKeyName(app_guid);
} else {
return GetComponentClientStateKeyName(parent_app_guid, app_guid);
}
}
HRESULT AppManager::OpenClientKey(const GUID& parent_app_guid,
const GUID& app_guid,
RegKey* client_key) {
ASSERT1(client_key);
return client_key->Open(GetClientKeyName(parent_app_guid, app_guid),
KEY_READ);
}
HRESULT AppManager::OpenClientStateKey(const GUID& parent_app_guid,
const GUID& app_guid,
REGSAM sam_desired,
RegKey* client_state_key) {
ASSERT1(client_state_key);
CString key_name = GetClientStateKeyName(parent_app_guid, app_guid);
return client_state_key->Open(key_name, sam_desired);
}
// Also creates the ClientStateMedium key for machine apps, ensuring it exists
// whenever ClientState exists. Does not create ClientStateMedium for Omaha.
// This method is called for self-updates, so it must explicitly avoid this.
HRESULT AppManager::CreateClientStateKey(const GUID& parent_app_guid,
const GUID& app_guid,
RegKey* client_state_key) {
ASSERT(::IsEqualGUID(parent_app_guid, GUID_NULL),
(_T("Legacy components not supported; ClientStateMedium ignores.")));
ASSERT1(client_state_key);
const CString key_name = GetClientStateKeyName(parent_app_guid, app_guid);
HRESULT hr = client_state_key->Create(key_name);
if (FAILED(hr)) {
CORE_LOG(L3, (_T("[RegKey::Create failed][0x%08x]"), hr));
return hr;
}
if (!is_machine_) {
return S_OK;
}
if (::IsEqualGUID(kGoopdateGuid, app_guid)) {
return S_OK;
}
const CString medium_key_name = GetProductClientStateMediumKeyName(app_guid);
hr = RegKey::CreateKey(medium_key_name);
if (FAILED(hr)) {
CORE_LOG(L3, (_T("[RegKey::Create ClientStateMedium failed][0x%08x]"), hr));
return hr;
}
return S_OK;
}
// Reads the following values from the registry:
// Clients Key
// product version
// language
// Client State Key
// previous product version.
// last checked
// ap
// client id
// iid
// Clients key in HKCU/HKLM/Low integrity
// did run
// Note: If the application is uninstalled, the clients key may not exist.
HRESULT AppManager::ReadProductDataFromStore(const GUID& app_guid,
ProductData* product_data) {
ASSERT1(product_data);
AppData app_data;
HRESULT hr = ReadAppDataFromStore(GUID_NULL, app_guid, &app_data);
if (FAILED(hr)) {
CORE_LOG(LE, (_T("[AppManager::ReadAppDataFromStore failed][0x%08x]"), hr));
return hr;
}
ProductData product(app_data);
// Read components for this product from the registry.
CollectComponentsFunc func(&product, is_machine_);
EnumerateSubKeys(GetProductClientComponentsKeyName(app_data.app_guid()),
&func);
*product_data = product;
return S_OK;
}
HRESULT AppManager::ReadAppDataFromStore(const GUID& parent_app_guid,
const GUID& app_guid,
AppData* app_data) {
ASSERT1(app_data);
AppData temp_data(app_guid, is_machine_);
temp_data.set_parent_app_guid(parent_app_guid);
bool client_key_exists = false;
RegKey client_key;
HRESULT hr = OpenClientKey(parent_app_guid, app_guid, &client_key);
if (SUCCEEDED(hr)) {
CString version;
hr = client_key.GetValue(kRegValueProductVersion, &version);
temp_data.set_version(version);
CORE_LOG(L3, (_T("[AppManager::ReadAppDataFromStore]")
_T("[parent_app_guid=%s]")
_T("[app_guid=%s]")
_T("[version=%s]"),
GuidToString(parent_app_guid),
GuidToString(app_guid),
version));
if (FAILED(hr)) {
return hr;
}
// Language might not be written by an installer, so ignore failures.
CString language;
client_key.GetValue(kRegValueLanguage, &language);
temp_data.set_language(language);
client_key_exists = true;
}
// If ClientState registry key doesn't exist, the function could return.
// Before opening the key, set days_since_last* to -1, which is the
// default value if reg key doesn't exist. If later we find that the values
// are readable, new values will overwrite current ones.
temp_data.set_days_since_last_active_ping(-1);
temp_data.set_days_since_last_roll_call(-1);
RegKey client_state_key;
hr = OpenClientStateKey(parent_app_guid,
app_guid,
KEY_READ,
&client_state_key);
if (FAILED(hr)) {
// It is possible that the client state key has not yet been populated.
// In this case just return the information that we have gathered thus far.
// However if both keys dont exist, then we are doing something wrong.
CORE_LOG(LW, (_T("[AppManager::ReadAppDataFromStore - No ClientState]")));
if (client_key_exists) {
*app_data = temp_data;
return S_OK;
} else {
return hr;
}
}
// The value is not essential for omaha's operation, so ignore errors.
CString previous_version;
HRESULT previous_version_hr =
client_state_key.GetValue(kRegValueProductVersion, &previous_version);
temp_data.set_previous_version(previous_version);
// An app is uninstalled if the Client key exists and the pv value in the
// ClientState key does not exist.
// Omaha may create the app's ClientState key and write values from the
// metainstaller tag before running the installer, which creates the Client
// key. Requiring pv in ClientState avoids mistakenly determining that the
// Omaha-created key indicates an uninstall.
bool app_is_uninstalled =
!client_key_exists &&
HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) != previous_version_hr;
temp_data.set_is_uninstalled(app_is_uninstalled);
CString ap;
client_state_key.GetValue(kRegValueAdditionalParams, &ap);
temp_data.set_ap(ap);
CString tt_token;
client_state_key.GetValue(kRegValueTTToken, &tt_token);
temp_data.set_tt_token(tt_token);
CString iid;
client_state_key.GetValue(kRegValueInstallationId, &iid);
temp_data.set_iid(StringToGuid(iid));
CString brand_code;
client_state_key.GetValue(kRegValueBrandCode, &brand_code);
ASSERT1(brand_code.GetLength() <= kBrandIdLength);
temp_data.set_brand_code(brand_code);
CString client_id;
client_state_key.GetValue(kRegValueClientId, &client_id);
temp_data.set_client_id(client_id);
DWORD last_active_ping_sec(0);
if (SUCCEEDED(client_state_key.GetValue(kRegValueActivePingDayStartSec,
&last_active_ping_sec))) {
int days_since_last_active_ping =
GetNumberOfDaysSince(static_cast<int32>(last_active_ping_sec));
temp_data.set_days_since_last_active_ping(days_since_last_active_ping);
}
DWORD last_roll_call_sec(0);
if (SUCCEEDED(client_state_key.GetValue(kRegValueRollCallDayStartSec,
&last_roll_call_sec))) {
int days_since_last_roll_call =
GetNumberOfDaysSince(static_cast<int32>(last_roll_call_sec));
temp_data.set_days_since_last_roll_call(days_since_last_roll_call);
}
// We do not need the referral_id.
ASSERT(::IsEqualGUID(parent_app_guid, GUID_NULL),
(_T("Legacy components not supported.")));
temp_data.set_install_time_diff_sec(GetInstallTimeDiffSec(app_guid));
temp_data.set_is_oem_install(client_state_key.HasValue(kRegValueOemInstall));
bool is_eula_accepted = true;
DWORD eula_accepted = 0;
ASSERT(::IsEqualGUID(parent_app_guid, GUID_NULL),
(_T("Legacy components not supported; IsAppEulaAccepted ignores.")));
temp_data.set_is_eula_accepted(
goopdate_utils::IsAppEulaAccepted(is_machine_,
GuidToString(app_guid),
false));
if (temp_data.language().IsEmpty()) {
// Read the language from the client state key if we did not find
// it in the client key.
CString language;
client_state_key.GetValue(kRegValueLanguage, &language);
temp_data.set_language(language);
}
// Read the did run value.
// TODO(omaha): Try to move this logic into the application_usage_data class.
ApplicationUsageData app_usage(is_machine_, vista_util::IsVistaOrLater());
hr = app_usage.ReadDidRun(GuidToString(app_guid));
if (FAILED(hr)) {
CORE_LOG(LEVEL_WARNING, (_T("[ReadDidRun failed][0x%08x]"), hr));
}
AppData::ActiveStates active = AppData::ACTIVE_NOTRUN;
if (app_usage.exists()) {
active = app_usage.did_run() ? AppData::ACTIVE_RUN :
AppData::ACTIVE_NOTRUN;
} else {
active = AppData::ACTIVE_UNKNOWN;
}
temp_data.set_did_run(active);
*app_data = temp_data;
return S_OK;
}
// Sets brand code, client ID, usage stats, browser, ap, and Omaha language in
// the app's ClientState. Also, sets the installed by OEM flag if appropriate.
// Sets eulaaccepted=0 if the app is not already registered and the app's EULA
// has not been accepted. Deletes eulaaccepted if the EULA has been accepted.
// Only call for initial or over-installs. Do not call for updates to avoid
// mistakenly replacing data, such as the application's language, and causing
// unexpected changes to the app during a silent update.
HRESULT AppManager::WritePreInstallData(const AppData& app_data) {
CORE_LOG(L3, (_T("[AppManager::WritePreInstallData]")));
RegKey client_state_key;
HRESULT hr = CreateClientStateKey(app_data.parent_app_guid(),
app_data.app_guid(),
&client_state_key);
if (FAILED(hr)) {
return hr;
}
if (app_data.is_eula_accepted()) {
hr = goopdate_utils::ClearAppEulaNotAccepted(
is_machine_,
GuidToString(app_data.app_guid()));
} else {
if (!IsProductRegistered(app_data.app_guid())) {
hr = goopdate_utils::SetAppEulaNotAccepted(
is_machine_,
GuidToString(app_data.app_guid()));
}
}
if (FAILED(hr)) {
return hr;
}
CString state_key_path = GetClientStateKeyName(app_data.parent_app_guid(),
app_data.app_guid());
VERIFY1(SUCCEEDED(goopdate_utils::SetAppBranding(state_key_path,
app_data.brand_code(),
app_data.client_id(),
app_data.referral_id())));
ASSERT(::IsEqualGUID(app_data.parent_app_guid(), GUID_NULL),
(_T("Legacy components not supported; SetUsageStatsEnable ignores.")));
if (TRISTATE_NONE != app_data.usage_stats_enable()) {
VERIFY1(SUCCEEDED(goopdate_utils::SetUsageStatsEnable(
is_machine_,
GuidToString(app_data.app_guid()),
app_data.usage_stats_enable())));
}
if (BROWSER_UNKNOWN == app_data.browser_type()) {
VERIFY1(SUCCEEDED(client_state_key.DeleteValue(kRegValueBrowser)));
} else {
DWORD browser_type = app_data.browser_type();
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueBrowser,
browser_type)));
}
if (app_data.ap().IsEmpty()) {
VERIFY1(SUCCEEDED(
client_state_key.DeleteValue(kRegValueAdditionalParams)));
} else {
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueAdditionalParams,
app_data.ap())));
}
if (!app_data.language().IsEmpty()) {
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueLanguage,
app_data.language())));
}
if (ConfigManager::Instance()->IsOemInstalling(is_machine_)) {
ASSERT1(is_machine_);
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueOemInstall, _T("1"))));
}
return S_OK;
}
// Sets installation ID in the app's ClientState.
// Copies version and language from clients to client state reg key.
HRESULT AppManager::InitializeApplicationState(AppData* app_data) {
CORE_LOG(L3, (_T("[AppManager::InitializeApplicationState]")));
ASSERT1(app_data);
RegKey client_state_key;
HRESULT hr = CreateClientStateKey(app_data->parent_app_guid(),
app_data->app_guid(),
&client_state_key);
if (FAILED(hr)) {
return hr;
}
RegKey client_key;
hr = OpenClientKey(app_data->parent_app_guid(),
app_data->app_guid(),
&client_key);
if (FAILED(hr)) {
return hr;
}
if (!::IsEqualGUID(app_data->iid(), GUID_NULL)) {
VERIFY1(SUCCEEDED(client_state_key.SetValue(
kRegValueInstallationId,
GuidToString(app_data->iid()))));
}
return CopyVersionAndLanguageToClientState(app_data,
client_state_key,
client_key);
}
// Copies language and version from clients into client state reg key.
// Removes installation id, if did run = true or if goopdate.
// Clears did run.
// Also updates the last active ping and roll call time in registry
// if the corresponding ping was sent to server.
HRESULT AppManager::UpdateApplicationState(int time_since_midnight_sec,
AppData* app_data) {
ASSERT1(app_data);
RegKey client_state_key;
HRESULT hr = CreateClientStateKey(app_data->parent_app_guid(),
app_data->app_guid(),
&client_state_key);
if (FAILED(hr)) {
return hr;
}
RegKey client_key;
hr = OpenClientKey(app_data->parent_app_guid(),
app_data->app_guid(),
&client_key);
if (FAILED(hr)) {
return hr;
}
hr = CopyVersionAndLanguageToClientState(app_data,
client_state_key,
client_key);
if (FAILED(hr)) {
return hr;
}
// Handle the installation id.
hr = ClearInstallationId(app_data, client_state_key);
if (FAILED(hr)) {
return hr;
}
SetLastPingDayStartTime(time_since_midnight_sec,
app_data,
client_state_key);
// Reset did_run after updating the days_since_last_active_ping since that
// need previous did_run status to determine whether an active ping has been
// sent.
ResetDidRun(app_data);
return S_OK;
}
HRESULT AppManager::WriteTTToken(const AppData& app_data,
const UpdateResponseData& response_data) {
CORE_LOG(L3, (_T("[WriteTTToken][app_data token=%s][response_data token=%s]"),
app_data.tt_token(), response_data.tt_token()));
RegKey client_state_key;
HRESULT hr = CreateClientStateKey(app_data.parent_app_guid(),
app_data.app_guid(),
&client_state_key);
if (FAILED(hr)) {
return hr;
}
if (response_data.tt_token().IsEmpty()) {
VERIFY1(SUCCEEDED(client_state_key.DeleteValue(kRegValueTTToken)));
} else {
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueTTToken,
response_data.tt_token())));
}
return S_OK;
}
// The registry reads and writes are not thread safe, but there should not be
// other threads or processes calling this at the same time.
void AppManager::UpdateUpdateAvailableStats(const GUID& parent_app_guid,
const GUID& app_guid) {
RegKey state_key;
HRESULT hr = CreateClientStateKey(parent_app_guid, app_guid, &state_key);
if (FAILED(hr)) {
ASSERT1(false);
return;
}
DWORD update_available_count(0);
hr = state_key.GetValue(kRegValueUpdateAvailableCount,
&update_available_count);
if (FAILED(hr)) {
update_available_count = 0;
}
++update_available_count;
VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueUpdateAvailableCount,
update_available_count)));
DWORD64 update_available_since_time(0);
hr = state_key.GetValue(kRegValueUpdateAvailableSince,
&update_available_since_time);
if (FAILED(hr)) {
// There is no existing value, so this must be the first update notice.
VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueUpdateAvailableSince,
GetCurrent100NSTime())));
// TODO(omaha): It would be nice to report the version that we were first
// told to update to. This is available in UpdateResponse but we do not
// currently send it down in update responses. If we start using it, add
// kRegValueFirstUpdateResponseVersion.
}
}
void AppManager::ClearUpdateAvailableStats(const GUID& parent_app_guid,
const GUID& app_guid) {
RegKey state_key;
HRESULT hr =
OpenClientStateKey(parent_app_guid, app_guid, KEY_ALL_ACCESS, &state_key);
if (FAILED(hr)) {
return;
}
VERIFY1(SUCCEEDED(state_key.DeleteValue(kRegValueUpdateAvailableCount)));
VERIFY1(SUCCEEDED(state_key.DeleteValue(kRegValueUpdateAvailableSince)));
}
void AppManager::ClearOemInstalled(const GUID& parent_app_guid,
const GUID& app_guid) {
RegKey state_key;
HRESULT hr =
OpenClientStateKey(parent_app_guid, app_guid, KEY_ALL_ACCESS, &state_key);
ASSERT1(SUCCEEDED(hr));
if (FAILED(hr)) {
return;
}
VERIFY1(SUCCEEDED(state_key.DeleteValue(kRegValueOemInstall)));
}
// Returns 0 for any values that are not found.
void AppManager::ReadUpdateAvailableStats(
const GUID& parent_app_guid,
const GUID& app_guid,
DWORD* update_responses,
DWORD64* time_since_first_response_ms) {
ASSERT1(update_responses);
ASSERT1(time_since_first_response_ms);
*update_responses = 0;
*time_since_first_response_ms = 0;
RegKey state_key;
HRESULT hr = OpenClientStateKey(parent_app_guid,
app_guid,
KEY_READ,
&state_key);
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[App ClientState key does not exist][%s]"),
GuidToString(app_guid)));
return;
}
DWORD update_responses_in_reg(0);
hr = state_key.GetValue(kRegValueUpdateAvailableCount,
&update_responses_in_reg);
if (SUCCEEDED(hr)) {
*update_responses = update_responses_in_reg;
}
DWORD64 update_available_since_time(0);
hr = state_key.GetValue(kRegValueUpdateAvailableSince,
&update_available_since_time);
if (SUCCEEDED(hr)) {
const DWORD64 current_time = GetCurrent100NSTime();
ASSERT1(update_available_since_time <= current_time);
const DWORD64 time_since_first_response_in_100ns =
current_time - update_available_since_time;
*time_since_first_response_ms =
time_since_first_response_in_100ns / kMillisecsTo100ns;
}
}
// The update success and update check success times are not updated for
// installs even if it is an over-install. At this point, we do not know for
// sure that it was an online update.
void AppManager::RecordSuccessfulInstall(const GUID& parent_app_guid,
const GUID& app_guid,
bool is_update,
bool is_offline) {
ASSERT1(!is_update || !is_offline);
ClearUpdateAvailableStats(parent_app_guid, app_guid);
if (!is_offline) {
// Assumes that all updates are online.
RecordSuccessfulUpdateCheck(parent_app_guid, app_guid);
}
if (is_update) {
RegKey state_key;
HRESULT hr = CreateClientStateKey(parent_app_guid, app_guid, &state_key);
if (FAILED(hr)) {
ASSERT1(false);
return;
}
const DWORD now = Time64ToInt32(GetCurrent100NSTime());
VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueLastUpdateTimeSec, now)));
}
}
void AppManager::RecordSuccessfulUpdateCheck(const GUID& parent_app_guid,
const GUID& app_guid) {
RegKey state_key;
HRESULT hr = CreateClientStateKey(parent_app_guid, app_guid, &state_key);
if (FAILED(hr)) {
ASSERT1(false);
return;
}
const DWORD now = Time64ToInt32(GetCurrent100NSTime());
VERIFY1(SUCCEEDED(state_key.SetValue(kRegValueLastSuccessfulCheckSec, now)));
}
// Assumes the app is registered and has a ClientState. The new install case
// should be handled differently.
uint32 AppManager::GetInstallTimeDiffSec(const GUID& app_guid) {
RegKey client_state_key;
HRESULT hr = OpenClientStateKey(GUID_NULL,
app_guid,
KEY_READ,
&client_state_key);
ASSERT(SUCCEEDED(hr), (_T("Assumes app is registered.")));
if (FAILED(hr)) {
return 0;
}
DWORD install_time(0);
DWORD install_time_diff_sec(0);
if (SUCCEEDED(client_state_key.GetValue(kRegValueInstallTimeSec,
&install_time))) {
const uint32 now = Time64ToInt32(GetCurrent100NSTime());
if (0 != install_time && now >= install_time) {
install_time_diff_sec = now - install_time;
// TODO(omaha3): Restore this assert. In Omaha 2, this method gets called
// as part of installation verification and Job::UpdateJob(), so the value
// can be 0. This will not be the case in Omaha 3.
// ASSERT1(install_time_diff_sec != 0);
}
}
return install_time_diff_sec;
}
// Clear the Installation ID if at least one of the conditions is true:
// 1) DidRun==yes. First run is the last time we want to use the Installation
// ID. So delete Installation ID if it is present.
// 2) kMaxLifeOfInstallationIDSec has passed since the app was installed. This
// is to ensure that Installation ID is cleared even if DidRun is never set.
// 3) The app is Omaha. Always delete Installation ID if it is present
// because DidRun does not apply.
HRESULT AppManager::ClearInstallationId(AppData* app_data,
const RegKey& client_state_key) {
ASSERT1(app_data);
if (::IsEqualGUID(app_data->iid(), GUID_NULL)) {
return S_OK;
}
if ((AppData::ACTIVE_RUN == app_data->did_run()) ||
(kMaxLifeOfInstallationIDSec <= app_data->install_time_diff_sec()) ||
(::IsEqualGUID(kGoopdateGuid, app_data->app_guid()))) {
CORE_LOG(L1, (_T("[Deleting iid for app][%s]"),
GuidToString(app_data->app_guid())));
// Relies on installation_id not empty to indicate state_key is valid.
VERIFY1(S_OK == client_state_key.DeleteValue(kRegValueInstallationId));
app_data->set_iid(GUID_NULL);
}
return S_OK;
}
void AppManager::ResetDidRun(AppData* app_data) {
ApplicationUsageData app_usage(app_data->is_machine_app(),
vista_util::IsVistaOrLater());
VERIFY1(SUCCEEDED(app_usage.ResetDidRun(GuidToString(app_data->app_guid()))));
app_data->set_did_run(app_usage.exists() ? AppData::ACTIVE_NOTRUN :
AppData::ACTIVE_UNKNOWN);
}
// Write the day start time when last active ping/roll call happened to
// registry.
void AppManager::SetLastPingDayStartTime(int time_since_midnight_sec,
AppData* app_data,
const RegKey& client_state_key) {
ASSERT1(time_since_midnight_sec >= 0);
ASSERT1(time_since_midnight_sec < kMaxTimeSinceMidnightSec);
int now = Time64ToInt32(GetCurrent100NSTime());
bool did_send_active_ping = (app_data->did_run() == AppData::ACTIVE_RUN &&
app_data->days_since_last_active_ping() != 0);
if (did_send_active_ping) {
VERIFY1(SUCCEEDED(client_state_key.SetValue(
kRegValueActivePingDayStartSec,
static_cast<DWORD>(now - time_since_midnight_sec))));
app_data->set_days_since_last_active_ping(0);
}
bool did_send_roll_call = (app_data->days_since_last_roll_call() != 0);
if (did_send_roll_call) {
VERIFY1(SUCCEEDED(client_state_key.SetValue(
kRegValueRollCallDayStartSec,
static_cast<DWORD>(now - time_since_midnight_sec))));
app_data->set_days_since_last_roll_call(0);
}
}
// Only replaces the language in ClientState and app_data if the language in
// Clients is not empty.
HRESULT AppManager::CopyVersionAndLanguageToClientState(
AppData* app_data,
const RegKey& client_state_key,
const RegKey& client_key) {
// TODO(omaha): need to handle components too.
ASSERT1(app_data);
// Read the version and language from the client key.
CString version;
HRESULT hr = client_key.GetValue(kRegValueProductVersion, &version);
if (FAILED(hr)) {
return hr;
}
CString language;
client_key.GetValue(kRegValueLanguage, &language);
// Write the version and language in the client state key.
hr = client_state_key.SetValue(kRegValueProductVersion, version);
if (FAILED(hr)) {
return hr;
}
app_data->set_version(version);
app_data->set_previous_version(version);
if (!language.IsEmpty()) {
VERIFY1(SUCCEEDED(client_state_key.SetValue(kRegValueLanguage, language)));
app_data->set_language(language);
}
return S_OK;
}
HRESULT AppManager::RemoveClientState(const AppData& app_data) {
ASSERT(::IsEqualGUID(app_data.parent_app_guid(), GUID_NULL),
(_T("Legacy components not supported; ClientStateMedium ignores.")));
ASSERT1(app_data.is_uninstalled());
const CString state_key = GetClientStateKeyName(app_data.parent_app_guid(),
app_data.app_guid());
HRESULT state_hr = RegKey::DeleteKey(state_key, true);
if (!is_machine_) {
return state_hr;
}
const CString state_medium_key =
GetProductClientStateMediumKeyName(app_data.app_guid());
HRESULT state_medium_hr = RegKey::DeleteKey(state_medium_key, true);
return FAILED(state_hr) ? state_hr : state_medium_hr;
}
// Returns true if the absolute difference between time moments is greater than
// the interval between update checks.
// Deals with clocks rolling backwards, in scenarios where the clock indicates
// some time in the future, for example next year, last_checked_ is updated to
// reflect that time, and then the clock is adjusted back to present.
bool AppManager::ShouldCheckForUpdates() const {
ConfigManager* cm = ConfigManager::Instance();
bool is_period_overridden = false;
const int update_interval = cm->GetLastCheckPeriodSec(&is_period_overridden);
if (0 == update_interval) {
ASSERT1(is_period_overridden);
OPT_LOG(L1, (_T("[ShouldCheckForUpdates returned 0][checks disabled]")));
return false;
}
const int time_difference = cm->GetTimeSinceLastCheckedSec(is_machine_);
const bool result = time_difference >= update_interval ? true : false;
CORE_LOG(L3, (_T("[ShouldCheckForUpdates returned %d][%u]"),
result, is_period_overridden));
return result;
}
HRESULT AppManager::UpdateLastChecked() {
// Set the last check value to the current value.
DWORD now = Time64ToInt32(GetCurrent100NSTime());
HRESULT hr = ConfigManager::Instance()->SetLastCheckedTime(is_machine_, now);
CORE_LOG(L3, (_T("[AppManager::UpdateLastChecked][now %d]"), now));
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[UpdateLastChecked returned 0x%08x]"), hr));
return hr;
}
return S_OK;
}
HRESULT AppManager::ReadProductDataFromUserOrMachineStore(
const GUID& guid,
ProductData* product_data) {
ASSERT1(product_data);
AppManager app_manager_user(false);
HRESULT hr = app_manager_user.ReadProductDataFromStore(guid, product_data);
if (SUCCEEDED(hr)) {
return hr;
}
AppManager app_manager_machine(true);
return app_manager_machine.ReadProductDataFromStore(guid, product_data);
}
// Writes 0.0.0.1 to pv. This value avoids any special cases, such as initial
// install rules, for 0.0.0.0, while being unlikely to be higher than the
// product's actual current version.
HRESULT AppManager::RegisterProduct(const GUID& product_guid,
const CString& product_name) {
const TCHAR* const kRegisterProductVersion = _T("0.0.0.1");
RegKey client_key;
HRESULT hr = client_key.Create(GetClientKeyName(GUID_NULL, product_guid));
if (FAILED(hr)) {
return hr;
}
hr = client_key.SetValue(kRegValueProductVersion, kRegisterProductVersion);
if (FAILED(hr)) {
return hr;
}
// AppName is not a required parameter since it's only used for being able to
// easily tell what application is there when reading the registry.
VERIFY1(SUCCEEDED(client_key.SetValue(kRegValueAppName, product_name)));
return S_OK;
}
HRESULT AppManager::UnregisterProduct(const GUID& product_guid) {
return RegKey::DeleteKey(GetClientKeyName(GUID_NULL, product_guid), true);
}
} // namespace omaha.