diff options
author | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-01 06:00:25 +0000 |
---|---|---|
committer | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-01 06:00:25 +0000 |
commit | bf6117c7e9b66f3648af83c047a3428d66353451 (patch) | |
tree | 42d42291596c7b70ff1bdace230473277d5a1cd5 | |
parent | 8a0989f01fff18edb0f56dcc06db2742eb6a1dcb (diff) | |
download | chromium_src-bf6117c7e9b66f3648af83c047a3428d66353451.zip chromium_src-bf6117c7e9b66f3648af83c047a3428d66353451.tar.gz chromium_src-bf6117c7e9b66f3648af83c047a3428d66353451.tar.bz2 |
Refactor the installer to support multi-install.
The installer now does its work based on distributions and target installation paths.
Each distribution has exactly one target installation path but each installation path can have more than one distribution.
In the absense of the --multi-install switch, the installer should continue to work as before.
The biggest difference here is that we don't rely on a single global distribution object that controls the entire installation flow and we have a few classes for the new abstractions instead of global functions.
It's far from perfect, but it's a step towards separating the core file package required for all distributions from the distributions themselves.
Additionally, there are tons of little changes here such as consistant usage of FilePath and CommandLine instead of mixing them with std::wstring.
TEST=Install, uninstall, upgrade, etc. Everything install related.
BUG=61609
Review URL: http://codereview.chromium.org/5172011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@67818 0039d316-1c4b-4281-b951-d872f2087c98
43 files changed, 2414 insertions, 1277 deletions
diff --git a/chrome/browser/browser_main_win.cc b/chrome/browser/browser_main_win.cc index e4b52da..273a113 100644 --- a/chrome/browser/browser_main_win.cc +++ b/chrome/browser/browser_main_win.cc @@ -29,6 +29,7 @@ #include "chrome/common/env_vars.h" #include "chrome/common/main_function_params.h" #include "chrome/common/result_codes.h" +#include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/helper.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/shell_util.h" @@ -99,9 +100,12 @@ int DoUninstallTasks(bool chrome_still_running) { VLOG(1) << "Failed to delete sentinel file."; // We want to remove user level shortcuts and we only care about the ones // created by us and not by the installer so |alternate| is false. - if (!ShellUtil::RemoveChromeDesktopShortcut(ShellUtil::CURRENT_USER, false)) + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + if (!ShellUtil::RemoveChromeDesktopShortcut(dist, ShellUtil::CURRENT_USER, + false)) VLOG(1) << "Failed to delete desktop shortcut."; - if (!ShellUtil::RemoveChromeQuickLaunchShortcut(ShellUtil::CURRENT_USER)) + if (!ShellUtil::RemoveChromeQuickLaunchShortcut(dist, + ShellUtil::CURRENT_USER)) VLOG(1) << "Failed to delete quick launch shortcut."; } return ret; @@ -176,22 +180,23 @@ int HandleIconsCommands(const CommandLine &parsed_command_line) { // allow the user level Chrome to run. So we notify the user and uninstall // user level Chrome. bool CheckMachineLevelInstall() { - scoped_ptr<installer::Version> version(InstallUtil::GetChromeVersion(true)); + // TODO(tommi): Check if using the default distribution is always the right + // thing to do. + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + scoped_ptr<installer::Version> version(InstallUtil::GetChromeVersion(dist, + true)); if (version.get()) { FilePath exe_path; PathService::Get(base::DIR_EXE, &exe_path); std::wstring exe = exe_path.value(); - std::transform(exe.begin(), exe.end(), exe.begin(), tolower); - std::wstring user_exe_path = installer::GetChromeInstallPath(false); - std::transform(user_exe_path.begin(), user_exe_path.end(), - user_exe_path.begin(), tolower); - if (exe == user_exe_path) { + FilePath user_exe_path(installer::GetChromeInstallPath(false, dist)); + if (FilePath::CompareEqualIgnoreCase(exe, user_exe_path.value())) { const std::wstring text = l10n_util::GetString(IDS_MACHINE_LEVEL_INSTALL_CONFLICT); const std::wstring caption = l10n_util::GetString(IDS_PRODUCT_NAME); const UINT flags = MB_OK | MB_ICONERROR | MB_TOPMOST; win_util::MessageBox(NULL, text, caption, flags); - FilePath uninstall_path(InstallUtil::GetChromeUninstallCmd(false)); + FilePath uninstall_path(InstallUtil::GetChromeUninstallCmd(false, dist)); CommandLine uninstall_cmd(uninstall_path); if (!uninstall_cmd.GetProgram().value().empty()) { uninstall_cmd.AppendSwitch(installer_util::switches::kForceUninstall); diff --git a/chrome/browser/first_run/first_run_win.cc b/chrome/browser/first_run/first_run_win.cc index bffded1..55016c4 100644 --- a/chrome/browser/first_run/first_run_win.cc +++ b/chrome/browser/first_run/first_run_win.cc @@ -224,7 +224,7 @@ bool FirstRun::CreateChromeDesktopShortcut() { BrowserDistribution *dist = BrowserDistribution::GetDistribution(); if (!dist) return false; - return ShellUtil::CreateChromeDesktopShortcut(chrome_exe.value(), + return ShellUtil::CreateChromeDesktopShortcut(dist, chrome_exe.value(), dist->GetAppDescription(), ShellUtil::CURRENT_USER, false, true); // create if doesn't exist. } @@ -233,7 +233,8 @@ bool FirstRun::CreateChromeQuickLaunchShortcut() { FilePath chrome_exe; if (!PathService::Get(base::FILE_EXE, &chrome_exe)) return false; - return ShellUtil::CreateChromeQuickLaunchShortcut(chrome_exe.value(), + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + return ShellUtil::CreateChromeQuickLaunchShortcut(dist, chrome_exe.value(), ShellUtil::CURRENT_USER, // create only for current user. true); // create if doesn't exist. } diff --git a/chrome/browser/shell_integration_win.cc b/chrome/browser/shell_integration_win.cc index 044a787..ad1f653 100644 --- a/chrome/browser/shell_integration_win.cc +++ b/chrome/browser/shell_integration_win.cc @@ -272,7 +272,8 @@ bool ShellIntegration::SetAsDefaultBrowser() { } // From UI currently we only allow setting default browser for current user. - if (!ShellUtil::MakeChromeDefault(ShellUtil::CURRENT_USER, + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER, chrome_exe.value(), true)) { LOG(ERROR) << "Chrome could not be set as default browser."; return false; @@ -315,7 +316,7 @@ ShellIntegration::DefaultBrowserState ShellIntegration::IsDefaultBrowser() { // app name being default. If not, then default browser is just called // Google Chrome or Chromium so we do not append suffix to app name. std::wstring suffix; - if (ShellUtil::GetUserSpecificDefaultBrowserSuffix(&suffix)) + if (ShellUtil::GetUserSpecificDefaultBrowserSuffix(dist, &suffix)) app_name += suffix; for (int i = 0; i < _countof(kChromeProtocols); i++) { diff --git a/chrome/browser/ui/views/about_chrome_view.cc b/chrome/browser/ui/views/about_chrome_view.cc index 2808d98..11cede9 100644 --- a/chrome/browser/ui/views/about_chrome_view.cc +++ b/chrome/browser/ui/views/about_chrome_view.cc @@ -26,6 +26,7 @@ #include "chrome/common/chrome_version_info.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" +#include "chrome/installer/util/browser_distribution.h" #include "gfx/canvas.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -739,8 +740,11 @@ void AboutChromeView::UpdateStatus(GoogleUpdateUpgradeResult result, // Google Update reported that Chrome is up-to-date. Now make sure that we // are running the latest version and if not, notify the user by falling // into the next case of UPGRADE_SUCCESSFUL. + // TODO(tommi): Check if using the default distribution is always the + // right thing to do. + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); scoped_ptr<installer::Version> installed_version( - InstallUtil::GetChromeVersion(false)); + InstallUtil::GetChromeVersion(dist, false)); scoped_ptr<installer::Version> running_version( installer::Version::GetVersionFromString(current_version_)); if (!installed_version.get() || diff --git a/chrome/browser/ui/views/uninstall_view.cc b/chrome/browser/ui/views/uninstall_view.cc index a7535b0..ecd2ac7 100644 --- a/chrome/browser/ui/views/uninstall_view.cc +++ b/chrome/browser/ui/views/uninstall_view.cc @@ -65,10 +65,11 @@ void UninstallView::SetupControls() { layout->AddView(delete_profile_); // Set default browser combo box - if (BrowserDistribution::GetDistribution()->CanSetAsDefault() && + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + if (dist->CanSetAsDefault() && ShellIntegration::IsDefaultBrowser()) { browsers_.reset(new BrowsersMap()); - ShellUtil::GetRegisteredBrowsers(browsers_.get()); + ShellUtil::GetRegisteredBrowsers(dist, browsers_.get()); if (!browsers_->empty()) { layout->AddPaddingRow(0, kRelatedControlVerticalSpacing); diff --git a/chrome/browser/upgrade_detector.cc b/chrome/browser/upgrade_detector.cc index 6a65c7a..56886bf 100644 --- a/chrome/browser/upgrade_detector.cc +++ b/chrome/browser/upgrade_detector.cc @@ -86,10 +86,13 @@ class DetectUpgradeTask : public Task { // Get the version of the currently *installed* instance of Chrome, // which might be newer than the *running* instance if we have been // upgraded in the background. - installed_version.reset(InstallUtil::GetChromeVersion(false)); + // TODO(tommi): Check if using the default distribution is always the right + // thing to do. + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + installed_version.reset(InstallUtil::GetChromeVersion(dist, false)); if (!installed_version.get()) { // User level Chrome is not installed, check system level. - installed_version.reset(InstallUtil::GetChromeVersion(true)); + installed_version.reset(InstallUtil::GetChromeVersion(dist, true)); } #elif defined(OS_MACOSX) installed_version.reset( diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index c431fac..468c95d 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -92,6 +92,9 @@ 'installer/util/lzma_util_unittest.cc', 'installer/util/master_preferences_unittest.cc', 'installer/util/move_tree_work_item_unittest.cc', + 'installer/util/package_unittest.cc', + 'installer/util/product_unittest.h', + 'installer/util/product_unittest.cc', 'installer/util/run_all_unittests.cc', 'installer/util/set_reg_value_work_item_unittest.cc', 'installer/util/shell_util_unittest.cc', diff --git a/chrome/chrome_installer_util.gypi b/chrome/chrome_installer_util.gypi index 02603bf..2c922cd 100644 --- a/chrome/chrome_installer_util.gypi +++ b/chrome/chrome_installer_util.gypi @@ -101,6 +101,10 @@ 'installer/util/lzma_util.h', 'installer/util/master_preferences.cc', 'installer/util/master_preferences.h', + 'installer/util/package.h', + 'installer/util/package.cc', + 'installer/util/product.h', + 'installer/util/product.cc', 'installer/util/shell_util.cc', 'installer/util/shell_util.h', ], diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc index 8aefcf3..3817aa1 100644 --- a/chrome/installer/setup/install.cc +++ b/chrome/installer/setup/install.cc @@ -22,6 +22,8 @@ #include "chrome/installer/util/helper.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/master_preferences_constants.h" +#include "chrome/installer/util/package.h" +#include "chrome/installer/util/product.h" #include "chrome/installer/util/set_reg_value_work_item.h" #include "chrome/installer/util/shell_util.h" #include "chrome/installer/util/util_constants.h" @@ -33,12 +35,10 @@ namespace { -std::wstring AppendPath(const std::wstring& parent_path, - const std::wstring& path) { - std::wstring new_path(parent_path); - file_util::AppendToPath(&new_path, path); - return new_path; -} +using installer::Products; +using installer::Product; +using installer::Package; +using installer::Version; void AddChromeToMediaPlayerList() { std::wstring reg_path(installer::kMediaPlayerRegPath); @@ -53,87 +53,95 @@ void AddChromeToMediaPlayerList() { LOG(ERROR) << "Could not add Chrome to media player inclusion list."; } -void AddInstallerCopyTasks(const std::wstring& exe_path, - const std::wstring& archive_path, - const std::wstring& temp_path, - const std::wstring& install_path, - const std::wstring& new_version, +void AddInstallerCopyTasks(const FilePath& setup_path, + const FilePath& archive_path, + const FilePath& temp_path, + const Version& new_version, WorkItemList* install_list, - bool system_level) { - std::wstring installer_dir(installer::GetInstallerPathUnderChrome( - install_path, new_version)); - install_list->AddCreateDirWorkItem( - FilePath::FromWStringHack(installer_dir)); - - std::wstring exe_dst(installer_dir); - std::wstring archive_dst(installer_dir); - file_util::AppendToPath(&exe_dst, - file_util::GetFilenameFromPath(exe_path)); - file_util::AppendToPath(&archive_dst, - file_util::GetFilenameFromPath(archive_path)); - - install_list->AddCopyTreeWorkItem(exe_path, exe_dst, temp_path, - WorkItem::ALWAYS); - if (system_level) { - install_list->AddCopyTreeWorkItem(archive_path, archive_dst, temp_path, - WorkItem::ALWAYS); + const Package& package) { + DCHECK(install_list); + FilePath installer_dir(package.GetInstallerDirectory(new_version)); + install_list->AddCreateDirWorkItem(installer_dir); + + FilePath exe_dst(installer_dir.Append(setup_path.BaseName())); + FilePath archive_dst(installer_dir.Append(archive_path.BaseName())); + + install_list->AddCopyTreeWorkItem(setup_path.value(), exe_dst.value(), + temp_path.value(), WorkItem::ALWAYS); + if (package.system_level()) { + install_list->AddCopyTreeWorkItem(archive_path.value(), archive_dst.value(), + temp_path.value(), WorkItem::ALWAYS); } else { - install_list->AddMoveTreeWorkItem(archive_path, archive_dst, temp_path); + install_list->AddMoveTreeWorkItem(archive_path.value(), archive_dst.value(), + temp_path.value()); } } void AppendUninstallCommandLineFlags(CommandLine* uninstall_cmd, - bool is_system) { + const Product& product) { DCHECK(uninstall_cmd); + uninstall_cmd->AppendSwitch(installer_util::switches::kUninstall); - // TODO(tommi): In case of multiple installations, we need to create multiple - // uninstall entries, and not one magic one for all. const installer_util::MasterPreferences& prefs = InstallUtil::GetMasterPreferencesForCurrentProcess(); - DCHECK(!prefs.is_multi_install()); - if (prefs.install_chrome_frame()) { + bool cf_switch_added = false; + + if (prefs.is_multi_install()) { + uninstall_cmd->AppendSwitch(installer_util::switches::kMultiInstall); + switch (product.distribution()->GetType()) { + case BrowserDistribution::CHROME_BROWSER: + uninstall_cmd->AppendSwitch(installer_util::switches::kChrome); + break; + case BrowserDistribution::CHROME_FRAME: + uninstall_cmd->AppendSwitch(installer_util::switches::kChromeFrame); + cf_switch_added = true; + break; + case BrowserDistribution::CEEE: + uninstall_cmd->AppendSwitch(installer_util::switches::kCeee); + break; + default: + NOTREACHED(); + break; + } + } + + if (product.distribution()->GetType() == BrowserDistribution::CHROME_FRAME) { + DCHECK(prefs.install_chrome_frame()); uninstall_cmd->AppendSwitch(installer_util::switches::kDeleteProfile); - uninstall_cmd->AppendSwitch(installer_util::switches::kChromeFrame); + if (!cf_switch_added) { + uninstall_cmd->AppendSwitch(installer_util::switches::kChromeFrame); + } } - if (InstallUtil::IsChromeSxSProcess()) { + if (InstallUtil::IsChromeSxSProcess()) uninstall_cmd->AppendSwitch(installer_util::switches::kChromeSxS); - } - if (InstallUtil::IsMSIProcess(is_system)) { + if (product.IsMsi()) uninstall_cmd->AppendSwitch(installer_util::switches::kMsi); - } // Propagate the verbose logging switch to uninstalls too. - const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - if (command_line.HasSwitch(installer_util::switches::kVerboseLogging)) { + bool value = false; + if (prefs.GetBool(installer_util::master_preferences::kVerboseLogging, + &value) && value) uninstall_cmd->AppendSwitch(installer_util::switches::kVerboseLogging); - } - if (is_system) { + if (product.system_level()) uninstall_cmd->AppendSwitch(installer_util::switches::kSystemLevel); - } } // This method adds work items to create (or update) Chrome uninstall entry in // either the Control Panel->Add/Remove Programs list or in the Omaha client // state key if running under an MSI installer. -void AddUninstallShortcutWorkItems(HKEY reg_root, - const std::wstring& exe_path, - const std::wstring& install_path, - const std::wstring& product_name, - const std::wstring& new_version, - WorkItemList* install_list) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - const CommandLine& command_line = *CommandLine::ForCurrentProcess(); - - // TODO(tommi): Support this for multi-install. We need to add work items - // for each product being installed. - const installer_util::MasterPreferences& prefs = - InstallUtil::GetMasterPreferencesForCurrentProcess(); - DCHECK(!prefs.is_multi_install()) << "TODO"; +void AddUninstallShortcutWorkItems(const FilePath& setup_path, + const Version& new_version, + WorkItemList* install_list, + const Product& product) { + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + BrowserDistribution* browser_dist = product.distribution(); + DCHECK(browser_dist); // When we are installed via an MSI, we need to store our uninstall strings // in the Google Update client state key. We do this even for non-MSI @@ -141,16 +149,15 @@ void AddUninstallShortcutWorkItems(HKEY reg_root, // install is updated by a non-msi installer (which would confuse the MSI // machinery if these strings were not also updated). // Do not quote the command line for the MSI invocation. - FilePath installer_path(installer::GetInstallerPathUnderChrome(install_path, - new_version)); - installer_path = installer_path.Append( - file_util::GetFilenameFromPath(exe_path)); + FilePath install_path(product.package().path()); + FilePath installer_path( + product.package().GetInstallerDirectory(new_version)); + installer_path = installer_path.Append(setup_path.BaseName()); CommandLine uninstall_arguments(CommandLine::NO_PROGRAM); - AppendUninstallCommandLineFlags(&uninstall_arguments, - reg_root == HKEY_LOCAL_MACHINE); + AppendUninstallCommandLineFlags(&uninstall_arguments, product); - std::wstring update_state_key = dist->GetStateKey(); + std::wstring update_state_key(browser_dist->GetStateKey()); install_list->AddCreateRegKeyWorkItem(reg_root, update_state_key); install_list->AddSetRegValueWorkItem(reg_root, update_state_key, installer_util::kUninstallStringField, installer_path.value(), true); @@ -159,31 +166,32 @@ void AddUninstallShortcutWorkItems(HKEY reg_root, uninstall_arguments.command_line_string(), true); // MSI installations will manage their own uninstall shortcuts. - if (!InstallUtil::IsMSIProcess(reg_root == HKEY_LOCAL_MACHINE)) { + if (!product.IsMsi()) { // We need to quote the command line for the Add/Remove Programs dialog. CommandLine quoted_uninstall_cmd(installer_path); DCHECK_EQ(quoted_uninstall_cmd.command_line_string()[0], '"'); quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false); - std::wstring uninstall_reg = dist->GetUninstallRegPath(); + std::wstring uninstall_reg = browser_dist->GetUninstallRegPath(); install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - installer_util::kUninstallDisplayNameField, product_name, true); + installer_util::kUninstallDisplayNameField, + browser_dist->GetAppShortCutName(), true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, installer_util::kUninstallStringField, quoted_uninstall_cmd.command_line_string(), true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, L"InstallLocation", - install_path, + install_path.value(), true); // DisplayIcon, NoModify and NoRepair - std::wstring chrome_icon = AppendPath(install_path, - installer_util::kChromeExe); - ShellUtil::GetChromeIcon(chrome_icon); + FilePath chrome_icon(install_path.Append(installer_util::kChromeExe)); + ShellUtil::GetChromeIcon(product.distribution(), chrome_icon.value()); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"DisplayIcon", chrome_icon, true); + L"DisplayIcon", chrome_icon.value(), + true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, L"NoModify", 1, true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, @@ -191,12 +199,14 @@ void AddUninstallShortcutWorkItems(HKEY reg_root, install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, L"Publisher", - dist->GetPublisherName(), true); + browser_dist->GetPublisherName(), + true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"Version", new_version.c_str(), true); + L"Version", new_version.GetString(), + true); install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, L"DisplayVersion", - new_version.c_str(), true); + new_version.GetString(), true); time_t rawtime = time(NULL); struct tm timeinfo = {0}; localtime_s(&timeinfo, &rawtime); @@ -214,31 +224,31 @@ void AddUninstallShortcutWorkItems(HKEY reg_root, // If so, try and remove any existing uninstallation shortcuts, as we want the // uninstall to be managed entirely by the MSI machinery (accessible via the // Add/Remove programs dialog). -void DeleteUninstallShortcutsForMSI(bool is_system_install) { - DCHECK(InstallUtil::IsMSIProcess(is_system_install)) - << "This must only be called for MSI installations!"; +void DeleteUninstallShortcutsForMSI(const Product& product) { + DCHECK(product.IsMsi()) << "This must only be called for MSI installations!"; // First attempt to delete the old installation's ARP dialog entry. - HKEY reg_root = is_system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; base::win::RegKey root_key(reg_root, L"", KEY_ALL_ACCESS); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - std::wstring uninstall_reg = dist->GetUninstallRegPath(); + std::wstring uninstall_reg(product.distribution()->GetUninstallRegPath()); InstallUtil::DeleteRegistryKey(root_key, uninstall_reg); // Then attempt to delete the old installation's start menu shortcut. FilePath uninstall_link; - if (is_system_install) { + if (product.system_level()) { PathService::Get(base::DIR_COMMON_START_MENU, &uninstall_link); } else { PathService::Get(base::DIR_START_MENU, &uninstall_link); } + if (uninstall_link.empty()) { LOG(ERROR) << "Failed to get location for shortcut."; } else { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - uninstall_link = uninstall_link.Append(dist->GetAppShortCutName()); uninstall_link = uninstall_link.Append( - dist->GetUninstallLinkName() + L".lnk"); + product.distribution()->GetAppShortCutName()); + uninstall_link = uninstall_link.Append( + product.distribution()->GetUninstallLinkName() + L".lnk"); VLOG(1) << "Deleting old uninstall shortcut (if present): " << uninstall_link.value(); if (!file_util::Delete(uninstall_link, true)) @@ -249,15 +259,14 @@ void DeleteUninstallShortcutsForMSI(bool is_system_install) { // Copy master preferences file provided to installer, in the same folder // as chrome.exe so Chrome first run can find it. This function will be called // only on the first install of Chrome. -void CopyPreferenceFileForFirstRun(bool system_level, - const std::wstring& prefs_source_path) { - FilePath prefs_dest_path = FilePath::FromWStringHack( - installer::GetChromeInstallPath(system_level)); - prefs_dest_path = prefs_dest_path.AppendASCII( - installer_util::kDefaultMasterPrefs); - if (!file_util::CopyFile(FilePath::FromWStringHack(prefs_source_path), - prefs_dest_path)) - VLOG(1) << "Failed to copy master preferences."; +void CopyPreferenceFileForFirstRun(const Package& package, + const FilePath& prefs_source_path) { + FilePath prefs_dest_path(package.path().AppendASCII( + installer_util::kDefaultMasterPrefs)); + if (!file_util::CopyFile(prefs_source_path, prefs_dest_path)) { + VLOG(1) << "Failed to copy master preferences from:" + << prefs_source_path.value() << " gle: " << ::GetLastError(); + } } // This method creates Chrome shortcuts in Start->Programs for all users or @@ -274,25 +283,32 @@ void CopyPreferenceFileForFirstRun(bool system_level, // // If the shortcuts do not exist, the function does not recreate them during // update. -bool CreateOrUpdateChromeShortcuts(const std::wstring& exe_path, - const std::wstring& install_path, - const std::wstring& new_version, +bool CreateOrUpdateChromeShortcuts(const FilePath& setup_path, + const Version& new_version, installer_util::InstallStatus install_status, - bool system_install, + const Product& product, bool create_all_shortcut, bool alt_shortcut) { + // TODO(tommi): Change this function to use WorkItemList. +#ifndef NDEBUG + const installer_util::MasterPreferences& prefs = + InstallUtil::GetMasterPreferencesForCurrentProcess(); + DCHECK(prefs.install_chrome()); +#endif + FilePath shortcut_path; - int dir_enum = (system_install) ? base::DIR_COMMON_START_MENU : - base::DIR_START_MENU; + int dir_enum = product.system_level() ? base::DIR_COMMON_START_MENU : + base::DIR_START_MENU; if (!PathService::Get(dir_enum, &shortcut_path)) { LOG(ERROR) << "Failed to get location for shortcut."; return false; } + BrowserDistribution* browser_dist = product.distribution(); + // The location of Start->Programs->Google Chrome folder - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - const std::wstring& product_name = dist->GetAppShortCutName(); - const std::wstring& product_desc = dist->GetAppDescription(); + const std::wstring product_name(browser_dist->GetAppShortCutName()); + const std::wstring product_desc(browser_dist->GetAppDescription()); shortcut_path = shortcut_path.Append(product_name); // Create/update Chrome link (points to chrome.exe) & Uninstall Chrome link @@ -305,151 +321,191 @@ bool CreateOrUpdateChromeShortcuts(const std::wstring& exe_path, bool ret = true; FilePath chrome_link(shortcut_path); // Chrome link (launches Chrome) chrome_link = chrome_link.Append(product_name + L".lnk"); - std::wstring chrome_exe(install_path); // Chrome link target - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); + // Chrome link target + FilePath chrome_exe( + product.package().path().Append(installer_util::kChromeExe)); if ((install_status == installer_util::FIRST_INSTALL_SUCCESS) || (install_status == installer_util::INSTALL_REPAIRED)) { if (!file_util::PathExists(shortcut_path)) file_util::CreateDirectoryW(shortcut_path); - VLOG(1) << "Creating shortcut to " << chrome_exe << " at " + VLOG(1) << "Creating shortcut to " << chrome_exe.value() << " at " << chrome_link.value(); - ret = ret && ShellUtil::UpdateChromeShortcut(chrome_exe, - chrome_link.value(), - product_desc, true); + ret = ShellUtil::UpdateChromeShortcut(browser_dist, chrome_exe.value(), + chrome_link.value(), product_desc, true); } else if (file_util::PathExists(chrome_link)) { VLOG(1) << "Updating shortcut at " << chrome_link.value() - << " to point to " << chrome_exe; - ret = ret && ShellUtil::UpdateChromeShortcut(chrome_exe, - chrome_link.value(), - product_desc, false); + << " to point to " << chrome_exe.value(); + ret = ShellUtil::UpdateChromeShortcut(browser_dist, chrome_exe.value(), + chrome_link.value(), product_desc, false); + } else { + VLOG(1) + << "not first or repaired install, link file doesn't exist. status: " + << install_status; } // Create/update uninstall link if we are not an MSI install. MSI // installations are, for the time being, managed only through the // Add/Remove Programs dialog. // TODO(robertshield): We could add a shortcut to msiexec /X {GUID} here. - if (!InstallUtil::IsMSIProcess(system_install)) { + if (ret && !product.IsMsi()) { FilePath uninstall_link(shortcut_path); // Uninstall Chrome link uninstall_link = uninstall_link.Append( - dist->GetUninstallLinkName() + L".lnk"); + browser_dist->GetUninstallLinkName() + L".lnk"); if ((install_status == installer_util::FIRST_INSTALL_SUCCESS) || (install_status == installer_util::INSTALL_REPAIRED) || (file_util::PathExists(uninstall_link))) { if (!file_util::PathExists(shortcut_path)) - file_util::CreateDirectoryW(shortcut_path); - std::wstring setup_exe(installer::GetInstallerPathUnderChrome( - install_path, new_version)); - file_util::AppendToPath(&setup_exe, - file_util::GetFilenameFromPath(exe_path)); - - CommandLine arguments(CommandLine::NO_PROGRAM); - AppendUninstallCommandLineFlags(&arguments, system_install); - VLOG(1) << "Creating/updating uninstall link at " - << uninstall_link.value(); - ret = ret && file_util::CreateShortcutLink(setup_exe.c_str(), - uninstall_link.value().c_str(), - NULL, - arguments.command_line_string().c_str(), - NULL, - setup_exe.c_str(), - 0, - NULL); + file_util::CreateDirectory(shortcut_path); + + FilePath setup_exe( + product.package().GetInstallerDirectory(new_version) + .Append(setup_path.BaseName())); + + CommandLine arguments(CommandLine::NO_PROGRAM); + AppendUninstallCommandLineFlags(&arguments, product); + VLOG(1) << "Creating/updating uninstall link at " + << uninstall_link.value(); + ret = file_util::CreateShortcutLink(setup_exe.value().c_str(), + uninstall_link.value().c_str(), + NULL, + arguments.command_line_string().c_str(), + NULL, + setup_exe.value().c_str(), + 0, + NULL); } } // Update Desktop and Quick Launch shortcuts. If --create-new-shortcuts // is specified we want to create them, otherwise we update them only if // they exist. - if (system_install) { - ret = ret && ShellUtil::CreateChromeDesktopShortcut(chrome_exe, - product_desc, ShellUtil::SYSTEM_LEVEL, alt_shortcut, - create_all_shortcut); - ret = ret && ShellUtil::CreateChromeQuickLaunchShortcut(chrome_exe, - ShellUtil::CURRENT_USER | ShellUtil::SYSTEM_LEVEL, create_all_shortcut); - } else { - ret = ret && ShellUtil::CreateChromeDesktopShortcut(chrome_exe, - product_desc, ShellUtil::CURRENT_USER, alt_shortcut, - create_all_shortcut); - ret = ret && ShellUtil::CreateChromeQuickLaunchShortcut(chrome_exe, - ShellUtil::CURRENT_USER, create_all_shortcut); + if (ret) { + if (product.system_level()) { + ret = ShellUtil::CreateChromeDesktopShortcut(product.distribution(), + chrome_exe.value(), product_desc, ShellUtil::SYSTEM_LEVEL, + alt_shortcut, create_all_shortcut); + if (ret) { + ret = ShellUtil::CreateChromeQuickLaunchShortcut( + product.distribution(), chrome_exe.value(), + ShellUtil::CURRENT_USER | ShellUtil::SYSTEM_LEVEL, + create_all_shortcut); + } + } else { + ret = ShellUtil::CreateChromeDesktopShortcut(product.distribution(), + chrome_exe.value(), product_desc, ShellUtil::CURRENT_USER, + alt_shortcut, create_all_shortcut); + if (ret) { + ret = ShellUtil::CreateChromeQuickLaunchShortcut( + product.distribution(), chrome_exe.value(), ShellUtil::CURRENT_USER, + create_all_shortcut); + } + } } return ret; } +bool RegisterComDlls(const Package& install, + const Version* current_version, + const Version& new_version) { + // TODO(tommi): setup.exe should always have at least one DLL to register. + // Currently we rely on scan_server_dlls.py to populate the array for us, + // but we might as well use an explicit static array of required components. + if (kNumDllsToRegister <= 0) { + NOTREACHED() << "no dlls to register"; + return false; + } + + // Unregister DLLs that were left from the old version that is being upgraded. + if (current_version) { + FilePath old_dll_path(install.path().Append(current_version->GetString())); + // Ignore failures to unregister old DLLs. + installer::RegisterComDllList(old_dll_path, install.system_level(), false, + false); + } + + FilePath dll_path(install.path().Append(new_version.GetString())); + return installer::RegisterComDllList(dll_path, install.system_level(), true, + true); +} + // After a successful copying of all the files, this function is called to // do a few post install tasks: -// - Handle the case of in-use-update by updating "opv" key or deleting it if -// not required. +// - Handle the case of in-use-update by updating "opv" (old version) key or +// deleting it if not required. // - Register any new dlls and unregister old dlls. // - If this is an MSI install, ensures that the MSI marker is set, and sets // it if not. // If these operations are successful, the function returns true, otherwise // false. -bool DoPostInstallTasks(HKEY reg_root, - const std::wstring& exe_path, - const std::wstring& install_path, - const std::wstring& new_chrome_exe, - const std::wstring& current_version, - const installer::Version& new_version) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - std::wstring version_key = dist->GetVersionKey(); - - bool is_system_install = (reg_root == HKEY_LOCAL_MACHINE); - const installer_util::MasterPreferences& prefs = - InstallUtil::GetMasterPreferencesForCurrentProcess(); - - if (file_util::PathExists(FilePath::FromWStringHack(new_chrome_exe))) { +bool DoPostInstallTasks(const FilePath& setup_path, + const FilePath& new_chrome_exe, + const Version* current_version, + const Version& new_version, + const Package& package) { + HKEY root = package.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + const Products& products = package.products(); + + if (file_util::PathExists(new_chrome_exe)) { // Looks like this was in use update. So make sure we update the 'opv' key // with the current version that is active and 'cmd' key with the rename // command to run. - if (current_version.empty()) { - LOG(ERROR) << "New chrome.exe exists but current version is empty!"; + if (!current_version) { + LOG(ERROR) << "New chrome.exe exists but current version is NULL!"; return false; } - scoped_ptr<WorkItemList> inuse_list(WorkItem::CreateWorkItemList()); - inuse_list->AddSetRegValueWorkItem(reg_root, - version_key, - google_update::kRegOldVersionField, - current_version.c_str(), - true); - FilePath installer_path(installer::GetInstallerPathUnderChrome(install_path, - new_version.GetString())); - installer_path = installer_path.Append( - file_util::GetFilenameFromPath(exe_path)); - CommandLine rename_cmd(installer_path); - rename_cmd.AppendSwitch(installer_util::switches::kRenameChromeExe); - if (is_system_install) - rename_cmd.AppendSwitch(installer_util::switches::kSystemLevel); + scoped_ptr<WorkItemList> inuse_list(WorkItem::CreateWorkItemList()); + FilePath installer_path(package.GetInstallerDirectory(new_version) + .Append(setup_path.BaseName())); - if (prefs.install_chrome_frame()) - rename_cmd.AppendSwitch(installer_util::switches::kChromeFrame); + CommandLine rename(installer_path); + rename.AppendSwitch(installer_util::switches::kRenameChromeExe); + if (package.system_level()) + rename.AppendSwitch(installer_util::switches::kSystemLevel); if (InstallUtil::IsChromeSxSProcess()) - rename_cmd.AppendSwitch(installer_util::switches::kChromeSxS); + rename.AppendSwitch(installer_util::switches::kChromeSxS); + + for (size_t i = 0; i < products.size(); ++i) { + BrowserDistribution* dist = products[i]->distribution(); + std::wstring version_key(dist->GetVersionKey()); + inuse_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegOldVersionField, + current_version->GetString(), true); + + // Adding this registry entry for all products is overkill. + // However, as it stands, we don't have a way to know which distribution + // will check the key and run the command, so we add it for all. + // After the first run, the subsequent runs should just be noops. + // (see Upgrade::SwapNewChromeExeIfPresent). + inuse_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegRenameCmdField, + rename.command_line_string(), true); + } - inuse_list->AddSetRegValueWorkItem(reg_root, - version_key, - google_update::kRegRenameCmdField, - rename_cmd.command_line_string(), - true); if (!inuse_list->Do()) { LOG(ERROR) << "Couldn't write opv/cmd values to registry."; inuse_list->Rollback(); return false; } } else { - // Since this was not in-use-update, delete 'opv' and 'cmd' keys. + // Since this was not an in-use-update, delete 'opv' and 'cmd' keys. scoped_ptr<WorkItemList> inuse_list(WorkItem::CreateWorkItemList()); - inuse_list->AddDeleteRegValueWorkItem(reg_root, version_key, - google_update::kRegOldVersionField, - true); - inuse_list->AddDeleteRegValueWorkItem(reg_root, version_key, - google_update::kRegRenameCmdField, - true); + for (size_t i = 0; i < products.size(); ++i) { + BrowserDistribution* dist = products[i]->distribution(); + std::wstring version_key(dist->GetVersionKey()); + inuse_list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegOldVersionField, + true); + inuse_list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegRenameCmdField, + true); + } + if (!inuse_list->Do()) { LOG(ERROR) << "Couldn't delete opv/cmd values from registry."; inuse_list->Rollback(); @@ -457,55 +513,27 @@ bool DoPostInstallTasks(HKEY reg_root, } } - if (prefs.install_chrome_frame()) { - // TODO(tommi): setup.exe should always have at least one DLL to - // register. Currently we rely on scan_server_dlls.py to populate - // the array for us, but we might as well use an explicit static - // array of required components. - if (kNumDllsToRegister <= 0) { - NOTREACHED() << "no dlls to register"; - return false; - } - - // any that were left from the old version that is being upgraded: - if (!current_version.empty()) { - std::wstring old_dll_path(install_path); - file_util::AppendToPath(&old_dll_path, current_version); - scoped_ptr<WorkItemList> old_dll_list(WorkItem::CreateWorkItemList()); - if (InstallUtil::BuildDLLRegistrationList(old_dll_path, kDllsToRegister, - kNumDllsToRegister, false, - !is_system_install, - old_dll_list.get())) { - // Don't abort the install as a result of a failure to unregister old - // DLLs. - old_dll_list->Do(); - } - } - - std::wstring dll_path(install_path); - file_util::AppendToPath(&dll_path, new_version.GetString()); - scoped_ptr<WorkItemList> dll_list(WorkItem::CreateWorkItemList()); - if (InstallUtil::BuildDLLRegistrationList(dll_path, kDllsToRegister, - kNumDllsToRegister, true, - !is_system_install, - dll_list.get())) { - if (!dll_list->Do()) { - dll_list->Rollback(); - return false; - } - } + if (FindProduct(products, BrowserDistribution::CHROME_FRAME) || + FindProduct(products, BrowserDistribution::CEEE)) { + // TODO(robershield): move the "which DLLs should be registered" policy + // into the installer. + RegisterComDlls(package, current_version, new_version); } // If we're told that we're an MSI install, make sure to set the marker // in the client state key so that future updates do the right thing. - if (InstallUtil::IsMSIProcess(is_system_install)) { - if (!InstallUtil::SetMSIMarker(is_system_install, true)) - return false; + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + if (product->IsMsi()) { + if (!product->SetMsiMarker(true)) + return false; - // We want MSI installs to take over the Add/Remove Programs shortcut. Make - // a best-effort attempt to delete any shortcuts left over from previous - // non-MSI installations for the same type of install (system or per user). - DeleteUninstallShortcutsForMSI(is_system_install); + // We want MSI installs to take over the Add/Remove Programs shortcut. + // Make a best-effort attempt to delete any shortcuts left over from + // previous non-MSI installations for the same type of install (system or + // per user). + DeleteUninstallShortcutsForMSI(*product); + } } return true; @@ -529,9 +557,11 @@ bool Is64bit() { return false; } -void RegisterChromeOnMachine(const std::wstring& install_path, - bool system_level, +void RegisterChromeOnMachine(const Product& product, bool make_chrome_default) { + DCHECK_EQ(product.distribution()->GetType(), + BrowserDistribution::CHROME_BROWSER); + // Try to add Chrome to Media Player shim inclusion list. We don't do any // error checking here because this operation will fail if user doesn't // have admin rights and we want to ignore the error. @@ -539,246 +569,229 @@ void RegisterChromeOnMachine(const std::wstring& install_path, // Is --make-chrome-default option is given we make Chrome default browser // otherwise we only register it on the machine as a valid browser. - std::wstring chrome_exe(install_path); - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); - VLOG(1) << "Registering Chrome as browser"; + FilePath chrome_exe( + product.package().path().Append(installer_util::kChromeExe)); + VLOG(1) << "Registering Chrome as browser: " << chrome_exe.value(); if (make_chrome_default) { int level = ShellUtil::CURRENT_USER; - if (system_level) + if (product.system_level()) level = level | ShellUtil::SYSTEM_LEVEL; - ShellUtil::MakeChromeDefault(level, chrome_exe, true); + ShellUtil::MakeChromeDefault(product.distribution(), level, + chrome_exe.value(), true); } else { - ShellUtil::RegisterChromeBrowser(chrome_exe, L"", false); + ShellUtil::RegisterChromeBrowser(product.distribution(), chrome_exe.value(), + L"", false); } } // This function installs a new version of Chrome to the specified location. // -// exe_path: Path to the executable (setup.exe) as it will be copied +// setup_path: Path to the executable (setup.exe) as it will be copied // to Chrome install folder after install is complete // archive_path: Path to the archive (chrome.7z) as it will be copied // to Chrome install folder after install is complete // src_path: the path that contains a complete and unpacked Chrome package // to be installed. -// install_path: the destination path for Chrome to be installed to. This -// path does not need to exist. // temp_dir: the path of working directory used during installation. This path // does not need to exist. -// reg_root: the root of registry where the function applies settings for the -// new Chrome version. It should be either HKLM or HKCU. // new_version: new Chrome version that needs to be installed -// current_version: returns the current active version (if any) +// oldest_installed_version: returns the oldest active version (if any) // // This function makes best effort to do installation in a transactional // manner. If failed it tries to rollback all changes on the file system -// and registry. For example, if install_path exists before calling the +// and registry. For example, if package exists before calling the // function, it rolls back all new file and directory changes under -// install_path. If install_path does not exist before calling the function -// (typical new install), the function creates install_path during install +// package. If package does not exist before calling the function +// (typical new install), the function creates package during install // and removes the whole directory during rollback. installer_util::InstallStatus InstallNewVersion( - const std::wstring& exe_path, - const std::wstring& archive_path, - const std::wstring& src_path, - const std::wstring& install_path, - const std::wstring& temp_dir, - const HKEY reg_root, - const installer::Version& new_version, - std::wstring* current_version) { - if (reg_root != HKEY_LOCAL_MACHINE && reg_root != HKEY_CURRENT_USER) - return installer_util::INSTALL_FAILED; - - if (InstallUtil::IsChromeFrameProcess()) { + const FilePath& setup_path, + const FilePath& archive_path, + const FilePath& src_path, + const FilePath& temp_dir, + const Version& new_version, + scoped_ptr<Version>* current_version, + const Package& package) { + DCHECK(current_version); + + const Products& products = package.products(); + DCHECK(products.size()); + + if (FindProduct(products, BrowserDistribution::CHROME_FRAME)) { // Make sure that we don't end up deleting installed files on next reboot. - if (!RemoveFromMovesPendingReboot(install_path.c_str())) { + if (!RemoveFromMovesPendingReboot(package.path().value().c_str())) { LOG(ERROR) << "Error accessing pending moves value."; } } + // TODO(tommi): See if we can't get rid of this parameter. + current_version->reset(package.GetCurrentVersion()); + scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); // A temp directory that work items need and the actual install directory. - install_list->AddCreateDirWorkItem(FilePath::FromWStringHack(temp_dir)); - install_list->AddCreateDirWorkItem(FilePath::FromWStringHack(install_path)); + install_list->AddCreateDirWorkItem(temp_dir); + install_list->AddCreateDirWorkItem(package.path()); // Delete any new_chrome.exe if present (we will end up creating a new one // if required) and then copy chrome.exe - std::wstring new_chrome_exe = AppendPath(install_path, - installer_util::kChromeNewExe); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - base::win::RegKey chrome_key(reg_root, dist->GetVersionKey().c_str(), - KEY_READ); - if (file_util::PathExists(FilePath::FromWStringHack(new_chrome_exe))) - chrome_key.ReadValue(google_update::kRegOldVersionField, current_version); - - if (current_version->empty()) - chrome_key.ReadValue(google_update::kRegVersionField, current_version); - - chrome_key.Close(); + FilePath new_chrome_exe( + package.path().Append(installer_util::kChromeNewExe)); - install_list->AddDeleteTreeWorkItem(new_chrome_exe, std::wstring()); + install_list->AddDeleteTreeWorkItem(new_chrome_exe.value(), std::wstring()); install_list->AddCopyTreeWorkItem( - AppendPath(src_path, installer_util::kChromeExe), - AppendPath(install_path, installer_util::kChromeExe), - temp_dir, WorkItem::NEW_NAME_IF_IN_USE, new_chrome_exe); + src_path.Append(installer_util::kChromeExe).value(), + package.path().Append(installer_util::kChromeExe).value(), + temp_dir.value(), WorkItem::NEW_NAME_IF_IN_USE, new_chrome_exe.value()); // Extra executable for 64 bit systems. if (Is64bit()) { install_list->AddCopyTreeWorkItem( - AppendPath(src_path, installer::kWowHelperExe), - AppendPath(install_path, installer::kWowHelperExe), - temp_dir, WorkItem::ALWAYS); + src_path.Append(installer::kWowHelperExe).value(), + package.path().Append(installer::kWowHelperExe).value(), + temp_dir.value(), WorkItem::ALWAYS); } // If it is system level install copy the version folder (since we want to // take the permissions of %ProgramFiles% folder) otherwise just move it. - if (reg_root == HKEY_LOCAL_MACHINE) { + if (package.system_level()) { install_list->AddCopyTreeWorkItem( - AppendPath(src_path, new_version.GetString()), - AppendPath(install_path, new_version.GetString()), - temp_dir, WorkItem::ALWAYS); + src_path.Append(new_version.GetString()).value(), + package.path().Append(new_version.GetString()).value(), + temp_dir.value(), WorkItem::ALWAYS); } else { install_list->AddMoveTreeWorkItem( - AppendPath(src_path, new_version.GetString()), - AppendPath(install_path, new_version.GetString()), - temp_dir); + src_path.Append(new_version.GetString()).value(), + package.path().Append(new_version.GetString()).value(), + temp_dir.value()); } - // Copy the default Dictionaries only if the folder doesnt exist already + // Copy the default Dictionaries only if the folder doesn't exist already. install_list->AddCopyTreeWorkItem( - AppendPath(src_path, installer::kDictionaries), - AppendPath(install_path, installer::kDictionaries), - temp_dir, WorkItem::IF_NOT_PRESENT); - - // Copy installer in install directory and - // add shortcut in Control Panel->Add/Remove Programs. - AddInstallerCopyTasks(exe_path, archive_path, temp_dir, install_path, - new_version.GetString(), install_list.get(), - (reg_root == HKEY_LOCAL_MACHINE)); - std::wstring product_name = dist->GetAppShortCutName(); - - AddUninstallShortcutWorkItems(reg_root, exe_path, install_path, - product_name, new_version.GetString(), install_list.get()); + src_path.Append(installer::kDictionaries).value(), + package.path().Append(installer::kDictionaries).value(), + temp_dir.value(), WorkItem::IF_NOT_PRESENT); // Delete any old_chrome.exe if present. install_list->AddDeleteTreeWorkItem( - AppendPath(install_path, installer_util::kChromeOldExe), std::wstring()); - - // Create Version key (if not already present) and set the new Chrome - // version as last step. - std::wstring version_key = dist->GetVersionKey(); - install_list->AddCreateRegKeyWorkItem(reg_root, version_key); - install_list->AddSetRegValueWorkItem(reg_root, version_key, - google_update::kRegNameField, - product_name, - true); // overwrite name also - install_list->AddSetRegValueWorkItem(reg_root, version_key, - google_update::kRegOopcrashesField, - 1, - false); // set during first install - install_list->AddSetRegValueWorkItem(reg_root, version_key, - google_update::kRegVersionField, - new_version.GetString(), - true); // overwrite version + package.path().Append(installer_util::kChromeOldExe).value(), + std::wstring()); + + // Copy installer in install directory and + // add shortcut in Control Panel->Add/Remove Programs. + AddInstallerCopyTasks(setup_path, archive_path, temp_dir, new_version, + install_list.get(), package); + + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + + AddUninstallShortcutWorkItems(setup_path, new_version, install_list.get(), + *product); + + // Create Version key for each distribution (if not already present) and set + // the new product version as the last step. + HKEY root = product->system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + std::wstring version_key(product->distribution()->GetVersionKey()); + install_list->AddCreateRegKeyWorkItem(root, version_key); + + std::wstring product_name(product->distribution()->GetAppShortCutName()); + install_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegNameField, + product_name, + true); // overwrite name also + install_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegOopcrashesField, + 1, + false); // set during first install + install_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegVersionField, + new_version.GetString(), + true); // overwrite version + } if (!install_list->Do() || - !DoPostInstallTasks(reg_root, exe_path, install_path, - new_chrome_exe, *current_version, new_version)) { + !DoPostInstallTasks(setup_path, new_chrome_exe, current_version->get(), + new_version, package)) { installer_util::InstallStatus result = - (file_util::PathExists(FilePath::FromWStringHack(new_chrome_exe)) && - !current_version->empty() && - (new_version.GetString() == *current_version)) ? + file_util::PathExists(new_chrome_exe) && current_version->get() && + new_version.IsEqual(*current_version->get()) ? installer_util::SAME_VERSION_REPAIR_FAILED : installer_util::INSTALL_FAILED; - LOG(ERROR) << "Install failed, rolling back... "; + LOG(ERROR) << "Install failed, rolling back... result: " << result; install_list->Rollback(); LOG(ERROR) << "Rollback complete. "; return result; } - scoped_ptr<installer::Version> installed_version; - if (!current_version->empty()) - installed_version.reset( - installer::Version::GetVersionFromString(*current_version)); - if (!installed_version.get()) { + + if (!current_version->get()) { VLOG(1) << "First install of version " << new_version.GetString(); return installer_util::FIRST_INSTALL_SUCCESS; } - if (new_version.GetString() == installed_version->GetString()) { + + if (new_version.IsEqual(*current_version->get())) { VLOG(1) << "Install repaired of version " << new_version.GetString(); return installer_util::INSTALL_REPAIRED; } - if (new_version.IsHigherThan(installed_version.get())) { - if (file_util::PathExists(FilePath::FromWStringHack(new_chrome_exe))) { + + if (new_version.IsHigherThan(current_version->get())) { + if (file_util::PathExists(new_chrome_exe)) { VLOG(1) << "Version updated to " << new_version.GetString() - << " while running " << installed_version->GetString(); + << " while running " << (*current_version)->GetString(); return installer_util::IN_USE_UPDATED; } VLOG(1) << "Version updated to " << new_version.GetString(); return installer_util::NEW_VERSION_UPDATED; } + LOG(ERROR) << "Not sure how we got here while updating" << ", new version: " << new_version.GetString() - << ", old version: " << installed_version->GetString(); + << ", old version: " << (*current_version)->GetString(); + return installer_util::INSTALL_FAILED; } -} // namespace +} // end namespace -std::wstring installer::GetInstallerPathUnderChrome( - const std::wstring& install_path, const std::wstring& new_version) { - std::wstring installer_path(install_path); - file_util::AppendToPath(&installer_path, new_version); - file_util::AppendToPath(&installer_path, installer_util::kInstallerDir); - return installer_path; -} +namespace installer { -installer_util::InstallStatus installer::InstallOrUpdateChrome( - const std::wstring& exe_path, const std::wstring& archive_path, - const std::wstring& install_temp_path, const std::wstring& prefs_path, +installer_util::InstallStatus InstallOrUpdateChrome( + const FilePath& setup_path, const FilePath& archive_path, + const FilePath& install_temp_path, const FilePath& prefs_path, const installer_util::MasterPreferences& prefs, const Version& new_version, - const Version* installed_version) { - bool system_install = false; - prefs.GetBool(installer_util::master_preferences::kSystemLevel, - &system_install); - std::wstring install_path(GetChromeInstallPath(system_install)); - if (install_path.empty()) { - LOG(ERROR) << "Could not get installation destination path."; - return installer_util::INSTALL_FAILED; - } - VLOG(1) << "install destination path: " << install_path; + const Package& install) { + bool system_install = install.system_level(); - std::wstring src_path(install_temp_path); - file_util::AppendToPath(&src_path, std::wstring(kInstallSourceDir)); - file_util::AppendToPath(&src_path, std::wstring(kInstallSourceChromeDir)); + FilePath src_path(install_temp_path); + src_path = src_path.Append(kInstallSourceDir).Append(kInstallSourceChromeDir); - HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - std::wstring current_version; - installer_util::InstallStatus result = InstallNewVersion(exe_path, - archive_path, src_path, install_path, install_temp_path, reg_root, - new_version, ¤t_version); + scoped_ptr<Version> existing_version; + installer_util::InstallStatus result = InstallNewVersion(setup_path, + archive_path, src_path, install_temp_path, new_version, + &existing_version, install); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - if (!dist->GetInstallReturnCode(result)) { + if (!BrowserDistribution::GetInstallReturnCode(result)) { if (result == installer_util::FIRST_INSTALL_SUCCESS) - CopyPreferenceFileForFirstRun(system_install, prefs_path); - - bool value = false; - // TODO(tommi): Currently this only creates shortcuts for Chrome, but - // for other products we might want to create shortcuts. - if (prefs.install_chrome() && - (!prefs.GetBool( - installer_util::master_preferences::kDoNotCreateShortcuts, - &value) || !value)) { + CopyPreferenceFileForFirstRun(install, prefs_path); + + bool do_not_create_shortcuts = false; + prefs.GetBool(installer_util::master_preferences::kDoNotCreateShortcuts, + &do_not_create_shortcuts); + + // Currently this only creates shortcuts for Chrome, but for other products + // we might want to create shortcuts. + const Product* chrome_install = + FindProduct(install.products(), BrowserDistribution::CHROME_BROWSER); + if (chrome_install && !do_not_create_shortcuts) { bool create_all_shortcut = false; prefs.GetBool(installer_util::master_preferences::kCreateAllShortcuts, &create_all_shortcut); bool alt_shortcut = false; prefs.GetBool(installer_util::master_preferences::kAltShortcutText, &alt_shortcut); - if (!CreateOrUpdateChromeShortcuts(exe_path, install_path, - new_version.GetString(), result, - system_install, create_all_shortcut, + if (!CreateOrUpdateChromeShortcuts(setup_path, new_version, result, + *chrome_install, create_all_shortcut, alt_shortcut)) { - LOG(WARNING) << "Failed to create/update start menu shortcut."; + PLOG(WARNING) << "Failed to create/update start menu shortcut."; } bool make_chrome_default = false; @@ -797,15 +810,43 @@ installer_util::InstallStatus installer::InstallOrUpdateChrome( &force_chrome_default_for_user); } - RegisterChromeOnMachine(install_path, system_install, + RegisterChromeOnMachine(*chrome_install, make_chrome_default || force_chrome_default_for_user); } - std::wstring latest_version_to_keep(new_version.GetString()); - if (!current_version.empty()) - latest_version_to_keep.assign(current_version); - RemoveOldVersionDirs(install_path, latest_version_to_keep); + install.RemoveOldVersionDirectories(existing_version.get() ? + *existing_version.get() : new_version); } return result; } + +bool RegisterComDllList(const FilePath& dll_folder, bool system_level, + bool do_register, bool rollback_on_failure) { + bool success = false; + scoped_ptr<WorkItemList> work_item_list; + if (rollback_on_failure) { + work_item_list.reset(WorkItem::CreateWorkItemList()); + } else { + work_item_list.reset(WorkItem::CreateNoRollbackWorkItemList()); + } + + // TODO(robertshield): What if the list of old dlls and new ones isn't + // the same? I (elmo) think we should start storing the list of DLLs + // somewhere. + if (InstallUtil::BuildDLLRegistrationList(dll_folder.value(), kDllsToRegister, + kNumDllsToRegister, do_register, + !system_level, + work_item_list.get())) { + // Ignore failures to unregister old DLLs. + success = work_item_list->Do(); + if (!success && rollback_on_failure) { + work_item_list->Rollback(); + } + } + + return success; +} + +} // namespace installer + diff --git a/chrome/installer/setup/install.h b/chrome/installer/setup/install.h index 1c8ccc4..bccb7e2 100644 --- a/chrome/installer/setup/install.h +++ b/chrome/installer/setup/install.h @@ -9,7 +9,9 @@ #pragma once #include <string> +#include <vector> +#include "chrome/installer/util/product.h" #include "chrome/installer/util/master_preferences.h" #include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/version.h" @@ -17,15 +19,13 @@ class DictionaryValue; namespace installer { -// Get path to the installer under Chrome version folder -// (for example <path>\Google\Chrome\<Version>\installer) -std::wstring GetInstallerPathUnderChrome(const std::wstring& install_path, - const std::wstring& new_version); + +class Package; // This function installs or updates a new version of Chrome. It returns // install status (failed, new_install, updated etc). // -// exe_path: Path to the executable (setup.exe) as it will be copied +// setup_path: Path to the executable (setup.exe) as it will be copied // to Chrome install folder after install is complete // archive_path: Path to the archive (chrome.7z) as it will be copied // to Chrome install folder after install is complete @@ -34,16 +34,25 @@ std::wstring GetInstallerPathUnderChrome(const std::wstring& install_path, // and unpacked Chrome package. // prefs: master preferences. See chrome/installer/util/master_preferences.h. // new_version: new Chrome version that needs to be installed -// installed_version: currently installed version of Chrome, if any, or -// NULL otherwise +// package: Represents the target installation folder and all distributions +// to be installed in that folder. // // Note: since caller unpacks Chrome to install_temp_path\source, the caller // is responsible for cleaning up install_temp_path. installer_util::InstallStatus InstallOrUpdateChrome( - const std::wstring& exe_path, const std::wstring& archive_path, - const std::wstring& install_temp_path, const std::wstring& prefs_path, + const FilePath& setup_path, const FilePath& archive_path, + const FilePath& install_temp_path, const FilePath& prefs_path, const installer_util::MasterPreferences& prefs, const Version& new_version, - const Version* installed_version); -} + const Package& package); + +// Registers or unregisters COM DLLs in a specific folder as declared in +// kDllsToRegister. +// TODO(robertshield): What if the list of old dlls and new ones isn't +// the same? I think we should start storing the list of DLLs somewhere as +// part of the installation data. +bool RegisterComDllList(const FilePath& dll_folder, bool system_level, + bool do_register, bool rollback_on_failure); + +} // namespace installer #endif // CHROME_INSTALLER_SETUP_INSTALL_H_ diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc index 7d0725c..d9ace56 100644 --- a/chrome/installer/setup/setup_main.cc +++ b/chrome/installer/setup/setup_main.cc @@ -44,6 +44,14 @@ #include "installer_util_strings.h" +using installer::Product; +using installer::ProductPackageMapping; +using installer::Products; +using installer::Package; +using installer::Packages; +using installer::Version; +using installer_util::MasterPreferences; + namespace { // This method unpacks and uncompresses the given archive file. For Chrome @@ -55,39 +63,41 @@ namespace { // (chrome.7z) or uncompressed archive patch file (chrome_patch.diff). If it // is patch file, it is applied to the old archive file that should be // present on the system already. As the final step the new archive file -// is unpacked in the path specified by parameter "path". -DWORD UnPackArchive(const std::wstring& archive, bool system_install, - const installer::Version* installed_version, - const std::wstring& temp_path, const std::wstring& path, +// is unpacked in the path specified by parameter "output_directory". +DWORD UnPackArchive(const FilePath& archive, + const Package& installation, + const FilePath& temp_path, + const FilePath& output_directory, bool& incremental_install) { // First uncompress the payload. This could be a differential // update (patch.7z) or full archive (chrome.7z). If this uncompress fails // return with error. std::wstring unpacked_file; - int32 ret = LzmaUtil::UnPackArchive(archive, temp_path, &unpacked_file); + int32 ret = LzmaUtil::UnPackArchive(archive.value(), temp_path.value(), + &unpacked_file); if (ret != NO_ERROR) return ret; - std::wstring uncompressed_archive(temp_path); - file_util::AppendToPath(&uncompressed_archive, installer::kChromeArchive); + FilePath uncompressed_archive(temp_path.Append(installer::kChromeArchive)); + scoped_ptr<Version> archive_version( + setup_util::GetVersionFromArchiveDir(installation.path())); // Check if this is differential update and if it is, patch it to the // installer archive that should already be on the machine. We assume // it is a differential installer if chrome.7z is not found. - if (!file_util::PathExists(FilePath::FromWStringHack(uncompressed_archive))) { + if (!file_util::PathExists(uncompressed_archive)) { incremental_install = true; VLOG(1) << "Differential patch found. Applying to existing archive."; - if (!installed_version) { + if (!archive_version.get()) { LOG(ERROR) << "Can not use differential update when Chrome is not " << "installed on the system."; return installer_util::CHROME_NOT_INSTALLED; } - std::wstring existing_archive = - installer::GetChromeInstallPath(system_install); - file_util::AppendToPath(&existing_archive, - installed_version->GetString()); - file_util::AppendToPath(&existing_archive, installer_util::kInstallerDir); - file_util::AppendToPath(&existing_archive, installer::kChromeArchive); + + FilePath existing_archive( + installation.path().Append(archive_version->GetString())); + existing_archive = existing_archive.Append(installer_util::kInstallerDir); + existing_archive = existing_archive.Append(installer::kChromeArchive); if (int i = setup_util::ApplyDiffPatch(FilePath(existing_archive), FilePath(unpacked_file), FilePath(uncompressed_archive))) { @@ -97,7 +107,8 @@ DWORD UnPackArchive(const std::wstring& archive, bool system_install, } // Unpack the uncompressed archive. - return LzmaUtil::UnPackArchive(uncompressed_archive, path, &unpacked_file); + return LzmaUtil::UnPackArchive(uncompressed_archive.value(), + output_directory.value(), &unpacked_file); } @@ -106,39 +117,45 @@ DWORD UnPackArchive(const std::wstring& archive, bool system_install, // for Chrome so there should be a file called new_chrome.exe on the file // system and a key called 'opv' in the registry. This function will move // new_chrome.exe to chrome.exe and delete 'opv' key in one atomic operation. -installer_util::InstallStatus RenameChromeExecutables(bool system_install) { - std::wstring chrome_path(installer::GetChromeInstallPath(system_install)); - - std::wstring chrome_exe(chrome_path); - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); - std::wstring chrome_old_exe(chrome_path); - file_util::AppendToPath(&chrome_old_exe, installer_util::kChromeOldExe); - std::wstring chrome_new_exe(chrome_path); - file_util::AppendToPath(&chrome_new_exe, installer_util::kChromeNewExe); +installer_util::InstallStatus RenameChromeExecutables( + const Package& installation) { + FilePath chrome_exe(installation.path().Append(installer_util::kChromeExe)); + FilePath chrome_old_exe(installation.path().Append( + installer_util::kChromeOldExe)); + FilePath chrome_new_exe(installation.path().Append( + installer_util::kChromeNewExe)); scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); - install_list->AddDeleteTreeWorkItem(chrome_old_exe, std::wstring()); + install_list->AddDeleteTreeWorkItem(chrome_old_exe.value(), std::wstring()); FilePath temp_path; if (!file_util::CreateNewTempDirectory(L"chrome_", &temp_path)) { LOG(ERROR) << "Failed to create Temp directory " << temp_path.value(); return installer_util::RENAME_FAILED; } - install_list->AddCopyTreeWorkItem(chrome_new_exe, - chrome_exe, + + install_list->AddCopyTreeWorkItem(chrome_new_exe.value(), + chrome_exe.value(), temp_path.ToWStringHack(), WorkItem::IF_DIFFERENT, std::wstring()); - HKEY reg_root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - BrowserDistribution *dist = BrowserDistribution::GetDistribution(); - install_list->AddDeleteRegValueWorkItem(reg_root, - dist->GetVersionKey(), - google_update::kRegOldVersionField, - true); - install_list->AddDeleteTreeWorkItem(chrome_new_exe, std::wstring()); - install_list->AddDeleteRegValueWorkItem(reg_root, - dist->GetVersionKey(), - google_update::kRegRenameCmdField, - true); + install_list->AddDeleteTreeWorkItem(chrome_new_exe.value(), std::wstring()); + + HKEY reg_root = installation.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + const Products& products = installation.products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + BrowserDistribution* browser_dist = product->distribution(); + std::wstring version_key(browser_dist->GetVersionKey()); + install_list->AddDeleteRegValueWorkItem(reg_root, + version_key, + google_update::kRegOldVersionField, + true); + install_list->AddDeleteRegValueWorkItem(reg_root, + version_key, + google_update::kRegRenameCmdField, + true); + } installer_util::InstallStatus ret = installer_util::RENAME_SUCCESSFUL; if (!install_list->Do()) { LOG(ERROR) << "Renaming of executables failed. Rolling back any changes."; @@ -149,67 +166,82 @@ installer_util::InstallStatus RenameChromeExecutables(bool system_install) { return ret; } -bool CheckPreInstallConditions(const installer::Version* installed_version, - bool system_install, +bool CheckPreInstallConditions(const Package& installation, installer_util::InstallStatus& status) { - bool is_first_install = (NULL == installed_version); - // Check to avoid simultaneous per-user and per-machine installs. - scoped_ptr<installer::Version> - chrome_version(InstallUtil::GetChromeVersion(!system_install)); - if (chrome_version.get()) { - LOG(ERROR) << "Already installed version " << chrome_version->GetString() - << " conflicts with the current install mode."; - if (!system_install && is_first_install) { - // This is user-level install and there is a system-level chrome - // installation. Instruct omaha to launch the existing one. There - // should be no error dialog. - std::wstring chrome_exe(installer::GetChromeInstallPath(!system_install)); - if (chrome_exe.empty()) { - // If we failed to construct install path. Give up. - status = installer_util::OS_ERROR; - InstallUtil::WriteInstallerResult(system_install, status, - IDS_INSTALL_OS_ERROR_BASE, NULL); - return false; - } else { - status = installer_util::EXISTING_VERSION_LAUNCHED; - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); - chrome_exe = L"\"" + chrome_exe + L"\" --" - + ASCIIToWide(switches::kFirstRun); - InstallUtil::WriteInstallerResult(system_install, status, - 0, NULL); - VLOG(1) << "Launching existing system-level chrome instead."; - base::LaunchApp(chrome_exe, false, false, NULL); + const Products& products = installation.products(); + DCHECK(products.size()); + + bool is_first_install = true; + + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + BrowserDistribution* browser_dist = product->distribution(); + scoped_ptr<Version> installed_version(product->GetInstalledVersion()); + // TODO(tommi): If the current install is for a different distribution than + // that might already be installed, then this check is incorrect. + if (installed_version.get()) + is_first_install = false; + + // Check to avoid simultaneous per-user and per-machine installs. + scoped_ptr<Version> chrome_version( + InstallUtil::GetChromeVersion(browser_dist, !product->system_level())); + + if (chrome_version.get()) { + LOG(ERROR) << "Already installed version " << chrome_version->GetString() + << " conflicts with the current install mode."; + if (!product->system_level() && is_first_install) { + // This is user-level install and there is a system-level chrome + // installation. Instruct Omaha to launch the existing one. There + // should be no error dialog. + FilePath chrome_exe(installer::GetChromeInstallPath( + !product->system_level(), browser_dist)); + if (chrome_exe.empty()) { + // If we failed to construct install path. Give up. + status = installer_util::OS_ERROR; + product->WriteInstallerResult(status, IDS_INSTALL_OS_ERROR_BASE, + NULL); + } else { + status = installer_util::EXISTING_VERSION_LAUNCHED; + chrome_exe = chrome_exe.Append(installer_util::kChromeExe); + CommandLine cmd(chrome_exe); + cmd.AppendSwitch(switches::kFirstRun); + product->WriteInstallerResult(status, 0, NULL); + VLOG(1) << "Launching existing system-level chrome instead."; + base::LaunchApp(cmd, false, false, NULL); + } return false; } + + // If the following compile assert fires it means that the InstallStatus + // enumeration changed which will break the contract between the old + // chrome installed and the new setup.exe that is trying to upgrade. + COMPILE_ASSERT(installer_util::SXS_OPTION_NOT_SUPPORTED == 33, + dont_change_enum); + + // This is an update, not an install. Omaha should know the difference + // and not show a dialog. + status = product->system_level() ? + installer_util::USER_LEVEL_INSTALL_EXISTS : + installer_util::SYSTEM_LEVEL_INSTALL_EXISTS; + int str_id = product->system_level() ? + IDS_INSTALL_USER_LEVEL_EXISTS_BASE : + IDS_INSTALL_SYSTEM_LEVEL_EXISTS_BASE; + product->WriteInstallerResult(status, str_id, NULL); + return false; } - // If the following compile assert fires it means that the InstallStatus - // enumeration changed which will break the contract between the old chrome - // installed and the new setup.exe that is trying to upgrade. - COMPILE_ASSERT(installer_util::SXS_OPTION_NOT_SUPPORTED == 33, - dont_change_enum); - - // This is an update, not an install. Omaha should know the difference - // and not show a dialog. - status = system_install ? installer_util::USER_LEVEL_INSTALL_EXISTS : - installer_util::SYSTEM_LEVEL_INSTALL_EXISTS; - int str_id = system_install ? IDS_INSTALL_USER_LEVEL_EXISTS_BASE : - IDS_INSTALL_SYSTEM_LEVEL_EXISTS_BASE; - InstallUtil::WriteInstallerResult(system_install, status, str_id, NULL); - return false; } + // If no previous installation of Chrome, make sure installation directory // either does not exist or can be deleted (i.e. is not locked by some other // process). if (is_first_install) { - FilePath install_path = FilePath::FromWStringHack( - installer::GetChromeInstallPath(system_install)); - if (file_util::PathExists(install_path) && - !file_util::Delete(install_path, true)) { - LOG(ERROR) << "Installation directory " << install_path.value() + if (file_util::PathExists(installation.path()) && + !file_util::Delete(installation.path(), true)) { + LOG(ERROR) << "Installation directory " << installation.path().value() << " exists and can not be deleted."; status = installer_util::INSTALL_DIR_IN_USE; int str_id = IDS_INSTALL_DIR_IN_USE_BASE; - InstallUtil::WriteInstallerResult(system_install, status, str_id, NULL); + WriteInstallerResult(products, status, str_id, NULL); return false; } } @@ -218,21 +250,15 @@ bool CheckPreInstallConditions(const installer::Version* installed_version, } installer_util::InstallStatus InstallChrome(const CommandLine& cmd_line, - const installer::Version* installed_version, - const installer_util::MasterPreferences& prefs) { - bool system_level = false; - prefs.GetBool(installer_util::master_preferences::kSystemLevel, - &system_level); + const Package& installation, const MasterPreferences& prefs) { installer_util::InstallStatus install_status = installer_util::UNKNOWN_STATUS; - if (!CheckPreInstallConditions(installed_version, - system_level, install_status)) + if (!CheckPreInstallConditions(installation, install_status)) return install_status; // For install the default location for chrome.packed.7z is in current // folder, so get that value first. - FilePath archive = - cmd_line.GetProgram().DirName().Append( - installer::kChromeCompressedArchive); + FilePath archive(cmd_line.GetProgram().DirName().Append( + installer::kChromeCompressedArchive)); // If --install-archive is given, get the user specified value if (cmd_line.HasSwitch(installer_util::switches::kInstallArchive)) { @@ -240,120 +266,133 @@ installer_util::InstallStatus InstallChrome(const CommandLine& cmd_line, installer_util::switches::kInstallArchive); } VLOG(1) << "Archive found to install Chrome " << archive.value(); + bool system_level = installation.system_level(); + const Products& products = installation.products(); // Create a temp folder where we will unpack Chrome archive. If it fails, // then we are doomed, so return immediately and no cleanup is required. FilePath temp_path; if (!file_util::CreateNewTempDirectory(L"chrome_", &temp_path)) { LOG(ERROR) << "Could not create temporary path."; - InstallUtil::WriteInstallerResult(system_level, - installer_util::TEMP_DIR_FAILED, - IDS_INSTALL_TEMP_DIR_FAILED_BASE, - NULL); + WriteInstallerResult(products, installer_util::TEMP_DIR_FAILED, + IDS_INSTALL_TEMP_DIR_FAILED_BASE, NULL); return installer_util::TEMP_DIR_FAILED; } VLOG(1) << "created path " << temp_path.value(); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - std::wstring unpack_path(temp_path.ToWStringHack()); - file_util::AppendToPath(&unpack_path, - std::wstring(installer::kInstallSourceDir)); + FilePath unpack_path(temp_path.Append(installer::kInstallSourceDir)); bool incremental_install = false; - if (UnPackArchive(archive.value(), system_level, installed_version, - temp_path.ToWStringHack(), unpack_path, + if (UnPackArchive(archive, installation,temp_path, unpack_path, incremental_install)) { install_status = installer_util::UNCOMPRESSION_FAILED; - InstallUtil::WriteInstallerResult(system_level, install_status, - IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, - NULL); + WriteInstallerResult(products, install_status, + IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL); } else { - VLOG(1) << "unpacked to " << unpack_path; - std::wstring src_path(unpack_path); - file_util::AppendToPath(&src_path, - std::wstring(installer::kInstallSourceChromeDir)); - scoped_ptr<installer::Version> - installer_version(setup_util::GetVersionFromDir(FilePath(src_path))); + VLOG(1) << "unpacked to " << unpack_path.value(); + FilePath src_path(unpack_path.Append(installer::kInstallSourceChromeDir)); + scoped_ptr<Version> + installer_version(setup_util::GetVersionFromArchiveDir(src_path)); if (!installer_version.get()) { LOG(ERROR) << "Did not find any valid version in installer."; install_status = installer_util::INVALID_ARCHIVE; - InstallUtil::WriteInstallerResult(system_level, install_status, - IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); + WriteInstallerResult(products, install_status, + IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); } else { + // TODO(tommi): Move towards having only a single version that is common + // to all products. Only the package should have a version since it + // represents all the binaries. When a single product is upgraded, all + // currently installed product for the shared binary installation, should + // (or rather must) be upgraded. VLOG(1) << "version to install: " << installer_version->GetString(); - if (installed_version && - installed_version->IsHigherThan(installer_version.get())) { - LOG(ERROR) << "Higher version is already installed."; - install_status = installer_util::HIGHER_VERSION_EXISTS; - - int result_resource_id = IDS_INSTALL_HIGHER_VERSION_BASE; - if (InstallUtil::IsChromeFrameProcess()) { - result_resource_id = IDS_INSTALL_HIGHER_VERSION_CF_BASE; + bool higher_version_installed = false; + for (size_t i = 0; i < installation.products().size(); ++i) { + const Product* product = installation.products()[i]; + scoped_ptr<Version> v(product->GetInstalledVersion()); + if (v.get() && v->IsHigherThan(installer_version.get())) { + LOG(ERROR) << "Higher version is already installed."; + higher_version_installed = true; + install_status = installer_util::HIGHER_VERSION_EXISTS; + + if (product->distribution()->GetType() != + BrowserDistribution::CHROME_BROWSER) { + // TODO(robertshield): We should take the installer result text + // strings from the Product. + product->WriteInstallerResult(install_status, + IDS_INSTALL_HIGHER_VERSION_BASE, NULL); + } else { + product->WriteInstallerResult(install_status, + IDS_INSTALL_HIGHER_VERSION_CF_BASE, NULL); + } } - InstallUtil::WriteInstallerResult(system_level, - install_status, - result_resource_id, - NULL); - } else { + } + + if (!higher_version_installed) { // We want to keep uncompressed archive (chrome.7z) that we get after // uncompressing and binary patching. Get the location for this file. - FilePath archive_to_copy = temp_path.Append(installer::kChromeArchive); - std::wstring prefs_source_path = cmd_line.GetSwitchValueNative( - installer_util::switches::kInstallerData); - install_status = installer::InstallOrUpdateChrome( - cmd_line.GetProgram().value(), archive_to_copy.value(), - temp_path.ToWStringHack(), - prefs_source_path, prefs, *installer_version, installed_version); + FilePath archive_to_copy(temp_path.Append(installer::kChromeArchive)); + FilePath prefs_source_path(cmd_line.GetSwitchValueNative( + installer_util::switches::kInstallerData)); + install_status = installer::InstallOrUpdateChrome(cmd_line.GetProgram(), + archive_to_copy, temp_path, prefs_source_path, prefs, + *installer_version, installation); int install_msg_base = IDS_INSTALL_FAILED_BASE; std::wstring chrome_exe; if (install_status == installer_util::SAME_VERSION_REPAIR_FAILED) { - if (InstallUtil::IsChromeFrameProcess()) { + if (FindProduct(products, BrowserDistribution::CHROME_FRAME)) { install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_CF_BASE; } else { install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_BASE; } } else if (install_status != installer_util::INSTALL_FAILED) { - chrome_exe = installer::GetChromeInstallPath(system_level); - if (chrome_exe.empty()) { + if (installation.path().empty()) { // If we failed to construct install path, it means the OS call to // get %ProgramFiles% or %AppData% failed. Report this as failure. install_msg_base = IDS_INSTALL_OS_ERROR_BASE; install_status = installer_util::OS_ERROR; } else { - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); + chrome_exe = installation.path() + .Append(installer_util::kChromeExe).value(); chrome_exe = L"\"" + chrome_exe + L"\""; install_msg_base = 0; } } + const Product* chrome_install = + FindProduct(products, BrowserDistribution::CHROME_BROWSER); + bool value = false; - if (prefs.install_chrome()) { + if (chrome_install) { prefs.GetBool( installer_util::master_preferences::kDoNotRegisterForUpdateLaunch, &value); } else { value = true; // Never register. } + bool write_chrome_launch_string = (!value) && (install_status != installer_util::IN_USE_UPDATED); - InstallUtil::WriteInstallerResult(system_level, install_status, + WriteInstallerResult(products, install_status, install_msg_base, write_chrome_launch_string ? &chrome_exe : NULL); if (install_status == installer_util::FIRST_INSTALL_SUCCESS) { VLOG(1) << "First install successful."; - if (prefs.install_chrome()) { + if (chrome_install) { // We never want to launch Chrome in system level install mode. bool do_not_launch_chrome = false; prefs.GetBool( installer_util::master_preferences::kDoNotLaunchChrome, &do_not_launch_chrome); - if (!system_level && !do_not_launch_chrome) - installer::LaunchChrome(system_level); + if (!chrome_install->system_level() && !do_not_launch_chrome) + chrome_install->LaunchChrome(); } } else if ((install_status == installer_util::NEW_VERSION_UPDATED) || (install_status == installer_util::IN_USE_UPDATED)) { - installer_setup::RemoveLegacyRegistryKeys(); + for (size_t i = 0; i < products.size(); ++i) { + installer::RemoveLegacyRegistryKeys( + products[i]->distribution()); + } } } } @@ -364,8 +403,11 @@ installer_util::InstallStatus InstallChrome(const CommandLine& cmd_line, // // There is another way to reach this same function if this is a system // level install. See HandleNonInstallCmdLineOptions(). - dist->LaunchUserExperiment(install_status, *installer_version, - system_level); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + product->distribution()->LaunchUserExperiment(install_status, + *installer_version, *product, product->system_level()); + } } // Delete temporary files. These include install temporary directory @@ -395,31 +437,36 @@ installer_util::InstallStatus InstallChrome(const CommandLine& cmd_line, } } - dist->UpdateDiffInstallStatus(system_level, incremental_install, - install_status); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + product->distribution()->UpdateDiffInstallStatus(system_level, + incremental_install, install_status); + } + return install_status; } installer_util::InstallStatus UninstallChrome(const CommandLine& cmd_line, - const installer::Version* version, - bool system_install) { + const Product& product) { VLOG(1) << "Uninstalling Chome"; + + scoped_ptr<Version> installed_version(product.GetInstalledVersion()); + if (installed_version.get()) + VLOG(1) << "version on the system: " << installed_version->GetString(); + bool force = cmd_line.HasSwitch(installer_util::switches::kForceUninstall); - if (!version && !force) { + if (!installed_version.get() && !force) { LOG(ERROR) << "No Chrome installation found for uninstall."; - InstallUtil::WriteInstallerResult(system_install, - installer_util::CHROME_NOT_INSTALLED, - IDS_UNINSTALL_FAILED_BASE, NULL); + product.WriteInstallerResult(installer_util::CHROME_NOT_INSTALLED, + IDS_UNINSTALL_FAILED_BASE, NULL); return installer_util::CHROME_NOT_INSTALLED; } bool remove_all = !cmd_line.HasSwitch( installer_util::switches::kDoNotRemoveSharedItems); - return installer_setup::UninstallChrome(cmd_line.GetProgram().value(), - system_install, - remove_all, force, - cmd_line); + return installer::UninstallChrome(cmd_line.GetProgram(), product, remove_all, + force, cmd_line); } installer_util::InstallStatus ShowEULADialog(const std::wstring& inner_frame) { @@ -454,9 +501,9 @@ installer_util::InstallStatus ShowEULADialog(const std::wstring& inner_frame) { // among others). This function returns true if any such command line option // has been found and processed (so setup.exe should exit at that point). bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line, - bool system_install, - int& exit_code) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + int& exit_code, + const ProductPackageMapping& installs) { + DCHECK(installs.products().size()); if (cmd_line.HasSwitch(installer_util::switches::kUpdateSetupExe)) { installer_util::InstallStatus status = installer_util::SETUP_PATCH_FAILED; // If --update-setup-exe command line option is given, we apply the given @@ -483,11 +530,11 @@ bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line, } } - exit_code = dist->GetInstallReturnCode(status); + exit_code = BrowserDistribution::GetInstallReturnCode(status); if (exit_code) { LOG(WARNING) << "setup.exe patching failed."; - InstallUtil::WriteInstallerResult(system_install, status, - IDS_SETUP_PATCH_FAILED_BASE, NULL); + WriteInstallerResult(installs.products(), status, + IDS_SETUP_PATCH_FAILED_BASE, NULL); } file_util::Delete(temp_path, true); return true; @@ -502,25 +549,37 @@ bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line, return true; } else if (cmd_line.HasSwitch( installer_util::switches::kRegisterChromeBrowser)) { - // If --register-chrome-browser option is specified, register all - // Chrome protocol/file associations as well as register it as a valid - // browser for Start Menu->Internet shortcut. This option should only - // be used when setup.exe is launched with admin rights. We do not - // make any user specific changes in this option. - std::wstring chrome_exe(cmd_line.GetSwitchValueNative( - installer_util::switches::kRegisterChromeBrowser)); - std::wstring suffix; - if (cmd_line.HasSwitch( - installer_util::switches::kRegisterChromeBrowserSuffix)) { - suffix = cmd_line.GetSwitchValueNative( - installer_util::switches::kRegisterChromeBrowserSuffix); + const Product* chrome_install = + FindProduct(installs.products(), BrowserDistribution::CHROME_BROWSER); + DCHECK(chrome_install); + if (chrome_install) { + // If --register-chrome-browser option is specified, register all + // Chrome protocol/file associations as well as register it as a valid + // browser for Start Menu->Internet shortcut. This option should only + // be used when setup.exe is launched with admin rights. We do not + // make any user specific changes in this option. + std::wstring chrome_exe(cmd_line.GetSwitchValueNative( + installer_util::switches::kRegisterChromeBrowser)); + std::wstring suffix; + if (cmd_line.HasSwitch( + installer_util::switches::kRegisterChromeBrowserSuffix)) { + suffix = cmd_line.GetSwitchValueNative( + installer_util::switches::kRegisterChromeBrowserSuffix); + } + exit_code = ShellUtil::RegisterChromeBrowser( + chrome_install->distribution(), chrome_exe, suffix, false); + } else { + LOG(ERROR) << "Can't register browser - Chrome distribution not found"; + exit_code = installer_util::UNKNOWN_STATUS; } - exit_code = ShellUtil::RegisterChromeBrowser(chrome_exe, suffix, false); return true; } else if (cmd_line.HasSwitch(installer_util::switches::kRenameChromeExe)) { // If --rename-chrome-exe is specified, we want to rename the executables // and exit. - exit_code = RenameChromeExecutables(system_install); + const Packages& packages = installs.packages(); + DCHECK_EQ(1U, packages.size()); + for (size_t i = 0; i < packages.size(); ++i) + exit_code = RenameChromeExecutables(*packages[i].get()); return true; } else if (cmd_line.HasSwitch( installer_util::switches::kRemoveChromeRegistration)) { @@ -535,26 +594,44 @@ bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line, installer_util::switches::kRegisterChromeBrowserSuffix); } installer_util::InstallStatus tmp = installer_util::UNKNOWN_STATUS; - installer_setup::DeleteChromeRegistrationKeys(HKEY_LOCAL_MACHINE, - suffix, tmp); + const Product* chrome_install = + FindProduct(installs.products(), BrowserDistribution::CHROME_BROWSER); + DCHECK(chrome_install); + if (chrome_install) { + installer::DeleteChromeRegistrationKeys(chrome_install->distribution(), + HKEY_LOCAL_MACHINE, suffix, tmp); + } exit_code = tmp; return true; } else if (cmd_line.HasSwitch(installer_util::switches::kInactiveUserToast)) { // Launch the inactive user toast experiment. - std::string flavor = cmd_line.GetSwitchValueASCII( - installer_util::switches::kInactiveUserToast); - int flavor_int; - base::StringToInt(flavor, &flavor_int); - dist->InactiveUserToastExperiment(flavor_int, - cmd_line.HasSwitch(installer_util::switches::kSystemLevelToast)); + int flavor = -1; + base::StringToInt(cmd_line.GetSwitchValueNative( + installer_util::switches::kInactiveUserToast), &flavor); + DCHECK_NE(-1, flavor); + if (flavor == -1) { + exit_code = installer_util::UNKNOWN_STATUS; + } else { + const Products& products = installs.products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + BrowserDistribution* browser_dist = product->distribution(); + browser_dist->InactiveUserToastExperiment(flavor, *product); + } + } return true; } else if (cmd_line.HasSwitch(installer_util::switches::kSystemLevelToast)) { - // We started as system-level and have been re-launched as user level - // to continue with the toast experiment. - scoped_ptr<installer::Version> - installed_version(InstallUtil::GetChromeVersion(system_install)); - dist->LaunchUserExperiment(installer_util::REENTRY_SYS_UPDATE, - *installed_version, true); + const Products& products = installs.products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + BrowserDistribution* browser_dist = product->distribution(); + // We started as system-level and have been re-launched as user level + // to continue with the toast experiment. + scoped_ptr<Version> installed_version( + InstallUtil::GetChromeVersion(browser_dist, installs.system_level())); + browser_dist->LaunchUserExperiment(installer_util::REENTRY_SYS_UPDATE, + *installed_version, *product, true); + } return true; } return false; @@ -604,9 +681,6 @@ class AutoCom { bool Init(bool system_install) { if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) { LOG(ERROR) << "COM initialization failed."; - InstallUtil::WriteInstallerResult(system_install, - installer_util::OS_ERROR, - IDS_INSTALL_OS_ERROR_BASE, NULL); return false; } initialized_ = true; @@ -617,6 +691,25 @@ class AutoCom { bool initialized_; }; +void PopulateInstallations(const MasterPreferences& prefs, + ProductPackageMapping* installations) { + DCHECK(installations); + if (prefs.install_chrome()) { + VLOG(1) << "Install distribution: Chrome"; + installations->AddDistribution(BrowserDistribution::CHROME_BROWSER); + } + + if (prefs.install_chrome_frame()) { + VLOG(1) << "Install distribution: Chrome Frame"; + installations->AddDistribution(BrowserDistribution::CHROME_FRAME); + } + + if (prefs.install_ceee()) { + VLOG(1) << "Install distribution: CEEE"; + installations->AddDistribution(BrowserDistribution::CHROME_FRAME); + } +} + } // namespace int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, @@ -625,63 +718,62 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, base::AtExitManager exit_manager; CommandLine::Init(0, NULL); - const installer_util::MasterPreferences& prefs = + const MasterPreferences& prefs = InstallUtil::GetMasterPreferencesForCurrentProcess(); installer::InitInstallerLogging(prefs); - const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); - VLOG(1) << "Command Line: " << parsed_command_line.command_line_string(); + const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); + VLOG(1) << "Command Line: " << cmd_line.command_line_string(); bool system_install = false; prefs.GetBool(installer_util::master_preferences::kSystemLevel, &system_install); VLOG(1) << "system install is " << system_install; + ProductPackageMapping installations(system_install); + PopulateInstallations(prefs, &installations); + // Check to make sure current system is WinXP or later. If not, log // error message and get out. if (!InstallUtil::IsOSSupported()) { LOG(ERROR) << "Chrome only supports Windows XP or later."; - InstallUtil::WriteInstallerResult(system_install, - installer_util::OS_NOT_SUPPORTED, - IDS_INSTALL_OS_NOT_SUPPORTED_BASE, NULL); + WriteInstallerResult(installations.products(), + installer_util::OS_NOT_SUPPORTED, IDS_INSTALL_OS_NOT_SUPPORTED_BASE, + NULL); return installer_util::OS_NOT_SUPPORTED; } // Initialize COM for use later. AutoCom auto_com; if (!auto_com.Init(system_install)) { + WriteInstallerResult(installations.products(), + installer_util::OS_ERROR, IDS_INSTALL_OS_ERROR_BASE, NULL); return installer_util::OS_ERROR; } // Some command line options don't work with SxS install/uninstall if (InstallUtil::IsChromeSxSProcess()) { if (system_install || - parsed_command_line.HasSwitch( - installer_util::switches::kForceUninstall) || - parsed_command_line.HasSwitch( - installer_util::switches::kMakeChromeDefault) || - parsed_command_line.HasSwitch( - installer_util::switches::kRegisterChromeBrowser) || - parsed_command_line.HasSwitch( + cmd_line.HasSwitch(installer_util::switches::kForceUninstall) || + cmd_line.HasSwitch(installer_util::switches::kMakeChromeDefault) || + cmd_line.HasSwitch(installer_util::switches::kRegisterChromeBrowser) || + cmd_line.HasSwitch( installer_util::switches::kRemoveChromeRegistration) || - parsed_command_line.HasSwitch( - installer_util::switches::kInactiveUserToast) || - parsed_command_line.HasSwitch( - installer_util::switches::kSystemLevelToast)) { + cmd_line.HasSwitch(installer_util::switches::kInactiveUserToast) || + cmd_line.HasSwitch(installer_util::switches::kSystemLevelToast)) { return installer_util::SXS_OPTION_NOT_SUPPORTED; } } int exit_code = 0; - if (HandleNonInstallCmdLineOptions(parsed_command_line, system_install, - exit_code)) + if (HandleNonInstallCmdLineOptions(cmd_line, exit_code, installations)) return exit_code; if (system_install && !IsUserAnAdmin()) { if (base::win::GetVersion() >= base::win::VERSION_VISTA && - !parsed_command_line.HasSwitch(installer_util::switches::kRunAsAdmin)) { + !cmd_line.HasSwitch(installer_util::switches::kRunAsAdmin)) { CommandLine new_cmd(CommandLine::NO_PROGRAM); - new_cmd.AppendArguments(parsed_command_line, true); + new_cmd.AppendArguments(cmd_line, true); // Append --run-as-admin flag to let the new instance of setup.exe know // that we already tried to launch ourselves as admin. new_cmd.AppendSwitch(installer_util::switches::kRunAsAdmin); @@ -690,45 +782,46 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, return exit_code; } else { LOG(ERROR) << "Non admin user can not install system level Chrome."; - InstallUtil::WriteInstallerResult(system_install, - installer_util::INSUFFICIENT_RIGHTS, - IDS_INSTALL_INSUFFICIENT_RIGHTS_BASE, - NULL); + WriteInstallerResult(installations.products(), + installer_util::INSUFFICIENT_RIGHTS, + IDS_INSTALL_INSUFFICIENT_RIGHTS_BASE, NULL); return installer_util::INSUFFICIENT_RIGHTS; } } - // Check the existing version installed. - scoped_ptr<installer::Version> - installed_version(InstallUtil::GetChromeVersion(system_install)); - if (installed_version.get()) - VLOG(1) << "version on the system: " << installed_version->GetString(); + bool is_uninstall = cmd_line.HasSwitch(installer_util::switches::kUninstall); installer_util::InstallStatus install_status = installer_util::UNKNOWN_STATUS; // If --uninstall option is given, uninstall chrome - if (parsed_command_line.HasSwitch(installer_util::switches::kUninstall)) { - install_status = UninstallChrome(parsed_command_line, - installed_version.get(), - system_install); - // If --uninstall option is not specified, we assume it is install case. + if (is_uninstall) { + DCHECK_EQ(1U, installations.products().size()) << + "We currently really only support uninstalling one distribution " + "at a time"; + for (size_t i = 0; i < installations.products().size(); ++i) { + install_status = UninstallChrome(cmd_line, + *installations.products()[i]); + } } else { - install_status = InstallChrome(parsed_command_line, installed_version.get(), - prefs); + // If --uninstall option is not specified, we assume it is install case. + const Packages& packages = installations.packages(); + VLOG(1) << "Installing to " << packages.size() << " target paths"; + for (size_t i = 0; i < packages.size(); ++i) { + install_status = InstallChrome(cmd_line, *packages[i].get(), prefs); + } } - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + const Product* cf_install = + FindProduct(installations.products(), BrowserDistribution::CHROME_FRAME); - if (InstallUtil::IsChromeFrameProcess() && - !parsed_command_line.HasSwitch( - installer_util::switches::kForceUninstall)) { + if (cf_install && + !cmd_line.HasSwitch(installer_util::switches::kForceUninstall)) { if (install_status == installer_util::UNINSTALL_REQUIRES_REBOOT) { ShowRebootDialog(); - } else if (parsed_command_line.HasSwitch( - installer_util::switches::kUninstall)) { + } else if (is_uninstall) { ::MessageBoxW(NULL, installer_util::GetLocalizedString( IDS_UNINSTALL_COMPLETE_BASE).c_str(), - dist->GetApplicationName().c_str(), + cf_install->distribution()->GetApplicationName().c_str(), MB_OK); } } @@ -737,14 +830,19 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, // MSI demands that custom actions always return 0 (ERROR_SUCCESS) or it will // rollback the action. If we're uninstalling we want to avoid this, so always // report success, squashing any more informative return codes. - if (!(InstallUtil::IsMSIProcess(system_install) && - parsed_command_line.HasSwitch(installer_util::switches::kUninstall))) { - // Note that we allow the status installer_util::UNINSTALL_REQUIRES_REBOOT - // to pass through, since this is only returned on uninstall which is never - // invoked directly by Google Update. - return_code = dist->GetInstallReturnCode(install_status); + // TODO(tommi): Fix this loop when IsMsi has been moved out of the Product + // class. + for (size_t i = 0; i < installations.products().size(); ++i) { + const Product* product = installations.products()[i]; + if (!(product->IsMsi() && is_uninstall)) { + // Note that we allow the status installer_util::UNINSTALL_REQUIRES_REBOOT + // to pass through, since this is only returned on uninstall which is + // never invoked directly by Google Update. + return_code = BrowserDistribution::GetInstallReturnCode(install_status); + } } VLOG(1) << "Installation complete, returning: " << return_code; + return return_code; } diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc index d4fbed1..08ea157 100644 --- a/chrome/installer/setup/setup_util.cc +++ b/chrome/installer/setup/setup_util.cc @@ -33,20 +33,26 @@ int setup_util::ApplyDiffPatch(const FilePath& src, dest.value().c_str()); } -installer::Version* setup_util::GetVersionFromDir( +installer::Version* setup_util::GetVersionFromArchiveDir( const FilePath& chrome_path) { VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value(); FilePath root_path = chrome_path.Append(L"*"); - WIN32_FIND_DATA find_data; + // TODO(tommi): The version directory really should match the version of + // setup.exe. To begin with, we should at least DCHECK that that's true. + + // TODO(tommi): use file_util::FileEnumerator. + WIN32_FIND_DATA find_data = {0}; HANDLE file_handle = FindFirstFile(root_path.value().c_str(), &find_data); BOOL ret = TRUE; - installer::Version *version = NULL; + installer::Version* version = NULL; // Here we are assuming that the installer we have is really valid so there // can not be two version directories. We exit as soon as we find a valid // version directory. while (ret) { - if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && + lstrcmpW(find_data.cFileName, L"..") != 0 && + lstrcmpW(find_data.cFileName, L".") != 0) { VLOG(1) << "directory found: " << find_data.cFileName; version = installer::Version::GetVersionFromString(find_data.cFileName); if (version) diff --git a/chrome/installer/setup/setup_util.h b/chrome/installer/setup/setup_util.h index 61aed71..0858070 100644 --- a/chrome/installer/setup/setup_util.h +++ b/chrome/installer/setup/setup_util.h @@ -23,7 +23,7 @@ namespace setup_util { // Find the version of Chrome from an install source directory. // Chrome_path should contain a version folder. // Returns the first version found or NULL if no version is found. - installer::Version* GetVersionFromDir(const FilePath& chrome_path); + installer::Version* GetVersionFromArchiveDir(const FilePath& chrome_path); } // namespace setup_util #endif // CHROME_INSTALLER_SETUP_SETUP_UTIL_H_ diff --git a/chrome/installer/setup/setup_util_unittest.cc b/chrome/installer/setup/setup_util_unittest.cc index 0e74de9..8161173 100644 --- a/chrome/installer/setup/setup_util_unittest.cc +++ b/chrome/installer/setup/setup_util_unittest.cc @@ -57,27 +57,27 @@ TEST_F(SetupUtilTest, ApplyDiffPatchTest) { } // Test that we are parsing Chrome version correctly. -TEST_F(SetupUtilTest, GetVersionFromDirTest) { +TEST_F(SetupUtilTest, GetVersionFromArchiveDirTest) { // Create a version dir FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0"); file_util::CreateDirectory(chrome_dir); ASSERT_TRUE(file_util::PathExists(chrome_dir)); scoped_ptr<installer::Version> version( - setup_util::GetVersionFromDir(test_dir_.path())); + setup_util::GetVersionFromArchiveDir(test_dir_.path())); ASSERT_TRUE(version->GetString() == L"1.0.0.0"); file_util::Delete(chrome_dir, true); ASSERT_FALSE(file_util::PathExists(chrome_dir)); - ASSERT_TRUE(setup_util::GetVersionFromDir(test_dir_.path()) == NULL); + ASSERT_TRUE(setup_util::GetVersionFromArchiveDir(test_dir_.path()) == NULL); chrome_dir = test_dir_.path().AppendASCII("ABC"); file_util::CreateDirectory(chrome_dir); ASSERT_TRUE(file_util::PathExists(chrome_dir)); - ASSERT_TRUE(setup_util::GetVersionFromDir(test_dir_.path()) == NULL); + ASSERT_TRUE(setup_util::GetVersionFromArchiveDir(test_dir_.path()) == NULL); chrome_dir = test_dir_.path().AppendASCII("2.3.4.5"); file_util::CreateDirectory(chrome_dir); ASSERT_TRUE(file_util::PathExists(chrome_dir)); - version.reset(setup_util::GetVersionFromDir(test_dir_.path())); + version.reset(setup_util::GetVersionFromArchiveDir(test_dir_.path())); ASSERT_TRUE(version->GetString() == L"2.3.4.5"); } diff --git a/chrome/installer/setup/uninstall.cc b/chrome/installer/setup/uninstall.cc index 4b3ddee..1dee62d 100644 --- a/chrome/installer/setup/uninstall.cc +++ b/chrome/installer/setup/uninstall.cc @@ -32,8 +32,9 @@ #include "registered_dlls.h" // NOLINT using base::win::RegKey; +using installer_util::InstallStatus; -namespace { +namespace installer { // This functions checks for any Chrome instances that are // running and first asks them to close politely by sending a Windows message. @@ -90,7 +91,7 @@ void CloseChromeFrameHelperProcess() { DWORD wait = ::WaitForSingleObject(process, kWaitMs); if (wait != WAIT_OBJECT_0) { LOG(WARNING) << "Wait for " << installer_util::kChromeFrameHelperExe - << " to exit failed or timed out."; + << " to exit failed or timed out."; } else { kill = false; VLOG(1) << installer_util::kChromeFrameHelperExe << " exited normally."; @@ -108,19 +109,18 @@ void CloseChromeFrameHelperProcess() { // It returns true iff: // - Software\Clients\StartMenuInternet\Chromium\"" key has a valid value. // - The value is same as chrome.exe path for the current installation. -bool CurrentUserHasDefaultBrowser(bool system_uninstall) { +bool CurrentUserHasDefaultBrowser(const Product& product) { std::wstring reg_key(ShellUtil::kRegStartMenuInternet); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - reg_key.append(L"\\" + dist->GetApplicationName() + ShellUtil::kRegShellOpen); + reg_key.append(L"\\" + product.distribution()->GetApplicationName() + + ShellUtil::kRegShellOpen); RegKey key(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_READ); std::wstring reg_exe; if (key.ReadValue(L"", ®_exe) && reg_exe.length() > 2) { - std::wstring chrome_exe = installer::GetChromeInstallPath(system_uninstall); - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); + FilePath chrome_exe(product.package().path() + .Append(installer_util::kChromeExe)); + // The path in the registry will always have quotes. reg_exe = reg_exe.substr(1, reg_exe.length() - 2); - if ((reg_exe.size() == chrome_exe.size()) && - (std::equal(chrome_exe.begin(), chrome_exe.end(), - reg_exe.begin(), base::CaseInsensitiveCompare<wchar_t>()))) + if (FilePath::CompareEqualIgnoreCase(reg_exe, chrome_exe.value())) return true; } @@ -133,29 +133,40 @@ bool CurrentUserHasDefaultBrowser(bool system_uninstall) { // We try to remove the standard desktop shortcut but if that fails we try // to remove the alternate desktop shortcut. Only one of them should be // present in a given install but at this point we don't know which one. -void DeleteChromeShortcuts(bool system_uninstall) { +void DeleteChromeShortcuts(const Product& product) { + if (product.distribution()->GetType() != + BrowserDistribution::CHROME_BROWSER) { + VLOG(1) << __FUNCTION__ " called for non-CHROME distribution"; + return; + } + FilePath shortcut_path; - if (system_uninstall) { + if (product.system_level()) { PathService::Get(base::DIR_COMMON_START_MENU, &shortcut_path); - if (!ShellUtil::RemoveChromeDesktopShortcut(ShellUtil::CURRENT_USER | - ShellUtil::SYSTEM_LEVEL, false)) - ShellUtil::RemoveChromeDesktopShortcut(ShellUtil::CURRENT_USER | - ShellUtil::SYSTEM_LEVEL, true); + if (!ShellUtil::RemoveChromeDesktopShortcut(product.distribution(), + ShellUtil::CURRENT_USER | ShellUtil::SYSTEM_LEVEL, false)) { + ShellUtil::RemoveChromeDesktopShortcut(product.distribution(), + ShellUtil::CURRENT_USER | ShellUtil::SYSTEM_LEVEL, true); + } - ShellUtil::RemoveChromeQuickLaunchShortcut(ShellUtil::CURRENT_USER | - ShellUtil::SYSTEM_LEVEL); + ShellUtil::RemoveChromeQuickLaunchShortcut(product.distribution(), + ShellUtil::CURRENT_USER | ShellUtil::SYSTEM_LEVEL); } else { PathService::Get(base::DIR_START_MENU, &shortcut_path); - if (!ShellUtil::RemoveChromeDesktopShortcut(ShellUtil::CURRENT_USER, false)) - ShellUtil::RemoveChromeDesktopShortcut(ShellUtil::CURRENT_USER, true); + if (!ShellUtil::RemoveChromeDesktopShortcut(product.distribution(), + ShellUtil::CURRENT_USER, false)) { + ShellUtil::RemoveChromeDesktopShortcut(product.distribution(), + ShellUtil::CURRENT_USER, true); + } - ShellUtil::RemoveChromeQuickLaunchShortcut(ShellUtil::CURRENT_USER); + ShellUtil::RemoveChromeQuickLaunchShortcut(product.distribution(), + ShellUtil::CURRENT_USER); } if (shortcut_path.empty()) { LOG(ERROR) << "Failed to get location for shortcut."; } else { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - shortcut_path = shortcut_path.Append(dist->GetAppShortCutName()); + shortcut_path = shortcut_path.Append( + product.distribution()->GetAppShortCutName()); VLOG(1) << "Deleting shortcut " << shortcut_path.value(); if (!file_util::Delete(shortcut_path, true)) LOG(ERROR) << "Failed to delete folder: " << shortcut_path.value(); @@ -206,124 +217,134 @@ enum DeleteResult { DELETE_REQUIRES_REBOOT }; -// Deletes all installed files of Chromium and Folders or schedules them for -// deletion on reboot if they are in use. Before deleting it -// needs to move setup.exe in a temp folder because the current process -// is using that file. -// Returns DELETE_SUCCEEDED if all files were successfully delete. -// Returns DELETE_FAILED if it could not get the path to the install dir. -// Returns DELETE_REQUIRES_REBOOT if the files were in use and so were -// scheduled for deletion on next reboot. -DeleteResult DeleteFilesAndFolders(const std::wstring& exe_path, - bool system_uninstall, const installer::Version& installed_version, - std::wstring* local_state_path, bool delete_profile) { - std::wstring install_path(installer::GetChromeInstallPath(system_uninstall)); - if (install_path.empty()) { - LOG(ERROR) << "Could not get installation destination path."; - return DELETE_FAILED; // Nothing else we can do to uninstall, so we return. +FilePath GetLocalStateFolder(const Product& product) { + // chrome_frame will be true for CHROME_FRAME and CEEE. + bool chrome_frame = (product.distribution()->GetType() != + BrowserDistribution::CHROME_BROWSER); + // Obtain the location of the user profile data. Chrome Frame needs to + // build this path manually since it doesn't use the Chrome default dir. + FilePath local_state_folder; + if (chrome_frame) { + chrome::GetChromeFrameUserDataDirectory(&local_state_folder); + } else { + chrome::GetDefaultUserDataDirectory(&local_state_folder); } - VLOG(1) << "install destination path: " << install_path; - // Move setup.exe to the temp path. - std::wstring setup_exe(installer::GetInstallerPathUnderChrome( - install_path, installed_version.GetString())); - file_util::AppendToPath(&setup_exe, file_util::GetFilenameFromPath(exe_path)); + LOG_IF(ERROR, local_state_folder.empty()) + << "Could not retrieve user's profile directory."; - FilePath temp_file; - if (!file_util::CreateTemporaryFile(&temp_file)) { - LOG(ERROR) << "Failed to create temporary file for setup.exe."; - } else { - FilePath setup_exe_path = FilePath::FromWStringHack(setup_exe); - file_util::Move(setup_exe_path, temp_file); - } + return local_state_folder; +} - // Obtain the location of the user profile data. Chrome Frame needs to - // build this path manually since it doesn't use the Chrome default dir. - FilePath user_local_state; - bool got_local_state = false; - if (InstallUtil::IsChromeFrameProcess()) { - got_local_state = - chrome::GetChromeFrameUserDataDirectory(&user_local_state); +// Creates a copy of the local state file and returns a path to the copy. +FilePath BackupLocalStateFile(const FilePath& local_state_folder) { + FilePath backup; + FilePath state_file(local_state_folder.Append(chrome::kLocalStateFilename)); + if (!file_util::CreateTemporaryFile(&backup)) { + LOG(ERROR) << "Failed to create temporary file for Local State."; } else { - got_local_state = chrome::GetDefaultUserDataDirectory(&user_local_state); + file_util::CopyFile(state_file, backup); } + return backup; +} - // Move the browser's persisted local state - if (got_local_state) { - FilePath user_local_file( - user_local_state.Append(chrome::kLocalStateFilename)); - FilePath path; - if (!file_util::CreateTemporaryFile(&path)) { - LOG(ERROR) << "Failed to create temporary file for Local State."; +// Copies the local state to the temp folder and then deletes it. +// The path to the copy is returned via the local_state_copy parameter. +DeleteResult DeleteLocalState(const Product& product) { + FilePath user_local_state(GetLocalStateFolder(product)); + if (user_local_state.empty()) + return DELETE_SUCCEEDED; + + DeleteResult result = DELETE_SUCCEEDED; + VLOG(1) << "Deleting user profile" << user_local_state.value(); + if (!file_util::Delete(user_local_state, true)) { + LOG(ERROR) << "Failed to delete user profile dir: " + << user_local_state.value(); + if (product.distribution()->GetType() == + BrowserDistribution::CHROME_FRAME) { + ScheduleDirectoryForDeletion(user_local_state.value().c_str()); + result = DELETE_REQUIRES_REBOOT; } else { - *local_state_path = path.value(); - file_util::CopyFile(user_local_file, path); + result = DELETE_FAILED; } + } + + if (result == DELETE_REQUIRES_REBOOT) { + ScheduleParentAndGrandparentForDeletion(user_local_state); } else { - LOG(ERROR) << "Could not retrieve user's profile directory."; + DeleteEmptyParentDir(user_local_state); + } + + return result; +} + +bool MoveSetupOutOfInstallFolder(const Package& package, + const FilePath& setup_path, + const Version& installed_version) { + bool ret = false; + FilePath setup_exe(package.GetInstallerDirectory(installed_version) + .Append(setup_path.BaseName())); + FilePath temp_file; + if (!file_util::CreateTemporaryFile(&temp_file)) { + LOG(ERROR) << "Failed to create temporary file for setup.exe."; + } else { + ret = file_util::Move(setup_exe, temp_file); + } + return ret; +} + +DeleteResult DeleteFilesAndFolders(const Package& package, + const installer::Version& installed_version) { + VLOG(1) << "DeleteFilesAndFolders: " << package.path().value(); + if (package.path().empty()) { + LOG(ERROR) << "Could not get installation destination path."; + return DELETE_FAILED; // Nothing else we can do to uninstall, so we return. } DeleteResult result = DELETE_SUCCEEDED; - VLOG(1) << "Deleting install path " << install_path; - if (!file_util::Delete(install_path, true)) { - LOG(ERROR) << "Failed to delete folder (1st try): " << install_path; - if (InstallUtil::IsChromeFrameProcess()) { + VLOG(1) << "Deleting install path " << package.path().value(); + if (!file_util::Delete(package.path(), true)) { + LOG(ERROR) << "Failed to delete folder (1st try): " + << package.path().value(); + if (FindProduct(package.products(), + BrowserDistribution::CHROME_FRAME)) { // We don't try killing Chrome processes for Chrome Frame builds since // that is unlikely to help. Instead, schedule files for deletion and // return a value that will trigger a reboot prompt. - ScheduleDirectoryForDeletion(install_path.c_str()); + ScheduleDirectoryForDeletion(package.path().value().c_str()); result = DELETE_REQUIRES_REBOOT; } else { // Try closing any running chrome processes and deleting files once again. CloseAllChromeProcesses(); - if (!file_util::Delete(install_path, true)) { - LOG(ERROR) << "Failed to delete folder (2nd try): " << install_path; - result = DELETE_FAILED; - } - } - } - - if (delete_profile && got_local_state) { - VLOG(1) << "Deleting user profile" << user_local_state.value(); - if (!file_util::Delete(user_local_state, true)) { - LOG(ERROR) << "Failed to delete user profile dir: " - << user_local_state.value(); - if (InstallUtil::IsChromeFrameProcess()) { - ScheduleDirectoryForDeletion(user_local_state.value().c_str()); - result = DELETE_REQUIRES_REBOOT; - } else { + if (!file_util::Delete(package.path(), true)) { + LOG(ERROR) << "Failed to delete folder (2nd try): " + << package.path().value(); result = DELETE_FAILED; } } - if (result == DELETE_REQUIRES_REBOOT) { - ScheduleParentAndGrandparentForDeletion(user_local_state); - } else { - DeleteEmptyParentDir(user_local_state); - } } if (result == DELETE_REQUIRES_REBOOT) { // If we need a reboot to continue, schedule the parent directories for // deletion unconditionally. If they are not empty, the session manager // will not delete them on reboot. - ScheduleParentAndGrandparentForDeletion(FilePath(install_path)); + ScheduleParentAndGrandparentForDeletion(package.path()); } else { // Now check and delete if the parent directories are empty // For example Google\Chrome or Chromium - DeleteEmptyParentDir(FilePath(install_path)); + DeleteEmptyParentDir(package.path()); } return result; } - // This method checks if Chrome is currently running or if the user has // cancelled the uninstall operation by clicking Cancel on the confirmation // box that Chrome pops up. -installer_util::InstallStatus IsChromeActiveOrUserCancelled( - bool system_uninstall) { - static const std::wstring kCmdLineOptions(L" --uninstall"); +InstallStatus IsChromeActiveOrUserCancelled(const Product& product) { int32 exit_code = ResultCodes::NORMAL_EXIT; + CommandLine options(CommandLine::NO_PROGRAM); + options.AppendSwitch(installer_util::switches::kUninstall); // Here we want to save user from frustration (in case of Chrome crashes) // and continue with the uninstallation as long as chrome.exe process exit @@ -334,35 +355,33 @@ installer_util::InstallStatus IsChromeActiveOrUserCancelled( // give this method some brains and not kill chrome.exe launched // by us, we will not uninstall if we get this return code). VLOG(1) << "Launching Chrome to do uninstall tasks."; - if (installer::LaunchChromeAndWaitForResult(system_uninstall, - kCmdLineOptions, - &exit_code)) { + if (product.LaunchChromeAndWait(options, &exit_code)) { VLOG(1) << "chrome.exe launched for uninstall confirmation returned: " << exit_code; if ((exit_code == ResultCodes::UNINSTALL_CHROME_ALIVE) || (exit_code == ResultCodes::UNINSTALL_USER_CANCEL) || (exit_code == ResultCodes::HUNG)) return installer_util::UNINSTALL_CANCELLED; + if (exit_code == ResultCodes::UNINSTALL_DELETE_PROFILE) return installer_util::UNINSTALL_DELETE_PROFILE; } else { - LOG(ERROR) << "Failed to launch chrome.exe for uninstall confirmation."; + PLOG(ERROR) << "Failed to launch chrome.exe for uninstall confirmation."; } return installer_util::UNINSTALL_CONFIRMED; } -bool ShouldDeleteProfile(const CommandLine& cmd_line, - installer_util::InstallStatus status, - bool system_uninstall) { +bool ShouldDeleteProfile(const CommandLine& cmd_line, InstallStatus status, + const Product& product) { bool should_delete = false; // Chrome Frame uninstallations always want to delete the profile (we have no // UI to prompt otherwise and the profile stores no useful data anyway) // unless they are managed by MSI. MSI uninstalls will explicitly include // the --delete-profile flag to distinguish them from MSI upgrades. - if (InstallUtil::IsChromeFrameProcess() && - !InstallUtil::IsMSIProcess(system_uninstall)) { + if (product.distribution()->GetType() != + BrowserDistribution::CHROME_BROWSER && !product.IsMsi()) { should_delete = true; } else { should_delete = @@ -373,18 +392,18 @@ bool ShouldDeleteProfile(const CommandLine& cmd_line, return should_delete; } -} // namespace - - -bool installer_setup::DeleteChromeRegistrationKeys(HKEY root, - const std::wstring& browser_entry_suffix, - installer_util::InstallStatus& exit_code) { - if (!BrowserDistribution::GetDistribution()->CanSetAsDefault()) { +bool DeleteChromeRegistrationKeys(BrowserDistribution* dist, HKEY root, + const std::wstring& browser_entry_suffix, + InstallStatus& exit_code) { + if (!dist->CanSetAsDefault()) { // We should have never set those keys. return true; } RegKey key(root, L"", KEY_ALL_ACCESS); + if (!key.Valid()) { + PLOG(ERROR) << "DeleteChromeRegistrationKeys: failed to open root key"; + } // Delete Software\Classes\ChromeHTML, std::wstring html_prog_id(ShellUtil::kRegClasses); @@ -393,7 +412,6 @@ bool installer_setup::DeleteChromeRegistrationKeys(HKEY root, InstallUtil::DeleteRegistryKey(key, html_prog_id); // Delete Software\Clients\StartMenuInternet\Chromium - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::wstring set_access_key(ShellUtil::kRegStartMenuInternet); file_util::AppendToPath(&set_access_key, dist->GetApplicationName()); set_access_key.append(browser_entry_suffix); @@ -435,7 +453,7 @@ bool installer_setup::DeleteChromeRegistrationKeys(HKEY root, return true; } -void installer_setup::RemoveLegacyRegistryKeys() { +void RemoveLegacyRegistryKeys(BrowserDistribution* dist) { // We used to register Chrome to handle crx files, but this turned out // to be not worth the hassle. Remove these old registry entries if // they exist. See: http://codereview.chromium.org/210007 @@ -452,7 +470,7 @@ const wchar_t kChromeExtProgId[] = L"ChromiumExt"; std::wstring suffix; if (roots[i] == HKEY_LOCAL_MACHINE && - !ShellUtil::GetUserSpecificDefaultBrowserSuffix(&suffix)) + !ShellUtil::GetUserSpecificDefaultBrowserSuffix(dist, &suffix)) suffix = L""; // Delete Software\Classes\ChromeExt, @@ -469,23 +487,32 @@ const wchar_t kChromeExtProgId[] = L"ChromiumExt"; } } -installer_util::InstallStatus installer_setup::UninstallChrome( - const std::wstring& exe_path, bool system_uninstall, - bool remove_all, bool force_uninstall, - const CommandLine& cmd_line) { - installer_util::InstallStatus status = installer_util::UNINSTALL_CONFIRMED; +InstallStatus UninstallChrome(const FilePath& setup_path, + const Product& product, + bool remove_all, + bool force_uninstall, + const CommandLine& cmd_line) { + InstallStatus status = installer_util::UNINSTALL_CONFIRMED; std::wstring suffix; - if (!ShellUtil::GetUserSpecificDefaultBrowserSuffix(&suffix)) + if (!ShellUtil::GetUserSpecificDefaultBrowserSuffix(product.distribution(), + &suffix)) suffix = L""; + BrowserDistribution* browser_dist = product.distribution(); + bool is_chrome = (browser_dist->GetType() == + BrowserDistribution::CHROME_BROWSER); + + VLOG(1) << "UninstallChrome: " << browser_dist->GetApplicationName(); + if (force_uninstall) { // Since --force-uninstall command line option is used, we are going to // do silent uninstall. Try to close all running Chrome instances. - if (!InstallUtil::IsChromeFrameProcess()) + // NOTE: We don't do this for Chrome Frame or CEEE. + if (is_chrome) CloseAllChromeProcesses(); - } else if (!InstallUtil::IsChromeFrameProcess()) { + } else if (is_chrome) { // no --force-uninstall so lets show some UI dialog boxes. - status = IsChromeActiveOrUserCancelled(system_uninstall); + status = IsChromeActiveOrUserCancelled(product); if (status != installer_util::UNINSTALL_CONFIRMED && status != installer_util::UNINSTALL_DELETE_PROFILE) return status; @@ -494,9 +521,9 @@ installer_util::InstallStatus installer_setup::UninstallChrome( // another uninstaller (silent) in elevated mode to do HKLM cleanup. // And continue uninstalling in the current process also to do HKCU cleanup. if (remove_all && - (!suffix.empty() || CurrentUserHasDefaultBrowser(system_uninstall)) && + (!suffix.empty() || CurrentUserHasDefaultBrowser(product)) && !::IsUserAnAdmin() && - (base::win::GetVersion() >= base::win::VERSION_VISTA) && + base::win::GetVersion() >= base::win::VERSION_VISTA && !cmd_line.HasSwitch(installer_util::switches::kRunAsAdmin)) { CommandLine new_cmd(CommandLine::NO_PROGRAM); new_cmd.AppendArguments(cmd_line, true); @@ -516,49 +543,51 @@ installer_util::InstallStatus installer_setup::UninstallChrome( // Get the version of installed Chrome (if any) scoped_ptr<installer::Version> - installed_version(InstallUtil::GetChromeVersion(system_uninstall)); + installed_version(InstallUtil::GetChromeVersion(browser_dist, + product.system_level())); // Chrome is not in use so lets uninstall Chrome by deleting various files // and registry entries. Here we will just make best effort and keep going // in case of errors. // First delete shortcuts from Start->Programs, Desktop & Quick Launch. - DeleteChromeShortcuts(system_uninstall); + DeleteChromeShortcuts(product); // Delete the registry keys (Uninstall key and Version key). - HKEY reg_root = system_uninstall ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; RegKey key(reg_root, L"", KEY_ALL_ACCESS); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); // Note that we must retrieve the distribution-specific data before deleting - // dist->GetVersionKey(). - std::wstring distribution_data(dist->GetDistributionData(&key)); + // product.GetVersionKey(). + std::wstring distribution_data(browser_dist->GetDistributionData(reg_root)); // Remove Control Panel uninstall link and Omaha product key. - InstallUtil::DeleteRegistryKey(key, dist->GetUninstallRegPath()); - InstallUtil::DeleteRegistryKey(key, dist->GetVersionKey()); + InstallUtil::DeleteRegistryKey(key, browser_dist->GetUninstallRegPath()); + InstallUtil::DeleteRegistryKey(key, browser_dist->GetVersionKey()); // Also try to delete the MSI value in the ClientState key (it might not be // there). This is due to a Google Update behaviour where an uninstall and a // rapid reinstall might result in stale values from the old ClientState key // being picked up on reinstall. - InstallUtil::SetMSIMarker(system_uninstall, false); + product.SetMsiMarker(false); // Remove all Chrome registration keys. - installer_util::InstallStatus ret = installer_util::UNKNOWN_STATUS; - DeleteChromeRegistrationKeys(reg_root, suffix, ret); + InstallStatus ret = installer_util::UNKNOWN_STATUS; + DeleteChromeRegistrationKeys(product.distribution(), reg_root, suffix, ret); // For user level install also we end up creating some keys in HKLM if user // sets Chrome as default browser. So delete those as well (needs admin). - if (remove_all && !system_uninstall && - (!suffix.empty() || CurrentUserHasDefaultBrowser(system_uninstall))) - DeleteChromeRegistrationKeys(HKEY_LOCAL_MACHINE, suffix, ret); + if (remove_all && !product.system_level() && + (!suffix.empty() || CurrentUserHasDefaultBrowser(product))) { + DeleteChromeRegistrationKeys(product.distribution(), HKEY_LOCAL_MACHINE, + suffix, ret); + } // Delete shared registry keys as well (these require admin rights) if // remove_all option is specified. if (remove_all) { - if (!InstallUtil::IsChromeSxSProcess() && - !InstallUtil::IsChromeFrameProcess()) { + if (!InstallUtil::IsChromeSxSProcess() && is_chrome) { // Delete media player registry key that exists only in HKLM. // We don't delete this key in SxS uninstall or Chrome Frame uninstall // as we never set the key for those products. @@ -570,18 +599,14 @@ installer_util::InstallStatus installer_setup::UninstallChrome( } // Unregister any dll servers that we may have registered for Chrome Frame - // builds only. - if (installed_version.get() && InstallUtil::IsChromeFrameProcess()) { - std::wstring dll_path(installer::GetChromeInstallPath(system_uninstall)); - file_util::AppendToPath(&dll_path, installed_version->GetString()); - - scoped_ptr<WorkItemList> dll_list(WorkItem::CreateWorkItemList()); - if (InstallUtil::BuildDLLRegistrationList(dll_path, kDllsToRegister, - kNumDllsToRegister, false, - !system_uninstall, - dll_list.get())) { - dll_list->Do(); - } + // and CEEE builds only. + // TODO(tommi): We should only do this when the folder itself is + // being removed and we know that the DLLs were previously registered. + // Simplest would be to always register them. + if (installed_version.get() && !is_chrome) { + RegisterComDllList(product.package().path().Append( + installed_version->GetString()), + product.system_level(), false, false); } } @@ -596,12 +621,26 @@ installer_util::InstallStatus installer_setup::UninstallChrome( // Finally delete all the files from Chrome folder after moving setup.exe // and the user's Local State to a temp location. - bool delete_profile = ShouldDeleteProfile(cmd_line, status, system_uninstall); - std::wstring local_state_path; + bool delete_profile = ShouldDeleteProfile(cmd_line, status, product); ret = installer_util::UNINSTALL_SUCCESSFUL; - DeleteResult delete_result = DeleteFilesAndFolders(exe_path, - system_uninstall, *installed_version, &local_state_path, delete_profile); + // In order to be able to remove the folder in which we're running, we + // need to move setup.exe out of the install folder. + // TODO(tommi): What if the temp folder is on a different volume? + MoveSetupOutOfInstallFolder(product.package(), setup_path, + *installed_version); + + FilePath backup_state_file(BackupLocalStateFile( + GetLocalStateFolder(product))); + + // TODO(tommi): We should only do this when the last distribution is being + // uninstalled. + DeleteResult delete_result = DeleteFilesAndFolders(product.package(), + *installed_version); + + if (delete_profile) + DeleteLocalState(product); + if (delete_result == DELETE_FAILED) { ret = installer_util::UNINSTALL_FAILED; } else if (delete_result == DELETE_REQUIRES_REBOOT) { @@ -610,14 +649,17 @@ installer_util::InstallStatus installer_setup::UninstallChrome( if (!force_uninstall) { VLOG(1) << "Uninstallation complete. Launching Uninstall survey."; - dist->DoPostUninstallOperations(*installed_version, local_state_path, - distribution_data); + browser_dist->DoPostUninstallOperations(*installed_version, + backup_state_file, distribution_data); } // Try and delete the preserved local state once the post-install // operations are complete. - if (!local_state_path.empty()) - file_util::Delete(local_state_path, false); + if (!backup_state_file.empty()) + file_util::Delete(backup_state_file, false); return ret; } + +} // namespace installer + diff --git a/chrome/installer/setup/uninstall.h b/chrome/installer/setup/uninstall.h index d0dc8c3..9165091 100644 --- a/chrome/installer/setup/uninstall.h +++ b/chrome/installer/setup/uninstall.h @@ -13,39 +13,37 @@ #include <shlobj.h> #include "base/command_line.h" +#include "chrome/installer/util/product.h" #include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/version.h" -namespace installer_setup { +namespace installer { // This function removes all Chrome registration related keys. It returns true // if successful, otherwise false. The error code is set in |exit_code|. // |root| is the registry root (HKLM|HKCU) and |browser_entry_suffix| is the // suffix for default browser entry name in the registry (optional). -bool DeleteChromeRegistrationKeys(HKEY root, +bool DeleteChromeRegistrationKeys(BrowserDistribution* dist, HKEY root, const std::wstring& browser_entry_suffix, installer_util::InstallStatus& exit_code); // Removes any legacy registry keys from earlier versions of Chrome that are no // longer needed. This is used during autoupdate since we don't do full // uninstalls/reinstalls to update. -void RemoveLegacyRegistryKeys(); +void RemoveLegacyRegistryKeys(BrowserDistribution* dist); // This function uninstalls Chrome. // -// exe_path: Path to the executable (setup.exe) as it will be copied +// setup_path: Path to the executable (setup.exe) as it will be copied // to temp folder before deleting Chrome folder. -// system_uninstall: if true, the function uninstalls Chrome installed system -// wise. otherwise, it uninstalls Chrome installed for the -// current user. +// dist: Represents the distribution to be uninstalled. // remove_all: Remove all shared files, registry entries as well. // force_uninstall: Uninstall without prompting for user confirmation or // any checks for Chrome running. // cmd_line: CommandLine that contains information about the command that // was used to launch current uninstaller. installer_util::InstallStatus UninstallChrome( - const std::wstring& exe_path, bool system_uninstall, - bool remove_all, bool force_uninstall, - const CommandLine& cmd_line); + const FilePath& setup_path, const Product& dist, bool remove_all, + bool force_uninstall, const CommandLine& cmd_line); } // namespace installer_setup diff --git a/chrome/installer/util/browser_distribution.cc b/chrome/installer/util/browser_distribution.cc index 2e2ccf2..db98298 100644 --- a/chrome/installer/util/browser_distribution.cc +++ b/chrome/installer/util/browser_distribution.cc @@ -9,10 +9,12 @@ #include "chrome/installer/util/browser_distribution.h" +#include "base/atomicops.h" #include "base/command_line.h" #include "base/file_path.h" #include "base/path_service.h" #include "base/lock.h" +#include "base/logging.h" #include "base/win/registry.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/env_vars.h" @@ -25,6 +27,11 @@ #include "installer_util_strings.h" namespace { +// The BrowserDistribution objects are never freed. +BrowserDistribution* g_browser_distribution = NULL; +BrowserDistribution* g_chrome_frame_distribution = NULL; +BrowserDistribution* g_ceee_distribution = NULL; + // Returns true if currently running in npchrome_frame.dll bool IsChromeFrameModule() { FilePath module_path; @@ -41,40 +48,69 @@ bool IsCeeeBrokerProcess() { installer_util::kCeeeBrokerExe); } +BrowserDistribution::DistributionType GetCurrentDistributionType() { + static BrowserDistribution::DistributionType type = + (InstallUtil::IsChromeFrameProcess() || IsChromeFrameModule()) ? + BrowserDistribution::CHROME_FRAME : + (IsCeeeBrokerProcess() ? BrowserDistribution::CEEE : + BrowserDistribution::CHROME_BROWSER); + return type; +} + } // end namespace +template<class DistributionClass> +BrowserDistribution* BrowserDistribution::GetOrCreateBrowserDistribution( + BrowserDistribution** dist) { + if (!*dist) { + DistributionClass* temp = new DistributionClass(); + if (base::subtle::NoBarrier_CompareAndSwap( + reinterpret_cast<base::subtle::AtomicWord*>(dist), NULL, + reinterpret_cast<base::subtle::AtomicWord>(temp)) != NULL) + delete temp; + } + + return *dist; +} + BrowserDistribution* BrowserDistribution::GetDistribution() { - return GetDistribution(InstallUtil::IsChromeFrameProcess() || - IsChromeFrameModule() || - IsCeeeBrokerProcess()); -} - -BrowserDistribution* BrowserDistribution::GetDistribution(bool chrome_frame) { - static BrowserDistribution* dist = NULL; - static Lock dist_lock; - AutoLock lock(dist_lock); - if (dist == NULL) { - if (chrome_frame) { - // TODO(robertshield): Make one of these for Google Chrome vs - // non Google Chrome builds? - dist = new ChromeFrameDistribution(); - } else { + return GetSpecificDistribution(GetCurrentDistributionType()); +} + +// static +BrowserDistribution* BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::DistributionType type) { + BrowserDistribution* dist = NULL; + + // TODO(tommi): initialize g_ceee_distribution when appropriate. Right now + // we treat CEEE as Chrome Frame. + + if (type == CHROME_FRAME || type == CEEE) { + // TODO(robertshield): Make one of these for Google Chrome vs + // non Google Chrome builds? + dist = GetOrCreateBrowserDistribution<ChromeFrameDistribution>( + &g_chrome_frame_distribution); + } else { + DCHECK_EQ(CHROME_BROWSER, type); #if defined(GOOGLE_CHROME_BUILD) if (InstallUtil::IsChromeSxSProcess()) { - dist = new GoogleChromeSxSDistribution(); + dist = GetOrCreateBrowserDistribution<GoogleChromeSxSDistribution>( + &g_browser_distribution); } else { - dist = new GoogleChromeDistribution(); + dist = GetOrCreateBrowserDistribution<GoogleChromeDistribution>( + &g_browser_distribution); } #else - dist = new BrowserDistribution(); + dist = GetOrCreateBrowserDistribution<BrowserDistribution>( + &g_browser_distribution); #endif - } } + return dist; } void BrowserDistribution::DoPostUninstallOperations( - const installer::Version& version, const std::wstring& local_data_path, + const installer::Version& version, const FilePath& local_data_path, const std::wstring& distribution_data) { } @@ -116,6 +152,7 @@ std::wstring BrowserDistribution::GetLongAppDescription() { return app_description; } +// static int BrowserDistribution::GetInstallReturnCode( installer_util::InstallStatus status) { switch (status) { @@ -145,7 +182,7 @@ std::wstring BrowserDistribution::GetStatsServerURL() { return L""; } -std::wstring BrowserDistribution::GetDistributionData(base::win::RegKey* key) { +std::wstring BrowserDistribution::GetDistributionData(HKEY root_key) { return L""; } @@ -183,10 +220,10 @@ void BrowserDistribution::UpdateDiffInstallStatus(bool system_install, void BrowserDistribution::LaunchUserExperiment( installer_util::InstallStatus status, const installer::Version& version, - bool system_install) { + const installer::Product& installation, bool system_level) { } void BrowserDistribution::InactiveUserToastExperiment(int flavor, - bool system_install) { + const installer::Product& installation) { } diff --git a/chrome/installer/util/browser_distribution.h b/chrome/installer/util/browser_distribution.h index 39eecdd..749d6af 100644 --- a/chrome/installer/util/browser_distribution.h +++ b/chrome/installer/util/browser_distribution.h @@ -9,23 +9,38 @@ #pragma once #include "base/basictypes.h" +#include "base/file_path.h" #include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/version.h" -namespace base { -namespace win { -class RegKey; -} // namespace win -} // namespace base +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace installer { +class Product; +} class BrowserDistribution { public: virtual ~BrowserDistribution() {} + typedef enum DistributionType { + CHROME_BROWSER, + CHROME_FRAME, + CEEE, + }; + static BrowserDistribution* GetDistribution(); + static BrowserDistribution* GetSpecificDistribution(DistributionType type); + + DistributionType GetType() const { return type_; } + + static int GetInstallReturnCode(installer_util::InstallStatus install_status); + virtual void DoPostUninstallOperations(const installer::Version& version, - const std::wstring& local_data_path, + const FilePath& local_data_path, const std::wstring& distribution_data); virtual std::wstring GetAppGuid(); @@ -46,9 +61,6 @@ class BrowserDistribution { virtual std::wstring GetLongAppDescription(); - virtual int GetInstallReturnCode( - installer_util::InstallStatus install_status); - virtual std::string GetSafeBrowsingName(); virtual std::wstring GetStateKey(); @@ -57,7 +69,9 @@ class BrowserDistribution { virtual std::wstring GetStatsServerURL(); - virtual std::wstring GetDistributionData(base::win::RegKey* key); +#if defined(OS_WIN) + virtual std::wstring GetDistributionData(HKEY root_key); +#endif virtual std::wstring GetUninstallLinkName(); @@ -80,17 +94,23 @@ class BrowserDistribution { // experiment. This function determines if the user qualifies and if so it // sets the wheels in motion or in simple cases does the experiment itself. virtual void LaunchUserExperiment(installer_util::InstallStatus status, - const installer::Version& version, - bool system_install); + const installer::Version& version, + const installer::Product& installation, + bool system_level); // The user has qualified for the inactive user toast experiment and this // function just performs it. - virtual void InactiveUserToastExperiment(int flavor, bool system_install); + virtual void InactiveUserToastExperiment(int flavor, + const installer::Product& installation); protected: - BrowserDistribution() {} + BrowserDistribution() : type_(CHROME_BROWSER) {} + + template<class DistributionClass> + static BrowserDistribution* GetOrCreateBrowserDistribution( + BrowserDistribution** dist); - static BrowserDistribution* GetDistribution(bool chrome_frame); + DistributionType type_; private: DISALLOW_COPY_AND_ASSIGN(BrowserDistribution); diff --git a/chrome/installer/util/browser_distribution_unittest.cc b/chrome/installer/util/browser_distribution_unittest.cc index 55d0d8f..2733d1f 100644 --- a/chrome/installer/util/browser_distribution_unittest.cc +++ b/chrome/installer/util/browser_distribution_unittest.cc @@ -24,14 +24,23 @@ class BrowserDistributionTest : public testing::Test { // The distribution strings should not be empty. The unit tests are not linking // with the chrome resources so we cannot test official build. TEST(BrowserDistributionTest, StringsTest) { - BrowserDistribution *dist = BrowserDistribution::GetDistribution(); - ASSERT_TRUE(dist != NULL); - std::wstring name = dist->GetApplicationName(); - EXPECT_FALSE(name.empty()); - std::wstring desc = dist->GetAppDescription(); - EXPECT_FALSE(desc.empty()); - std::wstring alt_name = dist->GetAlternateApplicationName(); - EXPECT_FALSE(alt_name.empty()); + BrowserDistribution::DistributionType types[] = { + BrowserDistribution::CHROME_BROWSER, + BrowserDistribution::CHROME_FRAME, + // TODO(tommi): Also include CEEE. + }; + + for (int i = 0; i < arraysize(types); ++i) { + BrowserDistribution* dist = + BrowserDistribution::GetSpecificDistribution(types[i]); + ASSERT_TRUE(dist != NULL); + std::wstring name = dist->GetApplicationName(); + EXPECT_FALSE(name.empty()); + std::wstring desc = dist->GetAppDescription(); + EXPECT_FALSE(desc.empty()); + std::wstring alt_name = dist->GetAlternateApplicationName(); + EXPECT_FALSE(alt_name.empty()); + } } // The shortcut strings obtained by the shell utility functions should not @@ -39,8 +48,9 @@ TEST(BrowserDistributionTest, StringsTest) { TEST(BrowserDistributionTest, AlternateAndNormalShortcutName) { std::wstring normal_name; std::wstring alternate_name; - EXPECT_TRUE(ShellUtil::GetChromeShortcutName(&normal_name, false)); - EXPECT_TRUE(ShellUtil::GetChromeShortcutName(&alternate_name, true)); + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + EXPECT_TRUE(ShellUtil::GetChromeShortcutName(dist, &normal_name, false)); + EXPECT_TRUE(ShellUtil::GetChromeShortcutName(dist, &alternate_name, true)); EXPECT_NE(normal_name, alternate_name); EXPECT_FALSE(normal_name.empty()); EXPECT_FALSE(alternate_name.empty()); diff --git a/chrome/installer/util/chrome_frame_distribution.h b/chrome/installer/util/chrome_frame_distribution.h index f42d41f..6b071ae 100644 --- a/chrome/installer/util/chrome_frame_distribution.h +++ b/chrome/installer/util/chrome_frame_distribution.h @@ -54,11 +54,13 @@ class ChromeFrameDistribution : public BrowserDistribution { virtual void UpdateDiffInstallStatus(bool system_install, bool incremental_install, installer_util::InstallStatus install_status); - private: + protected: friend class BrowserDistribution; // Disallow construction from non-friends. - ChromeFrameDistribution() {} + ChromeFrameDistribution() { + type_ = BrowserDistribution::CHROME_FRAME; + } }; diff --git a/chrome/installer/util/google_chrome_distribution.cc b/chrome/installer/util/google_chrome_distribution.cc index 34a6333..375a4e1 100644 --- a/chrome/installer/util/google_chrome_distribution.cc +++ b/chrome/installer/util/google_chrome_distribution.cc @@ -26,6 +26,7 @@ #include "chrome/common/json_value_serializer.h" #include "chrome/common/pref_names.h" #include "chrome/common/result_codes.h" +#include "chrome/installer/util/product.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/l10n_string_util.h" #include "chrome/installer/util/google_update_constants.h" @@ -324,7 +325,7 @@ bool GoogleChromeDistribution::ExtractUninstallMetrics( #endif void GoogleChromeDistribution::DoPostUninstallOperations( - const installer::Version& version, const std::wstring& local_data_path, + const installer::Version& version, const FilePath& local_data_path, const std::wstring& distribution_data) { // Send the Chrome version and OS version as params to the form. // It would be nice to send the locale, too, but I don't see an @@ -357,7 +358,8 @@ void GoogleChromeDistribution::DoPostUninstallOperations( os_version; std::wstring uninstall_metrics; - if (ExtractUninstallMetricsFromFile(local_data_path, &uninstall_metrics)) { + if (ExtractUninstallMetricsFromFile(local_data_path.value(), + &uninstall_metrics)) { // The user has opted into anonymous usage data collection, so append // metrics and distribution data. command += uninstall_metrics; @@ -436,14 +438,12 @@ std::wstring GoogleChromeDistribution::GetStatsServerURL() { return L"https://clients4.google.com/firefox/metrics/collect"; } -std::wstring GoogleChromeDistribution::GetDistributionData( - base::win::RegKey* key) { - DCHECK(NULL != key); +std::wstring GoogleChromeDistribution::GetDistributionData(HKEY root_key) { std::wstring sub_key(google_update::kRegPathClientState); sub_key.append(L"\\"); sub_key.append(product_guid()); - base::win::RegKey client_state_key(key->Handle(), sub_key.c_str(), KEY_READ); + base::win::RegKey client_state_key(root_key, sub_key.c_str(), KEY_READ); std::wstring result; std::wstring brand_value; if (client_state_key.ReadValue(google_update::kRegRLZBrandField, @@ -557,8 +557,8 @@ void SetClient(std::wstring experiment_group, bool last_write) { // this function with |system_install| true and a REENTRY_SYS_UPDATE status. void GoogleChromeDistribution::LaunchUserExperiment( installer_util::InstallStatus status, const installer::Version& version, - bool system_install) { - if (system_install) { + const installer::Product& installation, bool system_level) { + if (system_level) { if (installer_util::NEW_VERSION_UPDATED == status) { // We need to relaunch as the interactive user. RelaunchSetupAsConsoleUser(installer_util::switches::kSystemLevelToast); @@ -586,10 +586,12 @@ void GoogleChromeDistribution::LaunchUserExperiment( } else { // Check browser usage inactivity by the age of the last-write time of the // chrome user data directory. - std::wstring user_data_dir = installer::GetChromeUserDataPath(); + FilePath user_data_dir(installation.GetUserDataPath()); + // TODO(cpu): re-enable experiment. const int kThirtyDays = 3000 * 24; - int dir_age_hours = GetDirectoryWriteAgeInHours(user_data_dir.c_str()); + int dir_age_hours = GetDirectoryWriteAgeInHours( + user_data_dir.value().c_str()); if (dir_age_hours < 0) { // This means that we failed to find the user data dir. The most likely // cause is that this user has not ever used chrome at all which can @@ -617,26 +619,32 @@ void GoogleChromeDistribution::LaunchUserExperiment( // System level: We have already been relaunched, so we don't need to be // quick, but we relaunch to follow the exact same codepath. RelaunchSetup(installer_util::switches::kInactiveUserToast, flavor, - system_install); + system_level); } // User qualifies for the experiment. Launch chrome with --try-chrome=flavor. void GoogleChromeDistribution::InactiveUserToastExperiment(int flavor, - bool system_install) { + const installer::Product& installation) { bool has_welcome_url = (flavor == 0); // Possibly add a url to launch depending on the experiment flavor. - std::wstring options(StringPrintf(L"--%ls=%d", - ASCIIToUTF16(switches::kTryChromeAgain).c_str(), flavor)); + CommandLine options(CommandLine::NO_PROGRAM); + options.AppendSwitchNative(switches::kTryChromeAgain, + base::IntToString16(flavor)); if (has_welcome_url) { - const std::wstring url(GetWelcomeBackUrl()); - options.append(L" -- "); - options.append(url); + // Prepend the url with a space. + std::wstring url(GetWelcomeBackUrl()); + options.AppendArg("--"); + options.AppendArgNative(url); + // The command line should now have the url added as: + // "chrome.exe -- <url>" + DCHECK_NE(std::wstring::npos, + options.command_line_string().find(L" -- " + url)); } // Launch chrome now. It will show the toast UI. int32 exit_code = 0; - if (!installer::LaunchChromeAndWaitForResult(system_install, - options, &exit_code)) + if (!installation.LaunchChromeAndWait(options, &exit_code)) return; + // The chrome process has exited, figure out what happened. const wchar_t* outcome = NULL; switch (exit_code) { @@ -658,10 +666,16 @@ void GoogleChromeDistribution::InactiveUserToastExperiment(int flavor, if (outcome != kToastExpUninstallGroup) return; + // The user wants to uninstall. This is a best effort operation. Note that // we waited for chrome to exit so the uninstall would not detect chrome // running. - base::LaunchApp(InstallUtil::GetChromeUninstallCmd(system_install), - false, false, NULL); + bool system_level_toast = CommandLine::ForCurrentProcess()->HasSwitch( + installer_util::switches::kSystemLevelToast); + + std::wstring cmd(InstallUtil::GetChromeUninstallCmd( + system_level_toast, this)); + + base::LaunchApp(cmd, false, false, NULL); } #endif diff --git a/chrome/installer/util/google_chrome_distribution.h b/chrome/installer/util/google_chrome_distribution.h index 53d754b..d6fa281 100644 --- a/chrome/installer/util/google_chrome_distribution.h +++ b/chrome/installer/util/google_chrome_distribution.h @@ -27,7 +27,7 @@ class GoogleChromeDistribution : public BrowserDistribution { // concatenated to the survey url if the file in local_data_path indicates // the user has opted in to providing anonymous usage data. virtual void DoPostUninstallOperations(const installer::Version& version, - const std::wstring& local_data_path, + const FilePath& local_data_path, const std::wstring& distribution_data); virtual std::wstring GetAppGuid(); @@ -55,7 +55,7 @@ class GoogleChromeDistribution : public BrowserDistribution { // This method reads data from the Google Update ClientState key for // potential use in the uninstall survey. It must be called before the // key returned by GetVersionKey() is deleted. - virtual std::wstring GetDistributionData(base::win::RegKey* key); + virtual std::wstring GetDistributionData(HKEY root_key); virtual std::wstring GetUninstallLinkName(); @@ -69,13 +69,15 @@ class GoogleChromeDistribution : public BrowserDistribution { bool incremental_install, installer_util::InstallStatus install_status); virtual void LaunchUserExperiment(installer_util::InstallStatus status, - const installer::Version& version, - bool system_install); + const installer::Version& version, + const installer::Product& installation, + bool system_level); // Assuming that the user qualifies, this function performs the inactive user // toast experiment. It will use chrome to show the UI and it will record the // outcome in the registry. - virtual void InactiveUserToastExperiment(int flavor, bool system_install); + virtual void InactiveUserToastExperiment(int flavor, + const installer::Product& installation); std::wstring product_guid() { return product_guid_; } diff --git a/chrome/installer/util/google_chrome_distribution_dummy.cc b/chrome/installer/util/google_chrome_distribution_dummy.cc index a50dd11..0cb540c 100644 --- a/chrome/installer/util/google_chrome_distribution_dummy.cc +++ b/chrome/installer/util/google_chrome_distribution_dummy.cc @@ -19,7 +19,7 @@ GoogleChromeDistribution::GoogleChromeDistribution() { void GoogleChromeDistribution::DoPostUninstallOperations( const installer::Version& version, - const std::wstring& local_data_path, + const FilePath& local_data_path, const std::wstring& distribution_data) { } @@ -78,8 +78,7 @@ std::wstring GoogleChromeDistribution::GetStatsServerURL() { return std::wstring(); } -std::wstring GoogleChromeDistribution::GetDistributionData( - base::win::RegKey* key) { +std::wstring GoogleChromeDistribution::GetDistributionData(HKEY root_key) { NOTREACHED(); return std::wstring(); } @@ -111,13 +110,12 @@ void GoogleChromeDistribution::UpdateDiffInstallStatus(bool system_install, void GoogleChromeDistribution::LaunchUserExperiment( installer_util::InstallStatus status, const installer::Version& version, - bool system_install) { + const installer::Product& installation, bool system_level) { NOTREACHED(); } -void GoogleChromeDistribution::InactiveUserToastExperiment( - int flavor, - bool system_install) { +void GoogleChromeDistribution::InactiveUserToastExperiment(int flavor, + const installer::Product& installation) { NOTREACHED(); } diff --git a/chrome/installer/util/google_chrome_distribution_unittest.cc b/chrome/installer/util/google_chrome_distribution_unittest.cc index 1e0ede8..10c2022 100644 --- a/chrome/installer/util/google_chrome_distribution_unittest.cc +++ b/chrome/installer/util/google_chrome_distribution_unittest.cc @@ -51,7 +51,8 @@ TEST(GoogleChromeDistTest, TestExtractUninstallMetrics) { ASSERT_TRUE(root.get()); std::wstring uninstall_metrics_string; GoogleChromeDistribution* dist = static_cast<GoogleChromeDistribution*>( - BrowserDistribution::GetDistribution()); + BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER)); EXPECT_TRUE( dist->ExtractUninstallMetrics(*static_cast<DictionaryValue*>(root.get()), diff --git a/chrome/installer/util/helper.cc b/chrome/installer/util/helper.cc index 551b9c6..4da09a8 100644 --- a/chrome/installer/util/helper.cc +++ b/chrome/installer/util/helper.cc @@ -2,126 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <windows.h> +#include "chrome/installer/util/helper.h" -#include "base/file_util.h" -#include "base/logging.h" #include "base/path_service.h" -#include "base/process_util.h" -#include "base/scoped_ptr.h" #include "chrome/installer/util/browser_distribution.h" -#include "chrome/installer/util/delete_tree_work_item.h" -#include "chrome/installer/util/helper.h" -#include "chrome/installer/util/logging_installer.h" -#include "chrome/installer/util/util_constants.h" -#include "chrome/installer/util/version.h" -#include "chrome/installer/util/work_item_list.h" namespace { -std::wstring GetChromeInstallBasePath(bool system_install, - const wchar_t* subpath) { +FilePath GetChromeInstallBasePath(bool system, + BrowserDistribution* distribution, + const wchar_t* sub_path) { FilePath install_path; - if (system_install) { + if (system) { PathService::Get(base::DIR_PROGRAM_FILES, &install_path); } else { PathService::Get(base::DIR_LOCAL_APP_DATA, &install_path); } + if (!install_path.empty()) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - install_path = install_path.Append(dist->GetInstallSubDir()); - install_path = install_path.Append(subpath); + install_path = install_path.Append(distribution->GetInstallSubDir()); + install_path = install_path.Append(sub_path); } - return install_path.ToWStringHack(); + + return install_path; } } // namespace -std::wstring installer::GetChromeInstallPath(bool system_install) { - return GetChromeInstallBasePath(system_install, +FilePath installer::GetChromeInstallPath(bool system_install, + BrowserDistribution* dist) { + return GetChromeInstallBasePath(system_install, dist, installer_util::kInstallBinaryDir); } -std::wstring installer::GetChromeUserDataPath() { - return GetChromeInstallBasePath(false, installer_util::kInstallUserDataDir); -} - -bool installer::LaunchChrome(bool system_install) { - std::wstring chrome_exe(L"\""); - chrome_exe.append(installer::GetChromeInstallPath(system_install)); - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); - chrome_exe.append(L"\""); - return base::LaunchApp(chrome_exe, false, false, NULL); -} - -bool installer::LaunchChromeAndWaitForResult(bool system_install, - const std::wstring& options, - int32* exit_code) { - std::wstring chrome_exe(installer::GetChromeInstallPath(system_install)); - if (chrome_exe.empty()) - return false; - file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); - - std::wstring command_line(L"\"" + chrome_exe + L"\""); - command_line.append(options); - STARTUPINFOW si = {sizeof(si)}; - PROCESS_INFORMATION pi = {0}; - if (!::CreateProcessW(chrome_exe.c_str(), - const_cast<wchar_t*>(command_line.c_str()), - NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, - &si, &pi)) { - return false; - } - - DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE); - DWORD ret; - if (::GetExitCodeProcess(pi.hProcess, &ret) == 0) - return false; - - if (exit_code) - *exit_code = ret; - - ::CloseHandle(pi.hProcess); - ::CloseHandle(pi.hThread); - return true; -} - -void installer::RemoveOldVersionDirs(const std::wstring& chrome_path, - const std::wstring& latest_version_str) { - std::wstring search_path(chrome_path); - file_util::AppendToPath(&search_path, L"*"); - - WIN32_FIND_DATA find_file_data; - HANDLE file_handle = FindFirstFile(search_path.c_str(), &find_file_data); - if (file_handle == INVALID_HANDLE_VALUE) - return; - - BOOL ret = TRUE; - scoped_ptr<installer::Version> version; - scoped_ptr<installer::Version> latest_version( - installer::Version::GetVersionFromString(latest_version_str)); - - // We try to delete all directories whose versions are lower than - // latest_version. - while (ret) { - if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - VLOG(1) << "directory found: " << find_file_data.cFileName; - version.reset( - installer::Version::GetVersionFromString(find_file_data.cFileName)); - if (version.get() && latest_version->IsHigherThan(version.get())) { - std::wstring remove_dir(chrome_path); - file_util::AppendToPath(&remove_dir, find_file_data.cFileName); - std::wstring chrome_dll_path(remove_dir); - file_util::AppendToPath(&chrome_dll_path, installer_util::kChromeDll); - VLOG(1) << "deleting directory " << remove_dir; - scoped_ptr<DeleteTreeWorkItem> item; - item.reset(WorkItem::CreateDeleteTreeWorkItem(remove_dir, - chrome_dll_path)); - item->Do(); - } - } - ret = FindNextFile(file_handle, &find_file_data); - } - - FindClose(file_handle); +FilePath installer::GetChromeUserDataPath(BrowserDistribution* dist) { + return GetChromeInstallBasePath(false, dist, + installer_util::kInstallUserDataDir); } diff --git a/chrome/installer/util/helper.h b/chrome/installer/util/helper.h index dd4c3b8..747e11b 100644 --- a/chrome/installer/util/helper.h +++ b/chrome/installer/util/helper.h @@ -8,7 +8,9 @@ #define CHROME_INSTALLER_UTIL_HELPER_H_ #pragma once -#include <string> +#include "base/file_path.h" + +class BrowserDistribution; namespace installer { @@ -17,34 +19,13 @@ namespace installer { // system_install: if true, the function returns system wide location // (ProgramFiles\Google). Otherwise it returns user specific // location (Document And Settings\<user>\Local Settings...) -std::wstring GetChromeInstallPath(bool system_install); +FilePath GetChromeInstallPath(bool system_install, BrowserDistribution* dist); // This function returns the path to the directory that holds the user data, // this is always inside "Document And Settings\<user>\Local Settings.". Note // that this is the default user data directory and does not take into account // that it can be overriden with a command line parameter. -std::wstring GetChromeUserDataPath(); - -// Launches Chrome without waiting for its exit. -bool LaunchChrome(bool system_install); - -// Launches Chrome with given command line, waits for Chrome indefinitely -// (until it terminates), and gets the process exit code if available. -// The function returns true as long as Chrome is successfully launched. -// The status of Chrome at the return of the function is given by exit_code. -bool LaunchChromeAndWaitForResult(bool system_install, - const std::wstring& options, - int32* exit_code); - -// This function tries to remove all previous version directories after a new -// Chrome update. If an instance of Chrome with older version is still running -// on the system, its corresponding version directory will be left intact. -// (The version directory is subject for removal again during next update.) -// -// chrome_path: the root path of Chrome installation. -// latest_version_str: the latest version of Chrome installed. -void RemoveOldVersionDirs(const std::wstring& chrome_path, - const std::wstring& latest_version_str); +FilePath GetChromeUserDataPath(BrowserDistribution* dist); } // namespace installer diff --git a/chrome/installer/util/helper_unittest.cc b/chrome/installer/util/helper_unittest.cc index 7604e49..e9133bf 100644 --- a/chrome/installer/util/helper_unittest.cc +++ b/chrome/installer/util/helper_unittest.cc @@ -11,10 +11,15 @@ #include "base/path_service.h" #include "base/process_util.h" #include "base/string_util.h" +#include "chrome/installer/util/package.h" #include "chrome/installer/util/helper.h" +#include "chrome/installer/util/version.h" #include "chrome/installer/util/work_item.h" #include "testing/gtest/include/gtest/gtest.h" +using installer::Version; +using installer::Package; + namespace { class SetupHelperTest : public testing::Test { protected: @@ -103,8 +108,9 @@ TEST_F(SetupHelperTest, Delete) { CreateTextFile(chrome_dll_4.value(), text_content_1); ASSERT_TRUE(file_util::PathExists(chrome_dll_4)); - std::wstring latest_version(L"1.0.4.0"); - installer::RemoveOldVersionDirs(chrome_dir.value(), latest_version); + scoped_ptr<Version> latest_version(Version::GetVersionFromString(L"1.0.4.0")); + scoped_refptr<Package> package(new Package(chrome_dir)); + package->RemoveOldVersionDirectories(*latest_version.get()); // old versions should be gone EXPECT_FALSE(file_util::PathExists(chrome_dir_1)); @@ -176,8 +182,9 @@ TEST_F(SetupHelperTest, DeleteInUsed) { CreateTextFile(chrome_dll_4.value(), text_content_1); ASSERT_TRUE(file_util::PathExists(chrome_dll_4)); - std::wstring latest_version(L"1.0.4.0"); - installer::RemoveOldVersionDirs(chrome_dir.value(), latest_version); + scoped_ptr<Version> latest_version(Version::GetVersionFromString(L"1.0.4.0")); + scoped_refptr<Package> install_path(new Package(chrome_dir)); + install_path->RemoveOldVersionDirectories(*latest_version.get()); // old versions not in used should be gone EXPECT_FALSE(file_util::PathExists(chrome_dir_1)); diff --git a/chrome/installer/util/install_util.cc b/chrome/installer/util/install_util.cc index 6c11f99..ae9c67d 100644 --- a/chrome/installer/util/install_util.cc +++ b/chrome/installer/util/install_util.cc @@ -73,21 +73,23 @@ bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) { return true; } -std::wstring InstallUtil::GetChromeUninstallCmd(bool system_install) { +std::wstring InstallUtil::GetChromeUninstallCmd(bool system_install, + BrowserDistribution* dist) { + DCHECK(dist); HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); RegKey key(root, dist->GetUninstallRegPath().c_str(), KEY_READ); std::wstring uninstall_cmd; key.ReadValue(installer_util::kUninstallStringField, &uninstall_cmd); return uninstall_cmd; } -installer::Version* InstallUtil::GetChromeVersion(bool system_install) { +installer::Version* InstallUtil::GetChromeVersion(BrowserDistribution* dist, + bool system_install) { + DCHECK(dist); RegKey key; std::wstring version_str; HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); if (!key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_READ) || !key.ReadValue(google_update::kRegVersionField, &version_str)) { VLOG(1) << "No existing Chrome install found."; @@ -111,34 +113,6 @@ bool InstallUtil::IsOSSupported() { (version == base::win::VERSION_XP && major >= 2); } -void InstallUtil::WriteInstallerResult(bool system_install, - installer_util::InstallStatus status, - int string_resource_id, - const std::wstring* const launch_cmd) { - HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - std::wstring key = dist->GetStateKey(); - int installer_result = (dist->GetInstallReturnCode(status) == 0) ? 0 : 1; - scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); - install_list->AddCreateRegKeyWorkItem(root, key); - install_list->AddSetRegValueWorkItem(root, key, L"InstallerResult", - installer_result, true); - install_list->AddSetRegValueWorkItem(root, key, L"InstallerError", - status, true); - if (string_resource_id != 0) { - std::wstring msg = installer_util::GetLocalizedString(string_resource_id); - install_list->AddSetRegValueWorkItem(root, key, L"InstallerResultUIString", - msg, true); - } - if (launch_cmd != NULL && !launch_cmd->empty()) { - install_list->AddSetRegValueWorkItem(root, key, - L"InstallerSuccessLaunchCmdLine", - *launch_cmd, true); - } - if (!install_list->Do()) - LOG(ERROR) << "Failed to record installer error information in registry."; -} - bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) { wchar_t program_files_path[MAX_PATH] = {0}; if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, @@ -155,7 +129,7 @@ bool InstallUtil::IsChromeFrameProcess() { return prefs.install_chrome_frame(); } -bool InstallUtil::IsChromeSxSProcess() { +bool CheckIsChromeSxSProcess() { CommandLine* command_line = CommandLine::ForCurrentProcess(); CHECK(command_line); @@ -173,64 +147,9 @@ bool InstallUtil::IsChromeSxSProcess() { chrome_sxs_dir); } -bool InstallUtil::IsMSIProcess(bool system_level) { - // Initialize the static msi flags. - static bool is_msi_ = false; - static bool msi_checked_ = false; - - if (!msi_checked_) { - msi_checked_ = true; - - const MasterPreferences& prefs = GetMasterPreferencesForCurrentProcess(); - - bool is_msi = false; - prefs.GetBool(installer_util::master_preferences::kMsi, &is_msi); - - if (!is_msi) { - // We didn't find it in the preferences, try looking in the registry. - HKEY reg_root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - RegKey key; - DWORD msi_value; - - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - DCHECK(dist); - - bool success = false; - std::wstring reg_key(dist->GetStateKey()); - if (key.Open(reg_root, reg_key.c_str(), KEY_READ | KEY_WRITE)) { - if (key.ReadValueDW(google_update::kRegMSIField, &msi_value)) { - is_msi = (msi_value == 1); - } - } - } - - is_msi_ = is_msi; - } - - return is_msi_; -} - -bool InstallUtil::SetMSIMarker(bool system_level, bool set) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); - DCHECK(dist); - std::wstring client_state_path(dist->GetStateKey()); - - bool success = false; - HKEY reg_root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - RegKey client_state_key; - if (client_state_key.Open(reg_root, client_state_path.c_str(), - KEY_READ | KEY_WRITE)) { - DWORD msi_value = set ? 1 : 0; - if (client_state_key.WriteValue(google_update::kRegMSIField, msi_value)) { - success = true; - } else { - LOG(ERROR) << "Could not write msi value to client state key."; - } - } else { - LOG(ERROR) << "Could not open client state key!"; - } - - return success; +bool InstallUtil::IsChromeSxSProcess() { + static bool sxs = CheckIsChromeSxSProcess(); + return sxs; } bool InstallUtil::BuildDLLRegistrationList(const std::wstring& install_path, @@ -257,8 +176,8 @@ bool InstallUtil::DeleteRegistryKey(RegKey& root_key, const std::wstring& key_path) { VLOG(1) << "Deleting registry key " << key_path; if (!root_key.DeleteKey(key_path.c_str()) && - ::GetLastError() != ERROR_MOD_NOT_FOUND) { - LOG(ERROR) << "Failed to delete registry key: " << key_path; + ::GetLastError() != ERROR_FILE_NOT_FOUND) { + PLOG(ERROR) << "Failed to delete registry key: " << key_path; return false; } return true; diff --git a/chrome/installer/util/install_util.h b/chrome/installer/util/install_util.h index 027a8ee..5fd87be 100644 --- a/chrome/installer/util/install_util.h +++ b/chrome/installer/util/install_util.h @@ -21,6 +21,7 @@ #include "chrome/installer/util/version.h" class WorkItemList; +class BrowserDistribution; namespace base { namespace win { @@ -39,24 +40,20 @@ class InstallUtil { // Reads the uninstall command for Chromium from registry and returns it. // If system_install is true the command is read from HKLM, otherwise // from HKCU. - static std::wstring GetChromeUninstallCmd(bool system_install); + static std::wstring GetChromeUninstallCmd(bool system_install, + BrowserDistribution* dist); + // Find the version of Chrome installed on the system by checking the // Google Update registry key. Returns the version or NULL if no version is // found. // system_install: if true, looks for version number under the HKLM root, // otherwise looks under the HKCU. - static installer::Version* GetChromeVersion(bool system_install); + static installer::Version* GetChromeVersion(BrowserDistribution* dist, + bool system_install); // This function checks if the current OS is supported for Chromium. static bool IsOSSupported(); - // This function sets installer error information in registry so that Google - // Update can read it and display to the user. - static void WriteInstallerResult(bool system_install, - installer_util::InstallStatus status, - int string_resource_id, - const std::wstring* const launch_cmd); - // Returns true if this installation path is per user, otherwise returns // false (per machine install, meaning: the exe_path contains path to // Program Files). @@ -74,21 +71,6 @@ class InstallUtil { // by either --chrome-sxs or the executable path). static bool IsChromeSxSProcess(); - // Returns true if this setup process is running as an install managed by an - // MSI wrapper. There are three things that are checked: - // 1) the presence of --msi on the command line - // 2) the presence of "msi": true in the master preferences file - // 3) the presence of a DWORD value in the ClientState key called msi with - // value 1 - // NOTE: This method is NOT thread safe. - static bool IsMSIProcess(bool system_level); - - - // Sets the boolean MSI marker for this installation if set is true or clears - // it otherwise. The MSI marker is stored in the registry under the - // ClientState key. - static bool SetMSIMarker(bool system_level, bool set); - // Adds all DLLs in install_path whose names are given by dll_names to a // work item list containing registration or unregistration actions. // diff --git a/chrome/installer/util/package.cc b/chrome/installer/util/package.cc new file mode 100644 index 0000000..37dc97c --- /dev/null +++ b/chrome/installer/util/package.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/installer/util/package.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/win/registry.h" +#include "chrome/installer/util/delete_tree_work_item.h" +#include "chrome/installer/util/google_update_constants.h" +#include "chrome/installer/util/product.h" + +using base::win::RegKey; + +namespace installer { + +Package::Package(const FilePath& path) : path_(path) { +} + +Package::~Package() { +} + +const FilePath& Package::path() const { + return path_; +} + +const Products& Package::products() const { + return products_; +} + +bool Package::IsEqual(const FilePath& path) const { + return FilePath::CompareEqualIgnoreCase(path_.value(), path.value()); +} + +void Package::AssociateProduct(const Product* product) { +#ifndef NDEBUG + for (size_t i = 0; i < products_.size(); ++i) { + DCHECK_EQ(product->system_level(), products_[i]->system_level()); + DCHECK_NE(product->distribution()->GetType(), + products_[i]->distribution()->GetType()); + } +#endif + products_.push_back(product); +} + +bool Package::system_level() const { + // Convenience getter that returns the system_level value of the first + // product for this folder. All distributions must have the same + // value, so the function also checks this in debug builds. + if (!products_.size()) { + NOTREACHED() << "this should not be possible"; + return false; + } + return products_[0]->system_level(); +} + +FilePath Package::GetInstallerDirectory( + const Version& version) const { + return path_.Append(version.GetString()) + .Append(installer_util::kInstallerDir); +} + +Version* Package::GetCurrentVersion() const { + scoped_ptr<Version> current_version; + // Be aware that there might be a pending "new_chrome.exe" already in the + // installation path. + FilePath new_chrome_exe(path_.Append(installer_util::kChromeNewExe)); + bool new_chrome_exists = file_util::PathExists(new_chrome_exe); + + for (size_t i = 0; i < products_.size(); ++i) { + const Product* product = products_[i]; + HKEY root = product->system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + RegKey chrome_key(root, product->distribution()->GetVersionKey().c_str(), + KEY_READ); + std::wstring version; + if (new_chrome_exists) + chrome_key.ReadValue(google_update::kRegOldVersionField, &version); + + if (version.empty()) + chrome_key.ReadValue(google_update::kRegVersionField, &version); + + if (!version.empty()) { + scoped_ptr<Version> this_version(Version::GetVersionFromString(version)); + if (this_version.get()) { + if (!current_version.get() || + current_version->IsHigherThan(this_version.get())) { + current_version.reset(this_version.release()); + } else if (current_version.get()) { + DCHECK_EQ(current_version->GetString(), this_version->GetString()) + << "found distributions of different versions in the same " + "installation folder!"; + } + } + } + } + + return current_version.release(); +} + +void Package::RemoveOldVersionDirectories( + const Version& latest_version) const { + std::wstring search_path(path_.value()); + file_util::AppendToPath(&search_path, L"*"); + + // TODO(tommi): use file_util::FileEnumerator. + WIN32_FIND_DATA find_file_data; + HANDLE file_handle = FindFirstFile(search_path.c_str(), &find_file_data); + if (file_handle == INVALID_HANDLE_VALUE) { + VLOG(1) << "No directories found under: " << search_path; + return; + } + + BOOL ret = TRUE; + scoped_ptr<Version> version; + + // We try to delete all directories whose versions are lower than + // latest_version. + while (ret) { + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY && + lstrcmpW(find_file_data.cFileName, L"..") != 0 && + lstrcmpW(find_file_data.cFileName, L".") != 0) { + VLOG(1) << "directory found: " << find_file_data.cFileName; + version.reset(Version::GetVersionFromString(find_file_data.cFileName)); + if (version.get() && latest_version.IsHigherThan(version.get())) { + std::wstring remove_dir(path_.value()); + file_util::AppendToPath(&remove_dir, find_file_data.cFileName); + std::wstring chrome_dll_path(remove_dir); + file_util::AppendToPath(&chrome_dll_path, installer_util::kChromeDll); + VLOG(1) << "Deleting directory: " << remove_dir; + // TODO(tommi): We should support more "key files". One for each + // associated Product. Maybe the relative key file path should + // be a property of BrowserDistribution. + scoped_ptr<DeleteTreeWorkItem> item( + WorkItem::CreateDeleteTreeWorkItem(remove_dir, chrome_dll_path)); + if (!item->Do()) + item->Rollback(); + } + } + ret = FindNextFile(file_handle, &find_file_data); + } + + FindClose(file_handle); +} + +} // namespace installer + diff --git a/chrome/installer/util/package.h b/chrome/installer/util/package.h new file mode 100644 index 0000000..c7318e1 --- /dev/null +++ b/chrome/installer/util/package.h @@ -0,0 +1,73 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_INSTALLER_UTIL_PACKAGE_H_ +#define CHROME_INSTALLER_UTIL_PACKAGE_H_ +#pragma once + +#include <vector> + +#include "base/file_path.h" +#include "base/ref_counted.h" + +class CommandLine; + +namespace installer { + +class Product; +class Version; + +typedef std::vector<scoped_refptr<const Product> > Products; + +// Represents a physical installation. An instance of this class is associated +// with one or more Product instances. Product objects can share a Package but +// not vice versa. +class Package : public base::RefCounted<Package> { + public: + explicit Package(const FilePath& path); + + // Returns the path of the installation folder. + const FilePath& path() const; + + const Products& products() const; + + bool system_level() const; + + bool IsEqual(const FilePath& path) const; + + void AssociateProduct(const Product* distribution); + + // Get path to the installer under Chrome version folder + // (for example <path>\Google\Chrome\<Version>\installer) + FilePath GetInstallerDirectory(const Version& version) const; + + // Figure out the oldest currently installed version for this package + // Returns NULL if none is found. Caller is responsible for freeing + // the returned Version object if valid. + // The function DCHECKs if it finds that not all products in this + // folder have the same current version. + Version* GetCurrentVersion() const; + + // Tries to remove all previous version directories (after a new Chrome + // update). If an instance of Chrome with older version is still running + // on the system, its corresponding version directory will be left intact. + // (The version directory is subject for removal again during next update.) + // + // latest_version: the latest version of Chrome installed. + void RemoveOldVersionDirectories(const Version& latest_version) const; + + protected: + FilePath path_; + Products products_; + + private: + friend class base::RefCounted<Package>; + ~Package(); + + DISALLOW_COPY_AND_ASSIGN(Package); +}; + +} // namespace installer + +#endif // CHROME_INSTALLER_UTIL_PACKAGE_H_ diff --git a/chrome/installer/util/package_unittest.cc b/chrome/installer/util/package_unittest.cc new file mode 100644 index 0000000..575716d --- /dev/null +++ b/chrome/installer/util/package_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/google_update_constants.h" +#include "chrome/installer/util/package.h" +#include "chrome/installer/util/product.h" +#include "chrome/installer/util/product_unittest.h" +#include "chrome/installer/util/util_constants.h" +#include "chrome/installer/util/version.h" + +using base::win::RegKey; +using base::win::ScopedHandle; +using installer::Package; +using installer::Product; +using installer::Version; + +class PackageTest : public TestWithTempDirAndDeleteTempOverrideKeys { + protected: +}; + +// Tests a few basic things of the Package class. Makes sure that the path +// operations are correct +TEST_F(PackageTest, Basic) { + scoped_refptr<Package> package(new Package(test_dir_.path())); + EXPECT_EQ(test_dir_.path().value(), package->path().value()); + EXPECT_TRUE(package->IsEqual(test_dir_.path())); + EXPECT_EQ(0U, package->products().size()); + + const wchar_t kOldVersion[] = L"1.2.3.4"; + const wchar_t kNewVersion[] = L"2.3.4.5"; + + scoped_ptr<Version> new_version(Version::GetVersionFromString(kNewVersion)); + scoped_ptr<Version> old_version(Version::GetVersionFromString(kOldVersion)); + ASSERT_TRUE(new_version.get() != NULL); + ASSERT_TRUE(old_version.get() != NULL); + + FilePath installer_dir(package->GetInstallerDirectory(*new_version.get())); + EXPECT_FALSE(installer_dir.empty()); + + FilePath new_version_dir(package->path().Append(new_version->GetString())); + FilePath old_version_dir(package->path().Append(old_version->GetString())); + + EXPECT_FALSE(file_util::PathExists(new_version_dir)); + EXPECT_FALSE(file_util::PathExists(old_version_dir)); + + EXPECT_FALSE(file_util::PathExists(installer_dir)); + file_util::CreateDirectory(installer_dir); + EXPECT_TRUE(file_util::PathExists(new_version_dir)); + + file_util::CreateDirectory(old_version_dir); + EXPECT_TRUE(file_util::PathExists(old_version_dir)); + + // Create a fake chrome.dll key file in the old version directory. This + // should prevent the old version directory from getting deleted. + FilePath old_chrome_dll(old_version_dir.Append(installer_util::kChromeDll)); + EXPECT_FALSE(file_util::PathExists(old_chrome_dll)); + + // Hold on to the file exclusively to prevent the directory from + // being deleted. + ScopedHandle file(::CreateFile(old_chrome_dll.value().c_str(), GENERIC_READ, + 0, NULL, OPEN_ALWAYS, 0, NULL)); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file_util::PathExists(old_chrome_dll)); + + package->RemoveOldVersionDirectories(*new_version.get()); + // The old directory should still exist. + EXPECT_TRUE(file_util::PathExists(old_version_dir)); + EXPECT_TRUE(file_util::PathExists(new_version_dir)); + + // Now close the file handle to make it possible to delete our key file. + file.Close(); + + package->RemoveOldVersionDirectories(*new_version.get()); + // The new directory should still exist. + EXPECT_TRUE(file_util::PathExists(new_version_dir)); + + // Now, the old directory and key file should be gone. + EXPECT_FALSE(file_util::PathExists(old_chrome_dll)); + EXPECT_FALSE(file_util::PathExists(old_version_dir)); +} + +TEST_F(PackageTest, WithProduct) { + TempRegKeyOverride::DeleteAllTempKeys(); + + // TODO(tommi): We should mock this and use our mocked distribution. + const bool system_level = true; + BrowserDistribution* distribution = + BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER); + scoped_refptr<Package> package(new Package(test_dir_.path())); + scoped_refptr<Product> product(new Product(distribution, + system_level, + package.get())); + EXPECT_EQ(1U, package->products().size()); + EXPECT_EQ(system_level, package->system_level()); + + const wchar_t kCurrentVersion[] = L"1.2.3.4"; + scoped_ptr<Version> current_version( + Version::GetVersionFromString(kCurrentVersion)); + + HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + { + TempRegKeyOverride override(root, L"root_pit"); + RegKey chrome_key(root, distribution->GetVersionKey().c_str(), + KEY_ALL_ACCESS); + EXPECT_TRUE(chrome_key.Valid()); + if (chrome_key.Valid()) { + chrome_key.WriteValue(google_update::kRegVersionField, + current_version->GetString().c_str()); + // TODO(tommi): Also test for when there exists a new_chrome.exe. + scoped_ptr<Version> found_version(package->GetCurrentVersion()); + EXPECT_TRUE(found_version.get() != NULL); + if (found_version.get()) { + EXPECT_TRUE(current_version->IsEqual(*found_version.get())); + } + } + } + + TempRegKeyOverride::DeleteAllTempKeys(); +} diff --git a/chrome/installer/util/product.cc b/chrome/installer/util/product.cc new file mode 100644 index 0000000..60846e3 --- /dev/null +++ b/chrome/installer/util/product.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/installer/util/product.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/process_util.h" +#include "base/win/registry.h" +#include "chrome/installer/util/google_update_constants.h" +#include "chrome/installer/util/helper.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/l10n_string_util.h" +#include "chrome/installer/util/master_preferences.h" +#include "chrome/installer/util/master_preferences_constants.h" +#include "chrome/installer/util/package.h" +#include "chrome/installer/util/work_item_list.h" + +using base::win::RegKey; +using installer_util::MasterPreferences; + +namespace { +class ProductIsOfType { + public: + explicit ProductIsOfType(BrowserDistribution::DistributionType type) + : type_(type) { } + bool operator()( + const scoped_refptr<const installer::Product>& pi) const { + return pi->distribution()->GetType() == type_; + } + private: + BrowserDistribution::DistributionType type_; +}; // class ProductIsOfType +} // end namespace + +namespace installer { + +const Product* FindProduct(const Products& products, + BrowserDistribution::DistributionType type) { + Products::const_iterator i = + std::find_if(products.begin(), products.end(), ProductIsOfType(type)); + return i == products.end() ? NULL : *i; +} + +void WriteInstallerResult(const Products& products, + installer_util::InstallStatus status, + int string_resource_id, + const std::wstring* const launch_cmd) { + Products::const_iterator end = products.end(); + for (Products::const_iterator i = products.begin(); i != end; ++i) + (*i)->WriteInstallerResult(status, string_resource_id, launch_cmd); +} + +//////////////////////////////////////////////////////////////////////////////// + +Product::Product(BrowserDistribution* distribution, bool system_level, + Package* package) + : distribution_(distribution), + system_level_(system_level), + package_(package), + msi_(MSI_NOT_CHECKED) { + package_->AssociateProduct(this); +} + +const Package& Product::package() const { + return *package_.get(); +} + +FilePath Product::GetUserDataPath() const { + return GetChromeUserDataPath(distribution_); +} + +bool Product::LaunchChrome() const { + const FilePath& install_package = package_->path(); + bool success = !install_package.empty(); + if (success) { + CommandLine cmd(install_package.Append(installer_util::kChromeExe)); + success = base::LaunchApp(cmd, false, false, NULL); + } + return success; +} + +bool Product::LaunchChromeAndWait(const CommandLine& options, + int32* exit_code) const { + const FilePath& install_package = package_->path(); + if (install_package.empty()) + return false; + + CommandLine cmd(install_package.Append(installer_util::kChromeExe)); + cmd.AppendArguments(options, false); + + bool success = false; + STARTUPINFOW si = { sizeof(si) }; + PROCESS_INFORMATION pi = {0}; + // Cast away constness of the command_line_string() since CreateProcess + // might modify the string (insert \0 to separate the program from the + // arguments). Since we're not using the cmd variable beyond this point + // we don't care. + if (!::CreateProcess(cmd.GetProgram().value().c_str(), + const_cast<wchar_t*>(cmd.command_line_string().c_str()), + NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, + &si, &pi)) { + PLOG(ERROR) << "Failed to launch: " << cmd.command_line_string(); + } else { + ::CloseHandle(pi.hThread); + + DWORD ret = ::WaitForSingleObject(pi.hProcess, INFINITE); + DLOG_IF(ERROR, ret != WAIT_OBJECT_0) + << "Unexpected return value from WaitForSingleObject: " << ret; + if (::GetExitCodeProcess(pi.hProcess, &ret)) { + DCHECK(ret != STILL_ACTIVE); + success = true; + if (exit_code) + *exit_code = ret; + } else { + PLOG(ERROR) << "GetExitCodeProcess failed"; + } + + ::CloseHandle(pi.hProcess); + } + + return success; +} + +bool Product::IsMsi() const { + if (msi_ == MSI_NOT_CHECKED) { + msi_ = NOT_MSI; // Covers failure cases below. + + const MasterPreferences& prefs = + InstallUtil::GetMasterPreferencesForCurrentProcess(); + + bool is_msi = false; + prefs.GetBool(installer_util::master_preferences::kMsi, &is_msi); + + if (!is_msi) { + // We didn't find it in the preferences, try looking in the registry. + HKEY reg_root = system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + RegKey key; + if (key.Open(reg_root, distribution_->GetStateKey().c_str(), KEY_READ)) { + DWORD msi_value; + if (key.ReadValueDW(google_update::kRegMSIField, &msi_value)) { + msi_ = (msi_value == 1) ? IS_MSI : NOT_MSI; + } + } + } else { + msi_ = IS_MSI; + } + } + + return msi_ == IS_MSI; +} + +bool Product::SetMsiMarker(bool set) const { + bool success = false; + HKEY reg_root = system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + RegKey client_state_key; + if (client_state_key.Open(reg_root, distribution_->GetStateKey().c_str(), + KEY_READ | KEY_WRITE)) { + DWORD msi_value = set ? 1 : 0; + if (client_state_key.WriteValue(google_update::kRegMSIField, msi_value)) { + success = true; + } else { + LOG(ERROR) << "Could not write MSI value to client state key."; + } + } else { + LOG(ERROR) << "SetMsiMarker: Could not open client state key!"; + } + + return success; +} + +void Product::WriteInstallerResult( + installer_util::InstallStatus status, int string_resource_id, + const std::wstring* const launch_cmd) const { + HKEY root = system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + std::wstring key(distribution_->GetStateKey()); + int installer_result = distribution_->GetInstallReturnCode(status) == 0 ? + 0 : 1; + scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); + install_list->AddCreateRegKeyWorkItem(root, key); + install_list->AddSetRegValueWorkItem(root, key, + installer_util::kInstallerResult, + installer_result, true); + install_list->AddSetRegValueWorkItem(root, key, + installer_util::kInstallerError, + status, true); + if (string_resource_id != 0) { + std::wstring msg = installer_util::GetLocalizedString(string_resource_id); + install_list->AddSetRegValueWorkItem(root, key, + installer_util::kInstallerResultUIString, msg, true); + } + if (launch_cmd != NULL && !launch_cmd->empty()) { + install_list->AddSetRegValueWorkItem(root, key, + installer_util::kInstallerSuccessLaunchCmdLine, *launch_cmd, true); + } + if (!install_list->Do()) + LOG(ERROR) << "Failed to record installer error information in registry."; +} + +Version* Product::GetInstalledVersion() const { + return InstallUtil::GetChromeVersion(distribution_, system_level_); +} + +/////////////////////////////////////////////////////////////////////////////// +ProductPackageMapping::ProductPackageMapping(bool system_level) + : system_level_(system_level) { +} + +const Packages& ProductPackageMapping::packages() const { + return packages_; +} + +const Products& ProductPackageMapping::products() const { + return products_; +} + +bool ProductPackageMapping::AddDistribution(BrowserDistribution* distribution) { + DCHECK(distribution); + // Each product type can be added exactly once. + DCHECK(FindProduct(products_, distribution->GetType()) == NULL); + + FilePath install_package(GetChromeInstallPath(system_level_, distribution)); + + scoped_refptr<Package> target_package; + for (size_t i = 0; i < packages_.size(); ++i) { + if (packages_[i]->IsEqual(install_package)) { + // Use an existing Package. + target_package = packages_[i]; + break; + } + } + + if (!target_package.get()) { + // create new one and add. + target_package = new Package(install_package); + packages_.push_back(target_package); + } + + scoped_refptr<Product> product( + new Product(distribution, system_level_, target_package)); +#ifndef NDEBUG + for (size_t i = 0; i < products_.size(); ++i) { + DCHECK_EQ(product->IsMsi(), products_[i]->IsMsi()); + } +#endif + products_.push_back(product); + + return true; +} + +bool ProductPackageMapping::AddDistribution( + BrowserDistribution::DistributionType type) { + BrowserDistribution* distribution = + BrowserDistribution::GetSpecificDistribution(type); + if (!distribution) { + NOTREACHED(); + return false; + } + + return AddDistribution(distribution); +} + +} // namespace installer + diff --git a/chrome/installer/util/product.h b/chrome/installer/util/product.h new file mode 100644 index 0000000..639e48a --- /dev/null +++ b/chrome/installer/util/product.h @@ -0,0 +1,151 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_INSTALLER_UTIL_PRODUCT_H_ +#define CHROME_INSTALLER_UTIL_PRODUCT_H_ +#pragma once + +#include <vector> + +#include "base/ref_counted.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/package.h" + +class CommandLine; + +namespace installer { + +class Product; +class Package; +class Version; + +typedef std::vector<scoped_refptr<Package> > Packages; +typedef std::vector<scoped_refptr<const Product> > Products; + +const Product* FindProduct(const Products& products, + BrowserDistribution::DistributionType type); + +// Calls WriteInstallerResult for each Product object. +void WriteInstallerResult(const Products& products, + installer_util::InstallStatus status, + int string_resource_id, + const std::wstring* const launch_cmd); + +// Represents an installation of a specific product which has a one-to-one +// relation to a BrowserDistribution. A product has registry settings, related +// installation/uninstallation actions and exactly one Package that represents +// the files on disk. The Package may be shared with other Product instances, +// so only the last Product to be uninstalled should remove the package. +// Right now there are no classes that derive from Product, but in +// the future, as we move away from global functions and towards a data driven +// installation, each distribution could derive from this class and provide +// distribution specific functionality. +class Product : public base::RefCounted<Product> { + public: + Product(BrowserDistribution* distribution, bool system_level, + Package* installation_package); + + BrowserDistribution* distribution() const { + return distribution_; + } + + bool system_level() const { + return system_level_; + } + + // Returns the install package object for the installation of this product. + // If the product is installed at system level,the function returns a system + // wide location (ProgramFiles\Google). Otherwise it returns a package in a + // user specific location (Users\<user>\Local Settings...) + const Package& package() const; + + // Returns the path to the directory that holds the user data. This is always + // inside "Users\<user>\Local Settings". Note that this is the default user + // data directory and does not take into account that it can be overriden with + // a command line parameter. + FilePath GetUserDataPath() const; + + // Launches Chrome without waiting for it to exit. + bool LaunchChrome() const; + + // Launches Chrome with given command line, waits for Chrome indefinitely + // (until it terminates), and gets the process exit code if available. + // The function returns true as long as Chrome is successfully launched. + // The status of Chrome at the return of the function is given by exit_code. + // NOTE: The 'options' CommandLine object should only contain parameters. + // The program part will be ignored. + bool LaunchChromeAndWait(const CommandLine& options, int32* exit_code) const; + + // Returns true if this setup process is running as an install managed by an + // MSI wrapper. There are three things that are checked: + // 1) the presence of --msi on the command line + // 2) the presence of "msi": true in the master preferences file + // 3) the presence of a DWORD value in the ClientState key called msi with + // value 1 + bool IsMsi() const; + + // Sets the boolean MSI marker for this installation if set is true or clears + // it otherwise. The MSI marker is stored in the registry under the + // ClientState key. + bool SetMsiMarker(bool set) const; + + // Sets installer error information in registry so that Google Update can read + // it and display to the user. + void WriteInstallerResult(installer_util::InstallStatus status, + int string_resource_id, + const std::wstring* const launch_cmd) const; + + // Find the version of this product installed on the system by checking the + // Google Update registry key. Returns the version or NULL if no version is + // found. Caller must free the returned Version object. + Version* GetInstalledVersion() const; + + protected: + BrowserDistribution* distribution_; + scoped_refptr<Package> package_; + bool system_level_; + mutable enum MsiState { + MSI_NOT_CHECKED, + IS_MSI, + NOT_MSI, + } msi_; + + private: + friend class base::RefCounted<Product>; + ~Product() { + } + DISALLOW_COPY_AND_ASSIGN(Product); +}; + +// A collection of Product objects and related physical installation +// packages. Each Product is associated with a single installation +// package object, and each package object is associated with one or more +// Product objects. +class ProductPackageMapping { + public: + explicit ProductPackageMapping(bool system_level); + + bool system_level() const { + return system_level_; + } + + const Packages& packages() const; + + const Products& products() const; + + bool AddDistribution(BrowserDistribution::DistributionType type); + bool AddDistribution(BrowserDistribution* distribution); + + protected: + bool system_level_; + Packages packages_; + Products products_; + + private: + DISALLOW_COPY_AND_ASSIGN(ProductPackageMapping); +}; + +} // namespace installer + +#endif // CHROME_INSTALLER_UTIL_PRODUCT_H_ diff --git a/chrome/installer/util/product_unittest.cc b/chrome/installer/util/product_unittest.cc new file mode 100644 index 0000000..7dab702 --- /dev/null +++ b/chrome/installer/util/product_unittest.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/installer/util/product_unittest.h" + +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "chrome/installer/util/chrome_frame_distribution.h" +#include "chrome/installer/util/google_update_constants.h" +#include "chrome/installer/util/product.h" + +using base::win::RegKey; +using base::win::ScopedHandle; +using installer::Package; +using installer::Product; +using installer::ProductPackageMapping; +using installer::Version; + +void TestWithTempDir::SetUp() { + // Name a subdirectory of the user temp directory. + ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); +} + +void TestWithTempDir::TearDown() { + logging::CloseLogFile(); + ASSERT_TRUE(test_dir_.Delete()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestWithTempDirAndDeleteTempOverrideKeys::SetUp() { + TempRegKeyOverride::DeleteAllTempKeys(); + TestWithTempDir::SetUp(); +} + +void TestWithTempDirAndDeleteTempOverrideKeys::TearDown() { + TestWithTempDir::TearDown(); + TempRegKeyOverride::DeleteAllTempKeys(); +} + +//////////////////////////////////////////////////////////////////////////////// +const wchar_t TempRegKeyOverride::kTempTestKeyPath[] = + L"Software\\Chromium\\TempTestKeys"; + +TempRegKeyOverride::TempRegKeyOverride(HKEY override, const wchar_t* temp_name) + : override_(override), temp_name_(temp_name) { + DCHECK(temp_name && lstrlenW(temp_name)); + std::wstring key_path(kTempTestKeyPath); + key_path += L"\\" + temp_name_; + EXPECT_TRUE(temp_key_.Create(HKEY_CURRENT_USER, key_path.c_str(), + KEY_ALL_ACCESS)); + EXPECT_EQ(ERROR_SUCCESS, + ::RegOverridePredefKey(override_, temp_key_.Handle())); +} + +TempRegKeyOverride::~TempRegKeyOverride() { + ::RegOverridePredefKey(override_, NULL); + // The temp key will be deleted via a call to DeleteAllTempKeys(). +} + +// static +void TempRegKeyOverride::DeleteAllTempKeys() { + RegKey key; + if (key.Open(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS)) { + key.DeleteKey(kTempTestKeyPath); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +class ProductTest : public TestWithTempDirAndDeleteTempOverrideKeys { + protected: +}; + +TEST_F(ProductTest, ProductInstallBasic) { + // TODO(tommi): We should mock this and use our mocked distribution. + const bool system_level = true; + BrowserDistribution* distribution = + BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER); + scoped_refptr<Package> package(new Package(test_dir_.path())); + scoped_refptr<Product> product(new Product(distribution, system_level, + package.get())); + + EXPECT_EQ(system_level, product->system_level()); + FilePath user_data(product->GetUserDataPath()); + EXPECT_FALSE(user_data.empty()); + EXPECT_NE(std::wstring::npos, + user_data.value().find(installer_util::kInstallUserDataDir)); + + FilePath program_files; + PathService::Get(base::DIR_PROGRAM_FILES, &program_files); + // The User Data path should never be under program files, even though + // system_level is true. + EXPECT_EQ(std::wstring::npos, + user_data.value().find(program_files.value())); + + // We started out with a non-msi product. + EXPECT_FALSE(product->IsMsi()); + + HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + { + TempRegKeyOverride override(root, L"root_pit"); + + // Create a make-believe client state key. + RegKey key; + std::wstring state_key_path(distribution->GetStateKey()); + ASSERT_TRUE(key.Create(root, state_key_path.c_str(), KEY_ALL_ACCESS)); + + // Set the MSI marker, delete the objects, create new ones and verify + // that we now see the MSI marker. + EXPECT_TRUE(product->SetMsiMarker(true)); + package = new Package(test_dir_.path()); + product = new Product(distribution, system_level, package.get()); + EXPECT_TRUE(product->IsMsi()); + + // See if WriteInstallerResult writes anything. + std::wstring launch_cmd(L"chrome.exe --this-is-a-test"); + product->WriteInstallerResult(installer_util::TEMP_DIR_FAILED, + 0, &launch_cmd); + std::wstring found_launch_cmd; + key.ReadValue(installer_util::kInstallerSuccessLaunchCmdLine, + &found_launch_cmd); + EXPECT_EQ(launch_cmd, found_launch_cmd); + + // There should be no installed version in the registry. + EXPECT_TRUE(product->GetInstalledVersion() == NULL); + + // Let's pretend chrome is installed. + RegKey version_key(root, distribution->GetVersionKey().c_str(), + KEY_ALL_ACCESS); + ASSERT_TRUE(version_key.Valid()); + + const wchar_t kCurrentVersion[] = L"1.2.3.4"; + scoped_ptr<Version> current_version( + Version::GetVersionFromString(kCurrentVersion)); + version_key.WriteValue(google_update::kRegVersionField, + current_version->GetString().c_str()); + + scoped_ptr<Version> installed(product->GetInstalledVersion()); + EXPECT_TRUE(installed.get() != NULL); + if (installed.get()) { + EXPECT_TRUE(installed->IsEqual(*current_version.get())); + } + } +} + +TEST_F(ProductTest, LaunchChrome) { + // TODO(tommi): Test Product::LaunchChrome and + // Product::LaunchChromeAndWait. + LOG(ERROR) << "Test not implemented."; +} + +// Overrides ChromeFrameDistribution for the sole purpose of returning +// the Chrome (not Chrome Frame) installation path. +class FakeChromeFrameDistribution : public ChromeFrameDistribution { + public: + virtual std::wstring GetInstallSubDir() { + return BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER)->GetInstallSubDir(); + } +}; + +TEST_F(ProductTest, ProductInstallsBasic) { + const bool system_level = true; + ProductPackageMapping installs(system_level); + EXPECT_EQ(system_level, installs.system_level()); + EXPECT_EQ(0U, installs.packages().size()); + EXPECT_EQ(0U, installs.products().size()); + + installs.AddDistribution(BrowserDistribution::CHROME_BROWSER); + FakeChromeFrameDistribution fake_chrome_frame; + installs.AddDistribution(&fake_chrome_frame); + EXPECT_EQ(2U, installs.products().size()); + // Since our fake Chrome Frame distribution class is reporting the same + // installation directory as Chrome, we should have only one package object. + EXPECT_EQ(1U, installs.packages().size()); +} diff --git a/chrome/installer/util/product_unittest.h b/chrome/installer/util/product_unittest.h new file mode 100644 index 0000000..31f630c --- /dev/null +++ b/chrome/installer/util/product_unittest.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_INSTALLER_UTIL_PRODUCT_UNITTEST_H_ +#define CHROME_INSTALLER_UTIL_PRODUCT_UNITTEST_H_ +#pragma once + +#include <windows.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/scoped_temp_dir.h" +#include "base/win/registry.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestWithTempDir : public testing::Test { + protected: + virtual void SetUp(); + virtual void TearDown(); + + ScopedTempDir test_dir_; +}; + +class TestWithTempDirAndDeleteTempOverrideKeys : public TestWithTempDir { + protected: + virtual void SetUp(); + virtual void TearDown(); +}; + +// TODO(tommi): This is "borrowed" from Chrome Frame test code. It should be +// moved to some common test utility file. +class TempRegKeyOverride { + public: + static const wchar_t kTempTestKeyPath[]; + + TempRegKeyOverride(HKEY override, const wchar_t* temp_name); + ~TempRegKeyOverride(); + + static void DeleteAllTempKeys(); + + protected: + HKEY override_; + base::win::RegKey temp_key_; + std::wstring temp_name_; +}; + +#endif // CHROME_INSTALLER_UTIL_PRODUCT_UNITTEST_H_ diff --git a/chrome/installer/util/shell_util.cc b/chrome/installer/util/shell_util.cc index b82bbf8..734c90d9 100644 --- a/chrome/installer/util/shell_util.cc +++ b/chrome/installer/util/shell_util.cc @@ -47,10 +47,11 @@ class RegistryEntry { public: // This method returns a list of all the registry entries that // are needed to register Chromium ProgIds. - static bool GetProgIdEntries(const std::wstring& chrome_exe, + static bool GetProgIdEntries(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& suffix, std::list<RegistryEntry*>* entries) { - std::wstring icon_path = ShellUtil::GetChromeIcon(chrome_exe); + std::wstring icon_path = ShellUtil::GetChromeIcon(dist, chrome_exe); std::wstring open_cmd = ShellUtil::GetChromeShellOpenCmd(chrome_exe); // File association ProgId @@ -71,13 +72,13 @@ class RegistryEntry { // This method returns a list of all the system level registry entries that // are needed to register Chromium on the machine. - static bool GetSystemEntries(const std::wstring& chrome_exe, + static bool GetSystemEntries(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& suffix, std::list<RegistryEntry*>* entries) { - std::wstring icon_path = ShellUtil::GetChromeIcon(chrome_exe); + std::wstring icon_path = ShellUtil::GetChromeIcon(dist, chrome_exe); std::wstring quoted_exe_path = L"\"" + chrome_exe + L"\""; - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::wstring app_name = dist->GetApplicationName() + suffix; std::wstring start_menu_entry(ShellUtil::kRegStartMenuInternet); start_menu_entry.append(L"\\" + app_name); @@ -137,7 +138,8 @@ class RegistryEntry { // This method returns a list of all the user level registry entries that // are needed to make Chromium default browser. - static bool GetUserEntries(const std::wstring& chrome_exe, + static bool GetUserEntries(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& suffix, std::list<RegistryEntry*>* entries) { // File extension associations. @@ -151,7 +153,7 @@ class RegistryEntry { // Protocols associations. std::wstring chrome_open = ShellUtil::GetChromeShellOpenCmd(chrome_exe); - std::wstring chrome_icon = ShellUtil::GetChromeIcon(chrome_exe); + std::wstring chrome_icon = ShellUtil::GetChromeIcon(dist, chrome_exe); for (int i = 0; ShellUtil::kProtocolAssociations[i] != NULL; i++) { std::wstring url_key(ShellUtil::kRegClasses); file_util::AppendToPath(&url_key, ShellUtil::kProtocolAssociations[i]); @@ -175,7 +177,6 @@ class RegistryEntry { // start->Internet shortcut. std::wstring start_menu(ShellUtil::kRegStartMenuInternet); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::wstring app_name = dist->GetApplicationName() + suffix; entries->push_front(new RegistryEntry(start_menu, app_name)); return true; @@ -279,13 +280,14 @@ bool AddRegistryEntries(HKEY root, const std::list<RegistryEntry*>& entries) { // This method checks if Chrome is already registered on the local machine. // It gets all the required registry entries for Chrome and then checks if // they exist in HKLM. Returns true if all the entries exist, otherwise false. -bool IsChromeRegistered(const std::wstring& chrome_exe, +bool IsChromeRegistered(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& suffix) { bool registered = true; std::list<RegistryEntry*> entries; STLElementDeleter<std::list<RegistryEntry*>> entries_deleter(&entries); - RegistryEntry::GetProgIdEntries(chrome_exe, suffix, &entries); - RegistryEntry::GetSystemEntries(chrome_exe, suffix, &entries); + RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries); + RegistryEntry::GetSystemEntries(dist, chrome_exe, suffix, &entries); for (std::list<RegistryEntry*>::const_iterator itr = entries.begin(); itr != entries.end() && registered; ++itr) { // We do not need registered = registered && ... since the loop condition @@ -299,13 +301,13 @@ bool IsChromeRegistered(const std::wstring& chrome_exe, // That will show the user the standard Vista elevation prompt. If the user // accepts it the new process will make the necessary changes and return SUCCESS // that we capture and return. -bool ElevateAndRegisterChrome(const std::wstring& chrome_exe, +bool ElevateAndRegisterChrome(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& suffix) { FilePath exe_path = FilePath::FromWStringHack(chrome_exe).DirName() .Append(installer_util::kSetupExe); if (!file_util::PathExists(exe_path)) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); HKEY reg_root = InstallUtil::IsPerUserInstall(chrome_exe.c_str()) ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; RegKey key(reg_root, dist->GetUninstallRegPath().c_str(), KEY_READ); @@ -348,9 +350,9 @@ bool ElevateAndRegisterChrome(const std::wstring& chrome_exe, // - Finally to handle the default install path (C:\Document and Settings\ // <user>\Local Settings\Application Data\Chromium\Application) the value // of the above key should differ from |chrome_exe| only in user name. -bool AnotherUserHasDefaultBrowser(const std::wstring& chrome_exe) { +bool AnotherUserHasDefaultBrowser(BrowserDistribution* dist, + const std::wstring& chrome_exe) { std::wstring reg_key(ShellUtil::kRegStartMenuInternet); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); reg_key.append(L"\\" + dist->GetApplicationName() + ShellUtil::kRegShellOpen); RegKey key(HKEY_LOCAL_MACHINE, reg_key.c_str(), KEY_READ); std::wstring registry_chrome_exe; @@ -424,12 +426,13 @@ const wchar_t* ShellUtil::kProtocolAssociations[] = {L"ftp", L"http", L"https", NULL}; const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol"; -bool ShellUtil::AdminNeededForRegistryCleanup(const std::wstring& suffix) { +bool ShellUtil::AdminNeededForRegistryCleanup(BrowserDistribution* dist, + const std::wstring& suffix) { bool cleanup_needed = false; std::list<RegistryEntry*> entries; STLElementDeleter<std::list<RegistryEntry*>> entries_deleter(&entries); - RegistryEntry::GetProgIdEntries(L"chrome.exe", suffix, &entries); - RegistryEntry::GetSystemEntries(L"chrome.exe", suffix, &entries); + RegistryEntry::GetProgIdEntries(dist, L"chrome.exe", suffix, &entries); + RegistryEntry::GetSystemEntries(dist, L"chrome.exe", suffix, &entries); for (std::list<RegistryEntry*>::const_iterator itr = entries.begin(); itr != entries.end() && !cleanup_needed; ++itr) { cleanup_needed = (*itr)->NameExistsInHKLM(); @@ -437,12 +440,13 @@ bool ShellUtil::AdminNeededForRegistryCleanup(const std::wstring& suffix) { return cleanup_needed; } -bool ShellUtil::CreateChromeDesktopShortcut(const std::wstring& chrome_exe, +bool ShellUtil::CreateChromeDesktopShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& description, int shell_change, bool alternate, bool create_new) { std::wstring shortcut_name; - if (!ShellUtil::GetChromeShortcutName(&shortcut_name, alternate)) + if (!ShellUtil::GetChromeShortcutName(dist, &shortcut_name, alternate)) return false; bool ret = true; @@ -450,7 +454,7 @@ bool ShellUtil::CreateChromeDesktopShortcut(const std::wstring& chrome_exe, std::wstring shortcut_path; if (ShellUtil::GetDesktopPath(false, &shortcut_path)) { file_util::AppendToPath(&shortcut_path, shortcut_name); - ret = ShellUtil::UpdateChromeShortcut(chrome_exe, shortcut_path, + ret = ShellUtil::UpdateChromeShortcut(dist, chrome_exe, shortcut_path, description, create_new); } else { ret = false; @@ -462,7 +466,7 @@ bool ShellUtil::CreateChromeDesktopShortcut(const std::wstring& chrome_exe, file_util::AppendToPath(&shortcut_path, shortcut_name); // Note we need to call the create operation and then AND the result // with the create operation of user level shortcut. - ret = ShellUtil::UpdateChromeShortcut(chrome_exe, shortcut_path, + ret = ShellUtil::UpdateChromeShortcut(dist, chrome_exe, shortcut_path, description, create_new) && ret; } else { ret = false; @@ -471,11 +475,12 @@ bool ShellUtil::CreateChromeDesktopShortcut(const std::wstring& chrome_exe, return ret; } -bool ShellUtil::CreateChromeQuickLaunchShortcut(const std::wstring& chrome_exe, +bool ShellUtil::CreateChromeQuickLaunchShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, int shell_change, bool create_new) { std::wstring shortcut_name; - if (!ShellUtil::GetChromeShortcutName(&shortcut_name, false)) + if (!ShellUtil::GetChromeShortcutName(dist, &shortcut_name, false)) return false; bool ret = true; @@ -484,7 +489,7 @@ bool ShellUtil::CreateChromeQuickLaunchShortcut(const std::wstring& chrome_exe, std::wstring user_ql_path; if (ShellUtil::GetQuickLaunchPath(false, &user_ql_path)) { file_util::AppendToPath(&user_ql_path, shortcut_name); - ret = ShellUtil::UpdateChromeShortcut(chrome_exe, user_ql_path, + ret = ShellUtil::UpdateChromeShortcut(dist, chrome_exe, user_ql_path, L"", create_new); } else { ret = false; @@ -497,7 +502,7 @@ bool ShellUtil::CreateChromeQuickLaunchShortcut(const std::wstring& chrome_exe, std::wstring default_ql_path; if (ShellUtil::GetQuickLaunchPath(true, &default_ql_path)) { file_util::AppendToPath(&default_ql_path, shortcut_name); - ret = ShellUtil::UpdateChromeShortcut(chrome_exe, default_ql_path, + ret = ShellUtil::UpdateChromeShortcut(dist, chrome_exe, default_ql_path, L"", create_new) && ret; } else { ret = false; @@ -507,8 +512,8 @@ bool ShellUtil::CreateChromeQuickLaunchShortcut(const std::wstring& chrome_exe, return ret; } -std::wstring ShellUtil::GetChromeIcon(const std::wstring& chrome_exe) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); +std::wstring ShellUtil::GetChromeIcon(BrowserDistribution* dist, + const std::wstring& chrome_exe) { std::wstring chrome_icon(chrome_exe); chrome_icon.append(L","); chrome_icon.append(base::IntToString16(dist->GetIconIndex())); @@ -519,8 +524,8 @@ std::wstring ShellUtil::GetChromeShellOpenCmd(const std::wstring& chrome_exe) { return L"\"" + chrome_exe + L"\" -- \"%1\""; } -bool ShellUtil::GetChromeShortcutName(std::wstring* shortcut, bool alternate) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); +bool ShellUtil::GetChromeShortcutName(BrowserDistribution* dist, + std::wstring* shortcut, bool alternate) { shortcut->assign(alternate ? dist->GetAlternateApplicationName() : dist->GetAppShortCutName()); shortcut->append(L".lnk"); @@ -567,7 +572,8 @@ bool ShellUtil::GetQuickLaunchPath(bool system_level, std::wstring* path) { return true; } -void ShellUtil::GetRegisteredBrowsers(std::map<std::wstring, +void ShellUtil::GetRegisteredBrowsers(BrowserDistribution* dist, + std::map<std::wstring, std::wstring>* browsers) { std::wstring base_key(ShellUtil::kRegStartMenuInternet); HKEY root = HKEY_LOCAL_MACHINE; @@ -587,14 +593,14 @@ void ShellUtil::GetRegisteredBrowsers(std::map<std::wstring, if (!install_info.Valid() || !install_info.ReadValue(L"ReinstallCommand", &command)) continue; - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); if (!name.empty() && !command.empty() && name.find(dist->GetApplicationName()) == std::wstring::npos) (*browsers)[name] = command; } } -bool ShellUtil::GetUserSpecificDefaultBrowserSuffix(std::wstring* entry) { +bool ShellUtil::GetUserSpecificDefaultBrowserSuffix(BrowserDistribution* dist, + std::wstring* entry) { wchar_t user_name[256]; DWORD size = _countof(user_name); if (::GetUserName(user_name, &size) == 0) @@ -603,19 +609,19 @@ bool ShellUtil::GetUserSpecificDefaultBrowserSuffix(std::wstring* entry) { entry->append(user_name); std::wstring start_menu_entry(ShellUtil::kRegStartMenuInternet); - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); start_menu_entry.append(L"\\" + dist->GetApplicationName() + *entry); RegKey key(HKEY_LOCAL_MACHINE, start_menu_entry.c_str(), KEY_READ); return key.Valid(); } -bool ShellUtil::MakeChromeDefault(int shell_change, +bool ShellUtil::MakeChromeDefault(BrowserDistribution* dist, + int shell_change, const std::wstring& chrome_exe, bool elevate_if_not_admin) { - if (!BrowserDistribution::GetDistribution()->CanSetAsDefault()) + if (!dist->CanSetAsDefault()) return false; - ShellUtil::RegisterChromeBrowser(chrome_exe, L"", elevate_if_not_admin); + ShellUtil::RegisterChromeBrowser(dist, chrome_exe, L"", elevate_if_not_admin); bool ret = true; // First use the new "recommended" way on Vista to make Chrome default @@ -627,10 +633,9 @@ bool ShellUtil::MakeChromeDefault(int shell_change, NULL, CLSCTX_INPROC, __uuidof(IApplicationAssociationRegistration), (void**)&pAAR); if (SUCCEEDED(hr)) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::wstring app_name = dist->GetApplicationName(); std::wstring suffix; - if (ShellUtil::GetUserSpecificDefaultBrowserSuffix(&suffix)) + if (ShellUtil::GetUserSpecificDefaultBrowserSuffix(dist, &suffix)) app_name += suffix; hr = pAAR->SetAppAsDefaultAll(app_name.c_str()); @@ -650,9 +655,9 @@ bool ShellUtil::MakeChromeDefault(int shell_change, std::list<RegistryEntry*> entries; STLElementDeleter<std::list<RegistryEntry*>> entries_deleter(&entries); std::wstring suffix; - if (!GetUserSpecificDefaultBrowserSuffix(&suffix)) + if (!GetUserSpecificDefaultBrowserSuffix(dist, &suffix)) suffix = L""; - RegistryEntry::GetUserEntries(chrome_exe, suffix, &entries); + RegistryEntry::GetUserEntries(dist, chrome_exe, suffix, &entries); // Change the default browser for current user. if ((shell_change & ShellUtil::CURRENT_USER) && !AddRegistryEntries(HKEY_CURRENT_USER, entries)) @@ -669,10 +674,11 @@ bool ShellUtil::MakeChromeDefault(int shell_change, return ret; } -bool ShellUtil::RegisterChromeBrowser(const std::wstring& chrome_exe, +bool ShellUtil::RegisterChromeBrowser(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& unique_suffix, bool elevate_if_not_admin) { - if (!BrowserDistribution::GetDistribution()->CanSetAsDefault()) + if (!dist->CanSetAsDefault()) return false; // First figure out we need to append a suffix to the registry entries to @@ -681,41 +687,42 @@ bool ShellUtil::RegisterChromeBrowser(const std::wstring& chrome_exe, if (!unique_suffix.empty()) { suffix = unique_suffix; } else if (InstallUtil::IsPerUserInstall(chrome_exe.c_str()) && - !GetUserSpecificDefaultBrowserSuffix(&suffix) && - !AnotherUserHasDefaultBrowser(chrome_exe)) { + !GetUserSpecificDefaultBrowserSuffix(dist, &suffix) && + !AnotherUserHasDefaultBrowser(dist, chrome_exe)) { suffix = L""; } // Check if Chromium is already registered with this suffix. - if (IsChromeRegistered(chrome_exe, suffix)) + if (IsChromeRegistered(dist, chrome_exe, suffix)) return true; // If user is an admin try to register and return the status. if (IsUserAnAdmin()) { std::list<RegistryEntry*> entries; STLElementDeleter<std::list<RegistryEntry*>> entries_deleter(&entries); - RegistryEntry::GetProgIdEntries(chrome_exe, suffix, &entries); - RegistryEntry::GetSystemEntries(chrome_exe, suffix, &entries); + RegistryEntry::GetProgIdEntries(dist, chrome_exe, suffix, &entries); + RegistryEntry::GetSystemEntries(dist, chrome_exe, suffix, &entries); return AddRegistryEntries(HKEY_LOCAL_MACHINE, entries); } // If user is not an admin and OS is Vista, try to elevate and register. if (elevate_if_not_admin && base::win::GetVersion() >= base::win::VERSION_VISTA && - ElevateAndRegisterChrome(chrome_exe, suffix)) + ElevateAndRegisterChrome(dist, chrome_exe, suffix)) return true; // If we got to this point then all we can do is create ProgIds under HKCU // on XP as well as Vista. std::list<RegistryEntry*> entries; STLElementDeleter<std::list<RegistryEntry*>> entries_deleter(&entries); - RegistryEntry::GetProgIdEntries(chrome_exe, L"", &entries); + RegistryEntry::GetProgIdEntries(dist, chrome_exe, L"", &entries); return AddRegistryEntries(HKEY_CURRENT_USER, entries); } -bool ShellUtil::RemoveChromeDesktopShortcut(int shell_change, bool alternate) { +bool ShellUtil::RemoveChromeDesktopShortcut(BrowserDistribution* dist, + int shell_change, bool alternate) { std::wstring shortcut_name; - if (!ShellUtil::GetChromeShortcutName(&shortcut_name, alternate)) + if (!ShellUtil::GetChromeShortcutName(dist, &shortcut_name, alternate)) return false; bool ret = true; @@ -741,9 +748,10 @@ bool ShellUtil::RemoveChromeDesktopShortcut(int shell_change, bool alternate) { return ret; } -bool ShellUtil::RemoveChromeQuickLaunchShortcut(int shell_change) { +bool ShellUtil::RemoveChromeQuickLaunchShortcut(BrowserDistribution* dist, + int shell_change) { std::wstring shortcut_name; - if (!ShellUtil::GetChromeShortcutName(&shortcut_name, false)) + if (!ShellUtil::GetChromeShortcutName(dist, &shortcut_name, false)) return false; bool ret = true; @@ -772,11 +780,11 @@ bool ShellUtil::RemoveChromeQuickLaunchShortcut(int shell_change) { return ret; } -bool ShellUtil::UpdateChromeShortcut(const std::wstring& chrome_exe, +bool ShellUtil::UpdateChromeShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& shortcut, const std::wstring& description, bool create_new) { - BrowserDistribution* dist = BrowserDistribution::GetDistribution(); std::wstring chrome_path = file_util::GetDirectoryFromPath(chrome_exe); FilePath prefs_path(chrome_path); diff --git a/chrome/installer/util/shell_util.h b/chrome/installer/util/shell_util.h index ad72f64..5df73c6 100644 --- a/chrome/installer/util/shell_util.h +++ b/chrome/installer/util/shell_util.h @@ -17,6 +17,8 @@ #include "base/basictypes.h" #include "chrome/installer/util/work_item_list.h" +class BrowserDistribution; + // This is a utility class that provides common shell integration methods // that can be used by installer as well as Chrome. class ShellUtil { @@ -74,7 +76,8 @@ class ShellUtil { // Checks if we need Admin rights for registry cleanup by checking if any // entry exists in HKLM. - static bool AdminNeededForRegistryCleanup(const std::wstring& suffix); + static bool AdminNeededForRegistryCleanup(BrowserDistribution* dist, + const std::wstring& suffix); // Create Chrome shortcut on Desktop // If shell_change is CURRENT_USER, the shortcut is created in the @@ -84,7 +87,8 @@ class ShellUtil { // If alternate is true, an alternate text for the shortcut is used. // create_new: If false, will only update the shortcut. If true, the function // will create a new shortcut if it doesn't exist already. - static bool CreateChromeDesktopShortcut(const std::wstring& chrome_exe, + static bool CreateChromeDesktopShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& description, int shell_change, bool alternate, bool create_new); @@ -98,7 +102,8 @@ class ShellUtil { // system. // create_new: If false, will only update the shortcut. If true, the function // will create a new shortcut if it doesn't exist already. - static bool CreateChromeQuickLaunchShortcut(const std::wstring& chrome_exe, + static bool CreateChromeQuickLaunchShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, int shell_change, bool create_new); @@ -106,7 +111,8 @@ class ShellUtil { // chrome.exe path passed in as input, to generate the full path for // Chrome icon that can be used as value for Windows registry keys. // |chrome_exe| full path to chrome.exe. - static std::wstring GetChromeIcon(const std::wstring& chrome_exe); + static std::wstring GetChromeIcon(BrowserDistribution* dist, + const std::wstring& chrome_exe); // This method returns the command to open URLs/files using chrome. Typically // this command is written to the registry under shell\open\command key. @@ -116,7 +122,8 @@ class ShellUtil { // Returns the localized name of Chrome shortcut. If |alternate| is true // it returns a second localized text that is better suited for certain // scenarios. - static bool GetChromeShortcutName(std::wstring* shortcut, bool alternate); + static bool GetChromeShortcutName(BrowserDistribution* dist, + std::wstring* shortcut, bool alternate); // Gets the desktop path for the current user or all users (if system_level // is true) and returns it in 'path' argument. Return true if successful, @@ -131,8 +138,9 @@ class ShellUtil { static bool GetQuickLaunchPath(bool system_level, std::wstring* path); // Gets a mapping of all registered browser (on local machine) names and - // thier reinstall command (which usually sets browser as default). - static void GetRegisteredBrowsers(std::map<std::wstring, + // their reinstall command (which usually sets browser as default). + static void GetRegisteredBrowsers(BrowserDistribution* dist, + std::map<std::wstring, std::wstring>* browsers); // This function gets a suffix (user's login name) that can be added @@ -142,7 +150,8 @@ class ShellUtil { // This suffix value is assigned to |entry|. The function also checks for // existence of Default Browser registry key with this suffix and // returns true if it exists. In all other cases it returns false. - static bool GetUserSpecificDefaultBrowserSuffix(std::wstring* entry); + static bool GetUserSpecificDefaultBrowserSuffix(BrowserDistribution* dist, + std::wstring* entry); // Make Chrome default browser. // shell_change: Defined whether to register as default browser at system @@ -151,7 +160,8 @@ class ShellUtil { // chrome_exe: The chrome.exe path to register as default browser. // elevate_if_not_admin: On Vista if user is not admin, try to elevate for // Chrome registration. - static bool MakeChromeDefault(int shell_change, + static bool MakeChromeDefault(BrowserDistribution* dist, + int shell_change, const std::wstring& chrome_exe, bool elevate_if_not_admin); @@ -176,7 +186,8 @@ class ShellUtil { // to default browser entries names that it creates in the registry. // |elevate_if_not_admin| if true will make this method try alternate methods // as described above. - static bool RegisterChromeBrowser(const std::wstring& chrome_exe, + static bool RegisterChromeBrowser(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& unique_suffix, bool elevate_if_not_admin); @@ -187,21 +198,24 @@ class ShellUtil { // Desktop folder of "All Users" profile. // If alternate is true, the shortcut with the alternate name is removed. See // CreateChromeDesktopShortcut() for more information. - static bool RemoveChromeDesktopShortcut(int shell_change, bool alternate); + static bool RemoveChromeDesktopShortcut(BrowserDistribution* dist, + int shell_change, bool alternate); // Remove Chrome shortcut from Quick Launch Bar. // If shell_change is CURRENT_USER, the shortcut is removed from // the Quick Launch folder of current user's profile. // If shell_change is SYSTEM_LEVEL, the shortcut is removed from // the Quick Launch folder of "Default User" profile. - static bool RemoveChromeQuickLaunchShortcut(int shell_change); + static bool RemoveChromeQuickLaunchShortcut(BrowserDistribution* dist, + int shell_change); // Updates shortcut (or creates a new shortcut) at destination given by // shortcut to a target given by chrome_exe. The arguments is left NULL // for the target and icon is set as icon at index 0 from exe. // If create_new is set to true, the function will create a new shortcut if // if doesn't exist. - static bool UpdateChromeShortcut(const std::wstring& chrome_exe, + static bool UpdateChromeShortcut(BrowserDistribution* dist, + const std::wstring& chrome_exe, const std::wstring& shortcut, const std::wstring& description, bool create_new); diff --git a/chrome/installer/util/shell_util_unittest.cc b/chrome/installer/util/shell_util_unittest.cc index ae74556..a5d917e 100644 --- a/chrome/installer/util/shell_util_unittest.cc +++ b/chrome/installer/util/shell_util_unittest.cc @@ -11,6 +11,7 @@ #include "base/file_util.h" #include "base/path_service.h" #include "base/scoped_comptr_win.h" +#include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/master_preferences.h" #include "chrome/installer/util/shell_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -108,6 +109,8 @@ class ShellUtilTest : public testing::Test { // Test that we can open archives successfully. TEST_F(ShellUtilTest, UpdateChromeShortcutTest) { + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + ASSERT_TRUE(dist != NULL); // Create an executable in test path by copying ourself to it. wchar_t exe_full_path_str[MAX_PATH]; EXPECT_FALSE(::GetModuleFileName(NULL, exe_full_path_str, MAX_PATH) == 0); @@ -118,7 +121,7 @@ TEST_F(ShellUtilTest, UpdateChromeShortcutTest) { FilePath shortcut_path = test_dir_.AppendASCII("shortcut.lnk"); const std::wstring description(L"dummy description"); - EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(exe_path.value(), + EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(dist, exe_path.value(), shortcut_path.value(), description, true)); EXPECT_TRUE(VerifyChromeShortcut(exe_path.value(), @@ -139,7 +142,7 @@ TEST_F(ShellUtilTest, UpdateChromeShortcutTest) { "}"; file.close(); ASSERT_TRUE(file_util::Delete(shortcut_path, false)); - EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(exe_path.value(), + EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(dist, exe_path.value(), shortcut_path.value(), description, true)); EXPECT_TRUE(VerifyChromeShortcut(exe_path.value(), @@ -149,7 +152,7 @@ TEST_F(ShellUtilTest, UpdateChromeShortcutTest) { // Now change only description to update shortcut and make sure icon index // doesn't change. const std::wstring description2(L"dummy description 2"); - EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(exe_path.value(), + EXPECT_TRUE(ShellUtil::UpdateChromeShortcut(dist, exe_path.value(), shortcut_path.value(), description2, false)); EXPECT_TRUE(VerifyChromeShortcut(exe_path.value(), diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc index 66a0636..3b1d651 100644 --- a/chrome/installer/util/util_constants.cc +++ b/chrome/installer/util/util_constants.cc @@ -153,4 +153,10 @@ const wchar_t kUninstallArgumentsField[] = L"UninstallArguments"; const wchar_t kUninstallDisplayNameField[] = L"DisplayName"; const char kUninstallMetricsName[] = "uninstall_metrics"; const wchar_t kUninstallInstallationDate[] = L"installation_date"; +const wchar_t kInstallerResult[] = L"InstallerResult"; +const wchar_t kInstallerError[] = L"InstallerError"; +const wchar_t kInstallerResultUIString[] = L"InstallerResultUIString"; +const wchar_t kInstallerSuccessLaunchCmdLine[] = + L"InstallerSuccessLaunchCmdLine"; + } // namespace installer_util diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h index 7cc4042..3cd21b0 100644 --- a/chrome/installer/util/util_constants.h +++ b/chrome/installer/util/util_constants.h @@ -110,6 +110,12 @@ extern const wchar_t kUninstallInstallationDate[]; extern const char kUninstallMetricsName[]; extern const wchar_t kUninstallStringField[]; +// Used by ProductInstall::WriteInstallerResult. +extern const wchar_t kInstallerResult[]; +extern const wchar_t kInstallerError[]; +extern const wchar_t kInstallerResultUIString[]; +extern const wchar_t kInstallerSuccessLaunchCmdLine[]; + } // namespace installer_util #endif // CHROME_INSTALLER_UTIL_UTIL_CONSTANTS_H_ diff --git a/chrome/installer/util/version.h b/chrome/installer/util/version.h index 72e8271..e237260 100644 --- a/chrome/installer/util/version.h +++ b/chrome/installer/util/version.h @@ -11,6 +11,7 @@ namespace installer { +// TODO(tommi): We should be using the Version class from base. class Version { public: virtual ~Version(); @@ -24,6 +25,10 @@ class Version { return version_str_; } + bool IsEqual(const Version& other) const { + return version_str_ == other.GetString(); + } + // Assume that the version string is specified by four integers separated // by character '.'. Return NULL if string is not of this format. // Caller is responsible for freeing the Version object once done. diff --git a/chrome_frame/test/test_with_web_server.cc b/chrome_frame/test/test_with_web_server.cc index a030a76..8d4e3ba 100644 --- a/chrome_frame/test/test_with_web_server.cc +++ b/chrome_frame/test/test_with_web_server.cc @@ -8,9 +8,11 @@ #include "base/file_version_info.h" #include "base/path_service.h" #include "base/stringprintf.h" +#include "base/test/test_timeouts.h" #include "base/utf_string_conversions.h" #include "base/win/windows_version.h" #include "chrome/common/chrome_switches.h" +#include "chrome/installer/util/product.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/helper.h" #include "chrome_frame/html_utils.h" @@ -26,8 +28,6 @@ using testing::_; using testing::StrCaseEq; const wchar_t kDocRoot[] = L"chrome_frame\\test\\data"; -const int kLongWaitTimeout = 15 * 1000; -const int kShortWaitTimeout = 5 * 1000; namespace { @@ -92,7 +92,7 @@ void ChromeFrameTestWithWebServer::SetUp() { // Make sure our playground is clean before we start. CloseAllBrowsers(); - // Make sure that we are not accidently enabling gcf protocol. + // Make sure that we are not accidentally enabling gcf protocol. SetConfigBool(kAllowUnsafeURLs, false); FilePath chrome_frame_source_path; @@ -220,7 +220,7 @@ void ChromeFrameTestWithWebServer::SimpleBrowserTestExpectedResult( ASSERT_TRUE(LaunchBrowser(browser, page)); server_mock_.ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE), kPostedResultSubstring); - WaitForTestToComplete(kLongWaitTimeout); + WaitForTestToComplete(TestTimeouts::action_max_timeout_ms()); ASSERT_EQ(result, server_mock_.posted_result()); } @@ -237,7 +237,7 @@ void ChromeFrameTestWithWebServer::OptionalBrowserTest(BrowserKind browser, } else { server_mock_.ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE), kPostedResultSubstring); - WaitForTestToComplete(kLongWaitTimeout); + WaitForTestToComplete(TestTimeouts::action_max_timeout_ms()); ASSERT_EQ("OK", server_mock_.posted_result()); } } @@ -259,15 +259,17 @@ void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser, // If we can't find the Chrome Frame DLL in the src tree, we turn to // the directory where chrome is installed. if (!version_info) { - installer::Version* ver_system = InstallUtil::GetChromeVersion(true); - installer::Version* ver_user = InstallUtil::GetChromeVersion(false); - ASSERT_TRUE(ver_system || ver_user); - - bool system_install = ver_system ? true : false; - FilePath cf_dll_path = FilePath::FromWStringHack( - installer::GetChromeInstallPath(system_install)); + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + scoped_ptr<installer::Version> ver_system( + InstallUtil::GetChromeVersion(dist, true)); + scoped_ptr<installer::Version> ver_user( + InstallUtil::GetChromeVersion(dist, false)); + ASSERT_TRUE(ver_system.get() || ver_user.get()); + + bool system_install = ver_system.get() ? true : false; + FilePath cf_dll_path(installer::GetChromeInstallPath(system_install, dist)); cf_dll_path = cf_dll_path.Append( - ver_system ? ver_system->GetString() : ver_user->GetString()); + ver_system.get() ? ver_system->GetString() : ver_user->GetString()); cf_dll_path = cf_dll_path.Append(kChromeFrameDllName); version_info = FileVersionInfo::CreateFileVersionInfo(cf_dll_path); if (version_info) @@ -281,7 +283,7 @@ void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser, EXPECT_TRUE(LaunchBrowser(browser, page)); server_mock_.ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE), kPostedResultSubstring); - WaitForTestToComplete(kLongWaitTimeout); + WaitForTestToComplete(TestTimeouts::action_max_timeout_ms()); ASSERT_EQ(version, UTF8ToWide(server_mock_.posted_result())); } @@ -294,7 +296,7 @@ void ChromeFrameTestWithWebServer::SessionIdTest(BrowserKind browser, server_mock_.set_expected_result(expected_result); server_mock_.ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE), kPostedResultSubstring); - WaitForTestToComplete(kLongWaitTimeout); + WaitForTestToComplete(TestTimeouts::action_max_timeout_ms()); ASSERT_EQ(expected_result, server_mock_.posted_result()); } @@ -489,7 +491,7 @@ TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_ObjectFocus) { if (!LaunchBrowser(OPERA, kNavigateSimpleObjectFocus)) { LOG(ERROR) << "Failed to launch browser " << ToString(OPERA); } else { - ASSERT_TRUE(WaitForOnLoad(kLongWaitTimeout)); + ASSERT_TRUE(WaitForOnLoad(TestTimeouts::action_max_timeout_ms())); server_mock_.ExpectAndHandlePostedResult(CFInvocation(CFInvocation::NONE), kPostedResultSubstring); BringBrowserToTop(); |