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