root/chrome/installer/util/install_util.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CreateUACForegroundWindow
  2. GetActiveSetupPath
  3. TriggerActiveSetupCommand
  4. ExecuteExeAsAdmin
  5. GetChromeUninstallCmd
  6. GetChromeVersion
  7. GetCriticalUpdateVersion
  8. IsOSSupported
  9. AddInstallerResultItems
  10. UpdateInstallerStage
  11. IsPerUserInstall
  12. IsMultiInstall
  13. CheckIsChromeSxSProcess
  14. IsChromeSxSProcess
  15. IsFirstRunSentinelPresent
  16. GetSentinelFilePath
  17. DeleteRegistryKey
  18. DeleteRegistryValue
  19. DeleteRegistryKeyIf
  20. DeleteRegistryValueIf
  21. Evaluate
  22. GetInstallReturnCode
  23. MakeUninstallCommand
  24. GetCurrentDate
  25. OpenForInfo
  26. GetInfo
  27. file_info_
  28. Evaluate
  29. EvaluatePath

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// See the corresponding header file for description of the functions in this
// file.

#include "chrome/installer/util/install_util.h"

#include <shellapi.h>
#include <shlobj.h>
#include <shlwapi.h>

#include <algorithm>

#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/platform_file.h"
#include "base/process/launch.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/metro.h"
#include "base/win/registry.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/l10n_string_util.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item_list.h"

using base::win::RegKey;
using installer::ProductState;

namespace {

const wchar_t kStageBinaryPatching[] = L"binary_patching";
const wchar_t kStageBuilding[] = L"building";
const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch";
const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs";
const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts";
const wchar_t kStageEnsemblePatching[] = L"ensemble_patching";
const wchar_t kStageExecuting[] = L"executing";
const wchar_t kStageFinishing[] = L"finishing";
const wchar_t kStagePreconditions[] = L"preconditions";
const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy";
const wchar_t kStageRegisteringChrome[] = L"registering_chrome";
const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver";
const wchar_t kStageRollingback[] = L"rollingback";
const wchar_t kStageUncompressing[] = L"uncompressing";
const wchar_t kStageUnpacking[] = L"unpacking";
const wchar_t kStageUpdatingChannels[] = L"updating_channels";
const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest";
const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version";
const wchar_t kStageUninstallingBinaries[] = L"uninstalling_binaries";
const wchar_t kStageUninstallingChromeFrame[] = L"uninstalling_chrome_frame";

const wchar_t* const kStages[] = {
  NULL,
  kStagePreconditions,
  kStageUncompressing,
  kStageEnsemblePatching,
  kStageBinaryPatching,
  kStageUnpacking,
  kStageBuilding,
  kStageExecuting,
  kStageRollingback,
  kStageRefreshingPolicy,
  kStageUpdatingChannels,
  kStageCopyingPreferencesFile,
  kStageCreatingShortcuts,
  kStageRegisteringChrome,
  kStageRemovingOldVersions,
  kStageFinishing,
  kStageConfiguringAutoLaunch,
  kStageCreatingVisualManifest,
  kStageDeferringToHigherVersion,
  kStageUninstallingBinaries,
  kStageUninstallingChromeFrame,
};

COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages),
               kStages_disagrees_with_Stage_comma_they_must_match_bang);

// Creates a zero-sized non-decorated foreground window that doesn't appear
// in the taskbar. This is used as a parent window for calls to ShellExecuteEx
// in order for the UAC dialog to appear in the foreground and for focus
// to be returned to this process once the UAC task is dismissed. Returns
// NULL on failure, a handle to the UAC window on success.
HWND CreateUACForegroundWindow() {
  HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW,
                                            L"STATIC",
                                            NULL,
                                            WS_POPUP | WS_VISIBLE,
                                            0, 0, 0, 0,
                                            NULL, NULL,
                                            ::GetModuleHandle(NULL),
                                            NULL);
  if (foreground_window) {
    HMONITOR monitor = ::MonitorFromWindow(foreground_window,
                                           MONITOR_DEFAULTTONEAREST);
    if (monitor) {
      MONITORINFO mi = {0};
      mi.cbSize = sizeof(mi);
      ::GetMonitorInfo(monitor, &mi);
      RECT screen_rect = mi.rcWork;
      int x_offset = (screen_rect.right - screen_rect.left) / 2;
      int y_offset = (screen_rect.bottom - screen_rect.top) / 2;
      ::MoveWindow(foreground_window,
                   screen_rect.left + x_offset,
                   screen_rect.top + y_offset,
                   0, 0, FALSE);
    } else {
      NOTREACHED() << "Unable to get default monitor";
    }
    ::SetForegroundWindow(foreground_window);
  }
  return foreground_window;
}

}  // namespace

