diff options
-rw-r--r-- | chrome/chrome.gyp | 1 | ||||
-rw-r--r-- | chrome/chrome_installer.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_installer_util.gypi | 2 | ||||
-rw-r--r-- | chrome/installer/installer_tools.gyp | 49 | ||||
-rw-r--r-- | chrome/installer/setup/setup_main.cc | 11 | ||||
-rw-r--r-- | chrome/installer/tools/validate_installation.rc | 65 | ||||
-rw-r--r-- | chrome/installer/tools/validate_installation_main.cc | 189 | ||||
-rw-r--r-- | chrome/installer/tools/validate_installation_resource.h | 18 | ||||
-rw-r--r-- | chrome/installer/util/installation_validator.cc | 409 | ||||
-rw-r--r-- | chrome/installer/util/installation_validator.h | 161 | ||||
-rw-r--r-- | chrome/installer/util/installation_validator_unittest.cc | 315 |
11 files changed, 1222 insertions, 0 deletions
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 628fc75..49e6e81d 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1618,6 +1618,7 @@ 'type': 'none', 'dependencies': [ 'installer/mini_installer.gyp:*', + 'installer/installer_tools.gyp:*', 'installer/upgrade_test.gyp:*', '../app/app.gyp:*', '../base/base.gyp:*', diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index 45fbdde..62eebe2 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -68,6 +68,7 @@ '<(DEPTH)/base/base.gyp:base_i18n', '<(DEPTH)/base/base.gyp:test_support_base', '<(DEPTH)/build/temp_gyp/googleurl.gyp:googleurl', + '<(DEPTH)/testing/gmock.gyp:gmock', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'include_dirs': [ @@ -87,6 +88,7 @@ 'installer/util/google_chrome_distribution_unittest.cc', 'installer/util/google_update_settings_unittest.cc', 'installer/util/install_util_unittest.cc', + 'installer/util/installation_validator_unittest.cc', 'installer/util/installer_state_unittest.cc', 'installer/util/installer_util_unittests.rc', 'installer/util/installer_util_unittests_resource.h', diff --git a/chrome/chrome_installer_util.gypi b/chrome/chrome_installer_util.gypi index 3f852bc..b33b28a 100644 --- a/chrome/chrome_installer_util.gypi +++ b/chrome/chrome_installer_util.gypi @@ -113,6 +113,8 @@ 'installer/util/google_chrome_distribution.h', 'installer/util/html_dialog.h', 'installer/util/html_dialog_impl.cc', + 'installer/util/installation_validator.cc', + 'installer/util/installation_validator.h', 'installer/util/logging_installer.cc', 'installer/util/logging_installer.h', 'installer/util/lzma_util.cc', diff --git a/chrome/installer/installer_tools.gyp b/chrome/installer/installer_tools.gyp new file mode 100644 index 0000000..1f7474b --- /dev/null +++ b/chrome/installer/installer_tools.gyp @@ -0,0 +1,49 @@ +{ + 'variables': { + 'version_py': '<(DEPTH)/chrome/tools/build/version.py', + 'version_path': '<(DEPTH)/chrome/VERSION', + 'lastchange_path': '<(SHARED_INTERMEDIATE_DIR)/build/LASTCHANGE', + # 'branding_dir' is set in the 'conditions' section at the bottom. + 'msvs_use_common_release': 0, + 'msvs_use_common_linker_extras': 0, + }, + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'validate_installation', + 'msvs_guid': '7CC08DA8-E9CA-4573-A8C4-E7F0D0CF8EBA', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/chrome/chrome.gyp:common_constants', + '<(DEPTH)/chrome/chrome.gyp:installer_util', + '<(DEPTH)/chrome/chrome.gyp:installer_util_strings', + ], + 'include_dirs': [ + '<(DEPTH)', + ], + 'sources': [ + 'tools/validate_installation_main.cc', + 'tools/validate_installation.rc', + 'tools/validate_installation_resource.h', + ], + }, + ], + }], + [ 'branding == "Chrome"', { + 'variables': { + 'branding_dir': '<(DEPTH)/chrome/app/theme/google_chrome', + }, + }, { # else branding!="Chrome" + 'variables': { + 'branding_dir': '<(DEPTH)/chrome/app/theme/chromium', + }, + }], + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc index 1c2d648..6377114 100644 --- a/chrome/installer/setup/setup_main.cc +++ b/chrome/installer/setup/setup_main.cc @@ -41,6 +41,7 @@ #include "chrome/installer/util/helper.h" #include "chrome/installer/util/html_dialog.h" #include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/installation_validator.h" #include "chrome/installer/util/installer_state.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/l10n_string_util.h" @@ -55,6 +56,7 @@ using installer::InstallerState; using installer::InstallationState; +using installer::InstallationValidator; using installer::Product; using installer::ProductState; using installer::Products; @@ -1120,6 +1122,15 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, &installer_state); } + // Validate that the machine is now in a good state following the operation. + // TODO(grt): change this to log at DFATAL once we're convinced that the + // validator handles all cases properly. + InstallationValidator::InstallationType installation_type = + InstallationValidator::NO_PRODUCTS; + LOG_IF(ERROR, + !InstallationValidator::ValidateInstallationType(system_install, + &installation_type)); + const Product* cf_install = installer_state.FindProduct(BrowserDistribution::CHROME_FRAME); diff --git a/chrome/installer/tools/validate_installation.rc b/chrome/installer/tools/validate_installation.rc new file mode 100644 index 0000000..e6d2858 --- /dev/null +++ b/chrome/installer/tools/validate_installation.rc @@ -0,0 +1,65 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "validate_installation_resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE 9, 1
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "validate_installation_resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+#include "installer_util_strings.rc"
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/chrome/installer/tools/validate_installation_main.cc b/chrome/installer/tools/validate_installation_main.cc new file mode 100644 index 0000000..5661457 --- /dev/null +++ b/chrome/installer/tools/validate_installation_main.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2011 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. + +// A command-line tool that inspects the current system, displaying information +// about installed products. Violations are dumped to stderr. The process +// exit code is 0 if there are no violations, or 1 otherwise. + +#include <cstdio> +#include <cstdlib> + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "chrome/installer/util/installation_validator.h" + +using installer::InstallationValidator; + +namespace { + +// A helper class that initializes logging and installs a log message handler to +// direct ERROR messages to stderr. Only one instance of this class may be live +// at a time. +class ConsoleLogHelper { + public: + ConsoleLogHelper(); + ~ConsoleLogHelper(); + + private: + static FilePath GetLogFilePath(); + static bool DumpLogMessage(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str); + + static const wchar_t kLogFileName_[]; + static FILE* const kOutputStream_; + static const logging::LogSeverity kViolationSeverity_; + static logging::LogMessageHandlerFunction old_message_handler_; + FilePath log_file_path_; +}; + +// static +const wchar_t ConsoleLogHelper::kLogFileName_[] = L"validate_installation.log"; + +// Dump violations to stderr. +// static +FILE* const ConsoleLogHelper::kOutputStream_ = stderr; + +// InstallationValidator logs all violations at ERROR level. +// static +const logging::LogSeverity + ConsoleLogHelper::kViolationSeverity_ = logging::LOG_ERROR; + +// static +logging::LogMessageHandlerFunction + ConsoleLogHelper::old_message_handler_ = NULL; + +ConsoleLogHelper::ConsoleLogHelper() : log_file_path_(GetLogFilePath()) { + LOG_ASSERT(old_message_handler_ == NULL); + logging::InitLogging( + log_file_path_.value().c_str(), + logging::LOG_ONLY_TO_FILE, + logging::DONT_LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE, + logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); + + old_message_handler_ = logging::GetLogMessageHandler(); + logging::SetLogMessageHandler(&DumpLogMessage); +} + +ConsoleLogHelper::~ConsoleLogHelper() { + logging::SetLogMessageHandler(old_message_handler_); + old_message_handler_ = NULL; + + logging::CloseLogFile(); + + // Delete the log file if it wasn't written to (this is expected). + int64 file_size = 0; + if (file_util::GetFileSize(log_file_path_, &file_size) && file_size == 0) + file_util::Delete(log_file_path_, false); +} + +// Returns the path to the log file to create. The file should be empty at +// process exit since we redirect log messages to stderr. +// static +FilePath ConsoleLogHelper::GetLogFilePath() { + FilePath log_path; + + if (PathService::Get(base::DIR_TEMP, &log_path)) + return log_path.Append(kLogFileName_); + else + return FilePath(kLogFileName_); +} + +// A logging::LogMessageHandlerFunction that sends the body of messages logged +// at the severity of validation violations to stderr. All other messages are +// sent through the default logging pipeline. +// static +bool ConsoleLogHelper::DumpLogMessage(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str) { + if (severity == kViolationSeverity_) { + fprintf(kOutputStream_, "%s", str.c_str() + message_start); + return true; + } + + if (old_message_handler_ != NULL) + return (old_message_handler_)(severity, file, line, message_start, str); + + return false; +} + +const char* LevelToString(bool system_level) { + return system_level ? "System-level" : "User-level"; +} + +std::string InstallationTypeToString( + InstallationValidator::InstallationType type) { + std::string result; + + static const struct ProductData { + int bit; + const char* name; + } kProdBitToName[] = { + { + InstallationValidator::ProductBits::CHROME_SINGLE, + "Chrome" + }, { + InstallationValidator::ProductBits::CHROME_MULTI, + "Chrome (multi)" + }, { + InstallationValidator::ProductBits::CHROME_FRAME_SINGLE, + "Chrome Frame" + }, { + InstallationValidator::ProductBits::CHROME_FRAME_MULTI, + "Chrome Frame (multi)" + }, { + InstallationValidator::ProductBits::CHROME_FRAME_READY_MODE, + "Ready-mode Chrome Frame" + }, + }; + + for (size_t i = 0; i < arraysize(kProdBitToName); ++i) { + const ProductData& product_data = kProdBitToName[i]; + if ((type & product_data.bit) != 0) { + if (!result.empty()) + result.append(", "); + result.append(product_data.name); + } + } + + return result; +} + +} // namespace + +// The main program. +int wmain(int argc, wchar_t *argv[]) { + int result = EXIT_SUCCESS; + base::AtExitManager exit_manager; + + CommandLine::Init(0, NULL); + ConsoleLogHelper log_helper; + + // Check user-level and system-level for products. + for (int i = 0; i < 2; ++i) { + const bool system_level = (i != 0); + InstallationValidator::InstallationType type = + InstallationValidator::NO_PRODUCTS; + bool is_valid = + InstallationValidator::ValidateInstallationType(system_level, &type); + if (type != InstallationValidator::NO_PRODUCTS) { + FILE* stream = is_valid ? stdout : stderr; + fprintf(stream, "%s installations%s: %s\n", LevelToString(system_level), + (is_valid ? "" : " (with errors)"), + InstallationTypeToString(type).c_str()); + } + if (!is_valid) + result = EXIT_FAILURE; + } + + return result; +} diff --git a/chrome/installer/tools/validate_installation_resource.h b/chrome/installer/tools/validate_installation_resource.h new file mode 100644 index 0000000..4998008 --- /dev/null +++ b/chrome/installer/tools/validate_installation_resource.h @@ -0,0 +1,18 @@ +// Copyright (c) 2011 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. + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by validate_installation.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/chrome/installer/util/installation_validator.cc b/chrome/installer/util/installation_validator.cc new file mode 100644 index 0000000..1f76d61 --- /dev/null +++ b/chrome/installer/util/installation_validator.cc @@ -0,0 +1,409 @@ +// Copyright (c) 2011 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. + +// Implementation of the installation validator. + +#include "chrome/installer/util/installation_validator.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/version.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/helper.h" +#include "chrome/installer/util/installation_state.h" + +namespace installer { + +BrowserDistribution::Type + InstallationValidator::ChromeRules::distribution_type() const { + return BrowserDistribution::CHROME_BROWSER; +} + +void InstallationValidator::ChromeRules::AddUninstallSwitchExpectations( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + SwitchExpectations* expectations) const { + const bool is_multi_install = + product_state.uninstall_command().HasSwitch(switches::kMultiInstall); + + // --chrome should be present iff --multi-install. + expectations->push_back(std::make_pair(std::string(switches::kChrome), + is_multi_install)); + // --chrome-frame --ready-mode should be present iff CF in ready mode. + const ProductState* cf_state = + machine_state.GetProductState(system_install, + BrowserDistribution::CHROME_FRAME); + const bool ready_mode = + cf_state != NULL && + cf_state->uninstall_command().HasSwitch(switches::kChromeFrameReadyMode); + expectations->push_back(std::make_pair(std::string(switches::kChromeFrame), + ready_mode)); + expectations->push_back( + std::make_pair(std::string(switches::kChromeFrameReadyMode), ready_mode)); +} + +BrowserDistribution::Type + InstallationValidator::ChromeFrameRules::distribution_type() const { + return BrowserDistribution::CHROME_FRAME; +} + +void InstallationValidator::ChromeFrameRules::AddUninstallSwitchExpectations( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + SwitchExpectations* expectations) const { + // --chrome-frame must be present. + expectations->push_back(std::make_pair(std::string(switches::kChromeFrame), + true)); + // --chrome must not be present. + expectations->push_back(std::make_pair(std::string(switches::kChrome), + false)); +} + +// static +const InstallationValidator::InstallationType + InstallationValidator::kInstallationTypes[] = { + NO_PRODUCTS, + CHROME_SINGLE, + CHROME_MULTI, + CHROME_FRAME_SINGLE, + CHROME_FRAME_SINGLE_CHROME_SINGLE, + CHROME_FRAME_SINGLE_CHROME_MULTI, + CHROME_FRAME_MULTI, + CHROME_FRAME_MULTI_CHROME_MULTI, + CHROME_FRAME_READY_MODE_CHROME_MULTI +}; + +// Validates the multi-install binaries at level |system_level|. +void InstallationValidator::ValidateBinaries( + const InstallationState& machine_state, + bool system_install, + const ProductState& binaries_state, + bool* is_valid) { + const ChannelInfo& channel = binaries_state.channel(); + + // ap must have -multi + if (!channel.IsMultiInstall()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are missing \"-multi\" in channel name: \"" + << channel.value() << "\""; + } + + // ap must have -chrome iff Chrome is installed + const ProductState* chrome_state = machine_state.GetProductState( + system_install, BrowserDistribution::CHROME_BROWSER); + if (chrome_state != NULL) { + if (!channel.IsChrome()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are missing \"chrome\" in channel name:" + << " \"" << channel.value() << "\""; + } + } else if (channel.IsChrome()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries have \"-chrome\" in channel name, yet Chrome" + " is not installed: \"" << channel.value() << "\""; + } + + // ap must have -chromeframe iff Chrome Frame is installed multi + const ProductState* cf_state = machine_state.GetProductState( + system_install, BrowserDistribution::CHROME_FRAME); + if (cf_state != NULL && cf_state->is_multi_install()) { + if (!channel.IsChromeFrame()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are missing \"-chromeframe\" in channel" + " name: \"" << channel.value() << "\""; + } + } else if (channel.IsChromeFrame()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries have \"-chromeframe\" in channel name, yet " + "Chrome Frame is not installed multi: \"" << channel.value() + << "\""; + } + + // ap must have -readymode iff Chrome Frame is installed in ready-mode + if (cf_state != NULL && + cf_state->uninstall_command().HasSwitch( + installer::switches::kChromeFrameReadyMode)) { + if (!channel.IsReadyMode()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are missing \"-readymode\" in channel" + " name: \"" << channel.value() << "\""; + } + } else if (channel.IsReadyMode()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries have \"-readymode\" in channel name, yet " + "Chrome Frame is not in ready mode: \"" << channel.value() + << "\""; + } + + // Chrome or Chrome Frame must be present + if (chrome_state == NULL && cf_state == NULL) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are present with no other products."; + } + + // Chrome must be multi-install if present. + if (chrome_state != NULL && !chrome_state->is_multi_install()) { + *is_valid = false; + LOG(ERROR) + << "Chrome Binaries are present yet Chrome is not multi-install."; + } + + // Chrome Frame must be multi-install if Chrome is not present. + if (cf_state != NULL && chrome_state == NULL && + !cf_state->is_multi_install()) { + *is_valid = false; + LOG(ERROR) << "Chrome Binaries are present without Chrome yet Chrome Frame " + "is not multi-install."; + } +} + +// Validates the path to |setup_exe| for the product described by |ctx|. +void InstallationValidator::ValidateSetupPath(const ProductContext& ctx, + const FilePath& setup_exe, + const char* purpose, + bool* is_valid) { + DCHECK(is_valid); + + BrowserDistribution* bins_dist = ctx.dist; + if (ctx.state.is_multi_install()) { + bins_dist = BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BINARIES); + } + + FilePath expected_path = installer::GetChromeInstallPath(ctx.system_install, + bins_dist); + expected_path = expected_path + .AppendASCII(ctx.state.version().GetString()) + .Append(installer::kInstallerDir) + .Append(installer::kSetupExe); + if (!FilePath::CompareEqualIgnoreCase(expected_path.value(), + setup_exe.value())) { + *is_valid = false; + LOG(ERROR) << ctx.dist->GetApplicationName() << " path to " << purpose + << " is not " << expected_path.value() << ": " + << setup_exe.value(); + } +} + +// Validates that |command| meets the expectations described in |expected|. +void InstallationValidator::ValidateCommandExpectations( + const ProductContext& ctx, + const CommandLine& command, + const SwitchExpectations& expected, + const char* source, + bool* is_valid) { + for (SwitchExpectations::size_type i = 0, size = expected.size(); i < size; + ++i) { + const SwitchExpectations::value_type& expectation = expected[i]; + if (command.HasSwitch(expectation.first) != expectation.second) { + *is_valid = false; + LOG(ERROR) << ctx.dist->GetApplicationName() << " " << source + << (expectation.second ? " is missing" : " has") << " \"" + << expectation.first << "\"" + << (expectation.second ? "" : " but shouldn't") << ": " + << command.command_line_string(); + } + } +} + +// Validates that |command|, originating from |source|, is formed properly for +// the product described by |ctx| +void InstallationValidator::ValidateUninstallCommand(const ProductContext& ctx, + const CommandLine& command, + const char* source, + bool* is_valid) { + DCHECK(is_valid); + + ValidateSetupPath(ctx, command.GetProgram(), "uninstaller", is_valid); + + const bool is_multi_install = ctx.state.is_multi_install(); + SwitchExpectations expected; + + expected.push_back(std::make_pair(std::string(switches::kUninstall), true)); + expected.push_back(std::make_pair(std::string(switches::kSystemLevel), + ctx.system_install)); + expected.push_back(std::make_pair(std::string(switches::kMultiInstall), + is_multi_install)); + ctx.rules.AddUninstallSwitchExpectations(ctx.machine_state, + ctx.system_install, + ctx.state, &expected); + + ValidateCommandExpectations(ctx, command, expected, source, is_valid); +} + +// Validates the rename command for the product described by |ctx|. +void InstallationValidator::ValidateRenameCommand(const ProductContext& ctx, + bool* is_valid) { + DCHECK(is_valid); + DCHECK(!ctx.state.rename_cmd().empty()); + + CommandLine command = CommandLine::FromString(ctx.state.rename_cmd()); + + ValidateSetupPath(ctx, command.GetProgram(), "in-use renamer", is_valid); + + SwitchExpectations expected; + + expected.push_back(std::make_pair(std::string(switches::kRenameChromeExe), + true)); + expected.push_back(std::make_pair(std::string(switches::kSystemLevel), + ctx.system_install)); + expected.push_back(std::make_pair(std::string(switches::kMultiInstall), + ctx.state.is_multi_install())); + + ValidateCommandExpectations(ctx, command, expected, "in-use renamer", + is_valid); +} + +// Validates the "opv" and "cmd" values for the product described in |ctx|. +void InstallationValidator::ValidateOldVersionValues( + const ProductContext& ctx, + bool* is_valid) { + DCHECK(is_valid); + + // opv and cmd must both be present or both absent + if (ctx.state.old_version() == NULL) { + if (!ctx.state.rename_cmd().empty()) { + *is_valid = false; + LOG(ERROR) << ctx.dist->GetApplicationName() + << " has a rename command but no opv: " + << ctx.state.rename_cmd(); + } + } else { + if (ctx.state.rename_cmd().empty()) { + *is_valid = false; + LOG(ERROR) << ctx.dist->GetApplicationName() + << " has an opv but no rename command: " + << ctx.state.old_version()->GetString(); + } else { + ValidateRenameCommand(ctx, is_valid); + } + } +} + +// Validates the multi-install state of the product described in |ctx|. +void InstallationValidator::ValidateMultiInstallProduct( + const ProductContext& ctx, + bool* is_valid) { + DCHECK(is_valid); + + const ProductState* binaries = + ctx.machine_state.GetProductState(ctx.system_install, + BrowserDistribution::CHROME_BINARIES); + DCHECK(binaries); + + // Version must match that of binaries. + if (ctx.state.version().CompareTo(binaries->version()) != 0) { + *is_valid = false; + LOG(ERROR) << "Version of " << ctx.dist->GetApplicationName() + << " (" << ctx.state.version().GetString() << ") does not " + "match that of Chrome Binaries (" + << binaries->version().GetString() << ")."; + } + + // Channel value must match that of binaries. + if (!ctx.state.channel().Equals(binaries->channel())) { + *is_valid = false; + LOG(ERROR) << "Channel name of " << ctx.dist->GetApplicationName() + << " (" << ctx.state.channel().value() + << ") does not match that of Chrome Binaries (" + << binaries->channel().value() << ")."; + } +} + +// Validates the product described in |product_state| according to |rules|. +void InstallationValidator::ValidateProduct( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + const ProductRules& rules, + bool* is_valid) { + DCHECK(is_valid); + ProductContext ctx = { + machine_state, + system_install, + BrowserDistribution::GetSpecificDistribution(rules.distribution_type()), + product_state, + rules + }; + + ValidateUninstallCommand(ctx, product_state.uninstall_command(), + "Google Update uninstall command", is_valid); + + ValidateOldVersionValues(ctx, is_valid); + + if (product_state.is_multi_install()) + ValidateMultiInstallProduct(ctx, is_valid); +} + +// static +bool InstallationValidator::ValidateInstallationTypeForState( + const InstallationState& machine_state, + bool system_level, + InstallationType* type) { + DCHECK(type); + bool rock_on = true; + *type = NO_PRODUCTS; + + // Does the system have any multi-installed products? + const ProductState* multi_state = + machine_state.GetProductState(system_level, + BrowserDistribution::CHROME_BINARIES); + if (multi_state != NULL) + ValidateBinaries(machine_state, system_level, *multi_state, &rock_on); + + // Is Chrome installed? + const ProductState* product_state = + machine_state.GetProductState(system_level, + BrowserDistribution::CHROME_BROWSER); + if (product_state != NULL) { + ChromeRules chrome_rules; + ValidateProduct(machine_state, system_level, *product_state, + chrome_rules, &rock_on); + *type = static_cast<InstallationType>( + *type | (product_state->is_multi_install() ? + ProductBits::CHROME_MULTI : + ProductBits::CHROME_SINGLE)); + } + + // Is Chrome Frame installed? + product_state = + machine_state.GetProductState(system_level, + BrowserDistribution::CHROME_FRAME); + if (product_state != NULL) { + ChromeFrameRules chrome_frame_rules; + ValidateProduct(machine_state, system_level, *product_state, + chrome_frame_rules, &rock_on); + int cf_bit = !product_state->is_multi_install() ? + ProductBits::CHROME_FRAME_SINGLE : + (product_state->uninstall_command().HasSwitch( + switches::kChromeFrameReadyMode) ? + ProductBits::CHROME_FRAME_READY_MODE : + ProductBits::CHROME_FRAME_MULTI); + *type = static_cast<InstallationType>(*type | cf_bit); + } + + DCHECK_NE(std::find(&kInstallationTypes[0], + &kInstallationTypes[arraysize(kInstallationTypes)], + *type), + &kInstallationTypes[arraysize(kInstallationTypes)]) + << "Invalid combination of products found on system (" << *type << ")"; + + return rock_on; +} + +// static +bool InstallationValidator::ValidateInstallationType(bool system_level, + InstallationType* type) { + DCHECK(type); + InstallationState machine_state; + + machine_state.Initialize(); + + return ValidateInstallationTypeForState(machine_state, system_level, type); +} + +} // namespace installer diff --git a/chrome/installer/util/installation_validator.h b/chrome/installer/util/installation_validator.h new file mode 100644 index 0000000..101a249 --- /dev/null +++ b/chrome/installer/util/installation_validator.h @@ -0,0 +1,161 @@ +// Copyright (c) 2011 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_INSTALLATION_VALIDATOR_H_ +#define CHROME_INSTALLER_UTIL_INSTALLATION_VALIDATOR_H_ +#pragma once + +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/channel_info.h" + +class FilePath; +class Version; + +namespace installer { + +class InstallationState; +class ProductState; + +// A class that validates the state of an installation. Violations are logged +// via LOG(ERROR). +class InstallationValidator { + public: + class ProductBits { + public: + // Bits that form the components of an installation type. + enum { + CHROME_SINGLE = 0x01, + CHROME_MULTI = 0x02, + CHROME_FRAME_SINGLE = 0x04, + CHROME_FRAME_MULTI = 0x08, + CHROME_FRAME_READY_MODE = 0x10, + }; + }; // class ProductBits + + // Identifiers of all valid installation types. + enum InstallationType { + NO_PRODUCTS = 0, + CHROME_SINGLE = + ProductBits::CHROME_SINGLE, + CHROME_MULTI = + ProductBits::CHROME_MULTI, + CHROME_FRAME_SINGLE = + ProductBits::CHROME_FRAME_SINGLE, + CHROME_FRAME_SINGLE_CHROME_SINGLE = + ProductBits::CHROME_FRAME_SINGLE | ProductBits::CHROME_SINGLE, + CHROME_FRAME_SINGLE_CHROME_MULTI = + ProductBits::CHROME_FRAME_SINGLE | ProductBits::CHROME_MULTI, + CHROME_FRAME_MULTI = + ProductBits::CHROME_FRAME_MULTI, + CHROME_FRAME_MULTI_CHROME_MULTI = + ProductBits::CHROME_FRAME_MULTI | ProductBits::CHROME_MULTI, + CHROME_FRAME_READY_MODE_CHROME_MULTI = + ProductBits::CHROME_FRAME_READY_MODE | ProductBits::CHROME_MULTI, + }; + + // Validates |machine_state| at user or system level, returning true if valid. + // |type| is populated in either case, although it is a best-guess when the + // method returns false. + static bool ValidateInstallationTypeForState( + const InstallationState& machine_state, + bool system_level, + InstallationType* type); + + // Validates the machine's current installation at user or system level, + // returning true and populating |type| if valid. + static bool ValidateInstallationType(bool system_level, + InstallationType* type); + + protected: + typedef std::vector<std::pair<std::string, bool> > SwitchExpectations; + + // An interface to product-specific validation rules. + class ProductRules { + public: + virtual ~ProductRules() { } + virtual BrowserDistribution::Type distribution_type() const = 0; + virtual void AddUninstallSwitchExpectations( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + SwitchExpectations* expectations) const = 0; + }; + + // Validation rules for the Chrome browser. + class ChromeRules : public ProductRules { + public: + virtual BrowserDistribution::Type distribution_type() const OVERRIDE; + virtual void AddUninstallSwitchExpectations( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + SwitchExpectations* expectations) const OVERRIDE; + }; + + // Validation rules for Chrome Frame. + class ChromeFrameRules : public ProductRules { + public: + virtual BrowserDistribution::Type distribution_type() const OVERRIDE; + virtual void AddUninstallSwitchExpectations( + const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + SwitchExpectations* expectations) const OVERRIDE; + }; + + struct ProductContext { + const InstallationState& machine_state; + bool system_install; + BrowserDistribution* dist; + const ProductState& state; + const ProductRules& rules; + }; + + static void ValidateBinaries(const InstallationState& machine_state, + bool system_install, + const ProductState& binaries_state, + bool* is_valid); + static void ValidateSetupPath(const ProductContext& ctx, + const FilePath& setup_exe, + const char* purpose, + bool* is_valid); + static void ValidateCommandExpectations(const ProductContext& ctx, + const CommandLine& command, + const SwitchExpectations& expected, + const char* source, + bool* is_valid); + static void ValidateUninstallCommand(const ProductContext& ctx, + const CommandLine& command, + const char* source, + bool* is_valid); + static void ValidateRenameCommand(const ProductContext& ctx, + bool* is_valid); + static void ValidateOldVersionValues(const ProductContext& ctx, + bool* is_valid); + static void ValidateMultiInstallProduct(const ProductContext& ctx, + bool* is_valid); + static void ValidateProduct(const InstallationState& machine_state, + bool system_install, + const ProductState& product_state, + const ProductRules& rules, + bool* is_valid); + + // A collection of all valid installation types. + static const InstallationType kInstallationTypes[]; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(InstallationValidator); +}; + +} // namespace installer + +#endif // CHROME_INSTALLER_UTIL_INSTALLATION_VALIDATOR_H_ diff --git a/chrome/installer/util/installation_validator_unittest.cc b/chrome/installer/util/installation_validator_unittest.cc new file mode 100644 index 0000000..2bd5459 --- /dev/null +++ b/chrome/installer/util/installation_validator_unittest.cc @@ -0,0 +1,315 @@ +// Copyright (c) 2011 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/command_line.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/ref_counted.h" +#include "base/version.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/installer/util/channel_info.h" +#include "chrome/installer/util/helper.h" +#include "chrome/installer/util/installation_state.h" +#include "chrome/installer/util/installation_validator.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using installer::ChannelInfo; +using installer::InstallationValidator; +using installer::InstallationState; +using installer::ProductState; +using testing::_; +using testing::StrictMock; + +namespace { + +enum Channel { + STABLE_CHANNEL, + BETA_CHANNEL, + DEV_CHANNEL +}; + +enum PackageType { + SINGLE_INSTALL, + MULTI_INSTALL +}; + +enum Level { + USER_LEVEL, + SYSTEM_LEVEL +}; + +enum Vehicle { + GOOGLE_UPDATE, + MSI +}; + +enum ChannelModifier { + CM_MULTI = 0x01, + CM_CHROME = 0x02, + CM_CHROME_FRAME = 0x04, + CM_READY_MODE = 0x08, + CM_FULL = 0x10 +}; + +const wchar_t* const kChromeChannels[] = { + L"", + L"1.1-beta", + L"2.0-dev" +}; + +const wchar_t* const kChromeFrameChannels[] = { + L"", + L"beta", + L"dev" +}; + +class FakeProductState : public ProductState { + public: + void Clear(); + void SetChannel(const wchar_t* base, int channel_modifiers); + void SetVersion(const char* version); + void SetUninstallCommand(BrowserDistribution::Type dist_type, + Level install_level, + const char* version, + int channel_modifiers, + Vehicle vehicle); + void set_multi_install(bool is_multi_install) { + multi_install_ = is_multi_install; + } + + protected: + struct ChannelMethodForModifier { + ChannelModifier modifier; + bool (ChannelInfo::*method)(bool value); + }; + + static const ChannelMethodForModifier kChannelMethods[]; +}; + +class FakeInstallationState : public InstallationState { + public: + void SetProductState(BrowserDistribution::Type type, + Level install_level, + const ProductState& product) { + GetProducts(install_level)[IndexFromDistType(type)].CopyFrom(product); + } + + protected: + ProductState* GetProducts(Level install_level) { + return install_level == USER_LEVEL ? user_products_ : system_products_; + } +}; + +// static +const FakeProductState::ChannelMethodForModifier + FakeProductState::kChannelMethods[] = { + { CM_MULTI, &ChannelInfo::SetMultiInstall }, + { CM_CHROME, &ChannelInfo::SetChrome }, + { CM_CHROME_FRAME, &ChannelInfo::SetChromeFrame }, + { CM_READY_MODE, &ChannelInfo::SetReadyMode }, + { CM_FULL, &ChannelInfo::SetFullSuffix } +}; + +void FakeProductState::Clear() { + channel_.set_value(std::wstring()); + version_.reset(); + old_version_.reset(); + rename_cmd_.clear(); + uninstall_command_ = CommandLine(CommandLine::NO_PROGRAM); + msi_ = false; + multi_install_ = false; +} + +// Sets the channel_ member of this instance according to a base channel value +// and a set of modifiers. +void FakeProductState::SetChannel(const wchar_t* base, int channel_modifiers) { + channel_.set_value(base); + for (size_t i = 0; i < arraysize(kChannelMethods); ++i) { + if ((channel_modifiers & kChannelMethods[i].modifier) != 0) + (channel_.*kChannelMethods[i].method)(true); + } +} + +void FakeProductState::SetVersion(const char* version) { + version_.reset( + version == NULL ? NULL : Version::GetVersionFromString(version)); +} + +// Sets the uninstall command for this object. +void FakeProductState::SetUninstallCommand(BrowserDistribution::Type dist_type, + Level install_level, + const char* version, + int channel_modifiers, + Vehicle vehicle) { + DCHECK(version); + + const bool is_multi_install = (channel_modifiers & CM_MULTI) != 0; + FilePath setup_path = installer::GetChromeInstallPath( + install_level == SYSTEM_LEVEL, + BrowserDistribution::GetSpecificDistribution(is_multi_install ? + BrowserDistribution::CHROME_BINARIES : dist_type)); + setup_path = setup_path + .AppendASCII(version) + .Append(installer::kInstallerDir) + .Append(installer::kSetupExe); + uninstall_command_ = CommandLine(setup_path); + uninstall_command_.AppendSwitch(installer::switches::kUninstall); + if (install_level == SYSTEM_LEVEL) + uninstall_command_.AppendSwitch(installer::switches::kSystemLevel); + if (is_multi_install) { + uninstall_command_.AppendSwitch(installer::switches::kMultiInstall); + if (dist_type == BrowserDistribution::CHROME_BROWSER) { + uninstall_command_.AppendSwitch(installer::switches::kChrome); + if ((channel_modifiers & CM_READY_MODE) != 0) { + uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); + uninstall_command_.AppendSwitch( + installer::switches::kChromeFrameReadyMode); + } + } + } else if (dist_type == BrowserDistribution::CHROME_FRAME) { + uninstall_command_.AppendSwitch(installer::switches::kChromeFrame); + } + if (vehicle == MSI) + uninstall_command_.AppendSwitch(installer::switches::kMsi); +} + +// Populates |chrome_state| with the state of a valid Chrome browser +// installation. |channel_modifiers|, a field of bits defined by enum +// ChannelModifier, dictate properties of the installation (multi-install, +// ready-mode, etc). +void MakeChromeState(Level install_level, + Channel channel, + int channel_modifiers, + Vehicle vehicle, + FakeProductState* chrome_state) { + chrome_state->Clear(); + chrome_state->SetChannel(kChromeChannels[channel], channel_modifiers); + chrome_state->SetVersion(chrome::kChromeVersion); + chrome_state->SetUninstallCommand(BrowserDistribution::CHROME_BROWSER, + install_level, chrome::kChromeVersion, + channel_modifiers, vehicle); + chrome_state->set_multi_install((channel_modifiers & CM_MULTI) != 0); +} + +} // namespace + +// Fixture for testing the InstallationValidator. Errors logged by the +// validator are sent to an optional mock recipient (see +// set_validation_error_recipient) upon which expectations can be placed. +class InstallationValidatorTest : public testing::Test { + protected: + class ValidationErrorRecipient { + public: + virtual ~ValidationErrorRecipient() { } + virtual void ReceiveValidationError(const char* file, + int line, + const char* message) = 0; + }; + class MockValidationErrorRecipient : public ValidationErrorRecipient { + public: + MOCK_METHOD3(ReceiveValidationError, void(const char* file, + int line, + const char* message)); + }; + + static void SetUpTestCase(); + static void TearDownTestCase(); + static bool HandleLogMessage(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str); + static void set_validation_error_recipient( + ValidationErrorRecipient* recipient); + + virtual void TearDown(); + + static logging::LogMessageHandlerFunction old_log_message_handler_; + static ValidationErrorRecipient* validation_error_recipient_; +}; + +// static +logging::LogMessageHandlerFunction + InstallationValidatorTest::old_log_message_handler_ = NULL; + +// static +InstallationValidatorTest::ValidationErrorRecipient* + InstallationValidatorTest::validation_error_recipient_ = NULL; + +// static +void InstallationValidatorTest::SetUpTestCase() { + old_log_message_handler_ = logging::GetLogMessageHandler(); + logging::SetLogMessageHandler(&HandleLogMessage); +} + +// static +void InstallationValidatorTest::TearDownTestCase() { + logging::SetLogMessageHandler(old_log_message_handler_); + old_log_message_handler_ = NULL; +} + +// static +bool InstallationValidatorTest::HandleLogMessage(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str) { + // All validation failures result in LOG(ERROR) + if (severity == logging::LOG_ERROR) { + if (validation_error_recipient_ != NULL) { + validation_error_recipient_->ReceiveValidationError( + file, line, str.c_str() + message_start); + } else { + // Fail the test if an error wasn't handled. + ADD_FAILURE_AT(file, line) << (str.c_str() + message_start); + } + return true; + } + + if (old_log_message_handler_ != NULL) + return (old_log_message_handler_)(severity, file, line, message_start, str); + + return false; +} + +// static +void InstallationValidatorTest::set_validation_error_recipient( + ValidationErrorRecipient* recipient) { + validation_error_recipient_ = recipient; +} + +void InstallationValidatorTest::TearDown() { + validation_error_recipient_ = NULL; +} + +// Test that NO_PRODUCTS is returned. +TEST_F(InstallationValidatorTest, NoProducts) { + InstallationState empty_state; + InstallationValidator::InstallationType type = + static_cast<InstallationValidator::InstallationType>(-1); + StrictMock<MockValidationErrorRecipient> recipient; + set_validation_error_recipient(&recipient); + + EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState( + empty_state, true, &type)); + EXPECT_EQ(InstallationValidator::NO_PRODUCTS, type); +} + +// Test valid single Chrome. +TEST_F(InstallationValidatorTest, ChromeVersion) { + FakeProductState chrome_state; + FakeInstallationState machine_state; + InstallationValidator::InstallationType type; + StrictMock<MockValidationErrorRecipient> recipient; + set_validation_error_recipient(&recipient); + + MakeChromeState(SYSTEM_LEVEL, STABLE_CHANNEL, 0, GOOGLE_UPDATE, + &chrome_state); + machine_state.SetProductState(BrowserDistribution::CHROME_BROWSER, + SYSTEM_LEVEL, chrome_state); + EXPECT_TRUE(InstallationValidator::ValidateInstallationTypeForState( + machine_state, true, &type)); + EXPECT_EQ(InstallationValidator::CHROME_SINGLE, type); +} |