blob: e85f1a2524c9e1e540606e6964c28455c905afc4 [file] [log] [blame]
// Copyright 2007-2009 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.
// ========================================================================
//
// TODO(omaha): maybe introduce a new logging facility: WORK_LOG.
// TODO(omaha): Dig out the RefHolder in scope_guard.h so we can use const
// references instead pointers. This TODO was added for some code that no longer
// exists, but it is still a good idea.
#include "omaha/worker/worker.h"
#include <atlbase.h>
#include <atlstr.h>
#include <atlapp.h>
#include <atlsecurity.h>
#include "omaha/common/app_util.h"
#include "omaha/common/const_addresses.h"
#include "omaha/common/const_object_names.h"
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/firewall_product_detection.h"
#include "omaha/common/logging.h"
#include "omaha/common/module_utils.h"
#include "omaha/common/omaha_version.h"
#include "omaha/common/reactor.h"
#include "omaha/common/scope_guard.h"
#include "omaha/common/scoped_ptr_address.h"
#include "omaha/common/shutdown_handler.h"
#include "omaha/common/sta_call.h"
#include "omaha/common/system.h"
#include "omaha/common/string.h"
#include "omaha/common/thread_pool.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.h"
#include "omaha/goopdate/goopdate_utils.h"
#include "omaha/goopdate/resource.h"
#include "omaha/goopdate/stats_uploader.h"
#include "omaha/goopdate/ui_displayed_event.h"
#include "omaha/net/network_config.h"
#include "omaha/net/network_request.h"
#include "omaha/setup/setup.h"
#include "omaha/worker/application_manager.h"
#include "omaha/worker/ping.h"
#include "omaha/worker/ping_utils.h"
#include "omaha/worker/worker_event_logger.h"
#include "omaha/worker/worker_job.h"
#include "omaha/worker/worker_metrics.h"
#include "omaha/worker/ui.h"
// Since the net code is linked in as a lib, force the registration code to
// be a dependency, otherwise the linker is optimizing in out.
//
// TODO(omaha): fix this clunkiness and require explicit registration of the
// http creators with the factory. Not ideal but better then linker options.
//
// Design Notes:
// Following are the mutexes that are taken by the worker
// 1. SingleUpdateWorker. Only taken by the update worker.
// 2. SingleInstallWorker. This is application specific. Only taken by the
// install worker and for the specific application.
// 3. Before install, the install manager takes the global install lock.
// 4. A key thing to add to this code is after taking the install lock,
// to validate that the version of the applicaion that is present in the
// registry is the same as that we queried for. The reason to do this
// is to ensure that there are no races between update and install workers.
// 5. Termination of the worker happens because of four reasons:
// a. Shutdown event - Only applicable to the update worker. When this event
// is signalled, the main thread comes out of the wait. It then tries to
// destroy the contained thread pool, which causes a timed wait for the
// worker thread. The worker thread is notified by setting a
// cancelled flag on the worker.
// b. Install completes, user closes UI - Only applicable for the
// interactive installs. In this case the main thread comes out of
// the message loop and deletes the thread pool. The delete happens
// immediately, since the worker is doing nothing.
// c. User cancels install - Only applicable in case if interactive installs.
// The main thread sets the cancelled flag on the workerjob and comes out
// of the message loop. It then tries to delete the thread pool, causing
// a timed wait. The worker job queries the cancelled flag periodically
// and quits as soon as possible.
// d. The update worker completes - In this case we do not run on a thread
// pool.
#pragma comment(linker, "/INCLUDE:_kRegisterWinHttp")
namespace omaha {
namespace {
uint64 GetGuidMostSignificantUint64(const GUID& guid) {
return (static_cast<uint64>(guid.Data1) << 32) +
(static_cast<uint64>(guid.Data2) << 16) +
static_cast<uint64>(guid.Data3);
}
class WorkItem : public UserWorkItem {
public:
WorkItem(WorkerJob* worker_job, bool delete_after_run)
: worker_job_(worker_job),
delete_after_run_(delete_after_run) {
ASSERT1(worker_job);
}
private:
void DoProcess() {
worker_job_->DoProcess();
if (delete_after_run_) {
delete worker_job_;
worker_job_ = NULL;
}
}
WorkerJob* worker_job_;
bool delete_after_run_;
DISALLOW_EVIL_CONSTRUCTORS(WorkItem);
};
class ErrorWndEvents : public ProgressWndEvents {
public:
virtual void DoPause() {}
virtual void DoResume() {}
virtual void DoClose() {}
virtual void DoRestartBrowsers() {}
virtual void DoReboot() {}
virtual void DoLaunchBrowser(const CString& url) {
VERIFY1(SUCCEEDED(goopdate_utils::LaunchBrowser(BROWSER_DEFAULT, url)));
}
};
// Users with UAC disabled may need to be updated to a new build in 2010. Set
// the ap so we can make sure they get updated. See http://b/2194722.
// Do not replace any existing APs.
// TODO(omaha): Remove this code after June 2010.
void Set2010UpdateAp(bool is_machine) {
const TCHAR* const k2010UpdateAp = _T("2010update");
// Only shell 1.2.131.7 is affected.
const ULONGLONG kAffectedShellVersion = 0x0001000200830007;
if (!vista_util::IsVistaOrLater()) {
return;
}
if (!vista_util::IsUACDisabled()) {
return;
}
const CString key =
ConfigManager::Instance()->registry_client_state_goopdate(is_machine);
if (RegKey::HasValue(key, kRegValueAdditionalParams)) {
return;
}
CString shell_path;
if (FAILED(GetModuleFileName(NULL, &shell_path))) {
return;
}
if (app_util::GetVersionFromFile(shell_path) != kAffectedShellVersion) {
return;
}
VERIFY1(SUCCEEDED(
RegKey::SetValue(key, kRegValueAdditionalParams, k2010UpdateAp)));
}
} // namespace
namespace internal {
void RecordUpdateAvailableUsageStats(bool is_machine) {
AppManager app_manager(is_machine);
DWORD update_responses(0);
DWORD64 time_since_first_response_ms(0);
app_manager.ReadUpdateAvailableStats(GUID_NULL,
kGoopdateGuid,
&update_responses,
&time_since_first_response_ms);
if (update_responses) {
metric_worker_self_update_responses = update_responses;
}
if (time_since_first_response_ms) {
metric_worker_self_update_response_time_since_first_ms =
time_since_first_response_ms;
}
ProductDataVector products;
HRESULT hr = app_manager.GetRegisteredProducts(&products);
if (FAILED(hr)) {
ASSERT1(false);
return;
}
// These store information about the app with the most update responses.
GUID max_responses_product(GUID_NULL);
DWORD max_responses(0);
DWORD64 max_responses_time_since_first_response_ms(0);
for (size_t i = 0; i < products.size(); ++i) {
const ProductData& product_data = products[i];
const GUID& product_guid = product_data.app_data().app_guid();
if (::IsEqualGUID(kGoopdateGuid, product_guid)) {
continue;
}
DWORD update_responses(0);
DWORD64 time_since_first_response_ms(0);
app_manager.ReadUpdateAvailableStats(GUID_NULL,
product_guid,
&update_responses,
&time_since_first_response_ms);
if (max_responses < update_responses) {
max_responses_product = product_guid;
max_responses = update_responses;
max_responses_time_since_first_response_ms = time_since_first_response_ms;
}
}
if (max_responses) {
metric_worker_app_max_update_responses_app_high =
GetGuidMostSignificantUint64(max_responses_product);
metric_worker_app_max_update_responses = max_responses;
metric_worker_app_max_update_responses_ms_since_first =
max_responses_time_since_first_response_ms;
}
}
void SendSelfUpdateFailurePing(bool is_machine) {
DWORD self_update_error_code(0);
DWORD self_update_extra_code1(0);
CString self_update_version;
if (!Setup::ReadAndClearUpdateErrorInfo(is_machine,
&self_update_error_code,
&self_update_extra_code1,
&self_update_version)) {
return;
}
Ping ping;
HRESULT hr = ping_utils::SendGoopdatePing(
is_machine,
CommandLineExtraArgs(),
_T(""),
PingEvent::EVENT_SETUP_UPDATE_FAILURE,
self_update_error_code,
self_update_extra_code1,
self_update_version,
&ping);
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[SendGoopdatePing failed][0x%08x]"), hr));
// TODO(omaha): Consider writing the values back with
// Setup::PersistUpdateErrorInfo(so we can try pinging later. This would be
// useful if the user happens to be offline when update worker runs.
}
}
HRESULT SendFinalUninstallPingForApps(bool is_machine) {
CORE_LOG(L2, (_T("[SendFinalUninstallPingForApps]")));
scoped_ptr<Request> uninstall_ping;
HRESULT hr = BuildUninstallPing(is_machine, address(uninstall_ping));
if (SUCCEEDED(hr) && uninstall_ping->get_request_count()) {
Ping ping;
hr = ping.SendPing(uninstall_ping.get());
}
return hr;
}
} // namespace internal
Worker::Worker(bool is_machine)
: is_machine_(is_machine),
is_local_system_(false),
has_uninstalled_(false) {
CORE_LOG(L1, (_T("[Worker::Worker]")));
}
Worker::~Worker() {
CORE_LOG(L1, (_T("[Worker::~Worker]")));
// Ensure the threads are cleaned up regardless of the entry point or flow.
StopWorker(NULL);
CollectAmbientUsageStats();
}
// This method could be called multiple times, and thus needs to be idempotent.
// worker_job_error_code can be NULL.
void Worker::StopWorker(HRESULT* worker_job_error_code) {
// Stop the concurrent objects to avoid spurious events.
shutdown_handler_.reset();
reactor_.reset();
if (worker_job_.get()) {
worker_job_->Cancel();
}
// The thread pool destructor waits for any remaining jobs to complete.
thread_pool_.reset();
if (worker_job_.get() && worker_job_error_code) {
*worker_job_error_code = worker_job_->error_code();
}
// Uninstall should happen as late as possible. Since uninstall may occur
// after this method, destroy all objects now.
worker_job_.reset();
}
// This method must not depend on Omaha being registered because that may not
// occur until DoRun.
// Assumes is_machine_ is set correctly.
HRESULT Worker::Main(Goopdate* goopdate) {
ASSERT1(goopdate);
args_ = goopdate->args();
cmd_line_ = goopdate->cmd_line();
is_local_system_ = goopdate->is_local_system();
HRESULT hr = DoRun();
// If this is an interactive instance, UI should have been shown either by
// WorkerJob or an error.
ASSERT1(UIDisplayedEventManager::HasUIBeenDisplayed(is_machine_) ||
(args_.mode != COMMANDLINE_MODE_IG &&
args_.mode != COMMANDLINE_MODE_HANDOFF_INSTALL) ||
args_.is_silent_set);
// Stop WorkerJob and thread pool so we can get the final error code.
HRESULT worker_job_error_code(S_OK);
StopWorker(&worker_job_error_code);
// Get the error code from WorkerJob thread to return to the caller.
if (SUCCEEDED(hr)) {
hr = worker_job_error_code;
}
if (FAILED(hr) && args_.mode == COMMANDLINE_MODE_IG) {
MaybeUninstallGoogleUpdate();
}
return hr;
}
// This method must not depend on any Omaha "registration" because Google Update
// may not be completely installed yet.
HRESULT Worker::DoRun() {
OPT_LOG(L1, (_T("[Worker::DoRun]")));
HRESULT hr = InitializeThreadPool();
if (FAILED(hr)) {
return hr;
}
// The /install case does not really use the Worker and needs to be moved
// outside. This will be done as part of unifying Setup.
if (COMMANDLINE_MODE_INSTALL == args_.mode) {
return DoInstallGoogleUpdateAndApp();
}
if ((COMMANDLINE_MODE_IG == args_.mode ||
COMMANDLINE_MODE_HANDOFF_INSTALL == args_.mode) &&
!args_.is_silent_set) {
HRESULT hr = InitializeUI();
if (FAILED(hr)) {
CString error_text;
error_text.FormatMessage(IDS_INSTALL_FAILED, hr);
DisplayErrorInMessageBox(error_text);
return hr;
}
if (!args_.is_offline_set) {
CString caption;
caption.FormatMessage(IDS_WINDOW_TITLE, GetPrimaryJobInfo().app_name);
CString message;
message.LoadString(IDS_PROXY_PROMPT_MESSAGE);
const uint32 kProxyMaxPrompts = 1;
NetworkConfig::Instance().ConfigureProxyAuth(caption,
message,
*progress_wnd_,
kProxyMaxPrompts);
}
}
worker_job_.reset(WorkerJobFactory::CreateWorkerJob(is_machine_,
args_,
job_observer_.get()));
// TODO(omaha): It would be nice if we could move all the differences
// between installs and updates into the WorkerJobStrategy and just call
// StartWorkerJob at this point.
#pragma warning(push)
// C4061: enumerator 'xxx' in switch of enum 'yyy' is not explicitly handled by
// a case label.
#pragma warning(disable : 4061)
switch (args_.mode) {
case COMMANDLINE_MODE_IG:
case COMMANDLINE_MODE_HANDOFF_INSTALL:
hr = DoInstall();
break;
case COMMANDLINE_MODE_UA:
hr = DoUpdateApps();
OPT_LOG(L2, (_T("[Update worker finished]")));
break;
default:
ASSERT(false, (_T("Nothing to do in the worker.")));
hr = E_UNEXPECTED;
}
#pragma warning(pop)
return hr;
}
bool Worker::EnsureSingleAppInstaller(const CString& guid) {
// We allow only one instance of interactive install per application.
CString mutex_name;
mutex_name.Format(kSingleInstallWorker, guid);
single_install_worker_.reset(new ProgramInstance(mutex_name));
return !single_install_worker_->EnsureSingleInstance();
}
bool Worker::EnsureSingleUpdateWorker() {
// We allow only one instance of the update worker per user.
NamedObjectAttributes single_update_worker_attr;
GetNamedObjectAttributes(kSingleupdateWorker,
is_machine_,
&single_update_worker_attr);
single_update_worker_.reset(new ProgramInstance(
single_update_worker_attr.name));
return !single_update_worker_->EnsureSingleInstance();
}
HRESULT Worker::InitializeUI() {
OPT_LOG(L1, (_T("[InitializeUI]")));
ASSERT1((COMMANDLINE_MODE_IG == args_.mode ||
COMMANDLINE_MODE_HANDOFF_INSTALL == args_.mode ||
COMMANDLINE_MODE_INSTALL == args_.mode) &&
!args_.is_silent_set);
progress_wnd_.reset(new ProgressWnd(&message_loop_, NULL));
progress_wnd_->set_is_machine(is_machine_);
progress_wnd_->set_language(args_.extra.language);
ASSERT1(!args_.extra.apps.empty());
progress_wnd_->set_product_name(GetPrimaryJobInfo().app_name);
progress_wnd_->set_product_guid(GetPrimaryJobInfo().app_guid);
progress_wnd_->set_iid(args_.extra.installation_id);
progress_wnd_->set_brand_code(args_.extra.brand_code);
JobObserverCallMethodDecorator* decorator =
new JobObserverCallMethodDecorator(progress_wnd_.get());
job_observer_.reset(decorator);
HRESULT hr = decorator->Initialize();
if (FAILED(hr)) {
OPT_LOG(L1, (_T("JobObserver initialize failed][0x%08x]"), hr));
return hr;
}
return S_OK;
}
HRESULT Worker::DoInstall() {
OPT_LOG(L1, (_T("[DoInstall]")));
const bool is_already_installing =
EnsureSingleAppInstaller(GuidToString(GetPrimaryJobInfo().app_guid));
if (is_already_installing) {
OPT_LOG(L1, (_T("[Another Install of this application is running in the ")
_T("same session. Exiting.]")));
++metric_worker_another_install_in_progress;
HRESULT hr = GOOPDATE_E_APP_BEING_INSTALLED;
CString error_text;
error_text.FormatMessage(IDS_APPLICATION_ALREADY_INSTALLING,
GetPrimaryJobInfo().app_name);
DisplayError(error_text, hr);
return hr;
}
HRESULT hr = StartWorkerJob();
if (FAILED(hr)) {
CString error_text;
error_text.FormatMessage(IDS_INSTALL_FAILED, hr);
DisplayError(error_text, hr);
return hr;
}
return S_OK;
}
HRESULT Worker::InitializeShutDownHandler(ShutdownCallback* callback) {
CORE_LOG(L3, (_T("[InitializeShutDownHandler]")));
ASSERT1(callback);
reactor_.reset(new Reactor);
shutdown_handler_.reset(new ShutdownHandler);
return shutdown_handler_->Initialize(reactor_.get(),
callback,
is_machine_);
}
HRESULT Worker::DoUpdateApps() {
OPT_LOG(L1, (_T("[DoUpdateApps]")));
WriteUpdateAppsWorkerStartEvent(is_machine_);
if (EnsureSingleUpdateWorker()) {
OPT_LOG(L1, (_T("[Another worker is already running. Exiting.]")));
++metric_worker_another_update_in_progress;
return GOOPDATE_E_WORKER_ALREADY_RUNNING;
}
HRESULT hr = InitializeShutDownHandler(this);
if (FAILED(hr)) {
CORE_LOG(LE, (_T("[InitializeShutDownHandler failed][0x%08x]"), hr));
return hr;
}
internal::RecordUpdateAvailableUsageStats(is_machine_);
// A tentative uninstall check is done here. There are stronger checks,
// protected by locks, which are done by Setup.
size_t num_clients(0);
const bool is_uninstall =
FAILED(goopdate_utils::GetNumClients(is_machine_, &num_clients)) ||
num_clients <= 1;
CORE_LOG(L4, (_T("[Worker::DoUpdateApps][%u]"), num_clients));
if (is_uninstall) {
// Attempt a conditional uninstall and always return S_OK to avoid
// executing error handling code in the case of an actual uninstall.
// Do not attempt to uninstall if MSI is busy to avoid spurious uninstalls.
// See http://b/1436223. The call to WaitForMSIExecute blocks with a
// timeout. It is better to block here than block while holding the setup
// lock.
hr = WaitForMSIExecute(kWaitForMSIExecuteMs);
CORE_LOG(L2, (_T("[WaitForMSIExecute returned 0x%08x]"), hr));
if (SUCCEEDED(hr)) {
// Destroy all the objects before uninstalling.
StopWorker(NULL);
MaybeUninstallGoogleUpdate();
}
return S_OK;
}
hr = StartWorkerJob();
internal::SendSelfUpdateFailurePing(is_machine_);
return hr;
}
HRESULT Worker::DoInstallGoogleUpdateAndApp() {
OPT_LOG(L1, (_T("[DoInstallGoogleUpdateAndApp]")));
// For machine installs, do not use the UI displayed event in the non-elevated
// instance.
if (!is_machine_ || vista_util::IsUserAdmin()) {
VERIFY1(SUCCEEDED(UIDisplayedEventManager::CreateEvent(is_machine_)));
}
Setup setup(is_machine_, &args_);
HRESULT hr = setup.Install(cmd_line_);
if (FAILED(hr)) {
CORE_LOG(LE, (_T("[Setup::Install failed][0x%08x]"), hr));
VERIFY1(SUCCEEDED(HandleSetupError(hr, setup.extra_code1())));
return hr;
}
return S_OK;
}
HRESULT Worker::InitializeThreadPool() {
CORE_LOG(L3, (_T("[Worker::InitializeThreadPool]")));
thread_pool_.reset(new ThreadPool);
return thread_pool_->Initialize(kThreadPoolShutdownDelayMs);
}
HRESULT Worker::StartWorkerJob() {
CORE_LOG(L2, (_T("[Worker::StartWorker]")));
if (args_.is_silent_set || COMMANDLINE_MODE_UA == args_.mode) {
return worker_job_->DoProcess();
}
HRESULT hr = QueueWorkerJob(worker_job_.get(), false);
if (FAILED(hr)) {
return hr;
}
message_loop_.Run();
return S_OK;
}
HRESULT Worker::QueueWorkerJob(WorkerJob* worker_job, bool delete_after_run) {
CORE_LOG(L2, (_T("[Worker::QueueWorkerJob]")));
ASSERT1(thread_pool_.get());
scoped_ptr<WorkItem> work_item;
work_item.reset(new WorkItem(worker_job, delete_after_run));
HRESULT hr = thread_pool_->QueueUserWorkItem(work_item.get(),
WT_EXECUTELONGFUNCTION);
if (FAILED(hr)) {
return hr;
}
work_item.release();
return S_OK;
}
// Displays an error in the Google Update UI if not silent then sends a ping if
// allowed If the UI fails, uses a system message box as a fallback.
HRESULT Worker::HandleSetupError(HRESULT error, int extra_code1) {
ASSERT1(FAILED(error));
CString error_text;
ASSERT1(!args_.extra.apps.empty());
switch (error) {
case GOOPDATE_E_INSTALL_ELEVATED_PROCESS_NEEDS_ELEVATION:
case GOOPDATE_E_NONADMIN_INSTALL_ADMIN_APP:
error_text.FormatMessage(IDS_NEED_ADMIN_TO_INSTALL,
GetPrimaryJobInfo().app_name);
break;
case GOOPDATE_E_ELEVATION_FAILED_ADMIN:
case GOOPDATE_E_ELEVATION_FAILED_NON_ADMIN:
error_text.FormatMessage(IDS_ELEVATION_FAILED,
GetPrimaryJobInfo().app_name);
break;
case GOOPDATE_E_FAILED_TO_GET_LOCK:
case GOOPDATE_E_FAILED_TO_GET_LOCK_MATCHING_INSTALL_PROCESS_RUNNING:
case GOOPDATE_E_FAILED_TO_GET_LOCK_NONMATCHING_INSTALL_PROCESS_RUNNING:
case GOOPDATE_E_FAILED_TO_GET_LOCK_UPDATE_PROCESS_RUNNING:
error_text.FormatMessage(IDS_APPLICATION_INSTALLING_GOOGLE_UPDATE,
GetPrimaryJobInfo().app_name);
break;
case GOOPDATE_E_INSTANCES_RUNNING:
error_text.FormatMessage(IDS_INSTANCES_RUNNING_AFTER_SHUTDOWN,
GetPrimaryJobInfo().app_name);
break;
case GOOPDATE_E_RUNNING_INFERIOR_MSXML:
error_text.FormatMessage(IDS_WINDOWS_IS_NOT_UP_TO_DATE,
GetPrimaryJobInfo().app_name);
break;
case GOOPDATE_E_HANDOFF_FAILED:
error_text.FormatMessage(IDS_HANDOFF_FAILED,
GetPrimaryJobInfo().app_name);
break;
default:
error_text.FormatMessage(IDS_SETUP_FAILED, error);
break;
}
OPT_LOG(LE, (_T("[Failed to install Google Update][0x%08x][%s]"),
error, error_text));
if (UIDisplayedEventManager::HasUIBeenDisplayed(is_machine_)) {
// Do not display another UI, launch the web page, or ping.
CORE_LOG(L2, (_T("[second instance has UI; not displaying error UI]")));
return S_OK;
}
HRESULT hr = S_OK;
if (!args_.is_silent_set) {
hr = InitializeUI();
if (SUCCEEDED(hr)) {
DisplayError(error_text, error);
} else {
DisplayErrorInMessageBox(error_text);
}
}
// Do not ping. Cannot rely on the ping code to not send the ping because
// Setup may have failed before it could write eulaccepted=0 to the registry.
if (args().is_eula_required_set) {
return hr;
}
if (args().is_oem_set) {
// Do not ping.
// Cannot rely on the ping code to not send the ping because the OEM Mode
// value is not set after Setup returns.
return hr;
}
// This ping may cause a firewall prompt, but we are willing to cause a prompt
// in failure cases.
Request request(is_machine_);
Ping ping;
HRESULT hr_ping = ping_utils::SendGoopdatePing(
is_machine_,
args().extra,
args().install_source,
PingEvent::EVENT_SETUP_INSTALL_FAILURE,
error,
extra_code1,
NULL,
&ping);
if (FAILED(hr_ping)) {
CORE_LOG(LW, (_T("[SendGoopdatePing failed][0x%08x]"), hr_ping));
}
return hr;
}
HRESULT Worker::Shutdown() {
CORE_LOG(L2, (_T("[Worker::Shutdown]")));
ASSERT1(args_.mode == COMMANDLINE_MODE_UA);
if (worker_job_.get()) {
worker_job_->Cancel();
}
return S_OK;
}
HRESULT Worker::DoOnDemand(const WCHAR* guid,
const CString& lang,
IJobObserver* observer,
bool is_update_check_only) {
CORE_LOG(L3, (_T("[Worker::DoOnDemand][%d][%s][%d][%d]"),
is_machine_, guid, observer, is_update_check_only));
ASSERT1(guid);
ASSERT1(observer);
if (!is_update_check_only && is_machine_ && !vista_util::IsUserAdmin()) {
ASSERT(false, (_T("Need to be elevated for machine application.")));
return HRESULT_FROM_WIN32(ERROR_ELEVATION_REQUIRED);
}
// Create a fresh WorkerJob for each OnDemand request that comes in.
scoped_ptr<WorkerJob> worker_job;
HRESULT hr = WorkerJobFactory::CreateOnDemandWorkerJob(
is_machine_,
is_update_check_only,
lang,
StringToGuid(guid),
observer,
shutdown_callback_.get(),
address(worker_job));
if (FAILED(hr)) {
return hr;
}
hr = QueueWorkerJob(worker_job.get(), true);
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[QueueWorkerJob failed][0x%08x]"), hr));
return hr;
}
worker_job.release();
return S_OK;
}
void Worker::CollectAmbientUsageStats() {
if (args_.mode != COMMANDLINE_MODE_UA) {
return;
}
// Check every time the /ua worker exits in case the user later disables UAC.
Set2010UpdateAp(is_machine_);
CString name, version;
HRESULT hr = firewall_detection::Detect(&name, &version);
bool has_software_firewall = SUCCEEDED(hr) && !name.IsEmpty();
metric_worker_has_software_firewall.Set(has_software_firewall);
if (System::IsRunningOnBatteries()) {
++metric_worker_silent_update_running_on_batteries;
}
metric_worker_shell_version = app_util::GetVersionFromModule(NULL);
metric_worker_is_windows_installing.Set(IsWindowsInstalling());
metric_worker_is_uac_disabled.Set(vista_util::IsUACDisabled());
metric_worker_is_clickonce_disabled.Set(IsClickOnceDisabled());
}
// Uninstall is a tricky use case. Uninstall can primarily happen in three cases
// and there are two mechanisms to uninstall. The cases in which Omaha
// uninstalls are:
// 1. The last registered application uninstalls. Omaha monitors the
// client keys and it will trigger an immediate uninstall in this case.
// 2. The core starts an update worker, if there are no registered
// applications, the update worker will do the uninstall.
// 3. An error, including user cancel, happens during Omaha or app installation
// and there are no registered applications.
// The uninstall is implemented in terms of the following mechanisms:
// * An update worker launched with "/ua /uninstalled" by the core, in the
// first two cases above.
// * A direct uninstall, in the case of errors or user cancellations, in the
// last case above.
//
// Omaha can uninstall only if there are no install workers running and no
// registered applications. This check is done under the setup lock protection.
// In addition, the uninstall worker takes the update worker lock. Acquiring
// this lock is important since the silent installers can modify the
// registration of apps and trigger uninstalls workers. Therefore, both
// setup lock and the update worker locks are needed.
//
// In the direct uninstall case there is a small race condition, since there is
// no other single lock that can be acquired to prevent changes to the
// application registration. The code looks for install workers but the test is
// racy if not protected by locks.
void Worker::MaybeUninstallGoogleUpdate() {
CORE_LOG(L1, (_T("[Worker::MaybeUninstallGoogleUpdate]")));
ASSERT1(args_.mode == COMMANDLINE_MODE_UA ||
args_.mode == COMMANDLINE_MODE_IG);
internal::SendFinalUninstallPingForApps(is_machine_);
Setup setup(is_machine_, &args_);
has_uninstalled_ = !!SUCCEEDED(setup.Uninstall());
}
const CommandLineAppArgs& Worker::GetPrimaryJobInfo() const {
ASSERT1(!args_.extra.apps.empty());
return args_.extra.apps[0];
}
void Worker::DisplayError(const CString& error_text, HRESULT error) {
ASSERT1(!UIDisplayedEventManager::HasUIBeenDisplayed(is_machine_));
#pragma warning(push)
// C4061: enumerator 'xxx' in switch of enum 'yyy' is not explicitly handled by
// a case label.
#pragma warning(disable : 4061)
switch (args_.mode) {
case COMMANDLINE_MODE_INSTALL:
case COMMANDLINE_MODE_HANDOFF_INSTALL:
case COMMANDLINE_MODE_IG:
if (args_.is_silent_set) {
return;
}
break;
case COMMANDLINE_MODE_UA:
case COMMANDLINE_MODE_UNKNOWN: // OnDemand
return;
break;
default:
ASSERT1(false);
return;
}
#pragma warning(pop)
ASSERT1(job_observer_.get());
ErrorWndEvents error_wnd_events;
// error_wnd_events must not be destroyed until CMessageLoop::Run() returns.
job_observer_->SetEventSink(&error_wnd_events);
job_observer_->OnShow();
job_observer_->OnComplete(COMPLETION_CODE_ERROR, error_text, error);
message_loop_.Run();
}
void Worker::DisplayErrorInMessageBox(const CString& error_text) {
ASSERT1(args_.mode == COMMANDLINE_MODE_INSTALL ||
args_.mode == COMMANDLINE_MODE_IG ||
args_.mode == COMMANDLINE_MODE_HANDOFF_INSTALL);
if (args_.is_silent_set) {
return;
}
CString primary_app_name;
ASSERT1(!args_.extra.apps.empty());
if (!args_.extra.apps.empty()) {
primary_app_name = GetPrimaryJobInfo().app_name;
}
goopdate_utils::DisplayErrorInMessageBox(error_text, primary_app_name);
}
} // namespace omaha