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