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 <stddef.h>
29 #include <stdint.h>
30 
31 #include <algorithm>
32 #include <limits>
33 #include <sstream>
34 #include <string>
35 #include <utility>
36 #include <vector>
37 
38 #include "base/command_line.h"
39 #include "base/files/file.h"
40 #include "base/files/file_enumerator.h"
41 #include "base/files/file_path.h"
42 #include "base/files/file_util.h"
43 #include "base/files/memory_mapped_file.h"
44 #include "base/logging.h"
45 #include "base/path_service.h"
46 #include "base/process/launch.h"
47 #include "base/process/process_handle.h"
48 #include "base/stl_util.h"
49 #include "base/strings/string16.h"
50 #include "base/strings/string_util.h"
51 #include "base/strings/utf_string_conversions.h"
52 #include "base/time/time.h"
53 #include "base/version.h"
54 #include "base/win/pe_image.h"
55 #include "base/win/scoped_handle.h"
56 #include "chrome/installer/test/pe_image_resources.h"
57 #include "chrome/installer/test/resource_loader.h"
58 #include "chrome/installer/test/resource_updater.h"
59 #include "chrome/installer/util/lzma_util.h"
60 
61 namespace {
62 
63 const base::char16 k7zaExe[] = L"7za.exe";
64 const base::char16 k7zaPathRelative[] =
65     L"..\\..\\third_party\\lzma_sdk\\Executable";
66 const base::char16 kB7[] = L"B7";
67 const base::char16 kBl[] = L"BL";
68 const base::char16 kChromeBin[] = L"Chrome-bin";
69 const base::char16 kChromePacked7z[] = L"CHROME.PACKED.7Z";
70 const base::char16 kChrome7z[] = L"CHROME.7Z";
71 const base::char16 kExe[] = L"exe";
72 const base::char16 kExpandExe[] = L"expand.exe";
73 const base::char16 kExtDll[] = L".dll";
74 const base::char16 kExtExe[] = L".exe";
75 const base::char16 kMakeCab[] = L"makecab.exe";
76 const base::char16 kSetupEx_[] = L"setup.ex_";
77 const base::char16 kSetupExe[] = L"setup.exe";
78 const char kSwitch7zaPath[] = "7za_path";
79 const base::char16 kTempDirPrefix[] = L"mini_installer_test_temp";
80 
81 // A helper class for creating and cleaning a temporary directory.  A temporary
82 // directory is created in Initialize and destroyed (along with all of its
83 // contents) when the guard instance is destroyed.
84 class ScopedTempDirectory {
85  public:
ScopedTempDirectory()86   ScopedTempDirectory() {}
~ScopedTempDirectory()87   ~ScopedTempDirectory() {
88     if (!directory_.empty() && !base::DeletePathRecursively(directory_)) {
89       LOG(DFATAL) << "Failed deleting temporary directory \""
90                   << directory_.value() << "\"";
91     }
92   }
93   // Creates a temporary directory.
Initialize()94   bool Initialize() {
95     DCHECK(directory_.empty());
96     if (!base::CreateNewTempDirectory(&kTempDirPrefix[0], &directory_)) {
97       LOG(DFATAL) << "Failed creating temporary directory.";
98       return false;
99     }
100     return true;
101   }
directory() const102   const base::FilePath& directory() const {
103     DCHECK(!directory_.empty());
104     return directory_;
105   }
106 
107  private:
108   base::FilePath directory_;
109   DISALLOW_COPY_AND_ASSIGN(ScopedTempDirectory);
110 };  // class ScopedTempDirectory
111 
112 // A helper class for manipulating a Chrome product version.
113 class ChromeVersion {
114  public:
FromHighLow(DWORD high,DWORD low)115   static ChromeVersion FromHighLow(DWORD high, DWORD low) {
116     return ChromeVersion(static_cast<ULONGLONG>(high) << 32 |
117                          static_cast<ULONGLONG>(low));
118   }
FromString(const std::string & version_string)119   static ChromeVersion FromString(const std::string& version_string) {
120     base::Version version(version_string);
121     DCHECK(version.IsValid());
122     const std::vector<uint32_t>& c(version.components());
123     return ChromeVersion(static_cast<ULONGLONG>(c[0]) << 48 |
124                          static_cast<ULONGLONG>(c[1]) << 32 |
125                          static_cast<ULONGLONG>(c[2]) << 16 |
126                          static_cast<ULONGLONG>(c[3]));
127   }
128 
ChromeVersion()129   ChromeVersion() {}
ChromeVersion(ULONGLONG value)130   explicit ChromeVersion(ULONGLONG value) : version_(value) {}
major() const131   WORD major() const { return static_cast<WORD>(version_ >> 48); }
minor() const132   WORD minor() const { return static_cast<WORD>(version_ >> 32); }
build() const133   WORD build() const { return static_cast<WORD>(version_ >> 16); }
patch() const134   WORD patch() const { return static_cast<WORD>(version_); }
high() const135   DWORD high() const { return static_cast<DWORD>(version_ >> 32); }
low() const136   DWORD low() const { return static_cast<DWORD>(version_); }
value() const137   ULONGLONG value() const { return version_; }
set_value(ULONGLONG value)138   void set_value(ULONGLONG value) { version_ = value; }
139   base::string16 ToString() const;
140   std::string ToASCII() const;
141 
142  private:
143   ULONGLONG version_;
144 };  // class ChromeVersion
145 
ToString() const146 base::string16 ChromeVersion::ToString() const {
147   base::char16 buffer[24];
148   int string_len =
149       swprintf_s(&buffer[0], base::size(buffer), L"%hu.%hu.%hu.%hu", major(),
150                  minor(), build(), patch());
151   DCHECK_NE(-1, string_len);
152   DCHECK_GT(static_cast<int>(base::size(buffer)), string_len);
153   return base::string16(&buffer[0], string_len);
154 }
155 
ToASCII() const156 std::string ChromeVersion::ToASCII() const {
157   char buffer[24];
158   int string_len = sprintf_s(&buffer[0], base::size(buffer), "%hu.%hu.%hu.%hu",
159                              major(), minor(), build(), patch());
160   DCHECK_NE(-1, string_len);
161   DCHECK_GT(static_cast<int>(base::size(buffer)), string_len);
162   return std::string(&buffer[0], string_len);
163 }
164 
165 // Calls CreateProcess with good default parameters and waits for the process
166 // to terminate returning the process exit code.
RunProcessAndWait(const base::char16 * exe_path,const base::string16 & cmdline,int * exit_code)167 bool RunProcessAndWait(const base::char16* exe_path,
168                        const base::string16& cmdline,
169                        int* exit_code) {
170   bool result = true;
171   base::LaunchOptions options;
172   options.wait = true;
173   options.start_hidden = true;
174   base::Process process = base::LaunchProcess(cmdline, options);
175   if (process.IsValid()) {
176     if (exit_code) {
177       if (!GetExitCodeProcess(process.Handle(),
178                               reinterpret_cast<DWORD*>(exit_code))) {
179         PLOG(DFATAL) << "Failed getting the exit code for \"" << cmdline
180                      << "\".";
181         result = false;
182       } else {
183         DCHECK_NE(*exit_code, static_cast<int>(STILL_ACTIVE));
184       }
185     }
186   } else {
187     result = false;
188   }
189 
190   return result;
191 }
192 
193 // Retrieves the version number of |pe_file| from its version
194 // resource, placing the value in |version|.  Returns true on success.
GetFileVersion(const base::FilePath & pe_file,ChromeVersion * version)195 bool GetFileVersion(const base::FilePath& pe_file, ChromeVersion* version) {
196   DCHECK(version);
197   bool result = false;
198   upgrade_test::ResourceLoader pe_file_loader;
199   std::pair<const uint8_t*, DWORD> version_info_data;
200 
201   if (pe_file_loader.Initialize(pe_file) &&
202       pe_file_loader.Load(
203           VS_VERSION_INFO,
204           static_cast<WORD>(reinterpret_cast<uintptr_t>(RT_VERSION)),
205           &version_info_data)) {
206     const VS_FIXEDFILEINFO* fixed_file_info;
207     UINT ver_info_len;
208     if (VerQueryValue(version_info_data.first, L"\\",
209                       reinterpret_cast<void**>(
210                           const_cast<VS_FIXEDFILEINFO**>(&fixed_file_info)),
211                       &ver_info_len) != 0) {
212       DCHECK_EQ(sizeof(VS_FIXEDFILEINFO), static_cast<size_t>(ver_info_len));
213       *version = ChromeVersion::FromHighLow(fixed_file_info->dwFileVersionMS,
214                                             fixed_file_info->dwFileVersionLS);
215       result = true;
216     } else {
217       LOG(DFATAL) << "VerQueryValue failed to retrieve VS_FIXEDFILEINFO";
218     }
219   }
220 
221   return result;
222 }
223 
224 // Retrieves the version number of setup.exe in |work_dir| from its version
225 // resource, placing the value in |version|.  Returns true on success.
GetSetupExeVersion(const base::FilePath & work_dir,ChromeVersion * version)226 bool GetSetupExeVersion(const base::FilePath& work_dir,
227                         ChromeVersion* version) {
228   return GetFileVersion(work_dir.Append(&kSetupExe[0]), version);
229 }
230 
231 // Replace all occurrences in the sequence [|dest_first|, |dest_last|) that
232 // equals [|src_first|, |src_last|) with the sequence at |replacement_first| of
233 // the same length.  Returns true on success.  If non-nullptr,
234 // |replacements_made| is set to true/false accordingly.
ReplaceAll(uint8_t * dest_first,uint8_t * dest_last,const uint8_t * src_first,const uint8_t * src_last,const uint8_t * replacement_first,bool * replacements_made)235 bool ReplaceAll(uint8_t* dest_first,
236                 uint8_t* dest_last,
237                 const uint8_t* src_first,
238                 const uint8_t* src_last,
239                 const uint8_t* replacement_first,
240                 bool* replacements_made) {
241   bool result = true;
242   bool changed = false;
243   do {
244     dest_first = std::search(dest_first, dest_last, src_first, src_last);
245     if (dest_first == dest_last)
246       break;
247     changed = true;
248     if (memcpy_s(dest_first, dest_last - dest_first, replacement_first,
249                  src_last - src_first) != 0) {
250       result = false;
251       break;
252     }
253     dest_first += (src_last - src_first);
254   } while (true);
255 
256   if (replacements_made != nullptr)
257     *replacements_made = changed;
258 
259   return result;
260 }
261 
262 // A context structure in support of our EnumResource_Fn callback.
263 struct VisitResourceContext {
264   ChromeVersion current_version;
265   base::string16 current_version_str;
266   ChromeVersion new_version;
267   base::string16 new_version_str;
268 };  // struct VisitResourceContext
269 
270 // Replaces the old version with the new in a resource.  A first pass is made to
271 // replace the string form (e.g., "9.0.584.0").  If any replacements are made, a
272 // second pass is made to replace the binary form (e.g., 0x0000024800000009).
273 // A final pass is made to replace the ASCII string form.
VisitResource(const upgrade_test::EntryPath & path,uint8_t * data,DWORD size,DWORD code_page,uintptr_t context)274 void VisitResource(const upgrade_test::EntryPath& path,
275                    uint8_t* data,
276                    DWORD size,
277                    DWORD code_page,
278                    uintptr_t context) {
279   VisitResourceContext& ctx = *reinterpret_cast<VisitResourceContext*>(context);
280   DCHECK_EQ(ctx.current_version_str.size(), ctx.new_version_str.size());
281 
282   // Replace all occurrences of current_version_str with new_version_str
283   bool changing_version = false;
284   if (ReplaceAll(
285           data, data + size,
286           reinterpret_cast<const uint8_t*>(ctx.current_version_str.c_str()),
287           reinterpret_cast<const uint8_t*>(ctx.current_version_str.c_str() +
288                                            ctx.current_version_str.size() + 1),
289           reinterpret_cast<const uint8_t*>(ctx.new_version_str.c_str()),
290           &changing_version) &&
291       changing_version) {
292     // Replace all binary occurrences of current_version with new_version.
293     struct VersionPair {
294       DWORD high;
295       DWORD low;
296     };
297     VersionPair cur_ver = {ctx.current_version.high(),
298                            ctx.current_version.low()};
299     VersionPair new_ver = {ctx.new_version.high(), ctx.new_version.low()};
300     ReplaceAll(data, data + size, reinterpret_cast<const uint8_t*>(&cur_ver),
301                reinterpret_cast<const uint8_t*>(&cur_ver) + sizeof(cur_ver),
302                reinterpret_cast<const uint8_t*>(&new_ver), nullptr);
303   }
304 
305   // Replace all ASCII occurrences of current_version with new_version.
306   std::string current_version(ctx.current_version.ToASCII());
307   std::string new_version(ctx.new_version.ToASCII());
308   ReplaceAll(
309       data, data + size, reinterpret_cast<uint8_t*>(&current_version[0]),
310       reinterpret_cast<uint8_t*>(&current_version[current_version.size()]),
311       reinterpret_cast<uint8_t*>(&new_version[0]), nullptr);
312 }
313 
314 // Updates version strings found in an image's .rdata (read-only data) section.
315 // This handles uses of CHROME_VERSION_STRING (e.g., chrome::kChromeVersion).
316 // Version numbers found even as substrings of larger strings are updated.
UpdateVersionInData(base::win::PEImage * image,VisitResourceContext * context)317 bool UpdateVersionInData(base::win::PEImage* image,
318                          VisitResourceContext* context) {
319   DCHECK_EQ(context->current_version_str.size(),
320             context->new_version_str.size());
321   IMAGE_SECTION_HEADER* rdata_header =
322       image->GetImageSectionHeaderByName(".rdata");
323   if (!rdata_header)
324     return true;  // Nothing to update.
325 
326   size_t size = rdata_header->SizeOfRawData;
327   uint8_t* data = reinterpret_cast<uint8_t*>(image->module()) +
328                   rdata_header->PointerToRawData;
329 
330   // Replace all wide string occurrences of |current_version| with
331   // |new_version|. No attempt is made to ensure that the modified bytes are
332   // truly part of a wide string.
333   const base::string16& current_version_str = context->current_version_str;
334   ReplaceAll(data, data + size,
335              reinterpret_cast<const uint8_t*>(&current_version_str[0]),
336              reinterpret_cast<const uint8_t*>(
337                  &current_version_str[current_version_str.size()]),
338              reinterpret_cast<const uint8_t*>(context->new_version_str.c_str()),
339              nullptr);
340 
341   // Replace all ASCII occurrences of |current_version| with |new_version|. No
342   // attempt is made to ensure that the modified bytes are truly part of an
343   // ASCII string.
344   std::string current_version(context->current_version.ToASCII());
345   std::string new_version(context->new_version.ToASCII());
346   ReplaceAll(
347       data, data + size, reinterpret_cast<uint8_t*>(&current_version[0]),
348       reinterpret_cast<uint8_t*>(&current_version[current_version.size()]),
349       reinterpret_cast<uint8_t*>(&new_version[0]), nullptr);
350 
351   return true;
352 }
353 
354 // Updates the version strings and numbers in all of |image_file|'s resources.
UpdateVersionIfMatch(const base::FilePath & image_file,VisitResourceContext * context)355 bool UpdateVersionIfMatch(const base::FilePath& image_file,
356                           VisitResourceContext* context) {
357   if (!context ||
358       context->current_version_str.size() < context->new_version_str.size()) {
359     return false;
360   }
361 
362   uint32_t flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
363                    base::File::FLAG_WRITE | base::File::FLAG_EXCLUSIVE_READ |
364                    base::File::FLAG_EXCLUSIVE_WRITE;
365   base::File file(image_file, flags);
366   // It turns out that the underlying CreateFile can fail due to unhelpful
367   // security software locking the newly created DLL. So add a few brief
368   // retries to help tests that use this pass on machines thusly encumbered.
369   int retries = 3;
370   while (!file.IsValid() && retries-- > 0) {
371     LOG(WARNING) << "Failed to open \"" << image_file.value() << "\"."
372                  << " Retrying " << retries << " more times.";
373     Sleep(1000);
374     file.Initialize(image_file, flags);
375   }
376 
377   if (!file.IsValid()) {
378     PLOG(DFATAL) << "Failed to open \"" << image_file.value() << "\"";
379     return false;
380   }
381 
382   // Map the file into memory for modification (give the mapping its own handle
383   // to the file so that its last-modified time can be updated below).
384   base::MemoryMappedFile image_mapping;
385   if (!image_mapping.Initialize(file.Duplicate(),
386                                 base::MemoryMappedFile::READ_WRITE)) {
387     return false;
388   }
389 
390   base::win::PEImageAsData image(
391       reinterpret_cast<HMODULE>(image_mapping.data()));
392   // PEImage class does not support other-architecture images. Skip over such
393   // files.
394   if (image.GetNTHeaders()->OptionalHeader.Magic !=
395       IMAGE_NT_OPTIONAL_HDR_MAGIC) {
396     return true;
397   }
398 
399   // Explicitly update the last-modified time of the file if modifications are
400   // successfully made. This is required to convince the Windows loader that the
401   // modified file is distinct from the original. Windows does not necessarily
402   // update the last-modified time of a file that is written to via a read-write
403   // mapping.
404   return upgrade_test::EnumResources(image, &VisitResource,
405                                      reinterpret_cast<uintptr_t>(context)) &&
406          UpdateVersionInData(&image, context) &&
407          file.SetTimes(base::Time(), base::Time::Now());
408 }
409 
UpdateManifestVersion(const base::FilePath & manifest,VisitResourceContext * context)410 bool UpdateManifestVersion(const base::FilePath& manifest,
411                            VisitResourceContext* context) {
412   std::string contents;
413   if (!base::ReadFileToString(manifest, &contents))
414     return false;
415   std::string old_version(context->current_version.ToASCII());
416   std::string new_version(context->new_version.ToASCII());
417   bool modified = false;
418   if (!ReplaceAll(reinterpret_cast<uint8_t*>(&contents[0]),
419                   reinterpret_cast<uint8_t*>(&contents[contents.size()]),
420                   reinterpret_cast<uint8_t*>(&old_version[0]),
421                   reinterpret_cast<uint8_t*>(&old_version[old_version.size()]),
422                   reinterpret_cast<uint8_t*>(&new_version[0]), &modified)) {
423     return false;
424   }
425   DCHECK(modified);
426   int written = base::WriteFile(manifest, &contents[0],
427                                 static_cast<int>(contents.size()));
428   return written != -1 && static_cast<size_t>(written) == contents.size();
429 }
430 
IncrementNewVersion(upgrade_test::Direction direction,VisitResourceContext * ctx)431 bool IncrementNewVersion(upgrade_test::Direction direction,
432                          VisitResourceContext* ctx) {
433   DCHECK(ctx);
434 
435   // Figure out a past or future version with the same string length as this one
436   // by decrementing or incrementing each component.
437   LONGLONG incrementer = (direction == upgrade_test::PREVIOUS_VERSION ? -1 : 1);
438 
439   do {
440     if (incrementer == 0) {
441       LOG(DFATAL) << "Improbable version at the cusp of complete rollover";
442       return false;
443     }
444     ctx->new_version.set_value(ctx->current_version.value() + incrementer);
445     ctx->new_version_str = ctx->new_version.ToString();
446     incrementer <<= 16;
447   } while (ctx->new_version_str.size() != ctx->current_version_str.size());
448 
449   return true;
450 }
451 
452 // Raises or lowers the version of all .exe and .dll files in |work_dir| as well
453 // as the |work-dir|\Chrome-bin\w.x.y.z directory.  |original_version| and
454 // |new_version|, when non-null, are given the original and new version numbers
455 // on success.
ApplyAlternateVersion(const base::FilePath & work_dir,upgrade_test::Direction direction,base::string16 * original_version,base::string16 * new_version)456 bool ApplyAlternateVersion(const base::FilePath& work_dir,
457                            upgrade_test::Direction direction,
458                            base::string16* original_version,
459                            base::string16* new_version) {
460   VisitResourceContext ctx;
461   if (!GetSetupExeVersion(work_dir, &ctx.current_version))
462     return false;
463   ctx.current_version_str = ctx.current_version.ToString();
464 
465   if (!IncrementNewVersion(direction, &ctx))
466     return false;
467 
468   // Modify all .dll and .exe files with the current version.
469   base::FileEnumerator all_files(work_dir, true, base::FileEnumerator::FILES);
470   while (true) {
471     base::FilePath file = all_files.Next();
472     if (file.empty())
473       break;
474     base::string16 extension = file.Extension();
475     if ((extension == &kExtExe[0] || extension == &kExtDll[0]) &&
476         !UpdateVersionIfMatch(file, &ctx)) {
477       return false;
478     }
479   }
480 
481   // Change the versioned directory.
482   base::FilePath chrome_bin = work_dir.Append(&kChromeBin[0]);
483   if (!base::Move(chrome_bin.Append(ctx.current_version_str),
484                   chrome_bin.Append(ctx.new_version_str))) {
485     return false;
486   }
487 
488   // Update the manifest (revise post-XP; see https://crbug.com/581133).
489   base::FilePath current_manifest =
490       chrome_bin.Append(ctx.new_version_str)
491           .Append(ctx.current_version_str + L".manifest");
492   if (base::PathExists(current_manifest)) {
493     base::FilePath new_manifest =
494         current_manifest.DirName().Append(ctx.new_version_str + L".manifest");
495     if (!base::Move(current_manifest, new_manifest) ||
496         !UpdateManifestVersion(new_manifest, &ctx)) {
497       return false;
498     }
499   }
500 
501   // Report the version numbers if requested.
502   if (original_version)
503     original_version->assign(ctx.current_version_str);
504   if (new_version)
505     new_version->assign(ctx.new_version_str);
506 
507   return true;
508 }
509 
510 // Returns the path to the directory holding the 7za executable.  By default, it
511 // is assumed that the test resides in the tree's output directory, so the
512 // relative path "..\..\third_party\lzma_sdk\Executable" is applied to the host
513 // executable's directory.  This can be overridden with the --7za_path
514 // command-line switch.
Get7zaPath()515 base::FilePath Get7zaPath() {
516   base::FilePath l7za_path =
517       base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
518           &kSwitch7zaPath[0]);
519   if (l7za_path.empty()) {
520     base::FilePath dir_exe;
521     if (!base::PathService::Get(base::DIR_EXE, &dir_exe))
522       LOG(DFATAL) << "Failed getting directory of host executable";
523     l7za_path = dir_exe.Append(&k7zaPathRelative[0]);
524   }
525   return l7za_path;
526 }
527 
CreateArchive(const base::FilePath & output_file,const base::FilePath & input_path,int compression_level)528 bool CreateArchive(const base::FilePath& output_file,
529                    const base::FilePath& input_path,
530                    int compression_level) {
531   DCHECK(compression_level == 0 || compression_level >= 1 &&
532                                        compression_level <= 9 &&
533                                        (compression_level & 0x01) != 0);
534 
535   base::string16 command_line(1, L'"');
536   command_line.append(Get7zaPath().Append(&k7zaExe[0]).value())
537       .append(L"\" a -bd -t7z \"")
538       .append(output_file.value())
539       .append(L"\" \"")
540       .append(input_path.value())
541       .append(L"\" -mx")
542       .append(1, L'0' + compression_level);
543   int exit_code;
544   if (!RunProcessAndWait(nullptr, command_line, &exit_code))
545     return false;
546   if (exit_code != 0) {
547     LOG(DFATAL) << Get7zaPath().Append(&k7zaExe[0]).value()
548                 << " exited with code " << exit_code << " while creating "
549                 << output_file.value();
550     return false;
551   }
552   return true;
553 }
554 
555 }  // namespace
556 
557 namespace upgrade_test {
558 
GenerateAlternateVersion(const base::FilePath & original_installer_path,const base::FilePath & target_path,Direction direction,base::string16 * original_version,base::string16 * new_version)559 bool GenerateAlternateVersion(const base::FilePath& original_installer_path,
560                               const base::FilePath& target_path,
561                               Direction direction,
562                               base::string16* original_version,
563                               base::string16* new_version) {
564   // Create a temporary directory in which we'll do our work.
565   ScopedTempDirectory work_dir;
566   if (!work_dir.Initialize())
567     return false;
568 
569   // Copy the original mini_installer.
570   base::FilePath mini_installer =
571       work_dir.directory().Append(original_installer_path.BaseName());
572   if (!base::CopyFile(original_installer_path, mini_installer)) {
573     LOG(DFATAL) << "Failed copying \"" << original_installer_path.value()
574                 << "\" to \"" << mini_installer.value() << "\"";
575     return false;
576   }
577 
578   base::FilePath setup_ex_ = work_dir.directory().Append(&kSetupEx_[0]);
579   base::FilePath chrome_packed_7z;  // Empty for component builds.
580   base::FilePath chrome_7z;
581   const base::char16* archive_resource_name = nullptr;
582   base::FilePath* archive_file = nullptr;
583   // Load the original file and extract setup.ex_ and chrome.packed.7z
584   {
585     ResourceLoader resource_loader;
586     std::pair<const uint8_t*, DWORD> resource_data;
587 
588     if (!resource_loader.Initialize(mini_installer))
589       return false;
590 
591     // Write out setup.ex_
592     if (!resource_loader.Load(&kSetupEx_[0], &kBl[0], &resource_data))
593       return false;
594     int written = base::WriteFile(
595         setup_ex_, reinterpret_cast<const char*>(resource_data.first),
596         static_cast<int>(resource_data.second));
597     if (written != static_cast<int>(resource_data.second)) {
598       LOG(DFATAL) << "Failed writing \"" << setup_ex_.value() << "\"";
599       return false;
600     }
601 
602     // Write out chrome.packed.7z (static build) or chrome.7z (component build)
603     if (resource_loader.Load(&kChromePacked7z[0], &kB7[0], &resource_data)) {
604       archive_resource_name = &kChromePacked7z[0];
605       chrome_packed_7z = work_dir.directory().Append(archive_resource_name);
606       archive_file = &chrome_packed_7z;
607     } else if (resource_loader.Load(&kChrome7z[0], &kB7[0], &resource_data)) {
608       archive_resource_name = &kChrome7z[0];
609       chrome_7z = work_dir.directory().Append(archive_resource_name);
610       archive_file = &chrome_7z;
611     } else {
612       return false;
613     }
614     DCHECK(archive_resource_name);
615     DCHECK(!chrome_packed_7z.empty() || !chrome_7z.empty());
616     DCHECK(archive_file);
617     written = base::WriteFile(
618         *archive_file, reinterpret_cast<const char*>(resource_data.first),
619         static_cast<int>(resource_data.second));
620     if (written != static_cast<int>(resource_data.second)) {
621       LOG(DFATAL) << "Failed writing \"" << archive_file->value() << "\"";
622       return false;
623     }
624   }
625 
626   // Expand setup.ex_
627   base::FilePath setup_exe = setup_ex_.ReplaceExtension(&kExe[0]);
628   base::string16 command_line;
629   command_line.append(1, L'"')
630       .append(&kExpandExe[0])
631       .append(L"\" \"")
632       .append(setup_ex_.value())
633       .append(L"\" \"")
634       .append(setup_exe.value())
635       .append(1, L'\"');
636   int exit_code;
637   if (!RunProcessAndWait(nullptr, command_line, &exit_code))
638     return false;
639   if (exit_code != 0) {
640     LOG(DFATAL) << &kExpandExe[0] << " exited with code " << exit_code;
641     return false;
642   }
643 
644   // Unpack chrome.packed.7z (static build only).
645   if (!chrome_packed_7z.empty()) {
646     if (UnPackArchive(chrome_packed_7z, work_dir.directory(), &chrome_7z) !=
647         UNPACK_NO_ERROR) {
648       LOG(DFATAL) << "Failed unpacking \"" << chrome_packed_7z.value() << "\"";
649       return false;
650     }
651   }
652   DCHECK(!chrome_7z.empty());
653 
654   // Unpack chrome.7z
655   if (UnPackArchive(chrome_7z, work_dir.directory(), /*output_file=*/nullptr) !=
656       UNPACK_NO_ERROR) {
657     LOG(DFATAL) << "Failed unpacking \"" << chrome_7z.value() << "\"";
658     return false;
659   }
660 
661   // Get rid of intermediate files
662   if (!base::DeleteFile(chrome_7z) ||
663       (!chrome_packed_7z.empty() && !base::DeleteFile(chrome_packed_7z)) ||
664       !base::DeleteFile(setup_ex_)) {
665     LOG(DFATAL) << "Failed deleting intermediate files";
666     return false;
667   }
668 
669   // Increment the version in all files.
670   ApplyAlternateVersion(work_dir.directory(), direction, original_version,
671                         new_version);
672 
673   // Pack up files into chrome.7z
674   if (!CreateArchive(chrome_7z, work_dir.directory().Append(&kChromeBin[0]), 0))
675     return false;
676 
677   // Compress chrome.7z into chrome.packed.7z for static builds.
678   if (!chrome_packed_7z.empty() &&
679       !CreateArchive(chrome_packed_7z, chrome_7z, 9)) {
680     return false;
681   }
682 
683   // Compress setup.exe into setup.ex_
684   command_line.assign(1, L'"')
685       .append(&kMakeCab[0])
686       .append(L"\" /D CompressionType=LZX /L \"")
687       .append(work_dir.directory().value())
688       .append(L"\" \"")
689       .append(setup_exe.value());
690   if (!RunProcessAndWait(nullptr, command_line, &exit_code))
691     return false;
692   if (exit_code != 0) {
693     LOG(DFATAL) << &kMakeCab[0] << " exited with code " << exit_code;
694     return false;
695   }
696 
697   // Replace the mini_installer's setup.ex_ and chrome.packed.7z (or chrome.7z
698   // in component builds) resources.
699   ResourceUpdater updater;
700   if (!updater.Initialize(mini_installer) ||
701       !updater.Update(&kSetupEx_[0], &kBl[0],
702                       MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
703                       setup_ex_) ||
704       !updater.Update(archive_resource_name, &kB7[0],
705                       MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
706                       *archive_file) ||
707       !updater.Commit()) {
708     LOG(ERROR) << "It is common for this step to fail for very large resources,"
709                   " as is the case for Debug component=shared_library builds. "
710                   "Try with a Release or component=static_library build.";
711     return false;
712   }
713 
714   // Finally, move the updated mini_installer into place.
715   return base::Move(mini_installer, target_path);
716 }
717 
GenerateAlternatePEFileVersion(const base::FilePath & original_file,const base::FilePath & target_file,Direction direction)718 base::string16 GenerateAlternatePEFileVersion(
719     const base::FilePath& original_file,
720     const base::FilePath& target_file,
721     Direction direction) {
722   VisitResourceContext ctx;
723   if (!GetFileVersion(original_file, &ctx.current_version)) {
724     LOG(DFATAL) << "Failed reading version from \"" << original_file.value()
725                 << "\"";
726     return base::string16();
727   }
728   ctx.current_version_str = ctx.current_version.ToString();
729 
730   if (!IncrementNewVersion(direction, &ctx)) {
731     LOG(DFATAL) << "Failed to increment version from \""
732                 << original_file.value() << "\"";
733     return base::string16();
734   }
735 
736   DCHECK_EQ(ctx.current_version_str.size(), ctx.new_version_str.size());
737 
738   if (!base::CopyFile(original_file, target_file)) {
739     LOG(DFATAL) << "Failed copying \"" << original_file.value() << "\" to \""
740                 << target_file.value() << "\"";
741     return base::string16();
742   }
743 
744   if (!UpdateVersionIfMatch(target_file, &ctx))
745     return base::string16();
746 
747   return ctx.new_version_str;
748 }
749 
750 }  // namespace upgrade_test
751