1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// mini_installer.exe is the first exe that is run when chrome is being 6// installed or upgraded. It is designed to be extremely small (~5KB with no 7// extra resources linked) and it has two main jobs: 8// 1) unpack the resources (possibly decompressing some) 9// 2) run the real installer (setup.exe) with appropriate flags. 10// 11// In order to be really small the app doesn't link against the CRT and 12// defines the following compiler/linker flags: 13// EnableIntrinsicFunctions="true" compiler: /Oi 14// BasicRuntimeChecks="0" 15// BufferSecurityCheck="false" compiler: /GS- 16// EntryPointSymbol="MainEntryPoint" linker: /ENTRY 17// IgnoreAllDefaultLibraries="true" linker: /NODEFAULTLIB 18// OptimizeForWindows98="1" liker: /OPT:NOWIN98 19// linker: /SAFESEH:NO 20 21// have the linker merge the sections, saving us ~500 bytes. 22#pragma comment(linker, "/MERGE:.rdata=.text") 23 24#include <windows.h> 25#include <shellapi.h> 26 27#include "chrome/installer/mini_installer/appid.h" 28#include "chrome/installer/mini_installer/configuration.h" 29#include "chrome/installer/mini_installer/decompress.h" 30#include "chrome/installer/mini_installer/mini_installer.h" 31#include "chrome/installer/mini_installer/mini_string.h" 32#include "chrome/installer/mini_installer/pe_resource.h" 33 34namespace mini_installer { 35 36typedef StackString<MAX_PATH> PathString; 37typedef StackString<MAX_PATH * 4> CommandString; 38 39// This structure passes data back and forth for the processing 40// of resource callbacks. 41struct Context { 42 // Input to the call back method. Specifies the dir to save resources. 43 const wchar_t* base_path; 44 // First output from call back method. Full path of Chrome archive. 45 PathString* chrome_resource_path; 46 // Second output from call back method. Full path of Setup archive/exe. 47 PathString* setup_resource_path; 48}; 49 50// A helper class used to manipulate the Windows registry. Typically, members 51// return Windows last-error codes a la the Win32 registry API. 52class RegKey { 53 public: 54 RegKey() : key_(NULL) { } 55 ~RegKey() { Close(); } 56 57 // Opens the key named |sub_key| with given |access| rights. Returns 58 // ERROR_SUCCESS or some other error. 59 LONG Open(HKEY key, const wchar_t* sub_key, REGSAM access); 60 61 // Returns true if a key is open. 62 bool is_valid() const { return key_ != NULL; } 63 64 // Read a REG_SZ value from the registry into the memory indicated by |value| 65 // (of |value_size| wchar_t units). Returns ERROR_SUCCESS, 66 // ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA, or some other error. |value| is 67 // guaranteed to be null-terminated on success. 68 LONG ReadValue(const wchar_t* value_name, 69 wchar_t* value, 70 size_t value_size) const; 71 72 // Write a REG_SZ value to the registry. |value| must be null-terminated. 73 // Returns ERROR_SUCCESS or an error code. 74 LONG WriteValue(const wchar_t* value_name, const wchar_t* value); 75 76 // Closes the key if it was open. 77 void Close(); 78 79 private: 80 RegKey(const RegKey&); 81 RegKey& operator=(const RegKey&); 82 83 HKEY key_; 84}; // class RegKey 85 86LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) { 87 Close(); 88 return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_); 89} 90 91LONG RegKey::ReadValue(const wchar_t* value_name, 92 wchar_t* value, 93 size_t value_size) const { 94 DWORD type; 95 DWORD byte_length = static_cast<DWORD>(value_size * sizeof(wchar_t)); 96 LONG result = ::RegQueryValueEx(key_, value_name, NULL, &type, 97 reinterpret_cast<BYTE*>(value), 98 &byte_length); 99 if (result == ERROR_SUCCESS) { 100 if (type != REG_SZ) { 101 result = ERROR_NOT_SUPPORTED; 102 } else if (byte_length == 0) { 103 *value = L'\0'; 104 } else if (value[byte_length/sizeof(wchar_t) - 1] != L'\0') { 105 if ((byte_length / sizeof(wchar_t)) < value_size) 106 value[byte_length / sizeof(wchar_t)] = L'\0'; 107 else 108 result = ERROR_MORE_DATA; 109 } 110 } 111 return result; 112} 113 114LONG RegKey::WriteValue(const wchar_t* value_name, const wchar_t* value) { 115 return ::RegSetValueEx(key_, value_name, 0, REG_SZ, 116 reinterpret_cast<const BYTE*>(value), 117 (lstrlen(value) + 1) * sizeof(wchar_t)); 118} 119 120void RegKey::Close() { 121 if (key_ != NULL) { 122 ::RegCloseKey(key_); 123 key_ = NULL; 124 } 125} 126 127// Helper function to read a value from registry. Returns true if value 128// is read successfully and stored in parameter value. Returns false otherwise. 129// |size| is measured in wchar_t units. 130bool ReadValueFromRegistry(HKEY root_key, const wchar_t *sub_key, 131 const wchar_t *value_name, wchar_t *value, 132 size_t size) { 133 RegKey key; 134 135 if (key.Open(root_key, sub_key, KEY_QUERY_VALUE) == ERROR_SUCCESS && 136 key.ReadValue(value_name, value, size) == ERROR_SUCCESS) { 137 return true; 138 } 139 return false; 140} 141 142// Opens the Google Update ClientState key for a product. 143bool OpenClientStateKey(HKEY root_key, const wchar_t* app_guid, REGSAM access, 144 RegKey* key) { 145 PathString client_state_key; 146 return client_state_key.assign(kApRegistryKeyBase) && 147 client_state_key.append(app_guid) && 148 (key->Open(root_key, 149 client_state_key.get(), 150 access | KEY_WOW64_32KEY) == ERROR_SUCCESS); 151} 152 153// This function sets the flag in registry to indicate that Google Update 154// should try full installer next time. If the current installer works, this 155// flag is cleared by setup.exe at the end of install. The flag will by default 156// be written to HKCU, but if --system-level is included in the command line, 157// it will be written to HKLM instead. 158// TODO(grt): Write a unit test for this that uses registry virtualization. 159void SetInstallerFlags(const Configuration& configuration) { 160 RegKey key; 161 const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE; 162 const HKEY root_key = 163 configuration.is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 164 // This is ignored if multi-install is true. 165 const wchar_t* app_guid = 166 configuration.has_chrome_frame() ? 167 google_update::kChromeFrameAppGuid : 168 configuration.chrome_app_guid(); 169 StackString<128> value; 170 LONG ret; 171 172 // When multi_install is true, we are potentially: 173 // 1. Performing a multi-install of some product(s) on a clean machine. 174 // Neither the product(s) nor the multi-installer will have a ClientState 175 // key in the registry, so there is nothing to be done. 176 // 2. Upgrading an existing multi-install. The multi-installer will have a 177 // ClientState key in the registry. Only it need be modified. 178 // 3. Migrating a single-install into a multi-install. The product will have 179 // a ClientState key in the registry. Only it need be modified. 180 // To handle all cases, we inspect the product's ClientState to see if it 181 // exists and its "ap" value does not contain "-multi". This is case 3, so we 182 // modify the product's ClientState. Otherwise, we check the 183 // multi-installer's ClientState and modify it if it exists. 184 if (configuration.is_multi_install()) { 185 if (OpenClientStateKey(root_key, app_guid, key_access, &key)) { 186 // The product has a client state key. See if it's a single-install. 187 ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); 188 if (ret != ERROR_FILE_NOT_FOUND && 189 (ret != ERROR_SUCCESS || 190 FindTagInStr(value.get(), kMultiInstallTag, NULL))) { 191 // Error or case 2: modify the multi-installer's value. 192 key.Close(); 193 app_guid = google_update::kMultiInstallAppGuid; 194 } // else case 3: modify this value. 195 } else { 196 // case 1 or 2: modify the multi-installer's value. 197 key.Close(); 198 app_guid = google_update::kMultiInstallAppGuid; 199 } 200 } 201 202 if (!key.is_valid()) { 203 if (!OpenClientStateKey(root_key, app_guid, key_access, &key)) 204 return; 205 206 value.clear(); 207 ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); 208 } 209 210 // The conditions below are handling two cases: 211 // 1. When ap value is present, we want to add the required tag only if it is 212 // not present. 213 // 2. When ap value is missing, we are going to create it with the required 214 // tag. 215 if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) { 216 if (ret == ERROR_FILE_NOT_FOUND) 217 value.clear(); 218 219 if (!StrEndsWith(value.get(), kFullInstallerSuffix) && 220 value.append(kFullInstallerSuffix)) { 221 key.WriteValue(kApRegistryValueName, value.get()); 222 } 223 } 224} 225 226// Gets the setup.exe path from Registry by looking the value of Uninstall 227// string. |size| is measured in wchar_t units. 228bool GetSetupExePathForGuidFromRegistry(bool system_level, 229 const wchar_t* app_guid, 230 wchar_t* path, 231 size_t size) { 232 const HKEY root_key = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 233 RegKey key; 234 return OpenClientStateKey(root_key, app_guid, KEY_QUERY_VALUE, &key) && 235 (key.ReadValue(kUninstallRegistryValueName, path, size) == ERROR_SUCCESS); 236} 237 238// Gets the setup.exe path from Registry by looking the value of Uninstall 239// string. |size| is measured in wchar_t units. 240bool GetSetupExePathFromRegistry(const Configuration& configuration, 241 wchar_t* path, 242 size_t size) { 243 bool system_level = configuration.is_system_level(); 244 245 // If this is a multi install, first try looking in the binaries for the path. 246 if (configuration.is_multi_install() && GetSetupExePathForGuidFromRegistry( 247 system_level, google_update::kMultiInstallAppGuid, path, size)) { 248 return true; 249 } 250 251 // Failing that, look in Chrome Frame's client state key if --chrome-frame was 252 // specified. 253 if (configuration.has_chrome_frame() && GetSetupExePathForGuidFromRegistry( 254 system_level, google_update::kChromeFrameAppGuid, path, size)) { 255 return true; 256 } 257 258 // Make a last-ditch effort to look in the Chrome and App Host client state 259 // keys. 260 if (GetSetupExePathForGuidFromRegistry( 261 system_level, configuration.chrome_app_guid(), path, size)) { 262 return true; 263 } 264 if (configuration.has_app_host() && GetSetupExePathForGuidFromRegistry( 265 system_level, google_update::kChromeAppHostAppGuid, path, size)) { 266 return true; 267 } 268 269 return false; 270} 271 272// Calls CreateProcess with good default parameters and waits for the process 273// to terminate returning the process exit code. 274bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline, 275 int* exit_code) { 276 STARTUPINFOW si = {sizeof(si)}; 277 PROCESS_INFORMATION pi = {0}; 278 if (!::CreateProcess(exe_path, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, 279 NULL, NULL, &si, &pi)) { 280 return false; 281 } 282 283 ::CloseHandle(pi.hThread); 284 285 bool ret = true; 286 DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE); 287 if (WAIT_OBJECT_0 != wr) { 288 ret = false; 289 } else if (exit_code) { 290 if (!::GetExitCodeProcess(pi.hProcess, 291 reinterpret_cast<DWORD*>(exit_code))) { 292 ret = false; 293 } 294 } 295 296 ::CloseHandle(pi.hProcess); 297 298 return ret; 299} 300 301// Append any command line params passed to mini_installer to the given buffer 302// so that they can be passed on to setup.exe. We do not return any error from 303// this method and simply skip making any changes in case of error. 304void AppendCommandLineFlags(const Configuration& configuration, 305 CommandString* buffer) { 306 PathString full_exe_path; 307 size_t len = ::GetModuleFileName(NULL, full_exe_path.get(), 308 full_exe_path.capacity()); 309 if (!len || len >= full_exe_path.capacity()) 310 return; 311 312 const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len); 313 if (exe_name == NULL) 314 return; 315 316 const wchar_t* cmd_to_append = L""; 317 if (!StrEndsWith(configuration.program(), exe_name)) { 318 // Current executable name not in the command line so just append 319 // the whole command line. 320 cmd_to_append = configuration.command_line(); 321 } else if (configuration.argument_count() > 1) { 322 const wchar_t* tmp = SearchStringI(configuration.command_line(), exe_name); 323 tmp = SearchStringI(tmp, L" "); 324 cmd_to_append = tmp; 325 } 326 327 buffer->append(cmd_to_append); 328} 329 330 331// Windows defined callback used in the EnumResourceNames call. For each 332// matching resource found, the callback is invoked and at this point we write 333// it to disk. We expect resource names to start with 'chrome' or 'setup'. Any 334// other name is treated as an error. 335BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type, 336 wchar_t* name, LONG_PTR context) { 337 if (NULL == context) 338 return FALSE; 339 340 Context* ctx = reinterpret_cast<Context*>(context); 341 342 PEResource resource(name, type, module); 343 if ((!resource.IsValid()) || 344 (resource.Size() < 1) || 345 (resource.Size() > kMaxResourceSize)) { 346 return FALSE; 347 } 348 349 PathString full_path; 350 if (!full_path.assign(ctx->base_path) || 351 !full_path.append(name) || 352 !resource.WriteToDisk(full_path.get())) 353 return FALSE; 354 355 if (StrStartsWith(name, kChromePrefix)) { 356 if (!ctx->chrome_resource_path->assign(full_path.get())) 357 return FALSE; 358 } else if (StrStartsWith(name, kSetupPrefix)) { 359 if (!ctx->setup_resource_path->assign(full_path.get())) 360 return FALSE; 361 } else { 362 // Resources should either start with 'chrome' or 'setup'. We don't handle 363 // anything else. 364 return FALSE; 365 } 366 367 return TRUE; 368} 369 370// Finds and writes to disk resources of various types. Returns false 371// if there is a problem in writing any resource to disk. setup.exe resource 372// can come in one of three possible forms: 373// - Resource type 'B7', compressed using LZMA (*.7z) 374// - Resource type 'BL', compressed using LZ (*.ex_) 375// - Resource type 'BN', uncompressed (*.exe) 376// If setup.exe is present in more than one form, the precedence order is 377// BN < BL < B7 378// For more details see chrome/tools/build/win/create_installer_archive.py. 379bool UnpackBinaryResources(const Configuration& configuration, HMODULE module, 380 const wchar_t* base_path, PathString* archive_path, 381 PathString* setup_path) { 382 // Generate the setup.exe path where we patch/uncompress setup resource. 383 PathString setup_dest_path; 384 if (!setup_dest_path.assign(base_path) || 385 !setup_dest_path.append(kSetupName)) 386 return false; 387 388 // Prepare the input to OnResourceFound method that needs a location where 389 // it will write all the resources. 390 Context context = { 391 base_path, 392 archive_path, 393 setup_path, 394 }; 395 396 // Get the resources of type 'B7' (7zip archive). 397 // We need a chrome archive to do the installation. So if there 398 // is a problem in fetching B7 resource, just return an error. 399 if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound, 400 reinterpret_cast<LONG_PTR>(&context)) || 401 archive_path->length() == 0) 402 return false; 403 404 // If we found setup 'B7' resource, handle it. 405 if (setup_path->length() > 0) { 406 CommandString cmd_line; 407 // Get the path to setup.exe first. 408 bool success = true; 409 if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(), 410 cmd_line.capacity()) || 411 !cmd_line.append(kCmdUpdateSetupExe) || 412 !cmd_line.append(L"=\"") || 413 !cmd_line.append(setup_path->get()) || 414 !cmd_line.append(L"\"") || 415 !cmd_line.append(kCmdNewSetupExe) || 416 !cmd_line.append(L"=\"") || 417 !cmd_line.append(setup_dest_path.get()) || 418 !cmd_line.append(L"\"")) { 419 success = false; 420 } 421 422 // Get any command line option specified for mini_installer and pass them 423 // on to setup.exe. This is important since switches such as 424 // --multi-install and --chrome-frame affect where setup.exe will write 425 // installer results for consumption by Google Update. 426 AppendCommandLineFlags(configuration, &cmd_line); 427 428 int exit_code = 0; 429 if (success && 430 (!RunProcessAndWait(NULL, cmd_line.get(), &exit_code) || 431 exit_code != ERROR_SUCCESS)) { 432 success = false; 433 } 434 435 if (!success) 436 DeleteFile(setup_path->get()); 437 438 return success && setup_path->assign(setup_dest_path.get()); 439 } 440 441 // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL' 442 // (compressed setup). 443 if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound, 444 reinterpret_cast<LONG_PTR>(&context)) && 445 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) 446 return false; 447 448 if (setup_path->length() > 0) { 449 // Uncompress LZ compressed resource. Setup is packed with 'MSCF' 450 // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy. 451 bool success = mini_installer::Expand(setup_path->get(), 452 setup_dest_path.get()); 453 ::DeleteFile(setup_path->get()); 454 if (success) { 455 if (!setup_path->assign(setup_dest_path.get())) { 456 ::DeleteFile(setup_dest_path.get()); 457 success = false; 458 } 459 } 460 461 return success; 462 } 463 464 // setup.exe still not found. So finally check if it was sent as 'BN' 465 // (uncompressed setup). 466 // TODO(tommi): We don't need BN anymore so let's remove it (and remove 467 // it from create_installer_archive.py). 468 if (!::EnumResourceNames(module, kBinResourceType, OnResourceFound, 469 reinterpret_cast<LONG_PTR>(&context)) && 470 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) 471 return false; 472 473 if (setup_path->length() > 0) { 474 if (setup_path->comparei(setup_dest_path.get()) != 0) { 475 if (!::MoveFileEx(setup_path->get(), setup_dest_path.get(), 476 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) { 477 ::DeleteFile(setup_path->get()); 478 setup_path->clear(); 479 } else if (!setup_path->assign(setup_dest_path.get())) { 480 ::DeleteFile(setup_dest_path.get()); 481 } 482 } 483 } 484 485 return setup_path->length() > 0; 486} 487 488// Executes setup.exe, waits for it to finish and returns the exit code. 489bool RunSetup(const Configuration& configuration, const wchar_t* archive_path, 490 const wchar_t* setup_path, int* exit_code) { 491 // There could be three full paths in the command line for setup.exe (path 492 // to exe itself, path to archive and path to log file), so we declare 493 // total size as three + one additional to hold command line options. 494 CommandString cmd_line; 495 496 // Get the path to setup.exe first. 497 if (::lstrlen(setup_path) > 0) { 498 if (!cmd_line.assign(L"\"") || 499 !cmd_line.append(setup_path) || 500 !cmd_line.append(L"\"")) 501 return false; 502 } else if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(), 503 cmd_line.capacity())) { 504 return false; 505 } 506 507 // Append the command line param for chrome archive file 508 if (!cmd_line.append(kCmdInstallArchive) || 509 !cmd_line.append(L"=\"") || 510 !cmd_line.append(archive_path) || 511 !cmd_line.append(L"\"")) 512 return false; 513 514 // Get any command line option specified for mini_installer and pass them 515 // on to setup.exe 516 AppendCommandLineFlags(configuration, &cmd_line); 517 518 return RunProcessAndWait(NULL, cmd_line.get(), exit_code); 519} 520 521// Deletes given files and working dir. 522void DeleteExtractedFiles(const wchar_t* base_path, 523 const wchar_t* archive_path, 524 const wchar_t* setup_path) { 525 ::DeleteFile(archive_path); 526 ::DeleteFile(setup_path); 527 // Delete the temp dir (if it is empty, otherwise fail). 528 ::RemoveDirectory(base_path); 529} 530 531// Creates a temporary directory under |base_path| and returns the full path 532// of created directory in |work_dir|. If successful return true, otherwise 533// false. When successful, the returned |work_dir| will always have a trailing 534// backslash and this function requires that |base_path| always includes a 535// trailing backslash as well. 536// We do not use GetTempFileName here to avoid running into AV software that 537// might hold on to the temp file as soon as we create it and then we can't 538// delete it and create a directory in its place. So, we use our own mechanism 539// for creating a directory with a hopefully-unique name. In the case of a 540// collision, we retry a few times with a new name before failing. 541bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) { 542 if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix)) 543 return false; 544 545 // Store the location where we'll append the id. 546 size_t end = work_dir->length(); 547 548 // Check if we'll have enough buffer space to continue. 549 // The name of the directory will use up 11 chars and then we need to append 550 // the trailing backslash and a terminator. We've already added the prefix 551 // to the buffer, so let's just make sure we've got enough space for the rest. 552 if ((work_dir->capacity() - end) < (arraysize("fffff.tmp") + 1)) 553 return false; 554 555 // Generate a unique id. We only use the lowest 20 bits, so take the top 556 // 12 bits and xor them with the lower bits. 557 DWORD id = ::GetTickCount(); 558 id ^= (id >> 12); 559 560 int max_attempts = 10; 561 while (max_attempts--) { 562 // This converts 'id' to a string in the format "78563412" on windows 563 // because of little endianness, but we don't care since it's just 564 // a name. 565 if (!HexEncode(&id, sizeof(id), work_dir->get() + end, 566 work_dir->capacity() - end)) { 567 return false; 568 } 569 570 // We only want the first 5 digits to remain within the 8.3 file name 571 // format (compliant with previous implementation). 572 work_dir->truncate_at(end + 5); 573 574 // for consistency with the previous implementation which relied on 575 // GetTempFileName, we append the .tmp extension. 576 work_dir->append(L".tmp"); 577 if (::CreateDirectory(work_dir->get(), NULL)) { 578 // Yay! Now let's just append the backslash and we're done. 579 return work_dir->append(L"\\"); 580 } 581 ++id; // Try a different name. 582 } 583 584 return false; 585} 586 587// Creates and returns a temporary directory that can be used to extract 588// mini_installer payload. 589bool GetWorkDir(HMODULE module, PathString* work_dir) { 590 PathString base_path; 591 DWORD len = ::GetTempPath(base_path.capacity(), base_path.get()); 592 if (!len || len >= base_path.capacity() || 593 !CreateWorkDir(base_path.get(), work_dir)) { 594 // Problem creating the work dir under TEMP path, so try using the 595 // current directory as the base path. 596 len = ::GetModuleFileName(module, base_path.get(), base_path.capacity()); 597 if (len >= base_path.capacity() || !len) 598 return false; // Can't even get current directory? Return an error. 599 600 wchar_t* name = GetNameFromPathExt(base_path.get(), len); 601 if (!name) 602 return false; 603 604 *name = L'\0'; 605 606 return CreateWorkDir(base_path.get(), work_dir); 607 } 608 return true; 609} 610 611// Returns true for ".." and "." directories. 612bool IsCurrentOrParentDirectory(const wchar_t* dir) { 613 return dir && 614 dir[0] == L'.' && 615 (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0')); 616} 617 618// Best effort directory tree deletion including the directory specified 619// by |path|, which must not end in a separator. 620// The |path| argument is writable so that each recursion can use the same 621// buffer as was originally allocated for the path. The path will be unchanged 622// upon return. 623void RecursivelyDeleteDirectory(PathString* path) { 624 // |path| will never have a trailing backslash. 625 size_t end = path->length(); 626 if (!path->append(L"\\*.*")) 627 return; 628 629 WIN32_FIND_DATA find_data = {0}; 630 HANDLE find = ::FindFirstFile(path->get(), &find_data); 631 if (find != INVALID_HANDLE_VALUE) { 632 do { 633 // Use the short name if available to make the most of our buffer. 634 const wchar_t* name = find_data.cAlternateFileName[0] ? 635 find_data.cAlternateFileName : find_data.cFileName; 636 if (IsCurrentOrParentDirectory(name)) 637 continue; 638 639 path->truncate_at(end + 1); // Keep the trailing backslash. 640 if (!path->append(name)) 641 continue; // Continue in spite of too long names. 642 643 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 644 RecursivelyDeleteDirectory(path); 645 } else { 646 ::DeleteFile(path->get()); 647 } 648 } while (::FindNextFile(find, &find_data)); 649 ::FindClose(find); 650 } 651 652 // Restore the path and delete the directory before we return. 653 path->truncate_at(end); 654 ::RemoveDirectory(path->get()); 655} 656 657// Enumerates subdirectories of |parent_dir| and deletes all subdirectories 658// that match with a given |prefix|. |parent_dir| must have a trailing 659// backslash. 660// The process is done on a best effort basis, so conceivably there might 661// still be matches left when the function returns. 662void DeleteDirectoriesWithPrefix(const wchar_t* parent_dir, 663 const wchar_t* prefix) { 664 // |parent_dir| is guaranteed to always have a trailing backslash. 665 PathString spec; 666 if (!spec.assign(parent_dir) || !spec.append(prefix) || !spec.append(L"*.*")) 667 return; 668 669 WIN32_FIND_DATA find_data = {0}; 670 HANDLE find = ::FindFirstFileEx(spec.get(), FindExInfoStandard, &find_data, 671 FindExSearchLimitToDirectories, NULL, 0); 672 if (find == INVALID_HANDLE_VALUE) 673 return; 674 675 PathString path; 676 do { 677 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 678 // Use the short name if available to make the most of our buffer. 679 const wchar_t* name = find_data.cAlternateFileName[0] ? 680 find_data.cAlternateFileName : find_data.cFileName; 681 if (IsCurrentOrParentDirectory(name)) 682 continue; 683 if (path.assign(parent_dir) && path.append(name)) 684 RecursivelyDeleteDirectory(&path); 685 } 686 } while (::FindNextFile(find, &find_data)); 687 ::FindClose(find); 688} 689 690// Attempts to free up space by deleting temp directories that previous 691// installer runs have failed to clean up. 692void DeleteOldChromeTempDirectories() { 693 static const wchar_t* const kDirectoryPrefixes[] = { 694 kTempPrefix, 695 L"chrome_" // Previous installers created directories with this prefix 696 // and there are still some lying around. 697 }; 698 699 PathString temp; 700 // GetTempPath always returns a path with a trailing backslash. 701 DWORD len = ::GetTempPath(temp.capacity(), temp.get()); 702 // GetTempPath returns 0 or number of chars copied, not including the 703 // terminating '\0'. 704 if (!len || len >= temp.capacity()) 705 return; 706 707 for (int i = 0; i < arraysize(kDirectoryPrefixes); ++i) { 708 DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]); 709 } 710} 711 712// Checks the command line for specific mini installer flags. 713// If the function returns true, the command line has been processed and all 714// required actions taken. The installer must exit and return the returned 715// |exit_code|. 716bool ProcessNonInstallOperations(const Configuration& configuration, 717 int* exit_code) { 718 bool ret = false; 719 720 switch (configuration.operation()) { 721 case Configuration::CLEANUP: 722 // Cleanup has already taken place in DeleteOldChromeTempDirectories at 723 // this point, so just tell our caller to exit early. 724 *exit_code = 0; 725 ret = true; 726 break; 727 728 default: break; 729 } 730 731 return ret; 732} 733 734// Returns true if we should delete the temp files we create (default). 735// Returns false iff the user has manually created a ChromeInstallerCleanup 736// string value in the registry under HKCU\\Software\\[Google|Chromium] 737// and set its value to "0". That explicitly forbids the mini installer from 738// deleting these files. 739// Support for this has been publicly mentioned in troubleshooting tips so 740// we continue to support it. 741bool ShouldDeleteExtractedFiles() { 742 wchar_t value[2] = {0}; 743 if (ReadValueFromRegistry(HKEY_CURRENT_USER, kCleanupRegistryKey, 744 kCleanupRegistryValueName, value, 745 arraysize(value)) && 746 value[0] == L'0') { 747 return false; 748 } 749 750 return true; 751} 752 753// Main function. First gets a working dir, unpacks the resources and finally 754// executes setup.exe to do the install/upgrade. 755int WMain(HMODULE module) { 756#if defined(COMPONENT_BUILD) 757 if (::GetEnvironmentVariable(L"MINI_INSTALLER_TEST", NULL, 0) == 0) { 758 static const wchar_t kComponentBuildIncompatibleMessage[] = 759 L"mini_installer.exe is incompatible with the component build, please" 760 L" run setup.exe with the same command line instead. See" 761 L" http://crbug.com/127233#c17 for details."; 762 ::MessageBox(NULL, kComponentBuildIncompatibleMessage, NULL, MB_ICONERROR); 763 return 1; 764 } 765#endif 766 767 // Always start with deleting potential leftovers from previous installations. 768 // This can make the difference between success and failure. We've seen 769 // many installations out in the field fail due to out of disk space problems 770 // so this could buy us some space. 771 DeleteOldChromeTempDirectories(); 772 773 // TODO(grt): Make the exit codes more granular so we know where the popular 774 // errors truly are. 775 int exit_code = 101; 776 777 // Parse the command line. 778 Configuration configuration; 779 if (!configuration.Initialize()) 780 return exit_code; 781 782 if (configuration.query_component_build()) { 783 // Exit immediately with an exit code of 1 to indicate component build and 0 784 // to indicate static build. This is used by the tests in 785 // /src/chrome/test/mini_installer/. 786#if defined(COMPONENT_BUILD) 787 return 1; 788#else 789 return 0; 790#endif 791 } 792 793 // If the --cleanup switch was specified on the command line, then that means 794 // we should only do the cleanup and then exit. 795 if (ProcessNonInstallOperations(configuration, &exit_code)) 796 return exit_code; 797 798 // First get a path where we can extract payload 799 PathString base_path; 800 if (!GetWorkDir(module, &base_path)) 801 return 101; 802 803#if defined(GOOGLE_CHROME_BUILD) 804 // Set the magic suffix in registry to try full installer next time. We ignore 805 // any errors here and we try to set the suffix for user level unless 806 // --system-level is on the command line in which case we set it for system 807 // level instead. This only applies to the Google Chrome distribution. 808 SetInstallerFlags(configuration); 809#endif 810 811 PathString archive_path; 812 PathString setup_path; 813 if (!UnpackBinaryResources(configuration, module, base_path.get(), 814 &archive_path, &setup_path)) { 815 exit_code = 102; 816 } else { 817 // While unpacking the binaries, we paged in a whole bunch of memory that 818 // we don't need anymore. Let's give it back to the pool before running 819 // setup. 820 ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1); 821 if (!RunSetup(configuration, archive_path.get(), setup_path.get(), 822 &exit_code)) { 823 exit_code = 103; 824 } 825 } 826 827 if (ShouldDeleteExtractedFiles()) 828 DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get()); 829 830 return exit_code; 831} 832 833} // namespace mini_installer 834 835int MainEntryPoint() { 836 int result = mini_installer::WMain(::GetModuleHandle(NULL)); 837 ::ExitProcess(result); 838} 839 840// VC Express editions don't come with the memset CRT obj file and linking to 841// the obj files between versions becomes a bit problematic. Therefore, 842// simply implement memset. 843// 844// This also avoids having to explicitly set the __sse2_available hack when 845// linking with both the x64 and x86 obj files which is required when not 846// linking with the std C lib in certain instances (including Chromium) with 847// MSVC. __sse2_available determines whether to use SSE2 intructions with 848// std C lib routines, and is set by MSVC's std C lib implementation normally. 849extern "C" { 850#pragma function(memset) 851void* memset(void* dest, int c, size_t count) { 852 void* start = dest; 853 while (count--) { 854 *reinterpret_cast<char*>(dest) = static_cast<char>(c); 855 dest = reinterpret_cast<char*>(dest) + 1; 856 } 857 return start; 858} 859} // extern "C" 860