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, ¶ms); 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