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*>(¤t_version[0]),
310 reinterpret_cast<uint8_t*>(¤t_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*>(¤t_version_str[0]),
336 reinterpret_cast<const uint8_t*>(
337 ¤t_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*>(¤t_version[0]),
348 reinterpret_cast<uint8_t*>(¤t_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