Home | History | Annotate | Download | only in test
      1 // Copyright (c) 2011 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 // The file contains the implementation of the mini_installer re-versioner.
      6 // The main function (GenerateNextVersion) does the following in a temp dir:
      7 // - Extracts and unpacks setup.exe and the Chrome-bin folder from
      8 //   mini_installer.exe.
      9 // - Inspects setup.exe to determine the current version.
     10 // - Runs through all .dll and .exe files:
     11 //   - Replacing all occurrences of the Unicode version string in the files'
     12 //     resources with the updated string.
     13 //   - For all resources in which the string substitution is made, the binary
     14 //     form of the version is also replaced.
     15 // - Re-packs setup.exe and Chrome-bin.
     16 // - Inserts them into the target mini_installer.exe.
     17 //
     18 // This code assumes that the host program 1) initializes the process-wide
     19 // CommandLine instance, and 2) resides in the output directory of a build
     20 // tree.  When #2 is not the case, the --7za_path command-line switch may be
     21 // used to provide the (relative or absolute) path to the directory containing
     22 // 7za.exe.
     23 
     24 #include "chrome/installer/test/alternate_version_generator.h"
     25 
     26 #include <windows.h>
     27 
     28 #include <algorithm>
     29 #include <limits>
     30 #include <sstream>
     31 #include <utility>
     32 #include <vector>
     33 
     34 #include "base/basictypes.h"
     35 #include "base/command_line.h"
     36 #include "base/file_util.h"
     37 #include "base/files/file_enumerator.h"
     38 #include "base/files/file_path.h"
     39 #include "base/logging.h"
     40 #include "base/path_service.h"
     41 #include "base/platform_file.h"
     42 #include "base/process/launch.h"
     43 #include "base/process/process_handle.h"
     44 #include "base/strings/string_util.h"
     45 #include "base/version.h"
     46 #include "base/win/pe_image.h"
     47 #include "base/win/scoped_handle.h"
     48 #include "chrome/installer/test/pe_image_resources.h"
     49 #include "chrome/installer/test/resource_loader.h"
     50 #include "chrome/installer/test/resource_updater.h"
     51 #include "chrome/installer/util/lzma_util.h"
     52 
     53 namespace {
     54 
     55 const wchar_t k7zaExe[] = L"7za.exe";
     56 const wchar_t k7zaPathRelative[] = L"..\\..\\third_party\\lzma_sdk\\Executable";
     57 const wchar_t kB7[] = L"B7";
     58 const wchar_t kBl[] = L"BL";
     59 const wchar_t kChrome7z[] = L"chrome.7z";
     60 const wchar_t kChromeBin[] = L"Chrome-bin";
     61 const wchar_t kChromePacked7z[] = L"chrome.packed.7z";
     62 const wchar_t kExe[] = L"exe";
     63 const wchar_t kExpandExe[] = L"expand.exe";
     64 const wchar_t kExtDll[] = L".dll";
     65 const wchar_t kExtExe[] = L".exe";
     66 const wchar_t kMakeCab[] = L"makecab.exe";
     67 const wchar_t kSetupEx_[] = L"setup.ex_";
     68 const wchar_t kSetupExe[] = L"setup.exe";
     69 const char kSwitch7zaPath[] = "7za_path";
     70 const wchar_t kTempDirPrefix[] = L"mini_installer_test_temp";
     71 
     72 // A helper class for creating and cleaning a temporary directory.  A temporary
     73 // directory is created in Initialize and destroyed (along with all of its
     74 // contents) when the guard instance is destroyed.
     75 class ScopedTempDirectory {
     76  public:
     77   ScopedTempDirectory() { }
     78   ~ScopedTempDirectory() {
     79     if (!directory_.empty() && !base::DeleteFile(directory_, true)) {
     80       LOG(DFATAL) << "Failed deleting temporary directory \""
     81                   << directory_.value() << "\"";
     82     }
     83   }
     84   // Creates a temporary directory.
     85   bool Initialize() {
     86     DCHECK(directory_.empty());
     87     if (!base::CreateNewTempDirectory(&kTempDirPrefix[0], &directory_)) {
     88       LOG(DFATAL) << "Failed creating temporary directory.";
     89       return false;
     90     }
     91     return true;
     92   }
     93   const base::FilePath& directory() const {
     94     DCHECK(!directory_.empty());
     95     return directory_;
     96   }
     97 
     98  private:
     99   base::FilePath directory_;
    100   DISALLOW_COPY_AND_ASSIGN(ScopedTempDirectory);
    101 };  // class ScopedTempDirectory
    102 
    103 // A helper class for manipulating a Chrome product version.
    104 class ChromeVersion {
    105  public:
    106   static ChromeVersion FromHighLow(DWORD high, DWORD low) {
    107     return ChromeVersion(static_cast<ULONGLONG>(high) << 32 |
    108                          static_cast<ULONGLONG>(low));
    109   }
    110   static ChromeVersion FromString(const std::string& version_string) {
    111     Version version(version_string);
    112     DCHECK(version.IsValid());
    113     const std::vector<uint16>& c(version.components());
    114     return ChromeVersion(static_cast<ULONGLONG>(c[0]) << 48 |
    115                          static_cast<ULONGLONG>(c[1]) << 32 |
    116                          static_cast<ULONGLONG>(c[2]) << 16 |
    117                          static_cast<ULONGLONG>(c[3]));
    118   }
    119 
    120   ChromeVersion() { }
    121   explicit ChromeVersion(ULONGLONG value) : version_(value) { }
    122   WORD major() const { return static_cast<WORD>(version_ >> 48); }
    123   WORD minor() const { return static_cast<WORD>(version_ >> 32); }
    124   WORD build() const { return static_cast<WORD>(version_ >> 16); }
    125   WORD patch() const { return static_cast<WORD>(version_); }
    126   DWORD high() const { return static_cast<DWORD>(version_ >> 32); }
    127   DWORD low() const { return static_cast<DWORD>(version_); }
    128   ULONGLONG value() const { return version_; }
    129   void set_value(ULONGLONG value) { version_ = value; }
    130   std::wstring ToString() const;
    131  private:
    132   ULONGLONG version_;
    133 };  // class ChromeVersion
    134 
    135 std::wstring ChromeVersion::ToString() const {
    136   wchar_t buffer[24];
    137   int string_len =
    138       swprintf_s(&buffer[0], arraysize(buffer), L"%hu.%hu.%hu.%hu",
    139                  major(), minor(), build(), patch());
    140   DCHECK_NE(-1, string_len);
    141   DCHECK_GT(static_cast<int>(arraysize(buffer)), string_len);
    142   return std::wstring(&buffer[0], string_len);
    143 }
    144 
    145 
    146 // A read/write mapping of a file.
    147 // Note: base::MemoryMappedFile is not used because it doesn't support
    148 // read/write mappings.  Adding such support across all platforms for this
    149 // Windows-only test code seems like overkill.
    150 class MappedFile {
    151  public:
    152   MappedFile() : size_(), mapping_(), view_() { }
    153   ~MappedFile();
    154   bool Initialize(base::PlatformFile file);
    155   void* data() const { return view_; }
    156   size_t size() const { return size_; }
    157  private:
    158   size_t size_;
    159   HANDLE mapping_;
    160   void* view_;
    161   DISALLOW_COPY_AND_ASSIGN(MappedFile);
    162 };  // class MappedFile
    163 
    164 MappedFile::~MappedFile() {
    165   if (view_ != NULL) {
    166     if (UnmapViewOfFile(view_) == 0) {
    167       PLOG(DFATAL) << "MappedFile failed to unmap view.";
    168     }
    169   }
    170   if (mapping_ != NULL) {
    171     if (CloseHandle(mapping_) == 0) {
    172       PLOG(DFATAL) << "Could not close file mapping handle.";
    173     }
    174   }
    175 }
    176 
    177 bool MappedFile::Initialize(base::PlatformFile file) {
    178   DCHECK(mapping_ == NULL);
    179   bool result = false;
    180   base::PlatformFileInfo file_info;
    181 
    182   if (base::GetPlatformFileInfo(file, &file_info)) {
    183     if (file_info.size <=
    184         static_cast<int64>(std::numeric_limits<DWORD>::max())) {
    185       mapping_ = CreateFileMapping(file, NULL, PAGE_READWRITE, 0,
    186                                    static_cast<DWORD>(file_info.size), NULL);
    187       if (mapping_ != NULL) {
    188         view_ = MapViewOfFile(mapping_, FILE_MAP_WRITE, 0, 0,
    189                               static_cast<size_t>(file_info.size));
    190         if (view_ != NULL) {
    191           result = true;
    192         } else {
    193           PLOG(DFATAL) << "MapViewOfFile failed";
    194         }
    195       } else {
    196         PLOG(DFATAL) << "CreateFileMapping failed";
    197       }
    198     } else {
    199       LOG(DFATAL) << "Files larger than " << std::numeric_limits<DWORD>::max()
    200                   << " are not supported.";
    201     }
    202   } else {
    203     PLOG(DFATAL) << "GetPlatformFileInfo failed";
    204   }
    205   return result;
    206 }
    207 
    208 // Calls CreateProcess with good default parameters and waits for the process
    209 // to terminate returning the process exit code.
    210 bool RunProcessAndWait(const wchar_t* exe_path, const std::wstring& cmdline,
    211                        int* exit_code) {
    212   bool result = true;
    213   base::win::ScopedHandle process;
    214   base::LaunchOptions options;
    215   options.wait = true;
    216   options.start_hidden = true;
    217   if (base::LaunchProcess(cmdline, options, &process)) {
    218     if (exit_code) {
    219       if (!GetExitCodeProcess(process.Get(),
    220                               reinterpret_cast<DWORD*>(exit_code))) {
    221         PLOG(DFATAL) << "Failed getting the exit code for \""
    222                      << cmdline << "\".";
    223         result = false;
    224       } else {
    225         DCHECK_NE(*exit_code, STILL_ACTIVE);
    226       }
    227     }
    228   } else {
    229     result = false;
    230   }
    231 
    232   return result;
    233 }
    234 
    235 // Retrieves the version number of |pe_file| from its version
    236 // resource, placing the value in |version|.  Returns true on success.
    237 bool GetFileVersion(const base::FilePath& pe_file, ChromeVersion* version) {
    238   DCHECK(version);
    239   bool result = false;
    240   upgrade_test::ResourceLoader pe_file_loader;
    241   std::pair<const uint8*, DWORD> version_info_data;
    242 
    243   if (pe_file_loader.Initialize(pe_file) &&
    244       pe_file_loader.Load(VS_VERSION_INFO, reinterpret_cast<WORD>(RT_VERSION),
    245                           &version_info_data)) {
    246     const VS_FIXEDFILEINFO* fixed_file_info;
    247     UINT ver_info_len;
    248     if (VerQueryValue(version_info_data.first, L"\\",
    249                       reinterpret_cast<void**>(
    250                           const_cast<VS_FIXEDFILEINFO**>(&fixed_file_info)),
    251                       &ver_info_len) != 0) {
    252       DCHECK_EQ(sizeof(VS_FIXEDFILEINFO), static_cast<size_t>(ver_info_len));
    253       *version = ChromeVersion::FromHighLow(fixed_file_info->dwFileVersionMS,
    254                                             fixed_file_info->dwFileVersionLS);
    255       result = true;
    256     } else {
    257       LOG(DFATAL) << "VerQueryValue failed to retrieve VS_FIXEDFILEINFO";
    258     }
    259   }
    260 
    261   return result;
    262 }
    263 
    264 // Retrieves the version number of setup.exe in |work_dir| from its version
    265 // resource, placing the value in |version|.  Returns true on success.
    266 bool GetSetupExeVersion(const base::FilePath& work_dir,
    267                         ChromeVersion* version) {
    268   return GetFileVersion(work_dir.Append(&kSetupExe[0]), version);
    269 }
    270 
    271 
    272 // Replace all occurrences in the sequence [|dest_first|, |dest_last) that
    273 // equals [|src_first|, |src_last) with the sequence at |replacement_first| of
    274 // the same length.  Returns true on success.  If non-NULL, |replacements_made|
    275 // is set to true/false accordingly.
    276 bool ReplaceAll(uint8* dest_first, uint8* dest_last,
    277                 const uint8* src_first, const uint8* src_last,
    278                 const uint8* replacement_first, bool* replacements_made) {
    279   bool result = true;
    280   bool changed = false;
    281   do {
    282     dest_first = std::search(dest_first, dest_last, src_first, src_last);
    283     if (dest_first == dest_last) {
    284       break;
    285     }
    286     changed = true;
    287     if (memcpy_s(dest_first, dest_last - dest_first,
    288                  replacement_first, src_last - src_first) != 0) {
    289       result = false;
    290       break;
    291     }
    292     dest_first += (src_last - src_first);
    293   } while (true);
    294 
    295   if (replacements_made != NULL) {
    296     *replacements_made = changed;
    297   }
    298 
    299   return result;
    300 }
    301 
    302 // A context structure in support of our EnumResource_Fn callback.
    303 struct VisitResourceContext {
    304   ChromeVersion current_version;
    305   std::wstring current_version_str;
    306   ChromeVersion new_version;
    307   std::wstring new_version_str;
    308 };  // struct VisitResourceContext
    309 
    310 // Replaces the old version with the new in a resource.  A first pass is made to
    311 // replace the string form (e.g., "9.0.584.0").  If any replacements are made, a
    312 // second pass is made to replace the binary form (e.g., 0x0000024800000009).
    313 void VisitResource(const upgrade_test::EntryPath& path,
    314                    uint8* data, DWORD size, DWORD code_page,
    315                    uintptr_t context) {
    316   VisitResourceContext& ctx = *reinterpret_cast<VisitResourceContext*>(context);
    317 
    318   // Replace all occurrences of current_version_str with new_version_str
    319   bool changing_version = false;
    320   if (ReplaceAll(
    321           data,
    322           data + size,
    323           reinterpret_cast<const uint8*>(ctx.current_version_str.c_str()),
    324           reinterpret_cast<const uint8*>(ctx.current_version_str.c_str() +
    325               ctx.current_version_str.size() + 1),
    326           reinterpret_cast<const uint8*>(ctx.new_version_str.c_str()),
    327           &changing_version) &&
    328       changing_version) {
    329     // Replace all occurrences of current_version with new_version
    330     struct VersionPair {
    331       DWORD high;
    332       DWORD low;
    333     };
    334     VersionPair cur_ver = {
    335       ctx.current_version.high(), ctx.current_version.low()
    336     };
    337     VersionPair new_ver = {
    338       ctx.new_version.high(), ctx.new_version.low()
    339     };
    340     ReplaceAll(data, data + size, reinterpret_cast<const uint8*>(&cur_ver),
    341                reinterpret_cast<const uint8*>(&cur_ver) + sizeof(cur_ver),
    342                reinterpret_cast<const uint8*>(&new_ver), NULL);
    343   }
    344 }
    345 
    346 // Updates the version strings and numbers in all of |image_file|'s resources.
    347 bool UpdateVersionIfMatch(const base::FilePath& image_file,
    348                           VisitResourceContext* context) {
    349   if (!context ||
    350       context->current_version_str.size() < context->new_version_str.size()) {
    351     return false;
    352   }
    353 
    354   bool result = false;
    355   base::win::ScopedHandle image_handle(base::CreatePlatformFile(
    356       image_file,
    357       (base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ |
    358        base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_READ |
    359        base::PLATFORM_FILE_EXCLUSIVE_WRITE), NULL, NULL));
    360   // It turns out that the underlying CreateFile can fail due to unhelpful
    361   // security software locking the newly created DLL. So add a few brief
    362   // retries to help tests that use this pass on machines thusly encumbered.
    363   int retries = 3;
    364   while (!image_handle.IsValid() && retries-- > 0) {
    365     LOG(WARNING) << "Failed to open \"" << image_file.value() << "\"."
    366                  << " Retrying " << retries << " more times.";
    367     Sleep(1000);
    368     image_handle.Set(base::CreatePlatformFile(
    369       image_file,
    370       (base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ |
    371        base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_READ |
    372        base::PLATFORM_FILE_EXCLUSIVE_WRITE), NULL, NULL));
    373   }
    374 
    375   if (image_handle.IsValid()) {
    376     MappedFile image_mapping;
    377     if (image_mapping.Initialize(image_handle)) {
    378       base::win::PEImageAsData image(
    379           reinterpret_cast<HMODULE>(image_mapping.data()));
    380       // PEImage class does not support other-architecture images.
    381       if (image.GetNTHeaders()->OptionalHeader.Magic ==
    382           IMAGE_NT_OPTIONAL_HDR_MAGIC) {
    383         result = upgrade_test::EnumResources(
    384             image, &VisitResource, reinterpret_cast<uintptr_t>(context));
    385       } else {
    386         result = true;
    387       }
    388     }
    389   } else {
    390     PLOG(DFATAL) << "Failed to open \"" << image_file.value() << "\"";
    391   }
    392   return result;
    393 }
    394 
    395 bool IncrementNewVersion(upgrade_test::Direction direction,
    396                          VisitResourceContext* ctx) {
    397   DCHECK(ctx);
    398 
    399   // Figure out a past or future version with the same string length as this one
    400   // by decrementing or incrementing each component.
    401   LONGLONG incrementer = (direction == upgrade_test::PREVIOUS_VERSION ? -1 : 1);
    402 
    403   do {
    404     if (incrementer == 0) {
    405       LOG(DFATAL) << "Improbable version at the cusp of complete rollover";
    406       return false;
    407     }
    408     ctx->new_version.set_value(ctx->current_version.value() + incrementer);
    409     ctx->new_version_str = ctx->new_version.ToString();
    410     incrementer <<= 16;
    411   } while (ctx->new_version_str.size() != ctx->current_version_str.size());
    412 
    413   return true;
    414 }
    415 
    416 // Raises or lowers the version of all .exe and .dll files in |work_dir| as well
    417 // as the |work-dir|\Chrome-bin\w.x.y.z directory.  |original_version| and
    418 // |new_version|, when non-NULL, are given the original and new version numbers
    419 // on success.
    420 bool ApplyAlternateVersion(const base::FilePath& work_dir,
    421                            upgrade_test::Direction direction,
    422                            std::wstring* original_version,
    423                            std::wstring* new_version) {
    424   VisitResourceContext ctx;
    425   if (!GetSetupExeVersion(work_dir, &ctx.current_version)) {
    426     return false;
    427   }
    428   ctx.current_version_str = ctx.current_version.ToString();
    429 
    430   if (!IncrementNewVersion(direction, &ctx)) {
    431     return false;
    432   }
    433 
    434   // Modify all .dll and .exe files with the current version.
    435   bool doing_great = true;
    436   base::FileEnumerator all_files(work_dir, true, base::FileEnumerator::FILES);
    437   do {
    438     base::FilePath file = all_files.Next();
    439     if (file.empty()) {
    440       break;
    441     }
    442     std::wstring extension = file.Extension();
    443     if (extension == &kExtExe[0] || extension == &kExtDll[0]) {
    444       doing_great = UpdateVersionIfMatch(file, &ctx);
    445     }
    446   } while (doing_great);
    447 
    448   // Change the versioned directory.
    449   base::FilePath chrome_bin = work_dir.Append(&kChromeBin[0]);
    450   doing_great = base::Move(chrome_bin.Append(ctx.current_version_str),
    451                            chrome_bin.Append(ctx.new_version_str));
    452 
    453   if (doing_great) {
    454     // Report the version numbers if requested.
    455     if (original_version != NULL)
    456       original_version->assign(ctx.current_version_str);
    457     if (new_version != NULL)
    458       new_version->assign(ctx.new_version_str);
    459   }
    460 
    461   return doing_great;
    462 }
    463 
    464 // Returns the path to the directory holding the 7za executable.  By default, it
    465 // is assumed that the test resides in the tree's output directory, so the
    466 // relative path "..\..\third_party\lzma_sdk\Executable" is applied to the host
    467 // executable's directory.  This can be overridden with the --7za_path
    468 // command-line switch.
    469 base::FilePath Get7zaPath() {
    470   base::FilePath l7za_path =
    471       CommandLine::ForCurrentProcess()->GetSwitchValuePath(
    472           &kSwitch7zaPath[0]);
    473   if (l7za_path.empty()) {
    474     base::FilePath dir_exe;
    475     if (!PathService::Get(base::DIR_EXE, &dir_exe))
    476       LOG(DFATAL) << "Failed getting directory of host executable";
    477     l7za_path = dir_exe.Append(&k7zaPathRelative[0]);
    478   }
    479   return l7za_path;
    480 }
    481 
    482 bool CreateArchive(const base::FilePath& output_file,
    483                    const base::FilePath& input_path,
    484                    int compression_level) {
    485   DCHECK(compression_level == 0 ||
    486          compression_level >= 1 && compression_level <= 9 &&
    487          (compression_level & 0x01) != 0);
    488 
    489   std::wstring command_line(1, L'"');
    490   command_line
    491       .append(Get7zaPath().Append(&k7zaExe[0]).value())
    492       .append(L"\" a -bd -t7z \"")
    493       .append(output_file.value())
    494       .append(L"\" \"")
    495       .append(input_path.value())
    496       .append(L"\" -mx")
    497       .append(1, L'0' + compression_level);
    498   int exit_code;
    499   if (!RunProcessAndWait(NULL, command_line, &exit_code))
    500     return false;
    501   if (exit_code != 0) {
    502     LOG(DFATAL) << Get7zaPath().Append(&k7zaExe[0]).value()
    503                 << " exited with code " << exit_code
    504                 << " while creating " << output_file.value();
    505     return false;
    506   }
    507   return true;
    508 }
    509 
    510 }  // namespace
    511 
    512 namespace upgrade_test {
    513 
    514 bool GenerateAlternateVersion(const base::FilePath& original_installer_path,
    515                               const base::FilePath& target_path,
    516                               Direction direction,
    517                               std::wstring* original_version,
    518                               std::wstring* new_version) {
    519   // Create a temporary directory in which we'll do our work.
    520   ScopedTempDirectory work_dir;
    521   if (!work_dir.Initialize())
    522     return false;
    523 
    524   // Copy the original mini_installer.
    525   base::FilePath mini_installer =
    526       work_dir.directory().Append(original_installer_path.BaseName());
    527   if (!base::CopyFile(original_installer_path, mini_installer)) {
    528     LOG(DFATAL) << "Failed copying \"" << original_installer_path.value()
    529                 << "\" to \"" << mini_installer.value() << "\"";
    530     return false;
    531   }
    532 
    533   base::FilePath setup_ex_ = work_dir.directory().Append(&kSetupEx_[0]);
    534   base::FilePath chrome_packed_7z =
    535       work_dir.directory().Append(&kChromePacked7z[0]);
    536   // Load the original file and extract setup.ex_ and chrome.packed.7z
    537   {
    538     ResourceLoader resource_loader;
    539     std::pair<const uint8*, DWORD> resource_data;
    540 
    541     if (!resource_loader.Initialize(mini_installer))
    542       return false;
    543 
    544     // Write out setup.ex_
    545     if (!resource_loader.Load(&kSetupEx_[0], &kBl[0], &resource_data))
    546       return false;
    547     int written =
    548         file_util::WriteFile(setup_ex_,
    549                              reinterpret_cast<const char*>(resource_data.first),
    550                              static_cast<int>(resource_data.second));
    551     if (written != resource_data.second) {
    552       LOG(DFATAL) << "Failed writing \"" << setup_ex_.value() << "\"";
    553       return false;
    554     }
    555 
    556     // Write out chrome.packed.7z
    557     if (!resource_loader.Load(&kChromePacked7z[0], &kB7[0], &resource_data))
    558       return false;
    559     written =
    560         file_util::WriteFile(chrome_packed_7z,
    561                              reinterpret_cast<const char*>(resource_data.first),
    562                              static_cast<int>(resource_data.second));
    563     if (written != resource_data.second) {
    564       LOG(DFATAL) << "Failed writing \"" << chrome_packed_7z.value() << "\"";
    565       return false;
    566     }
    567   }
    568 
    569   // Expand setup.ex_
    570   base::FilePath setup_exe = setup_ex_.ReplaceExtension(&kExe[0]);
    571   std::wstring command_line;
    572   command_line.append(1, L'"')
    573     .append(&kExpandExe[0])
    574     .append(L"\" \"")
    575     .append(setup_ex_.value())
    576     .append(L"\" \"")
    577     .append(setup_exe.value())
    578     .append(1, L'\"');
    579   int exit_code;
    580   if (!RunProcessAndWait(NULL, command_line, &exit_code))
    581     return false;
    582   if (exit_code != 0) {
    583     LOG(DFATAL) << &kExpandExe[0] << " exited with code " << exit_code;
    584     return false;
    585   }
    586 
    587   // Unpack chrome.packed.7z
    588   std::wstring chrome_7z_name;
    589   if (LzmaUtil::UnPackArchive(chrome_packed_7z.value(),
    590                               work_dir.directory().value(),
    591                               &chrome_7z_name) != NO_ERROR) {
    592     LOG(DFATAL) << "Failed unpacking \"" << chrome_packed_7z.value() << "\"";
    593     return false;
    594   }
    595 
    596   // Unpack chrome.7z
    597   if (LzmaUtil::UnPackArchive(chrome_7z_name, work_dir.directory().value(),
    598                               NULL) != NO_ERROR) {
    599     LOG(DFATAL) << "Failed unpacking \"" << chrome_7z_name << "\"";
    600     return false;
    601   }
    602 
    603   // Get rid of intermediate files
    604   base::FilePath chrome_7z(chrome_7z_name);
    605   if (!base::DeleteFile(chrome_7z, false) ||
    606       !base::DeleteFile(chrome_packed_7z, false) ||
    607       !base::DeleteFile(setup_ex_, false)) {
    608     LOG(DFATAL) << "Failed deleting intermediate files";
    609     return false;
    610   }
    611 
    612   // Increment the version in all files.
    613   ApplyAlternateVersion(work_dir.directory(), direction, original_version,
    614                         new_version);
    615 
    616   // Pack up files into chrome.7z
    617   if (!CreateArchive(chrome_7z, work_dir.directory().Append(&kChromeBin[0]), 0))
    618     return false;
    619 
    620   // Compress chrome.7z into chrome.packed.7z
    621   if (!CreateArchive(chrome_packed_7z, chrome_7z, 9))
    622     return false;
    623 
    624   // Compress setup.exe into setup.ex_
    625   command_line.assign(1, L'"')
    626       .append(&kMakeCab[0])
    627       .append(L"\" /D CompressionType=LZX /L \"")
    628       .append(work_dir.directory().value())
    629       .append(L"\" \"")
    630       .append(setup_exe.value());
    631   if (!RunProcessAndWait(NULL, command_line, &exit_code))
    632     return false;
    633   if (exit_code != 0) {
    634     LOG(DFATAL) << &kMakeCab[0] << " exited with code " << exit_code;
    635     return false;
    636   }
    637 
    638   // Replace the mini_installer's setup.ex_ and chrome.packed.7z resources.
    639   ResourceUpdater updater;
    640   if (!updater.Initialize(mini_installer) ||
    641       !updater.Update(&kSetupEx_[0], &kBl[0],
    642                       MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
    643                       setup_ex_) ||
    644       !updater.Update(&kChromePacked7z[0], &kB7[0],
    645                       MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
    646                       chrome_packed_7z) ||
    647       !updater.Commit()) {
    648     return false;
    649   }
    650 
    651   // Finally, move the updated mini_installer into place.
    652   return base::Move(mini_installer, target_path);
    653 }
    654 
    655 bool GenerateAlternatePEFileVersion(const base::FilePath& original_file,
    656                                     const base::FilePath& target_file,
    657                                     Direction direction) {
    658   VisitResourceContext ctx;
    659   if (!GetFileVersion(original_file, &ctx.current_version)) {
    660     LOG(DFATAL) << "Failed reading version from \"" << original_file.value()
    661                 << "\"";
    662     return false;
    663   }
    664   ctx.current_version_str = ctx.current_version.ToString();
    665 
    666   if (!IncrementNewVersion(direction, &ctx)) {
    667     LOG(DFATAL) << "Failed to increment version from \""
    668                 << original_file.value() << "\"";
    669     return false;
    670   }
    671 
    672   Version new_version(WideToASCII(ctx.new_version_str));
    673   GenerateSpecificPEFileVersion(original_file, target_file, new_version);
    674 
    675   return true;
    676 }
    677 
    678 bool GenerateSpecificPEFileVersion(const base::FilePath& original_file,
    679                                    const base::FilePath& target_file,
    680                                    const Version& version) {
    681   // First copy original_file to target_file.
    682   if (!base::CopyFile(original_file, target_file)) {
    683     LOG(DFATAL) << "Failed copying \"" << original_file.value()
    684                 << "\" to \"" << target_file.value() << "\"";
    685     return false;
    686   }
    687 
    688   VisitResourceContext ctx;
    689   if (!GetFileVersion(target_file, &ctx.current_version)) {
    690     LOG(DFATAL) << "Failed reading version from \"" << target_file.value()
    691                 << "\"";
    692     return false;
    693   }
    694   ctx.current_version_str = ctx.current_version.ToString();
    695   ctx.new_version = ChromeVersion::FromString(version.GetString());
    696   ctx.new_version_str = ctx.new_version.ToString();
    697 
    698   return UpdateVersionIfMatch(target_file, &ctx);
    699 }
    700 
    701 }  // namespace upgrade_test
    702