base::string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) {
  static const wchar_t kInstalledComponentsPath[] =
      L"Software\\Microsoft\\Active Setup\\Installed Components\\";
  return kInstalledComponentsPath + dist->GetActiveSetupGuid();
}

void InstallUtil::TriggerActiveSetupCommand() {
  base::string16 active_setup_reg(
      GetActiveSetupPath(BrowserDistribution::GetDistribution()));
  base::win::RegKey active_setup_key(
      HKEY_LOCAL_MACHINE, active_setup_reg.c_str(), KEY_QUERY_VALUE);
  base::string16 cmd_str;
  LONG read_status = active_setup_key.ReadValue(L"StubPath", &cmd_str);
  if (read_status != ERROR_SUCCESS) {
    LOG(ERROR) << active_setup_reg << ", " << read_status;
    // This should never fail if Chrome is registered at system-level, but if it
    // does there is not much else to be done.
    return;
  }

  CommandLine cmd(CommandLine::FromString(cmd_str));
  // Force creation of shortcuts as the First Run beacon might land between now
  // and the time setup.exe checks for it.
  cmd.AppendSwitch(installer::switches::kForceConfigureUserSettings);

  base::LaunchOptions launch_options;
  if (base::win::IsMetroProcess())
    launch_options.force_breakaway_from_job_ = true;
  if (!base::LaunchProcess(cmd.GetCommandLineString(), launch_options, NULL))
    PLOG(ERROR) << cmd.GetCommandLineString();
}

bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) {
  base::FilePath::StringType program(cmd.GetProgram().value());
  DCHECK(!program.empty());
  DCHECK_NE(program[0], L'\"');

  CommandLine::StringType params(cmd.GetCommandLineString());
  if (params[0] == '"') {
    DCHECK_EQ('"', params[program.length() + 1]);
    DCHECK_EQ(program, params.substr(1, program.length()));
    params = params.substr(program.length() + 2);
  } else {
    DCHECK_EQ(program, params.substr(0, program.length()));
    params = params.substr(program.length());
  }

  base::TrimWhitespace(params, base::TRIM_ALL, &params);

  HWND uac_foreground_window = CreateUACForegroundWindow();

  SHELLEXECUTEINFO info = {0};
  info.cbSize = sizeof(SHELLEXECUTEINFO);
  info.fMask = SEE_MASK_NOCLOSEPROCESS;
  info.hwnd = uac_foreground_window;
  info.lpVerb = L"runas";
  info.lpFile = program.c_str();
  info.lpParameters = params.c_str();
  info.nShow = SW_SHOW;

  bool success = false;
  if (::ShellExecuteEx(&info) == TRUE) {
    ::WaitForSingleObject(info.hProcess, INFINITE);
    DWORD ret_val = 0;
    if (::GetExitCodeProcess(info.hProcess, &ret_val)) {
      success = true;
      if (exit_code)
        *exit_code = ret_val;
    }
  }

  if (uac_foreground_window) {
    DestroyWindow(uac_foreground_window);
  }

  return success;
}

CommandLine InstallUtil::GetChromeUninstallCmd(
    bool system_install, BrowserDistribution::Type distribution_type) {
  ProductState state;
  if (state.Initialize(system_install, distribution_type)) {
    return state.uninstall_command();
  }
  return CommandLine(CommandLine::NO_PROGRAM);
}

void InstallUtil::GetChromeVersion(BrowserDistribution* dist,
                                   bool system_install,
                                   Version* version) {
  DCHECK(dist);
  RegKey key;
  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(),
                         KEY_QUERY_VALUE);

  base::string16 version_str;
  if (result == ERROR_SUCCESS)
    result = key.ReadValue(google_update::kRegVersionField, &version_str);

  *version = Version();
  if (result == ERROR_SUCCESS && !version_str.empty()) {
    VLOG(1) << "Existing " << dist->GetDisplayName() << " version found "
            << version_str;
    *version = Version(base::UTF16ToASCII(version_str));
  } else {
    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
    VLOG(1) << "No existing " << dist->GetDisplayName()
            << " install found.";
  }
}

