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