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