Home | History | Annotate | Download | only in setup
      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 // This file declares util functions for setup project.
      6 
      7 #include "chrome/installer/setup/setup_util.h"
      8 
      9 #include <windows.h>
     10 
     11 #include "base/command_line.h"
     12 #include "base/cpu.h"
     13 #include "base/file_util.h"
     14 #include "base/files/file_enumerator.h"
     15 #include "base/files/file_path.h"
     16 #include "base/logging.h"
     17 #include "base/process/kill.h"
     18 #include "base/process/launch.h"
     19 #include "base/process/process_handle.h"
     20 #include "base/strings/string_util.h"
     21 #include "base/strings/utf_string_conversions.h"
     22 #include "base/version.h"
     23 #include "base/win/registry.h"
     24 #include "base/win/windows_version.h"
     25 #include "chrome/installer/setup/setup_constants.h"
     26 #include "chrome/installer/util/copy_tree_work_item.h"
     27 #include "chrome/installer/util/google_update_constants.h"
     28 #include "chrome/installer/util/installation_state.h"
     29 #include "chrome/installer/util/installer_state.h"
     30 #include "chrome/installer/util/master_preferences.h"
     31 #include "chrome/installer/util/util_constants.h"
     32 #include "chrome/installer/util/work_item.h"
     33 #include "courgette/courgette.h"
     34 #include "courgette/third_party/bsdiff.h"
     35 #include "third_party/bspatch/mbspatch.h"
     36 
     37 namespace installer {
     38 
     39 namespace {
     40 
     41 // Launches |setup_exe| with |command_line|, save --install-archive and its
     42 // value if present. Returns false if the process failed to launch. Otherwise,
     43 // waits indefinitely for it to exit and populates |exit_code| as expected. On
     44 // the off chance that waiting itself fails, |exit_code| is set to
     45 // WAIT_FOR_EXISTING_FAILED.
     46 bool LaunchAndWaitForExistingInstall(const base::FilePath& setup_exe,
     47                                      const CommandLine& command_line,
     48                                      int* exit_code) {
     49   DCHECK(exit_code);
     50   CommandLine new_cl(setup_exe);
     51 
     52   // Copy over all switches but --install-archive.
     53   CommandLine::SwitchMap switches(command_line.GetSwitches());
     54   switches.erase(switches::kInstallArchive);
     55   for (CommandLine::SwitchMap::const_iterator i = switches.begin();
     56        i != switches.end(); ++i) {
     57     if (i->second.empty())
     58       new_cl.AppendSwitch(i->first);
     59     else
     60       new_cl.AppendSwitchNative(i->first, i->second);
     61   }
     62 
     63   // Copy over all arguments.
     64   CommandLine::StringVector args(command_line.GetArgs());
     65   for (CommandLine::StringVector::const_iterator i = args.begin();
     66        i != args.end(); ++i) {
     67     new_cl.AppendArgNative(*i);
     68   }
     69 
     70   // Launch the process and wait for it to exit.
     71   VLOG(1) << "Launching existing installer with command: "
     72           << new_cl.GetCommandLineString();
     73   base::ProcessHandle handle = INVALID_HANDLE_VALUE;
     74   if (!base::LaunchProcess(new_cl, base::LaunchOptions(), &handle)) {
     75     PLOG(ERROR) << "Failed to launch existing installer with command: "
     76                 << new_cl.GetCommandLineString();
     77     return false;
     78   }
     79   if (!base::WaitForExitCode(handle, exit_code)) {
     80     PLOG(DFATAL) << "Failed to get exit code from existing installer";
     81     *exit_code = WAIT_FOR_EXISTING_FAILED;
     82   } else {
     83     VLOG(1) << "Existing installer returned exit code " << *exit_code;
     84   }
     85   return true;
     86 }
     87 
     88 // Returns true if product |type| cam be meaningfully installed without the
     89 // --multi-install flag.
     90 bool SupportsSingleInstall(BrowserDistribution::Type type) {
     91   return (type == BrowserDistribution::CHROME_BROWSER ||
     92           type == BrowserDistribution::CHROME_FRAME);
     93 }
     94 
     95 }  // namespace
     96 
     97 int CourgettePatchFiles(const base::FilePath& src,
     98                         const base::FilePath& patch,
     99                         const base::FilePath& dest) {
    100   VLOG(1) << "Applying Courgette patch " << patch.value()
    101           << " to file " << src.value()
    102           << " and generating file " << dest.value();
    103 
    104   if (src.empty() || patch.empty() || dest.empty())
    105     return installer::PATCH_INVALID_ARGUMENTS;
    106 
    107   const courgette::Status patch_status =
    108       courgette::ApplyEnsemblePatch(src.value().c_str(),
    109                                     patch.value().c_str(),
    110                                     dest.value().c_str());
    111   const int exit_code = (patch_status != courgette::C_OK) ?
    112       static_cast<int>(patch_status) + kCourgetteErrorOffset : 0;
    113 
    114   LOG_IF(ERROR, exit_code)
    115       << "Failed to apply Courgette patch " << patch.value()
    116       << " to file " << src.value() << " and generating file " << dest.value()
    117       << ". err=" << exit_code;
    118 
    119   return exit_code;
    120 }
    121 
    122 int BsdiffPatchFiles(const base::FilePath& src,
    123                      const base::FilePath& patch,
    124                      const base::FilePath& dest) {
    125   VLOG(1) << "Applying bsdiff patch " << patch.value()
    126           << " to file " << src.value()
    127           << " and generating file " << dest.value();
    128 
    129   if (src.empty() || patch.empty() || dest.empty())
    130     return installer::PATCH_INVALID_ARGUMENTS;
    131 
    132   const int patch_status = courgette::ApplyBinaryPatch(src, patch, dest);
    133   const int exit_code = patch_status != OK ?
    134                         patch_status + kBsdiffErrorOffset : 0;
    135 
    136   LOG_IF(ERROR, exit_code)
    137       << "Failed to apply bsdiff patch " << patch.value()
    138       << " to file " << src.value() << " and generating file " << dest.value()
    139       << ". err=" << exit_code;
    140 
    141   return exit_code;
    142 }
    143 
    144 Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) {
    145   VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value();
    146   base::FileEnumerator version_enum(chrome_path, false,
    147       base::FileEnumerator::DIRECTORIES);
    148   // TODO(tommi): The version directory really should match the version of
    149   // setup.exe.  To begin with, we should at least DCHECK that that's true.
    150 
    151   scoped_ptr<Version> max_version(new Version("0.0.0.0"));
    152   bool version_found = false;
    153 
    154   while (!version_enum.Next().empty()) {
    155     base::FileEnumerator::FileInfo find_data = version_enum.GetInfo();
    156     VLOG(1) << "directory found: " << find_data.GetName().value();
    157 
    158     scoped_ptr<Version> found_version(
    159         new Version(base::UTF16ToASCII(find_data.GetName().value())));
    160     if (found_version->IsValid() &&
    161         found_version->CompareTo(*max_version.get()) > 0) {
    162       max_version.reset(found_version.release());
    163       version_found = true;
    164     }
    165   }
    166 
    167   return (version_found ? max_version.release() : NULL);
    168 }
    169 
    170 base::FilePath FindArchiveToPatch(const InstallationState& original_state,
    171                                   const InstallerState& installer_state) {
    172   // Check based on the version number advertised to Google Update, since that
    173   // is the value used to select a specific differential update. If an archive
    174   // can't be found using that, fallback to using the newest version present.
    175   base::FilePath patch_source;
    176   const ProductState* product =
    177       original_state.GetProductState(installer_state.system_install(),
    178                                      installer_state.state_type());
    179   if (product) {
    180     patch_source = installer_state.GetInstallerDirectory(product->version())
    181         .Append(installer::kChromeArchive);
    182     if (base::PathExists(patch_source))
    183       return patch_source;
    184   }
    185   scoped_ptr<Version> version(
    186       installer::GetMaxVersionFromArchiveDir(installer_state.target_path()));
    187   if (version) {
    188     patch_source = installer_state.GetInstallerDirectory(*version)
    189         .Append(installer::kChromeArchive);
    190     if (base::PathExists(patch_source))
    191       return patch_source;
    192   }
    193   return base::FilePath();
    194 }
    195 
    196 bool DeleteFileFromTempProcess(const base::FilePath& path,
    197                                uint32 delay_before_delete_ms) {
    198   static const wchar_t kRunDll32Path[] =
    199       L"%SystemRoot%\\System32\\rundll32.exe";
    200   wchar_t rundll32[MAX_PATH];
    201   DWORD size =
    202       ExpandEnvironmentStrings(kRunDll32Path, rundll32, arraysize(rundll32));
    203   if (!size || size >= MAX_PATH)
    204     return false;
    205 
    206   STARTUPINFO startup = { sizeof(STARTUPINFO) };
    207   PROCESS_INFORMATION pi = {0};
    208   BOOL ok = ::CreateProcess(NULL, rundll32, NULL, NULL, FALSE, CREATE_SUSPENDED,
    209                             NULL, NULL, &startup, &pi);
    210   if (ok) {
    211     // We use the main thread of the new process to run:
    212     //   Sleep(delay_before_delete_ms);
    213     //   DeleteFile(path);
    214     //   ExitProcess(0);
    215     // This runs before the main routine of the process runs, so it doesn't
    216     // matter much which executable we choose except that we don't want to
    217     // use e.g. a console app that causes a window to be created.
    218     size = (path.value().length() + 1) * sizeof(path.value()[0]);
    219     void* mem = ::VirtualAllocEx(pi.hProcess, NULL, size, MEM_COMMIT,
    220                                  PAGE_READWRITE);
    221     if (mem) {
    222       SIZE_T written = 0;
    223       ::WriteProcessMemory(
    224           pi.hProcess, mem, path.value().c_str(),
    225           (path.value().size() + 1) * sizeof(path.value()[0]), &written);
    226       HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll");
    227       PAPCFUNC sleep = reinterpret_cast<PAPCFUNC>(
    228           ::GetProcAddress(kernel32, "Sleep"));
    229       PAPCFUNC delete_file = reinterpret_cast<PAPCFUNC>(
    230           ::GetProcAddress(kernel32, "DeleteFileW"));
    231       PAPCFUNC exit_process = reinterpret_cast<PAPCFUNC>(
    232           ::GetProcAddress(kernel32, "ExitProcess"));
    233       if (!sleep || !delete_file || !exit_process) {
    234         NOTREACHED();
    235         ok = FALSE;
    236       } else {
    237         ::QueueUserAPC(sleep, pi.hThread, delay_before_delete_ms);
    238         ::QueueUserAPC(delete_file, pi.hThread,
    239                        reinterpret_cast<ULONG_PTR>(mem));
    240         ::QueueUserAPC(exit_process, pi.hThread, 0);
    241         ::ResumeThread(pi.hThread);
    242       }
    243     } else {
    244       PLOG(ERROR) << "VirtualAllocEx";
    245       ::TerminateProcess(pi.hProcess, ~0);
    246     }
    247     ::CloseHandle(pi.hThread);
    248     ::CloseHandle(pi.hProcess);
    249   }
    250 
    251   return ok != FALSE;
    252 }
    253 
    254 bool GetExistingHigherInstaller(
    255     const InstallationState& original_state,
    256     bool system_install,
    257     const Version& installer_version,
    258     base::FilePath* setup_exe) {
    259   DCHECK(setup_exe);
    260   bool trying_single_browser = false;
    261   const ProductState* existing_state =
    262       original_state.GetProductState(system_install,
    263                                      BrowserDistribution::CHROME_BINARIES);
    264   if (!existing_state) {
    265     // The binaries aren't installed, but perhaps a single-install Chrome is.
    266     trying_single_browser = true;
    267     existing_state =
    268         original_state.GetProductState(system_install,
    269                                        BrowserDistribution::CHROME_BROWSER);
    270   }
    271 
    272   if (!existing_state ||
    273       existing_state->version().CompareTo(installer_version) <= 0) {
    274     return false;
    275   }
    276 
    277   *setup_exe = existing_state->GetSetupPath();
    278 
    279   VLOG_IF(1, !setup_exe->empty()) << "Found a higher version of "
    280       << (trying_single_browser ? "single-install Chrome."
    281           : "multi-install Chrome binaries.");
    282 
    283   return !setup_exe->empty();
    284 }
    285 
    286 bool DeferToExistingInstall(const base::FilePath& setup_exe,
    287                             const CommandLine& command_line,
    288                             const InstallerState& installer_state,
    289                             const base::FilePath& temp_path,
    290                             InstallStatus* install_status) {
    291   // Copy a master_preferences file if there is one.
    292   base::FilePath prefs_source_path(command_line.GetSwitchValueNative(
    293       switches::kInstallerData));
    294   base::FilePath prefs_dest_path(installer_state.target_path().AppendASCII(
    295       kDefaultMasterPrefs));
    296   scoped_ptr<WorkItem> copy_prefs(WorkItem::CreateCopyTreeWorkItem(
    297       prefs_source_path, prefs_dest_path, temp_path, WorkItem::ALWAYS,
    298       base::FilePath()));
    299   // There's nothing to rollback if the copy fails, so punt if so.
    300   if (!copy_prefs->Do())
    301     copy_prefs.reset();
    302 
    303   int exit_code = 0;
    304   if (!LaunchAndWaitForExistingInstall(setup_exe, command_line, &exit_code)) {
    305     if (copy_prefs)
    306       copy_prefs->Rollback();
    307     return false;
    308   }
    309   *install_status = static_cast<InstallStatus>(exit_code);
    310   return true;
    311 }
    312 
    313 // There are 4 disjoint cases => return values {false,true}:
    314 // (1) Product is being uninstalled => false.
    315 // (2) Product is being installed => true.
    316 // (3) Current operation ignores product, product is absent => false.
    317 // (4) Current operation ignores product, product is present => true.
    318 bool WillProductBePresentAfterSetup(
    319     const installer::InstallerState& installer_state,
    320     const installer::InstallationState& machine_state,
    321     BrowserDistribution::Type type) {
    322   DCHECK(SupportsSingleInstall(type) || installer_state.is_multi_install());
    323 
    324   const ProductState* product_state =
    325       machine_state.GetProductState(installer_state.system_install(), type);
    326 
    327   // Determine if the product is present prior to the current operation.
    328   bool is_present = (product_state != NULL);
    329   bool is_uninstall = installer_state.operation() == InstallerState::UNINSTALL;
    330 
    331   // Determine if current operation affects the product.
    332   const Product* product = installer_state.FindProduct(type);
    333   bool is_affected = (product != NULL);
    334 
    335   // Decide among {(1),(2),(3),(4)}.
    336   return is_affected ? !is_uninstall : is_present;
    337 }
    338 
    339 bool AdjustProcessPriority() {
    340   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
    341     DWORD priority_class = ::GetPriorityClass(::GetCurrentProcess());
    342     if (priority_class == 0) {
    343       PLOG(WARNING) << "Failed to get the process's priority class.";
    344     } else if (priority_class == BELOW_NORMAL_PRIORITY_CLASS ||
    345                priority_class == IDLE_PRIORITY_CLASS) {
    346       BOOL result = ::SetPriorityClass(::GetCurrentProcess(),
    347                                        PROCESS_MODE_BACKGROUND_BEGIN);
    348       PLOG_IF(WARNING, !result) << "Failed to enter background mode.";
    349       return !!result;
    350     }
    351   }
    352   return false;
    353 }
    354 
    355 void MigrateGoogleUpdateStateMultiToSingle(
    356     bool system_level,
    357     BrowserDistribution::Type to_migrate,
    358     const installer::InstallationState& machine_state) {
    359   const HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    360   const ProductState* product = NULL;
    361   BrowserDistribution* dist = NULL;
    362   LONG result = ERROR_SUCCESS;
    363   base::win::RegKey state_key;
    364 
    365   Product product_to_migrate(
    366       BrowserDistribution::GetSpecificDistribution(to_migrate));
    367 
    368   // Copy usagestats from the binaries to the product's ClientState key.
    369   product = machine_state.GetProductState(system_level,
    370                                           BrowserDistribution::CHROME_BINARIES);
    371   DWORD usagestats = 0;
    372   if (product && product->GetUsageStats(&usagestats)) {
    373     dist = product_to_migrate.distribution();
    374     result = state_key.Open(root, dist->GetStateKey().c_str(),
    375                             KEY_SET_VALUE);
    376     if (result != ERROR_SUCCESS) {
    377       LOG(ERROR) << "Failed opening ClientState key for "
    378                  << dist->GetDisplayName() << " to migrate usagestats.";
    379     } else {
    380       state_key.WriteValue(google_update::kRegUsageStatsField, usagestats);
    381     }
    382   }
    383 
    384   // Remove the migrating product from the "ap" value of other multi-install
    385   // products.
    386   for (int i = 0; i < BrowserDistribution::NUM_TYPES; ++i) {
    387     BrowserDistribution::Type type =
    388         static_cast<BrowserDistribution::Type>(i);
    389     if (type == to_migrate)
    390       continue;
    391     product = machine_state.GetProductState(system_level, type);
    392     if (product && product->is_multi_install()) {
    393       installer::ChannelInfo channel_info;
    394       dist = BrowserDistribution::GetSpecificDistribution(type);
    395       result = state_key.Open(root, dist->GetStateKey().c_str(),
    396                               KEY_QUERY_VALUE | KEY_SET_VALUE);
    397       if (result == ERROR_SUCCESS &&
    398           channel_info.Initialize(state_key) &&
    399           product_to_migrate.SetChannelFlags(false, &channel_info)) {
    400         VLOG(1) << "Moving " << dist->GetDisplayName()
    401                 << " to channel: " << channel_info.value();
    402         channel_info.Write(&state_key);
    403       }
    404     }
    405   }
    406 
    407   // Remove -multi, all product modifiers, and everything else but the channel
    408   // name from the "ap" value of the product to migrate.
    409   dist = product_to_migrate.distribution();
    410   result = state_key.Open(root, dist->GetStateKey().c_str(),
    411                           KEY_QUERY_VALUE | KEY_SET_VALUE);
    412   if (result == ERROR_SUCCESS) {
    413     installer::ChannelInfo channel_info;
    414     if (!channel_info.Initialize(state_key)) {
    415       LOG(ERROR) << "Failed reading " << dist->GetDisplayName()
    416                  << " channel info.";
    417     } else if (channel_info.RemoveAllModifiersAndSuffixes()) {
    418       VLOG(1) << "Moving " << dist->GetDisplayName()
    419               << " to channel: " << channel_info.value();
    420       channel_info.Write(&state_key);
    421     }
    422   }
    423 }
    424 
    425 bool IsUninstallSuccess(InstallStatus install_status) {
    426   // The following status values represent failed uninstalls:
    427   // 15: CHROME_NOT_INSTALLED
    428   // 20: UNINSTALL_FAILED
    429   // 21: UNINSTALL_CANCELLED
    430   return (install_status == UNINSTALL_SUCCESSFUL ||
    431           install_status == UNINSTALL_REQUIRES_REBOOT);
    432 }
    433 
    434 bool ContainsUnsupportedSwitch(const CommandLine& cmd_line) {
    435   static const char* const kLegacySwitches[] = {
    436     // Chrome Frame ready-mode.
    437     "ready-mode",
    438     "ready-mode-opt-in",
    439     "ready-mode-temp-opt-out",
    440     "ready-mode-end-temp-opt-out",
    441     // Chrome Frame quick-enable.
    442     "quick-enable-cf",
    443     // Installation of Chrome Frame.
    444     "chrome-frame",
    445     "migrate-chrome-frame",
    446   };
    447   for (size_t i = 0; i < arraysize(kLegacySwitches); ++i) {
    448     if (cmd_line.HasSwitch(kLegacySwitches[i]))
    449       return true;
    450   }
    451   return false;
    452 }
    453 
    454 bool IsProcessorSupported() {
    455   return base::CPU().has_sse2();
    456 }
    457 
    458 ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name)
    459     : is_enabled_(false) {
    460   HANDLE temp_handle;
    461   if (!::OpenProcessToken(::GetCurrentProcess(),
    462                           TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
    463                           &temp_handle)) {
    464     return;
    465   }
    466   token_.Set(temp_handle);
    467 
    468   LUID privilege_luid;
    469   if (!::LookupPrivilegeValue(NULL, privilege_name, &privilege_luid)) {
    470     token_.Close();
    471     return;
    472   }
    473 
    474   // Adjust the token's privileges to enable |privilege_name|. If this privilege
    475   // was already enabled, |previous_privileges_|.PrivilegeCount will be set to 0
    476   // and we then know not to disable this privilege upon destruction.
    477   TOKEN_PRIVILEGES tp;
    478   tp.PrivilegeCount = 1;
    479   tp.Privileges[0].Luid = privilege_luid;
    480   tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    481   DWORD return_length;
    482   if (!::AdjustTokenPrivileges(token_, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
    483                                &previous_privileges_, &return_length)) {
    484     token_.Close();
    485     return;
    486   }
    487 
    488   is_enabled_ = true;
    489 }
    490 
    491 ScopedTokenPrivilege::~ScopedTokenPrivilege() {
    492   if (is_enabled_ && previous_privileges_.PrivilegeCount != 0) {
    493     ::AdjustTokenPrivileges(token_, FALSE, &previous_privileges_,
    494                             sizeof(TOKEN_PRIVILEGES), NULL, NULL);
    495   }
    496 }
    497 
    498 }  // namespace installer
    499