void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist,
                                           bool system_install,
                                           Version* version) {
  DCHECK(dist);
  RegKey key;
  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  LONG result =
      key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE);

  base::string16 version_str;
  if (result == ERROR_SUCCESS)
    result = key.ReadValue(google_update::kRegCriticalVersionField,
                           &version_str);

  *version = Version();
  if (result == ERROR_SUCCESS && !version_str.empty()) {
    VLOG(1) << "Critical Update version for " << dist->GetDisplayName()
            << " found " << version_str;
    *version = Version(base::UTF16ToASCII(version_str));
  } else {
    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
    VLOG(1) << "No existing " << dist->GetDisplayName()
            << " install found.";
  }
}

bool InstallUtil::IsOSSupported() {
  // We do not support Win2K or older, or XP without service pack 2.
  VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
          << base::SysInfo::OperatingSystemVersion();
  base::win::Version version = base::win::GetVersion();
  return (version > base::win::VERSION_XP) ||
      ((version == base::win::VERSION_XP) &&
       (base::win::OSInfo::GetInstance()->service_pack().major >= 2));
}

void InstallUtil::AddInstallerResultItems(
    bool system_install,
    const base::string16& state_key,
    installer::InstallStatus status,
    int string_resource_id,
    const base::string16* const launch_cmd,
    WorkItemList* install_list) {
  DCHECK(install_list);
  const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  DWORD installer_result = (GetInstallReturnCode(status) == 0) ? 0 : 1;
  install_list->AddCreateRegKeyWorkItem(root, state_key);
  install_list->AddSetRegValueWorkItem(root, state_key,
                                       installer::kInstallerResult,
                                       installer_result, true);
  install_list->AddSetRegValueWorkItem(root, state_key,
                                       installer::kInstallerError,
                                       static_cast<DWORD>(status), true);
  if (string_resource_id != 0) {
    base::string16 msg = installer::GetLocalizedString(string_resource_id);
    install_list->AddSetRegValueWorkItem(root, state_key,
        installer::kInstallerResultUIString, msg, true);
  }
  if (launch_cmd != NULL && !launch_cmd->empty()) {
    install_list->AddSetRegValueWorkItem(root, state_key,
        installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true);
  }
}

void InstallUtil::UpdateInstallerStage(bool system_install,
                                       const base::string16& state_key_path,
                                       installer::InstallerStage stage) {
  DCHECK_LE(static_cast<installer::InstallerStage>(0), stage);
  DCHECK_GT(installer::NUM_STAGES, stage);
  const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  RegKey state_key;
  LONG result = state_key.Open(root, state_key_path.c_str(),
                               KEY_QUERY_VALUE | KEY_SET_VALUE);
  if (result == ERROR_SUCCESS) {
    if (stage == installer::NO_STAGE) {
      result = state_key.DeleteValue(installer::kInstallerExtraCode1);
      LOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND)
          << "Failed deleting installer stage from " << state_key_path
          << "; result: " << result;
    } else {
      const DWORD extra_code_1 = static_cast<DWORD>(stage);
      result = state_key.WriteValue(installer::kInstallerExtraCode1,
                                    extra_code_1);
      LOG_IF(ERROR, result != ERROR_SUCCESS)
          << "Failed writing installer stage to " << state_key_path
          << "; result: " << result;
    }
    // TODO(grt): Remove code below here once we're convinced that our use of
    // Google Update's new InstallerExtraCode1 value is good.
    installer::ChannelInfo channel_info;
    // This will return false if the "ap" value isn't present, which is fine.
    channel_info.Initialize(state_key);
    if (channel_info.SetStage(kStages[stage]) &&
        !channel_info.Write(&state_key)) {
      LOG(ERROR) << "Failed writing installer stage to " << state_key_path;
    }
  } else {
    LOG(ERROR) << "Failed opening " << state_key_path
               << " to update installer stage; result: " << result;
  }
}

bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) {
  wchar_t program_files_path[MAX_PATH] = {0};
  if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
                                SHGFP_TYPE_CURRENT, program_files_path))) {
    return !StartsWith(exe_path, program_files_path, false);
  } else {
    NOTREACHED();
  }
  return true;
}

bool InstallUtil::IsMultiInstall(BrowserDistribution* dist,
                                 bool system_install) {
  DCHECK(dist);
  ProductState state;
  return state.Initialize(system_install, dist) && state.is_multi_install();
}

