installer_state.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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#include "chrome/installer/util/installer_state.h"
6
7#include <algorithm>
8#include <functional>
9#include <utility>
10
11#include "base/command_line.h"
12#include "base/file_util.h"
13#include "base/file_version_info.h"
14#include "base/files/file_enumerator.h"
15#include "base/logging.h"
16#include "base/memory/scoped_ptr.h"
17#include "base/strings/string_util.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/win/registry.h"
20#include "base/win/scoped_handle.h"
21#include "chrome/installer/util/delete_tree_work_item.h"
22#include "chrome/installer/util/helper.h"
23#include "chrome/installer/util/install_util.h"
24#include "chrome/installer/util/installation_state.h"
25#include "chrome/installer/util/master_preferences.h"
26#include "chrome/installer/util/master_preferences_constants.h"
27#include "chrome/installer/util/product.h"
28#include "chrome/installer/util/work_item.h"
29#include "chrome/installer/util/work_item_list.h"
30
31namespace installer {
32
33bool InstallerState::IsMultiInstallUpdate(const MasterPreferences& prefs,
34    const InstallationState& machine_state) {
35  // First, is the package present?
36  const ProductState* package =
37      machine_state.GetProductState(level_ == SYSTEM_LEVEL,
38                                    BrowserDistribution::CHROME_BINARIES);
39  if (package == NULL) {
40    // The multi-install package has not been installed, so it certainly isn't
41    // being updated.
42    return false;
43  }
44
45  BrowserDistribution::Type types[2];
46  size_t num_types = 0;
47  if (prefs.install_chrome())
48    types[num_types++] = BrowserDistribution::CHROME_BROWSER;
49  if (prefs.install_chrome_frame())
50    types[num_types++] = BrowserDistribution::CHROME_FRAME;
51
52  for (const BrowserDistribution::Type* scan = &types[0],
53           *end = &types[num_types]; scan != end; ++scan) {
54    const ProductState* product =
55        machine_state.GetProductState(level_ == SYSTEM_LEVEL, *scan);
56    if (product == NULL) {
57      VLOG(2) << "It seems that distribution type " << *scan
58              << " is being installed for the first time.";
59      return false;
60    }
61    if (!product->channel().Equals(package->channel())) {
62      VLOG(2) << "It seems that distribution type " << *scan
63              << " is being over installed.";
64      return false;
65    }
66  }
67
68  VLOG(2) << "It seems that the package is being updated.";
69
70  return true;
71}
72
73InstallerState::InstallerState()
74    : operation_(UNINITIALIZED),
75      multi_package_distribution_(NULL),
76      level_(UNKNOWN_LEVEL),
77      package_type_(UNKNOWN_PACKAGE_TYPE),
78      state_type_(BrowserDistribution::CHROME_BROWSER),
79      root_key_(NULL),
80      msi_(false),
81      verbose_logging_(false),
82      ensure_google_update_present_(false) {
83}
84
85InstallerState::InstallerState(Level level)
86    : operation_(UNINITIALIZED),
87      multi_package_distribution_(NULL),
88      level_(UNKNOWN_LEVEL),
89      package_type_(UNKNOWN_PACKAGE_TYPE),
90      state_type_(BrowserDistribution::CHROME_BROWSER),
91      root_key_(NULL),
92      msi_(false),
93      verbose_logging_(false),
94      ensure_google_update_present_(false) {
95  // Use set_level() so that root_key_ is updated properly.
96  set_level(level);
97}
98
99void InstallerState::Initialize(const CommandLine& command_line,
100                                const MasterPreferences& prefs,
101                                const InstallationState& machine_state) {
102  bool pref_bool;
103  if (!prefs.GetBool(master_preferences::kSystemLevel, &pref_bool))
104    pref_bool = false;
105  set_level(pref_bool ? SYSTEM_LEVEL : USER_LEVEL);
106
107  if (!prefs.GetBool(master_preferences::kVerboseLogging, &verbose_logging_))
108    verbose_logging_ = false;
109
110  if (!prefs.GetBool(master_preferences::kMultiInstall, &pref_bool))
111    pref_bool = false;
112  set_package_type(pref_bool ? MULTI_PACKAGE : SINGLE_PACKAGE);
113
114  if (!prefs.GetBool(master_preferences::kMsi, &msi_))
115    msi_ = false;
116
117  ensure_google_update_present_ =
118      command_line.HasSwitch(installer::switches::kEnsureGoogleUpdatePresent);
119
120  const bool is_uninstall = command_line.HasSwitch(switches::kUninstall);
121
122  if (prefs.install_chrome()) {
123    Product* p = AddProductFromPreferences(
124        BrowserDistribution::CHROME_BROWSER, prefs, machine_state);
125    VLOG(1) << (is_uninstall ? "Uninstall" : "Install")
126            << " distribution: " << p->distribution()->GetDisplayName();
127  }
128  if (prefs.install_chrome_frame()) {
129    Product* p = AddProductFromPreferences(
130        BrowserDistribution::CHROME_FRAME, prefs, machine_state);
131    VLOG(1) << (is_uninstall ? "Uninstall" : "Install")
132            << " distribution: " << p->distribution()->GetDisplayName();
133  }
134
135  if (prefs.install_chrome_app_launcher()) {
136    Product* p = AddProductFromPreferences(
137        BrowserDistribution::CHROME_APP_HOST, prefs, machine_state);
138    VLOG(1) << (is_uninstall ? "Uninstall" : "Install")
139            << " distribution: " << p->distribution()->GetDisplayName();
140  }
141
142  if (!is_uninstall && is_multi_install()) {
143    bool need_binaries = false;
144    if (FindProduct(BrowserDistribution::CHROME_APP_HOST)) {
145      // App Host will happily use Chrome at system level, or binaries at system
146      // level, even if app host is user level.
147      const ProductState* chrome_state = machine_state.GetProductState(
148          true,  // system level
149          BrowserDistribution::CHROME_BROWSER);
150      // If Chrome is at system-level, multi- or otherwise. We'll use it.
151      if (!chrome_state) {
152        const ProductState* binaries_state = machine_state.GetProductState(
153            true,  // system level
154            BrowserDistribution::CHROME_BINARIES);
155        if (!binaries_state)
156          need_binaries = true;
157      }
158    }
159
160    // Chrome/Chrome Frame multi need Binaries at their own level.
161    if (FindProduct(BrowserDistribution::CHROME_BROWSER))
162      need_binaries = true;
163
164    if (FindProduct(BrowserDistribution::CHROME_FRAME))
165      need_binaries = true;
166
167    if (need_binaries && !FindProduct(BrowserDistribution::CHROME_BINARIES)) {
168      // Force binaries to be installed/updated.
169      Product* p = AddProductFromPreferences(
170          BrowserDistribution::CHROME_BINARIES, prefs, machine_state);
171      VLOG(1) << "Install distribution: "
172              << p->distribution()->GetDisplayName();
173    }
174  }
175
176  if (is_uninstall && prefs.is_multi_install()) {
177    if (FindProduct(BrowserDistribution::CHROME_BROWSER)) {
178      // Uninstall each product of type |type| listed below based on the
179      // presence or absence of |switch_name| in that product's uninstall
180      // command.
181      const struct {
182        BrowserDistribution::Type type;
183        const char* switch_name;
184        bool switch_expected;
185      } conditional_additions[] = {
186        // If Chrome Frame is installed in Ready Mode, remove it with Chrome.
187        { BrowserDistribution::CHROME_FRAME,
188          switches::kChromeFrameReadyMode,
189          true },
190        // If the App Host is installed, but not the App Launcher, remove it
191        // with Chrome. Note however that for system-level Chrome uninstalls,
192        // any installed user-level App Host will remain even if there is no
193        // App Launcher present (the orphaned app_host.exe will prompt the user
194        // for further action when executed).
195        { BrowserDistribution::CHROME_APP_HOST,
196          switches::kChromeAppLauncher,
197          false },
198      };
199
200      for (size_t i = 0; i < arraysize(conditional_additions); ++i) {
201        const ProductState* product_state = machine_state.GetProductState(
202            system_install(), conditional_additions[i].type);
203        if (product_state != NULL &&
204            product_state->uninstall_command().HasSwitch(
205                conditional_additions[i].switch_name) ==
206                    conditional_additions[i].switch_expected &&
207            !FindProduct(conditional_additions[i].type)) {
208          Product* p = AddProductFromPreferences(
209              conditional_additions[i].type, prefs, machine_state);
210          VLOG(1) << "Uninstall distribution: "
211                  << p->distribution()->GetDisplayName();
212        }
213      }
214    }
215
216    bool keep_binaries = false;
217    // Look for a multi-install product that is not the binaries and that is not
218    // being uninstalled. If not found, binaries are uninstalled too.
219    for (size_t i = 0; i < BrowserDistribution::NUM_TYPES; ++i) {
220      BrowserDistribution::Type type =
221          static_cast<BrowserDistribution::Type>(i);
222
223      if (type == BrowserDistribution::CHROME_BINARIES)
224        continue;
225
226      const ProductState* product_state =
227          machine_state.GetProductState(system_install(), type);
228      if (product_state == NULL) {
229        // The product is not installed.
230        continue;
231      }
232
233      if (!product_state->is_multi_install() &&
234          type != BrowserDistribution::CHROME_BROWSER) {
235        // The product is not sharing the binaries. It is ordinarily impossible
236        // for single-install Chrome to be installed along with any
237        // multi-install product. Treat single-install Chrome the same as any
238        // multi-install product just in case the impossible happens.
239        continue;
240      }
241
242      // The product is installed.
243
244      if (!FindProduct(type)) {
245        // The product is not being uninstalled.
246        if (type != BrowserDistribution::CHROME_APP_HOST) {
247          keep_binaries = true;
248          break;
249        } else {
250          // If binaries/chrome are at system-level, we can discard them at
251          // user-level...
252          if (!machine_state.GetProductState(
253                  true,  // system-level
254                  BrowserDistribution::CHROME_BROWSER) &&
255              !machine_state.GetProductState(
256                  true,  // system-level
257                  BrowserDistribution::CHROME_BINARIES)) {
258            // ... otherwise keep them.
259            keep_binaries = true;
260            break;
261          }
262
263        }
264      }
265
266      // The product is being uninstalled.
267    }
268    if (!keep_binaries &&
269        machine_state.GetProductState(system_install(),
270                                      BrowserDistribution::CHROME_BINARIES)) {
271      Product* p = AddProductFromPreferences(
272          BrowserDistribution::CHROME_BINARIES, prefs, machine_state);
273      VLOG(1) << (is_uninstall ? "Uninstall" : "Install")
274              << " distribution: " << p->distribution()->GetDisplayName();
275    }
276  }
277
278  BrowserDistribution* operand = NULL;
279
280  if (is_uninstall) {
281    operation_ = UNINSTALL;
282  } else if (!prefs.is_multi_install()) {
283    // For a single-install, the current browser dist is the operand.
284    operand = BrowserDistribution::GetDistribution();
285    operation_ = SINGLE_INSTALL_OR_UPDATE;
286  } else if (IsMultiInstallUpdate(prefs, machine_state)) {
287    // Updates driven by Google Update take place under the multi-installer's
288    // app guid.
289    operand = multi_package_distribution_;
290    operation_ = MULTI_UPDATE;
291  } else {
292    operation_ = MULTI_INSTALL;
293  }
294
295  // Initial, over, and un-installs will take place under one of the
296  // product app guids (Chrome, Chrome Frame, App Host, or Binaries, in order of
297  // preference).
298  if (operand == NULL) {
299    BrowserDistribution::Type operand_distribution_type =
300        BrowserDistribution::CHROME_BINARIES;
301    if (prefs.install_chrome())
302      operand_distribution_type = BrowserDistribution::CHROME_BROWSER;
303    else if (prefs.install_chrome_frame())
304      operand_distribution_type = BrowserDistribution::CHROME_FRAME;
305    else if (prefs.install_chrome_app_launcher())
306      operand_distribution_type = BrowserDistribution::CHROME_APP_HOST;
307
308    operand = BrowserDistribution::GetSpecificDistribution(
309        operand_distribution_type);
310  }
311
312  state_key_ = operand->GetStateKey();
313  state_type_ = operand->GetType();
314
315  // Parse --critical-update-version=W.X.Y.Z
316  std::string critical_version_value(
317      command_line.GetSwitchValueASCII(switches::kCriticalUpdateVersion));
318  critical_update_version_ = Version(critical_version_value);
319}
320
321void InstallerState::set_level(Level level) {
322  level_ = level;
323  switch (level) {
324    case USER_LEVEL:
325      root_key_ = HKEY_CURRENT_USER;
326      break;
327    case SYSTEM_LEVEL:
328      root_key_ = HKEY_LOCAL_MACHINE;
329      break;
330    default:
331      DCHECK(level == UNKNOWN_LEVEL);
332      level_ = UNKNOWN_LEVEL;
333      root_key_ = NULL;
334      break;
335  }
336}
337
338void InstallerState::set_package_type(PackageType type) {
339  package_type_ = type;
340  switch (type) {
341    case SINGLE_PACKAGE:
342      multi_package_distribution_ = NULL;
343      break;
344    case MULTI_PACKAGE:
345      multi_package_distribution_ =
346          BrowserDistribution::GetSpecificDistribution(
347              BrowserDistribution::CHROME_BINARIES);
348      break;
349    default:
350      DCHECK(type == UNKNOWN_PACKAGE_TYPE);
351      package_type_ = UNKNOWN_PACKAGE_TYPE;
352      multi_package_distribution_ = NULL;
353      break;
354  }
355}
356
357// Returns the Chrome binaries directory for multi-install or |dist|'s directory
358// otherwise.
359base::FilePath InstallerState::GetDefaultProductInstallPath(
360    BrowserDistribution* dist) const {
361  DCHECK(dist);
362  DCHECK(package_type_ != UNKNOWN_PACKAGE_TYPE);
363
364  if (package_type_ == SINGLE_PACKAGE) {
365    return GetChromeInstallPath(system_install(), dist);
366  } else {
367    return GetChromeInstallPath(system_install(),
368        BrowserDistribution::GetSpecificDistribution(
369            BrowserDistribution::CHROME_BINARIES));
370  }
371}
372
373// Evaluates a product's eligibility for participation in this operation.
374// We never expect these checks to fail, hence they all terminate the process in
375// debug builds.  See the log messages for details.
376bool InstallerState::CanAddProduct(const Product& product,
377                                   const base::FilePath* product_dir) const {
378  switch (package_type_) {
379    case SINGLE_PACKAGE:
380      if (!products_.empty()) {
381        LOG(DFATAL) << "Cannot process more than one single-install product.";
382        return false;
383      }
384      break;
385    case MULTI_PACKAGE:
386      if (!product.HasOption(kOptionMultiInstall)) {
387        LOG(DFATAL) << "Cannot process a single-install product with a "
388                       "multi-install state.";
389        return false;
390      }
391      if (FindProduct(product.distribution()->GetType()) != NULL) {
392        LOG(DFATAL) << "Cannot process more than one product of the same type.";
393        return false;
394      }
395      if (!target_path_.empty()) {
396        base::FilePath default_dir;
397        if (product_dir == NULL)
398          default_dir = GetDefaultProductInstallPath(product.distribution());
399        if (!base::FilePath::CompareEqualIgnoreCase(
400                (product_dir == NULL ? default_dir : *product_dir).value(),
401                target_path_.value())) {
402          LOG(DFATAL) << "Cannot process products in different directories.";
403          return false;
404        }
405      }
406      break;
407    default:
408      DCHECK_EQ(UNKNOWN_PACKAGE_TYPE, package_type_);
409      break;
410  }
411  return true;
412}
413
414// Adds |product|, installed in |product_dir| to this object's collection.  If
415// |product_dir| is NULL, the product's default install location is used.
416// Returns NULL if |product| is incompatible with this object.  Otherwise,
417// returns a pointer to the product (ownership is held by this object).
418Product* InstallerState::AddProductInDirectory(
419    const base::FilePath* product_dir,
420    scoped_ptr<Product>* product) {
421  DCHECK(product != NULL);
422  DCHECK(product->get() != NULL);
423  const Product& the_product = *product->get();
424
425  if (!CanAddProduct(the_product, product_dir))
426    return NULL;
427
428  if (package_type_ == UNKNOWN_PACKAGE_TYPE) {
429    set_package_type(the_product.HasOption(kOptionMultiInstall) ?
430                         MULTI_PACKAGE : SINGLE_PACKAGE);
431  }
432
433  if (target_path_.empty()) {
434    if (product_dir == NULL)
435      target_path_ = GetDefaultProductInstallPath(the_product.distribution());
436    else
437      target_path_ = *product_dir;
438  }
439
440  if (state_key_.empty())
441    state_key_ = the_product.distribution()->GetStateKey();
442
443  products_.push_back(product->release());
444  return products_[products_.size() - 1];
445}
446
447Product* InstallerState::AddProduct(scoped_ptr<Product>* product) {
448  return AddProductInDirectory(NULL, product);
449}
450
451// Adds a product of type |distribution_type| constructed on the basis of
452// |prefs|, setting this object's msi flag if the product is represented in
453// |machine_state| and is msi-installed.  Returns the product that was added,
454// or NULL if |state| is incompatible with this object.  Ownership is not passed
455// to the caller.
456Product* InstallerState::AddProductFromPreferences(
457    BrowserDistribution::Type distribution_type,
458    const MasterPreferences& prefs,
459    const InstallationState& machine_state) {
460  scoped_ptr<Product> product_ptr(
461      new Product(BrowserDistribution::GetSpecificDistribution(
462          distribution_type)));
463  product_ptr->InitializeFromPreferences(prefs);
464
465  Product* product = AddProductInDirectory(NULL, &product_ptr);
466
467  if (product != NULL && !msi_) {
468    const ProductState* product_state = machine_state.GetProductState(
469        system_install(), distribution_type);
470    if (product_state != NULL)
471      msi_ = product_state->is_msi();
472  }
473
474  return product;
475}
476
477Product* InstallerState::AddProductFromState(
478    BrowserDistribution::Type type,
479    const ProductState& state) {
480  scoped_ptr<Product> product_ptr(
481      new Product(BrowserDistribution::GetSpecificDistribution(type)));
482  product_ptr->InitializeFromUninstallCommand(state.uninstall_command());
483
484  // Strip off <version>/Installer/setup.exe; see GetInstallerDirectory().
485  base::FilePath product_dir =
486      state.GetSetupPath().DirName().DirName().DirName();
487
488  Product* product = AddProductInDirectory(&product_dir, &product_ptr);
489
490  if (product != NULL)
491    msi_ |= state.is_msi();
492
493  return product;
494}
495
496bool InstallerState::system_install() const {
497  DCHECK(level_ == USER_LEVEL || level_ == SYSTEM_LEVEL);
498  return level_ == SYSTEM_LEVEL;
499}
500
501bool InstallerState::is_multi_install() const {
502  DCHECK(package_type_ == SINGLE_PACKAGE || package_type_ == MULTI_PACKAGE);
503  return package_type_ != SINGLE_PACKAGE;
504}
505
506bool InstallerState::RemoveProduct(const Product* product) {
507  ScopedVector<Product>::iterator it =
508      std::find(products_.begin(), products_.end(), product);
509  if (it != products_.end()) {
510    products_.weak_erase(it);
511    return true;
512  }
513  return false;
514}
515
516const Product* InstallerState::FindProduct(
517    BrowserDistribution::Type distribution_type) const {
518  for (Products::const_iterator scan = products_.begin(), end = products_.end();
519       scan != end; ++scan) {
520     if ((*scan)->is_type(distribution_type))
521       return *scan;
522  }
523  return NULL;
524}
525
526Version* InstallerState::GetCurrentVersion(
527    const InstallationState& machine_state) const {
528  DCHECK(!products_.empty());
529  scoped_ptr<Version> current_version;
530  // If we're doing a multi-install, the current version may be either an
531  // existing multi or an existing single product that is being migrated
532  // in place (i.e., Chrome).  In the latter case, there is no existing
533  // CHROME_BINARIES installation so we need to search for the product.
534  BrowserDistribution::Type prod_type;
535  if (package_type_ == MULTI_PACKAGE) {
536    prod_type = BrowserDistribution::CHROME_BINARIES;
537    if (machine_state.GetProductState(level_ == SYSTEM_LEVEL,
538                                      prod_type) == NULL) {
539      // Search for a product on which we're operating that is installed in our
540      // target directory.
541      Products::const_iterator end = products().end();
542      for (Products::const_iterator scan = products().begin(); scan != end;
543           ++scan) {
544        BrowserDistribution::Type product_type =
545            (*scan)->distribution()->GetType();
546        const ProductState* state =
547            machine_state.GetProductState(level_ == SYSTEM_LEVEL, product_type);
548        if (state != NULL && target_path_.IsParent(state->GetSetupPath())) {
549          prod_type = product_type;
550          break;
551        }
552      }
553    }
554  } else {
555    prod_type = products_[0]->distribution()->GetType();
556  }
557  const ProductState* product_state =
558      machine_state.GetProductState(level_ == SYSTEM_LEVEL, prod_type);
559
560  if (product_state != NULL) {
561    const Version* version = NULL;
562
563    // Be aware that there might be a pending "new_chrome.exe" already in the
564    // installation path.  If so, we use old_version, which holds the version of
565    // "chrome.exe" itself.
566    if (base::PathExists(target_path().Append(kChromeNewExe)))
567      version = product_state->old_version();
568
569    if (version == NULL)
570      version = &product_state->version();
571
572    current_version.reset(new Version(*version));
573  }
574
575  return current_version.release();
576}
577
578Version InstallerState::DetermineCriticalVersion(
579    const Version* current_version,
580    const Version& new_version) const {
581  DCHECK(current_version == NULL || current_version->IsValid());
582  DCHECK(new_version.IsValid());
583  if (critical_update_version_.IsValid() &&
584      (current_version == NULL ||
585       (current_version->CompareTo(critical_update_version_) < 0)) &&
586      new_version.CompareTo(critical_update_version_) >= 0) {
587    return critical_update_version_;
588  }
589  return Version();
590}
591
592bool InstallerState::IsChromeFrameRunning(
593    const InstallationState& machine_state) const {
594  return AnyExistsAndIsInUse(machine_state, CHROME_FRAME_DLL);
595}
596
597bool InstallerState::AreBinariesInUse(
598    const InstallationState& machine_state) const {
599  return AnyExistsAndIsInUse(
600      machine_state,
601      CHROME_FRAME_DLL | CHROME_FRAME_HELPER_EXE | CHROME_DLL);
602}
603
604base::FilePath InstallerState::GetInstallerDirectory(
605    const Version& version) const {
606  return target_path().Append(ASCIIToWide(version.GetString()))
607      .Append(kInstallerDir);
608}
609
610// static
611bool InstallerState::IsFileInUse(const base::FilePath& file) {
612  // Call CreateFile with a share mode of 0 which should cause this to fail
613  // with ERROR_SHARING_VIOLATION if the file exists and is in-use.
614  return !base::win::ScopedHandle(CreateFile(file.value().c_str(),
615                                             GENERIC_WRITE, 0, NULL,
616                                             OPEN_EXISTING, 0, 0)).IsValid();
617}
618
619bool InstallerState::AnyExistsAndIsInUse(
620    const InstallationState& machine_state,
621    uint32 file_bits) const {
622  static const wchar_t* const kBinaryFileNames[] = {
623    kChromeFrameDll,
624    kChromeFrameHelperExe,
625    kChromeDll,
626  };
627  DCHECK_NE(file_bits, 0U);
628  DCHECK_LT(file_bits, 1U << NUM_BINARIES);
629  COMPILE_ASSERT(CHROME_FRAME_DLL == 1,no_youre_out_of_order);
630  COMPILE_ASSERT(CHROME_FRAME_HELPER_EXE == 2, no_youre_out_of_order);
631  COMPILE_ASSERT(CHROME_DLL == 4, no_youre_out_of_order);
632
633  // Check only for the current version (i.e., the version we are upgrading
634  // _from_). Later versions from pending in-use updates need not be checked
635  // since the current version is guaranteed to be in use if any such are.
636  bool in_use = false;
637  scoped_ptr<Version> current_version(GetCurrentVersion(machine_state));
638  if (!current_version)
639    return false;
640  base::FilePath directory(
641      target_path().AppendASCII(current_version->GetString()));
642  for (int i = 0; i < NUM_BINARIES; ++i) {
643    if (!(file_bits & (1U << i)))
644      continue;
645    base::FilePath file(directory.Append(kBinaryFileNames[i]));
646    if (base::PathExists(file) && IsFileInUse(file))
647      return true;
648  }
649  return false;
650}
651
652void InstallerState::GetExistingExeVersions(
653    std::set<std::string>* existing_versions) const {
654
655  static const wchar_t* const kChromeFilenames[] = {
656    installer::kChromeExe,
657    installer::kChromeNewExe,
658    installer::kChromeOldExe,
659  };
660
661  for (int i = 0; i < arraysize(kChromeFilenames); ++i) {
662    base::FilePath chrome_exe(target_path().Append(kChromeFilenames[i]));
663    scoped_ptr<FileVersionInfo> file_version_info(
664        FileVersionInfo::CreateFileVersionInfo(chrome_exe));
665    if (file_version_info) {
666      string16 version_string = file_version_info->file_version();
667      if (!version_string.empty() && IsStringASCII(version_string))
668        existing_versions->insert(WideToASCII(version_string));
669    }
670  }
671}
672
673void InstallerState::RemoveOldVersionDirectories(
674    const Version& new_version,
675    Version* existing_version,
676    const base::FilePath& temp_path) const {
677  Version version;
678  scoped_ptr<WorkItem> item;
679
680  std::set<std::string> existing_version_strings;
681  existing_version_strings.insert(new_version.GetString());
682  if (existing_version)
683    existing_version_strings.insert(existing_version->GetString());
684
685  // Make sure not to delete any version dir that is "referenced" by an existing
686  // Chrome executable.
687  GetExistingExeVersions(&existing_version_strings);
688
689  // Try to delete all directories that are not in the set we care to keep.
690  base::FileEnumerator version_enum(target_path(), false,
691                                    base::FileEnumerator::DIRECTORIES);
692  for (base::FilePath next_version = version_enum.Next(); !next_version.empty();
693       next_version = version_enum.Next()) {
694    base::FilePath dir_name(next_version.BaseName());
695    version = Version(WideToASCII(dir_name.value()));
696    // Delete the version folder if it is less than the new version and not
697    // equal to the old version (if we have an old version).
698    if (version.IsValid() &&
699        existing_version_strings.count(version.GetString()) == 0) {
700      // Note: temporarily log old version deletion at ERROR level to make it
701      // more likely we see this in the installer log.
702      LOG(ERROR) << "Deleting old version directory: " << next_version.value();
703
704      // Attempt to recursively delete the old version dir.
705      bool delete_succeeded = base::DeleteFile(next_version, true);
706
707      // Note: temporarily log old version deletion at ERROR level to make it
708      // more likely we see this in the installer log.
709      LOG_IF(ERROR, !delete_succeeded)
710          << "Failed to delete old version directory: " << next_version.value();
711    }
712  }
713}
714
715void InstallerState::AddComDllList(
716    std::vector<base::FilePath>* com_dll_list) const {
717  std::for_each(products_.begin(), products_.end(),
718                std::bind2nd(std::mem_fun(&Product::AddComDllList),
719                             com_dll_list));
720}
721
722bool InstallerState::SetChannelFlags(bool set,
723                                     ChannelInfo* channel_info) const {
724  bool modified = false;
725  for (Products::const_iterator scan = products_.begin(), end = products_.end();
726       scan != end; ++scan) {
727     modified |= (*scan)->SetChannelFlags(set, channel_info);
728  }
729  return modified;
730}
731
732void InstallerState::UpdateStage(installer::InstallerStage stage) const {
733  InstallUtil::UpdateInstallerStage(system_install(), state_key_, stage);
734}
735
736void InstallerState::UpdateChannels() const {
737  if (operation_ != MULTI_INSTALL && operation_ != MULTI_UPDATE) {
738    VLOG(1) << "InstallerState::UpdateChannels noop: " << operation_;
739    return;
740  }
741
742  // Update the "ap" value for the product being installed/updated.  We get the
743  // current value from the registry since the InstallationState instance used
744  // by the bulk of the installer does not track changes made by UpdateStage.
745  // Create the app's ClientState key if it doesn't exist.
746  ChannelInfo channel_info;
747  base::win::RegKey state_key;
748  LONG result = state_key.Create(root_key_, state_key_.c_str(),
749                                 KEY_QUERY_VALUE | KEY_SET_VALUE);
750  if (result == ERROR_SUCCESS) {
751    channel_info.Initialize(state_key);
752
753    // This is a multi-install product.
754    bool modified = channel_info.SetMultiInstall(true);
755
756    // Add the appropriate modifiers for all products and their options.
757    modified |= SetChannelFlags(true, &channel_info);
758
759    VLOG(1) << "ap: " << channel_info.value();
760
761    // Write the results if needed.
762    if (modified)
763      channel_info.Write(&state_key);
764
765    // Remove the -stage: modifier since we don't want to propagate that to the
766    // other app_guids.
767    channel_info.SetStage(NULL);
768
769    // Synchronize the other products and the package with this one.
770    ChannelInfo other_info;
771    for (int i = 0; i < BrowserDistribution::NUM_TYPES; ++i) {
772      BrowserDistribution::Type type =
773          static_cast<BrowserDistribution::Type>(i);
774      // Skip the app_guid we started with.
775      if (type == state_type_)
776        continue;
777      BrowserDistribution* dist = NULL;
778      // Always operate on the binaries.
779      if (i == BrowserDistribution::CHROME_BINARIES) {
780        dist = multi_package_distribution_;
781      } else {
782        const Product* product = FindProduct(type);
783        // Skip this one if it's for a product we're not operating on.
784        if (product == NULL)
785          continue;
786        dist = product->distribution();
787      }
788      result = state_key.Create(root_key_, dist->GetStateKey().c_str(),
789                                KEY_QUERY_VALUE | KEY_SET_VALUE);
790      if (result == ERROR_SUCCESS) {
791        other_info.Initialize(state_key);
792        if (!other_info.Equals(channel_info))
793          channel_info.Write(&state_key);
794      } else {
795        LOG(ERROR) << "Failed opening key " << dist->GetStateKey()
796                   << " to update app channels; result: " << result;
797      }
798    }
799  } else {
800    LOG(ERROR) << "Failed opening key " << state_key_
801               << " to update app channels; result: " << result;
802  }
803}
804
805void InstallerState::WriteInstallerResult(
806    InstallStatus status,
807    int string_resource_id,
808    const std::wstring* const launch_cmd) const {
809  // Use a no-rollback list since this is a best-effort deal.
810  scoped_ptr<WorkItemList> install_list(
811      WorkItem::CreateNoRollbackWorkItemList());
812  const bool system_install = this->system_install();
813  // Write the value for all products upon which we're operating.
814  Products::const_iterator end = products().end();
815  for (Products::const_iterator scan = products().begin(); scan != end;
816       ++scan) {
817    InstallUtil::AddInstallerResultItems(
818        system_install, (*scan)->distribution()->GetStateKey(), status,
819        string_resource_id, launch_cmd, install_list.get());
820  }
821  // And for the binaries if this is a multi-install.
822  if (is_multi_install()) {
823    InstallUtil::AddInstallerResultItems(
824        system_install, multi_package_binaries_distribution()->GetStateKey(),
825        status, string_resource_id, launch_cmd, install_list.get());
826  }
827  if (!install_list->Do())
828    LOG(ERROR) << "Failed to record installer error information in registry.";
829}
830
831bool InstallerState::RequiresActiveSetup() const {
832  return system_install() && FindProduct(BrowserDistribution::CHROME_BROWSER);
833}
834
835}  // namespace installer
836