Home | History | Annotate | Download | only in util
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 //
      5 // See the corresponding header file for description of the functions in this
      6 // file.
      7 
      8 #include "chrome/installer/util/install_util.h"
      9 
     10 #include <shellapi.h>
     11 #include <shlobj.h>
     12 #include <shlwapi.h>
     13 
     14 #include <algorithm>
     15 
     16 #include "base/command_line.h"
     17 #include "base/file_util.h"
     18 #include "base/logging.h"
     19 #include "base/memory/scoped_ptr.h"
     20 #include "base/path_service.h"
     21 #include "base/process/launch.h"
     22 #include "base/strings/string_util.h"
     23 #include "base/sys_info.h"
     24 #include "base/values.h"
     25 #include "base/version.h"
     26 #include "base/win/metro.h"
     27 #include "base/win/registry.h"
     28 #include "base/win/windows_version.h"
     29 #include "chrome/installer/util/browser_distribution.h"
     30 #include "chrome/installer/util/google_update_constants.h"
     31 #include "chrome/installer/util/helper.h"
     32 #include "chrome/installer/util/installation_state.h"
     33 #include "chrome/installer/util/l10n_string_util.h"
     34 #include "chrome/installer/util/util_constants.h"
     35 #include "chrome/installer/util/work_item_list.h"
     36 
     37 using base::win::RegKey;
     38 using installer::ProductState;
     39 
     40 namespace {
     41 
     42 const wchar_t kStageBinaryPatching[] = L"binary_patching";
     43 const wchar_t kStageBuilding[] = L"building";
     44 const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch";
     45 const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs";
     46 const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts";
     47 const wchar_t kStageEnsemblePatching[] = L"ensemble_patching";
     48 const wchar_t kStageExecuting[] = L"executing";
     49 const wchar_t kStageFinishing[] = L"finishing";
     50 const wchar_t kStagePreconditions[] = L"preconditions";
     51 const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy";
     52 const wchar_t kStageRegisteringChrome[] = L"registering_chrome";
     53 const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver";
     54 const wchar_t kStageRollingback[] = L"rollingback";
     55 const wchar_t kStageUncompressing[] = L"uncompressing";
     56 const wchar_t kStageUnpacking[] = L"unpacking";
     57 const wchar_t kStageUpdatingChannels[] = L"updating_channels";
     58 const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest";
     59 const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version";
     60 const wchar_t kStageUninstallingBinaries[] = L"uninstalling_binaries";
     61 const wchar_t kStageUninstallingChromeFrame[] = L"uninstalling_chrome_frame";
     62 
     63 const wchar_t* const kStages[] = {
     64   NULL,
     65   kStagePreconditions,
     66   kStageUncompressing,
     67   kStageEnsemblePatching,
     68   kStageBinaryPatching,
     69   kStageUnpacking,
     70   kStageBuilding,
     71   kStageExecuting,
     72   kStageRollingback,
     73   kStageRefreshingPolicy,
     74   kStageUpdatingChannels,
     75   kStageCopyingPreferencesFile,
     76   kStageCreatingShortcuts,
     77   kStageRegisteringChrome,
     78   kStageRemovingOldVersions,
     79   kStageFinishing,
     80   kStageConfiguringAutoLaunch,
     81   kStageCreatingVisualManifest,
     82   kStageDeferringToHigherVersion,
     83   kStageUninstallingBinaries,
     84   kStageUninstallingChromeFrame,
     85 };
     86 
     87 COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages),
     88                kStages_disagrees_with_Stage_comma_they_must_match_bang);
     89 
     90 // Creates a zero-sized non-decorated foreground window that doesn't appear
     91 // in the taskbar. This is used as a parent window for calls to ShellExecuteEx
     92 // in order for the UAC dialog to appear in the foreground and for focus
     93 // to be returned to this process once the UAC task is dismissed. Returns
     94 // NULL on failure, a handle to the UAC window on success.
     95 HWND CreateUACForegroundWindow() {
     96   HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW,
     97                                             L"STATIC",
     98                                             NULL,
     99                                             WS_POPUP | WS_VISIBLE,
    100                                             0, 0, 0, 0,
    101                                             NULL, NULL,
    102                                             ::GetModuleHandle(NULL),
    103                                             NULL);
    104   if (foreground_window) {
    105     HMONITOR monitor = ::MonitorFromWindow(foreground_window,
    106                                            MONITOR_DEFAULTTONEAREST);
    107     if (monitor) {
    108       MONITORINFO mi = {0};
    109       mi.cbSize = sizeof(mi);
    110       ::GetMonitorInfo(monitor, &mi);
    111       RECT screen_rect = mi.rcWork;
    112       int x_offset = (screen_rect.right - screen_rect.left) / 2;
    113       int y_offset = (screen_rect.bottom - screen_rect.top) / 2;
    114       ::MoveWindow(foreground_window,
    115                    screen_rect.left + x_offset,
    116                    screen_rect.top + y_offset,
    117                    0, 0, FALSE);
    118     } else {
    119       NOTREACHED() << "Unable to get default monitor";
    120     }
    121     ::SetForegroundWindow(foreground_window);
    122   }
    123   return foreground_window;
    124 }
    125 
    126 }  // namespace
    127 
    128 string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) {
    129   static const wchar_t kInstalledComponentsPath[] =
    130       L"Software\\Microsoft\\Active Setup\\Installed Components\\";
    131   return kInstalledComponentsPath + dist->GetActiveSetupGuid();
    132 }
    133 
    134 void InstallUtil::TriggerActiveSetupCommand() {
    135   string16 active_setup_reg(
    136       GetActiveSetupPath(BrowserDistribution::GetDistribution()));
    137   base::win::RegKey active_setup_key(
    138       HKEY_LOCAL_MACHINE, active_setup_reg.c_str(), KEY_QUERY_VALUE);
    139   string16 cmd_str;
    140   LONG read_status = active_setup_key.ReadValue(L"StubPath", &cmd_str);
    141   if (read_status != ERROR_SUCCESS) {
    142     LOG(ERROR) << active_setup_reg << ", " << read_status;
    143     // This should never fail if Chrome is registered at system-level, but if it
    144     // does there is not much else to be done.
    145     return;
    146   }
    147 
    148   CommandLine cmd(CommandLine::FromString(cmd_str));
    149   // Force creation of shortcuts as the First Run beacon might land between now
    150   // and the time setup.exe checks for it.
    151   cmd.AppendSwitch(installer::switches::kForceConfigureUserSettings);
    152 
    153   base::LaunchOptions launch_options;
    154   if (base::win::IsMetroProcess())
    155     launch_options.force_breakaway_from_job_ = true;
    156   if (!base::LaunchProcess(cmd.GetCommandLineString(), launch_options, NULL))
    157     PLOG(ERROR) << cmd.GetCommandLineString();
    158 }
    159 
    160 bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) {
    161   base::FilePath::StringType program(cmd.GetProgram().value());
    162   DCHECK(!program.empty());
    163   DCHECK_NE(program[0], L'\"');
    164 
    165   CommandLine::StringType params(cmd.GetCommandLineString());
    166   if (params[0] == '"') {
    167     DCHECK_EQ('"', params[program.length() + 1]);
    168     DCHECK_EQ(program, params.substr(1, program.length()));
    169     params = params.substr(program.length() + 2);
    170   } else {
    171     DCHECK_EQ(program, params.substr(0, program.length()));
    172     params = params.substr(program.length());
    173   }
    174 
    175   TrimWhitespace(params, TRIM_ALL, &params);
    176 
    177   HWND uac_foreground_window = CreateUACForegroundWindow();
    178 
    179   SHELLEXECUTEINFO info = {0};
    180   info.cbSize = sizeof(SHELLEXECUTEINFO);
    181   info.fMask = SEE_MASK_NOCLOSEPROCESS;
    182   info.hwnd = uac_foreground_window;
    183   info.lpVerb = L"runas";
    184   info.lpFile = program.c_str();
    185   info.lpParameters = params.c_str();
    186   info.nShow = SW_SHOW;
    187 
    188   bool success = false;
    189   if (::ShellExecuteEx(&info) == TRUE) {
    190     ::WaitForSingleObject(info.hProcess, INFINITE);
    191     DWORD ret_val = 0;
    192     if (::GetExitCodeProcess(info.hProcess, &ret_val)) {
    193       success = true;
    194       if (exit_code)
    195         *exit_code = ret_val;
    196     }
    197   }
    198 
    199   if (uac_foreground_window) {
    200     DestroyWindow(uac_foreground_window);
    201   }
    202 
    203   return success;
    204 }
    205 
    206 CommandLine InstallUtil::GetChromeUninstallCmd(
    207     bool system_install, BrowserDistribution::Type distribution_type) {
    208   ProductState state;
    209   if (state.Initialize(system_install, distribution_type)) {
    210     return state.uninstall_command();
    211   }
    212   return CommandLine(CommandLine::NO_PROGRAM);
    213 }
    214 
    215 void InstallUtil::GetChromeVersion(BrowserDistribution* dist,
    216                                    bool system_install,
    217                                    Version* version) {
    218   DCHECK(dist);
    219   RegKey key;
    220   HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    221   LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(),
    222                          KEY_QUERY_VALUE);
    223 
    224   string16 version_str;
    225   if (result == ERROR_SUCCESS)
    226     result = key.ReadValue(google_update::kRegVersionField, &version_str);
    227 
    228   *version = Version();
    229   if (result == ERROR_SUCCESS && !version_str.empty()) {
    230     VLOG(1) << "Existing " << dist->GetDisplayName() << " version found "
    231             << version_str;
    232     *version = Version(WideToASCII(version_str));
    233   } else {
    234     DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
    235     VLOG(1) << "No existing " << dist->GetDisplayName()
    236             << " install found.";
    237   }
    238 }
    239 
    240 void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist,
    241                                            bool system_install,
    242                                            Version* version) {
    243   DCHECK(dist);
    244   RegKey key;
    245   HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    246   LONG result =
    247       key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE);
    248 
    249   string16 version_str;
    250   if (result == ERROR_SUCCESS)
    251     result = key.ReadValue(google_update::kRegCriticalVersionField,
    252                            &version_str);
    253 
    254   *version = Version();
    255   if (result == ERROR_SUCCESS && !version_str.empty()) {
    256     VLOG(1) << "Critical Update version for " << dist->GetDisplayName()
    257             << " found " << version_str;
    258     *version = Version(WideToASCII(version_str));
    259   } else {
    260     DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
    261     VLOG(1) << "No existing " << dist->GetDisplayName()
    262             << " install found.";
    263   }
    264 }
    265 
    266 bool InstallUtil::IsOSSupported() {
    267   // We do not support Win2K or older, or XP without service pack 2.
    268   VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
    269           << base::SysInfo::OperatingSystemVersion();
    270   base::win::Version version = base::win::GetVersion();
    271   return (version > base::win::VERSION_XP) ||
    272       ((version == base::win::VERSION_XP) &&
    273        (base::win::OSInfo::GetInstance()->service_pack().major >= 2));
    274 }
    275 
    276 void InstallUtil::AddInstallerResultItems(bool system_install,
    277                                           const string16& state_key,
    278                                           installer::InstallStatus status,
    279                                           int string_resource_id,
    280                                           const string16* const launch_cmd,
    281                                           WorkItemList* install_list) {
    282   DCHECK(install_list);
    283   const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    284   DWORD installer_result = (GetInstallReturnCode(status) == 0) ? 0 : 1;
    285   install_list->AddCreateRegKeyWorkItem(root, state_key);
    286   install_list->AddSetRegValueWorkItem(root, state_key,
    287                                        installer::kInstallerResult,
    288                                        installer_result, true);
    289   install_list->AddSetRegValueWorkItem(root, state_key,
    290                                        installer::kInstallerError,
    291                                        static_cast<DWORD>(status), true);
    292   if (string_resource_id != 0) {
    293     string16 msg = installer::GetLocalizedString(string_resource_id);
    294     install_list->AddSetRegValueWorkItem(root, state_key,
    295         installer::kInstallerResultUIString, msg, true);
    296   }
    297   if (launch_cmd != NULL && !launch_cmd->empty()) {
    298     install_list->AddSetRegValueWorkItem(root, state_key,
    299         installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true);
    300   }
    301 }
    302 
    303 void InstallUtil::UpdateInstallerStage(bool system_install,
    304                                        const string16& state_key_path,
    305                                        installer::InstallerStage stage) {
    306   DCHECK_LE(static_cast<installer::InstallerStage>(0), stage);
    307   DCHECK_GT(installer::NUM_STAGES, stage);
    308   const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    309   RegKey state_key;
    310   LONG result = state_key.Open(root, state_key_path.c_str(),
    311                                KEY_QUERY_VALUE | KEY_SET_VALUE);
    312   if (result == ERROR_SUCCESS) {
    313     if (stage == installer::NO_STAGE) {
    314       result = state_key.DeleteValue(installer::kInstallerExtraCode1);
    315       LOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND)
    316           << "Failed deleting installer stage from " << state_key_path
    317           << "; result: " << result;
    318     } else {
    319       const DWORD extra_code_1 = static_cast<DWORD>(stage);
    320       result = state_key.WriteValue(installer::kInstallerExtraCode1,
    321                                     extra_code_1);
    322       LOG_IF(ERROR, result != ERROR_SUCCESS)
    323           << "Failed writing installer stage to " << state_key_path
    324           << "; result: " << result;
    325     }
    326     // TODO(grt): Remove code below here once we're convinced that our use of
    327     // Google Update's new InstallerExtraCode1 value is good.
    328     installer::ChannelInfo channel_info;
    329     // This will return false if the "ap" value isn't present, which is fine.
    330     channel_info.Initialize(state_key);
    331     if (channel_info.SetStage(kStages[stage]) &&
    332         !channel_info.Write(&state_key)) {
    333       LOG(ERROR) << "Failed writing installer stage to " << state_key_path;
    334     }
    335   } else {
    336     LOG(ERROR) << "Failed opening " << state_key_path
    337                << " to update installer stage; result: " << result;
    338   }
    339 }
    340 
    341 bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) {
    342   wchar_t program_files_path[MAX_PATH] = {0};
    343   if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
    344                                 SHGFP_TYPE_CURRENT, program_files_path))) {
    345     return !StartsWith(exe_path, program_files_path, false);
    346   } else {
    347     NOTREACHED();
    348   }
    349   return true;
    350 }
    351 
    352 bool InstallUtil::IsMultiInstall(BrowserDistribution* dist,
    353                                  bool system_install) {
    354   DCHECK(dist);
    355   ProductState state;
    356   return state.Initialize(system_install, dist) && state.is_multi_install();
    357 }
    358 
    359 bool CheckIsChromeSxSProcess() {
    360   CommandLine* command_line = CommandLine::ForCurrentProcess();
    361   CHECK(command_line);
    362 
    363   if (command_line->HasSwitch(installer::switches::kChromeSxS))
    364     return true;
    365 
    366   // Also return true if we are running from Chrome SxS installed path.
    367   base::FilePath exe_dir;
    368   PathService::Get(base::DIR_EXE, &exe_dir);
    369   string16 chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2);
    370   chrome_sxs_dir.append(installer::kSxSSuffix);
    371 
    372   // This is SxS if current EXE is in or under (possibly multiple levels under)
    373   // |chrome_sxs_dir|\|installer::kInstallBinaryDir|
    374   std::vector<base::FilePath::StringType> components;
    375   exe_dir.GetComponents(&components);
    376   // We need at least 1 element in the array for the behavior of the following
    377   // loop to be defined.  This should always be true, since we're splitting the
    378   // path to our executable and one of the components will be the drive letter.
    379   DCHECK(!components.empty());
    380   typedef std::vector<base::FilePath::StringType>::const_reverse_iterator
    381       ComponentsIterator;
    382   for (ComponentsIterator current = components.rbegin(), parent = current + 1;
    383        parent != components.rend(); current = parent++) {
    384     if (base::FilePath::CompareEqualIgnoreCase(
    385             *current, installer::kInstallBinaryDir) &&
    386         base::FilePath::CompareEqualIgnoreCase(*parent, chrome_sxs_dir)) {
    387       return true;
    388     }
    389   }
    390 
    391   return false;
    392 }
    393 
    394 bool InstallUtil::IsChromeSxSProcess() {
    395   static bool sxs = CheckIsChromeSxSProcess();
    396   return sxs;
    397 }
    398 
    399 bool InstallUtil::GetSentinelFilePath(const base::FilePath::CharType* file,
    400                                       BrowserDistribution* dist,
    401                                       base::FilePath* path) {
    402   base::FilePath exe_path;
    403   if (!PathService::Get(base::DIR_EXE, &exe_path))
    404     return false;
    405 
    406   if (IsPerUserInstall(exe_path.value().c_str())) {
    407     const base::FilePath maybe_product_dir(exe_path.DirName().DirName());
    408     if (base::PathExists(exe_path.Append(installer::kChromeExe))) {
    409       // DIR_EXE is most likely Chrome's directory in which case |exe_path| is
    410       // the user-level sentinel path.
    411       *path = exe_path;
    412     } else if (base::PathExists(
    413                    maybe_product_dir.Append(installer::kChromeExe))) {
    414       // DIR_EXE can also be the Installer directory if this is called from a
    415       // setup.exe running from Application\<version>\Installer (see
    416       // InstallerState::GetInstallerDirectory) in which case Chrome's directory
    417       // is two levels up.
    418       *path = maybe_product_dir;
    419     } else {
    420       NOTREACHED();
    421       return false;
    422     }
    423   } else {
    424     std::vector<base::FilePath> user_data_dir_paths;
    425     installer::GetChromeUserDataPaths(dist, &user_data_dir_paths);
    426 
    427     if (!user_data_dir_paths.empty())
    428       *path = user_data_dir_paths[0];
    429     else
    430       return false;
    431   }
    432 
    433   *path = path->Append(file);
    434   return true;
    435 }
    436 
    437 // This method tries to delete a registry key and logs an error message
    438 // in case of failure. It returns true if deletion is successful (or the key did
    439 // not exist), otherwise false.
    440 bool InstallUtil::DeleteRegistryKey(HKEY root_key,
    441                                     const string16& key_path) {
    442   VLOG(1) << "Deleting registry key " << key_path;
    443   LONG result = ::SHDeleteKey(root_key, key_path.c_str());
    444   if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
    445     LOG(ERROR) << "Failed to delete registry key: " << key_path
    446                << " error: " << result;
    447     return false;
    448   }
    449   return true;
    450 }
    451 
    452 // This method tries to delete a registry value and logs an error message
    453 // in case of failure. It returns true if deletion is successful (or the key did
    454 // not exist), otherwise false.
    455 bool InstallUtil::DeleteRegistryValue(HKEY reg_root,
    456                                       const string16& key_path,
    457                                       const string16& value_name) {
    458   RegKey key;
    459   LONG result = key.Open(reg_root, key_path.c_str(), KEY_SET_VALUE);
    460   if (result == ERROR_SUCCESS)
    461     result = key.DeleteValue(value_name.c_str());
    462   if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
    463     LOG(ERROR) << "Failed to delete registry value: " << value_name
    464                << " error: " << result;
    465     return false;
    466   }
    467   return true;
    468 }
    469 
    470 // static
    471 InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf(
    472     HKEY root_key,
    473     const string16& key_to_delete_path,
    474     const string16& key_to_test_path,
    475     const wchar_t* value_name,
    476     const RegistryValuePredicate& predicate) {
    477   DCHECK(root_key);
    478   ConditionalDeleteResult delete_result = NOT_FOUND;
    479   RegKey key;
    480   string16 actual_value;
    481   if (key.Open(root_key, key_to_test_path.c_str(),
    482                KEY_QUERY_VALUE) == ERROR_SUCCESS &&
    483       key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
    484       predicate.Evaluate(actual_value)) {
    485     key.Close();
    486     delete_result = DeleteRegistryKey(root_key, key_to_delete_path)
    487         ? DELETED : DELETE_FAILED;
    488   }
    489   return delete_result;
    490 }
    491 
    492 // static
    493 InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf(
    494     HKEY root_key,
    495     const wchar_t* key_path,
    496     const wchar_t* value_name,
    497     const RegistryValuePredicate& predicate) {
    498   DCHECK(root_key);
    499   DCHECK(key_path);
    500   ConditionalDeleteResult delete_result = NOT_FOUND;
    501   RegKey key;
    502   string16 actual_value;
    503   if (key.Open(root_key, key_path,
    504                KEY_QUERY_VALUE | KEY_SET_VALUE) == ERROR_SUCCESS &&
    505       key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
    506       predicate.Evaluate(actual_value)) {
    507     LONG result = key.DeleteValue(value_name);
    508     if (result != ERROR_SUCCESS) {
    509       LOG(ERROR) << "Failed to delete registry value: "
    510                  << (value_name ? value_name : L"(Default)")
    511                  << " error: " << result;
    512       delete_result = DELETE_FAILED;
    513     }
    514     delete_result = DELETED;
    515   }
    516   return delete_result;
    517 }
    518 
    519 bool InstallUtil::ValueEquals::Evaluate(const string16& value) const {
    520   return value == value_to_match_;
    521 }
    522 
    523 // static
    524 int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) {
    525   switch (status) {
    526     case installer::FIRST_INSTALL_SUCCESS:
    527     case installer::INSTALL_REPAIRED:
    528     case installer::NEW_VERSION_UPDATED:
    529     case installer::IN_USE_UPDATED:
    530     case installer::UNUSED_BINARIES_UNINSTALLED:
    531       return 0;
    532     default:
    533       return status;
    534   }
    535 }
    536 
    537 // static
    538 void InstallUtil::MakeUninstallCommand(const string16& program,
    539                                        const string16& arguments,
    540                                        CommandLine* command_line) {
    541   *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments);
    542 }
    543 
    544 // static
    545 string16 InstallUtil::GetCurrentDate() {
    546   static const wchar_t kDateFormat[] = L"yyyyMMdd";
    547   wchar_t date_str[arraysize(kDateFormat)] = {0};
    548   int len = GetDateFormatW(LOCALE_INVARIANT, 0, NULL, kDateFormat,
    549                            date_str, arraysize(date_str));
    550   if (len) {
    551     --len;  // Subtract terminating \0.
    552   } else {
    553     PLOG(DFATAL) << "GetDateFormat";
    554   }
    555 
    556   return string16(date_str, len);
    557 }
    558 
    559 // Open |path| with minimal access to obtain information about it, returning
    560 // true and populating |handle| on success.
    561 // static
    562 bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath& path,
    563                                               base::win::ScopedHandle* handle) {
    564   DCHECK(handle);
    565   handle->Set(base::CreatePlatformFile(path, base::PLATFORM_FILE_OPEN, NULL,
    566                                        NULL));
    567   return handle->IsValid();
    568 }
    569 
    570 // Populate |info| for |handle|, returning true on success.
    571 // static
    572 bool InstallUtil::ProgramCompare::GetInfo(const base::win::ScopedHandle& handle,
    573                                           BY_HANDLE_FILE_INFORMATION* info) {
    574   DCHECK(handle.IsValid());
    575   return GetFileInformationByHandle(
    576       const_cast<base::win::ScopedHandle&>(handle), info) != 0;
    577 }
    578 
    579 InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath& path_to_match)
    580     : path_to_match_(path_to_match),
    581       file_handle_(base::kInvalidPlatformFileValue),
    582       file_info_() {
    583   DCHECK(!path_to_match_.empty());
    584   if (!OpenForInfo(path_to_match_, &file_handle_)) {
    585     PLOG(WARNING) << "Failed opening " << path_to_match_.value()
    586                   << "; falling back to path string comparisons.";
    587   } else if (!GetInfo(file_handle_, &file_info_)) {
    588     PLOG(WARNING) << "Failed getting information for "
    589                   << path_to_match_.value()
    590                   << "; falling back to path string comparisons.";
    591     file_handle_.Close();
    592   }
    593 }
    594 
    595 InstallUtil::ProgramCompare::~ProgramCompare() {
    596 }
    597 
    598 bool InstallUtil::ProgramCompare::Evaluate(const string16& value) const {
    599   // Suss out the exe portion of the value, which is expected to be a command
    600   // line kinda (or exactly) like:
    601   // "c:\foo\bar\chrome.exe" -- "%1"
    602   base::FilePath program(CommandLine::FromString(value).GetProgram());
    603   if (program.empty()) {
    604     LOG(WARNING) << "Failed to parse an executable name from command line: \""
    605                  << value << "\"";
    606     return false;
    607   }
    608 
    609   return EvaluatePath(program);
    610 }
    611 
    612 bool InstallUtil::ProgramCompare::EvaluatePath(
    613     const base::FilePath& path) const {
    614   // Try the simple thing first: do the paths happen to match?
    615   if (base::FilePath::CompareEqualIgnoreCase(path_to_match_.value(),
    616                                              path.value()))
    617     return true;
    618 
    619   // If the paths don't match and we couldn't open the expected file, we've done
    620   // our best.
    621   if (!file_handle_.IsValid())
    622     return false;
    623 
    624   // Open the program and see if it references the expected file.
    625   base::win::ScopedHandle handle;
    626   BY_HANDLE_FILE_INFORMATION info = {};
    627 
    628   return (OpenForInfo(path, &handle) &&
    629           GetInfo(handle, &info) &&
    630           info.dwVolumeSerialNumber == file_info_.dwVolumeSerialNumber &&
    631           info.nFileIndexHigh == file_info_.nFileIndexHigh &&
    632           info.nFileIndexLow == file_info_.nFileIndexLow);
    633 }
    634