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