bool CheckIsChromeSxSProcess() {
  CommandLine* command_line = CommandLine::ForCurrentProcess();
  CHECK(command_line);

  if (command_line->HasSwitch(installer::switches::kChromeSxS))
    return true;

  // Also return true if we are running from Chrome SxS installed path.
  base::FilePath exe_dir;
  PathService::Get(base::DIR_EXE, &exe_dir);
  base::string16 chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2);
  chrome_sxs_dir.append(installer::kSxSSuffix);

  // This is SxS if current EXE is in or under (possibly multiple levels under)
  // |chrome_sxs_dir|\|installer::kInstallBinaryDir|
  std::vector<base::FilePath::StringType> components;
  exe_dir.GetComponents(&components);
  // We need at least 1 element in the array for the behavior of the following
  // loop to be defined.  This should always be true, since we're splitting the
  // path to our executable and one of the components will be the drive letter.
  DCHECK(!components.empty());
  typedef std::vector<base::FilePath::StringType>::const_reverse_iterator
      ComponentsIterator;
  for (ComponentsIterator current = components.rbegin(), parent = current + 1;
       parent != components.rend(); current = parent++) {
    if (base::FilePath::CompareEqualIgnoreCase(
            *current, installer::kInstallBinaryDir) &&
        base::FilePath::CompareEqualIgnoreCase(*parent, chrome_sxs_dir)) {
      return true;
    }
  }

  return false;
}

bool InstallUtil::IsChromeSxSProcess() {
  static bool sxs = CheckIsChromeSxSProcess();
  return sxs;
}

// static
bool InstallUtil::IsFirstRunSentinelPresent() {
  // TODO(msw): Consolidate with first_run::internal::IsFirstRunSentinelPresent.
  base::FilePath user_data_dir;
  return !PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) ||
         base::PathExists(user_data_dir.Append(chrome::kFirstRunSentinel));
}

// static
bool InstallUtil::GetSentinelFilePath(const base::FilePath::CharType* file,
                                      BrowserDistribution* dist,
                                      base::FilePath* path) {
  // TODO(msw): Use PathService to obtain the correct DIR_USER_DATA.
  base::FilePath user_data_dir(installer::GetChromeUserDataPath(dist));

  if (user_data_dir.empty())
    return false;
  *path = user_data_dir.Append(file);
  return true;
}

// This method tries to delete a registry key and logs an error message
// in case of failure. It returns true if deletion is successful (or the key did
// not exist), otherwise false.
bool InstallUtil::DeleteRegistryKey(HKEY root_key,
                                    const base::string16& key_path) {
  VLOG(1) << "Deleting registry key " << key_path;
  LONG result = ::SHDeleteKey(root_key, key_path.c_str());
  if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
    LOG(ERROR) << "Failed to delete registry key: " << key_path
               << " error: " << result;
    return false;
  }
  return true;
}

// This method tries to delete a registry value and logs an error message
// in case of failure. It returns true if deletion is successful (or the key did
// not exist), otherwise false.
bool InstallUtil::DeleteRegistryValue(HKEY reg_root,
                                      const base::string16& key_path,
                                      const base::string16& value_name) {
  RegKey key;
  LONG result = key.Open(reg_root, key_path.c_str(), KEY_SET_VALUE);
  if (result == ERROR_SUCCESS)
    result = key.DeleteValue(value_name.c_str());
  if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
    LOG(ERROR) << "Failed to delete registry value: " << value_name
               << " error: " << result;
    return false;
  }
  return true;
}

// static
InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf(
    HKEY root_key,
    const base::string16& key_to_delete_path,
    const base::string16& key_to_test_path,
    const wchar_t* value_name,
    const RegistryValuePredicate& predicate) {
  DCHECK(root_key);
  ConditionalDeleteResult delete_result = NOT_FOUND;
  RegKey key;
  base::string16 actual_value;
  if (key.Open(root_key, key_to_test_path.c_str(),
               KEY_QUERY_VALUE) == ERROR_SUCCESS &&
      key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
      predicate.Evaluate(actual_value)) {
    key.Close();
    delete_result = DeleteRegistryKey(root_key, key_to_delete_path)
        ? DELETED : DELETE_FAILED;
  }
  return delete_result;
}

