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