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