// static
InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf(
    HKEY root_key,
    const wchar_t* key_path,
    const wchar_t* value_name,
    const RegistryValuePredicate& predicate) {
  DCHECK(root_key);
  DCHECK(key_path);
  ConditionalDeleteResult delete_result = NOT_FOUND;
  RegKey key;
  base::string16 actual_value;
  if (key.Open(root_key, key_path,
               KEY_QUERY_VALUE | KEY_SET_VALUE) == ERROR_SUCCESS &&
      key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
      predicate.Evaluate(actual_value)) {
    LONG result = key.DeleteValue(value_name);
    if (result != ERROR_SUCCESS) {
      LOG(ERROR) << "Failed to delete registry value: "
                 << (value_name ? value_name : L"(Default)")
                 << " error: " << result;
      delete_result = DELETE_FAILED;
    }
    delete_result = DELETED;
  }
  return delete_result;
}

bool InstallUtil::ValueEquals::Evaluate(const base::string16& value) const {
  return value == value_to_match_;
}

// static
int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) {
  switch (status) {
    case installer::FIRST_INSTALL_SUCCESS:
    case installer::INSTALL_REPAIRED:
    case installer::NEW_VERSION_UPDATED:
    case installer::IN_USE_UPDATED:
    case installer::UNUSED_BINARIES_UNINSTALLED:
      return 0;
    default:
      return status;
  }
}

// static
void InstallUtil::MakeUninstallCommand(const base::string16& program,
                                       const base::string16& arguments,
                                       CommandLine* command_line) {
  *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments);
}

// static
base::string16 InstallUtil::GetCurrentDate() {
  static const wchar_t kDateFormat[] = L"yyyyMMdd";
  wchar_t date_str[arraysize(kDateFormat)] = {0};
  int len = GetDateFormatW(LOCALE_INVARIANT, 0, NULL, kDateFormat,
                           date_str, arraysize(date_str));
  if (len) {
    --len;  // Subtract terminating \0.
  } else {
    PLOG(DFATAL) << "GetDateFormat";
  }

  return base::string16(date_str, len);
}

// Open |path| with minimal access to obtain information about it, returning
// true and populating |file| on success.
// static
bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath& path,
                                              base::File* file) {
  DCHECK(file);
  file->Initialize(path, base::File::FLAG_OPEN);
  return file->IsValid();
}

// Populate |info| for |file|, returning true on success.
// static
bool InstallUtil::ProgramCompare::GetInfo(const base::File& file,
                                          BY_HANDLE_FILE_INFORMATION* info) {
  DCHECK(file.IsValid());
  return GetFileInformationByHandle(file.GetPlatformFile(), info) != 0;
}

InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath& path_to_match)
    : path_to_match_(path_to_match),
      file_info_() {
  DCHECK(!path_to_match_.empty());
  if (!OpenForInfo(path_to_match_, &file_)) {
    PLOG(WARNING) << "Failed opening " << path_to_match_.value()
                  << "; falling back to path string comparisons.";
  } else if (!GetInfo(file_, &file_info_)) {
    PLOG(WARNING) << "Failed getting information for "
                  << path_to_match_.value()
                  << "; falling back to path string comparisons.";
    file_.Close();
  }
}

InstallUtil::ProgramCompare::~ProgramCompare() {
}

bool InstallUtil::ProgramCompare::Evaluate(const base::string16& value) const {
  // Suss out the exe portion of the value, which is expected to be a command
  // line kinda (or exactly) like:
  // "c:\foo\bar\chrome.exe" -- "%1"
  base::FilePath program(CommandLine::FromString(value).GetProgram());
  if (program.empty()) {
    LOG(WARNING) << "Failed to parse an executable name from command line: \""
                 << value << "\"";
    return false;
  }

  return EvaluatePath(program);
}

bool InstallUtil::ProgramCompare::EvaluatePath(
    const base::FilePath& path) const {
  // Try the simple thing first: do the paths happen to match?
  if (base::FilePath::CompareEqualIgnoreCase(path_to_match_.value(),
                                             path.value()))
    return true;

  // If the paths don't match and we couldn't open the expected file, we've done
  // our best.
  if (!file_.IsValid())
    return false;

  // Open the program and see if it references the expected file.
  base::File file;
  BY_HANDLE_FILE_INFORMATION info = {};

  return (OpenForInfo(path, &file) &&
          GetInfo(file, &info) &&
          info.dwVolumeSerialNumber == file_info_.dwVolumeSerialNumber &&
          info.nFileIndexHigh == file_info_.nFileIndexHigh &&
          info.nFileIndexLow == file_info_.nFileIndexLow);
}

/* [<][>][^][v][top][bottom][index][help] */