Do not announce robot account token before account ID is available
[chromium-blink-merge.git] / chrome / installer / mini_installer / mini_installer.cc
blob600ea4653849b296b61abb25ddd06df553af80e0
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.
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.
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
21 // have the linker merge the sections, saving us ~500 bytes.
22 #pragma comment(linker, "/MERGE:.rdata=.text")
24 #include <windows.h>
25 #include <shellapi.h>
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"
35 namespace mini_installer {
37 typedef DWORD ProcessExitCode;
38 typedef StackString<MAX_PATH> PathString;
39 typedef StackString<MAX_PATH * 4> CommandString;
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;
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(); }
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);
63 // Returns true if a key is open.
64 bool is_valid() const { return key_ != NULL; }
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;
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);
78 // Closes the key if it was open.
79 void Close();
81 private:
82 RegKey(const RegKey&);
83 RegKey& operator=(const RegKey&);
85 HKEY key_;
86 }; // class RegKey
88 LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
89 Close();
90 return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_);
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;
113 return result;
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));
122 void RegKey::Close() {
123 if (key_ != NULL) {
124 ::RegCloseKey(key_);
125 key_ = NULL;
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;
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;
141 return false;
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);
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 = ERROR_SUCCESS;
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;
204 if (!key.is_valid()) {
205 if (!OpenClientStateKey(root_key, app_guid, key_access, &key))
206 return;
208 value.clear();
209 ret = key.ReadValue(kApRegistryValue, value.get(), value.capacity());
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();
221 if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
222 value.append(kFullInstallerSuffix)) {
223 key.WriteValue(kApRegistryValue, value.get());
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);
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();
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;
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;
260 // Make a last-ditch effort to look in the Chrome client state key.
261 if (GetSetupExePathForGuidFromRegistry(
262 system_level, configuration.chrome_app_guid(), path, size)) {
263 return true;
266 return false;
269 // Calls CreateProcess with good default parameters and waits for the process to
270 // terminate returning the process exit code. |exit_code|, if non-NULL, is
271 // populated with the process exit code.
272 bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline,
273 ProcessExitCode* 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;
281 ::CloseHandle(pi.hThread);
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, exit_code))
289 ret = false;
292 ::CloseHandle(pi.hProcess);
294 return ret;
297 // Append any command line params passed to mini_installer to the given buffer
298 // so that they can be passed on to setup.exe. We do not return any error from
299 // this method and simply skip making any changes in case of error.
300 void AppendCommandLineFlags(const Configuration& configuration,
301 CommandString* buffer) {
302 PathString full_exe_path;
303 size_t len = ::GetModuleFileName(NULL, full_exe_path.get(),
304 full_exe_path.capacity());
305 if (!len || len >= full_exe_path.capacity())
306 return;
308 const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len);
309 if (exe_name == NULL)
310 return;
312 const wchar_t* cmd_to_append = L"";
313 if (!StrEndsWith(configuration.program(), exe_name)) {
314 // Current executable name not in the command line so just append
315 // the whole command line.
316 cmd_to_append = configuration.command_line();
317 } else if (configuration.argument_count() > 1) {
318 const wchar_t* tmp = SearchStringI(configuration.command_line(), exe_name);
319 tmp = SearchStringI(tmp, L" ");
320 cmd_to_append = tmp;
323 buffer->append(cmd_to_append);
327 // Windows defined callback used in the EnumResourceNames call. For each
328 // matching resource found, the callback is invoked and at this point we write
329 // it to disk. We expect resource names to start with 'chrome' or 'setup'. Any
330 // other name is treated as an error.
331 BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type,
332 wchar_t* name, LONG_PTR context) {
333 if (NULL == context)
334 return FALSE;
336 Context* ctx = reinterpret_cast<Context*>(context);
338 PEResource resource(name, type, module);
339 if ((!resource.IsValid()) ||
340 (resource.Size() < 1) ||
341 (resource.Size() > kMaxResourceSize)) {
342 return FALSE;
345 PathString full_path;
346 if (!full_path.assign(ctx->base_path) ||
347 !full_path.append(name) ||
348 !resource.WriteToDisk(full_path.get()))
349 return FALSE;
351 if (StrStartsWith(name, kChromeArchivePrefix)) {
352 if (!ctx->chrome_resource_path->assign(full_path.get()))
353 return FALSE;
354 } else if (StrStartsWith(name, kSetupPrefix)) {
355 if (!ctx->setup_resource_path->assign(full_path.get()))
356 return FALSE;
357 } else {
358 // Resources should either start with 'chrome' or 'setup'. We don't handle
359 // anything else.
360 return FALSE;
363 return TRUE;
366 #if defined(COMPONENT_BUILD)
367 // An EnumResNameProc callback that writes the resource |name| to disk in the
368 // directory |base_path_ptr| (which must end with a path separator).
369 BOOL CALLBACK WriteResourceToDirectory(HMODULE module,
370 const wchar_t* type,
371 wchar_t* name,
372 LONG_PTR base_path_ptr) {
373 const wchar_t* base_path = reinterpret_cast<const wchar_t*>(base_path_ptr);
374 PathString full_path;
376 PEResource resource(name, type, module);
377 return (resource.IsValid() &&
378 full_path.assign(base_path) &&
379 full_path.append(name) &&
380 resource.WriteToDisk(full_path.get()));
382 #endif
384 // Finds and writes to disk resources of various types. Returns false
385 // if there is a problem in writing any resource to disk. setup.exe resource
386 // can come in one of three possible forms:
387 // - Resource type 'B7', compressed using LZMA (*.7z)
388 // - Resource type 'BL', compressed using LZ (*.ex_)
389 // - Resource type 'BN', uncompressed (*.exe)
390 // If setup.exe is present in more than one form, the precedence order is
391 // BN < BL < B7
392 // For more details see chrome/tools/build/win/create_installer_archive.py.
393 // For component builds (where setup.ex_ is always used), all files stored as
394 // uncompressed 'BN' resources are also extracted. This is generally the set of
395 // DLLs/resources needed by setup.exe to run.
396 bool UnpackBinaryResources(const Configuration& configuration, HMODULE module,
397 const wchar_t* base_path, PathString* archive_path,
398 PathString* setup_path) {
399 // Generate the setup.exe path where we patch/uncompress setup resource.
400 PathString setup_dest_path;
401 if (!setup_dest_path.assign(base_path) ||
402 !setup_dest_path.append(kSetupExe))
403 return false;
405 // Prepare the input to OnResourceFound method that needs a location where
406 // it will write all the resources.
407 Context context = {
408 base_path,
409 archive_path,
410 setup_path,
413 // Get the resources of type 'B7' (7zip archive).
414 // We need a chrome archive to do the installation. So if there
415 // is a problem in fetching B7 resource, just return an error.
416 if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
417 reinterpret_cast<LONG_PTR>(&context)) ||
418 archive_path->length() == 0)
419 return false;
421 // If we found setup 'B7' resource, handle it.
422 if (setup_path->length() > 0) {
423 CommandString cmd_line;
424 PathString exe_path;
425 // Get the path to setup.exe first.
426 bool success = true;
427 if (!GetSetupExePathFromRegistry(configuration, exe_path.get(),
428 exe_path.capacity()) ||
429 !cmd_line.append(exe_path.get()) ||
430 !cmd_line.append(L" --") ||
431 !cmd_line.append(kCmdUpdateSetupExe) ||
432 !cmd_line.append(L"=\"") ||
433 !cmd_line.append(setup_path->get()) ||
434 !cmd_line.append(L"\" --") ||
435 !cmd_line.append(kCmdNewSetupExe) ||
436 !cmd_line.append(L"=\"") ||
437 !cmd_line.append(setup_dest_path.get()) ||
438 !cmd_line.append(L"\"")) {
439 success = false;
442 // Get any command line option specified for mini_installer and pass them
443 // on to setup.exe. This is important since switches such as
444 // --multi-install and --chrome-frame affect where setup.exe will write
445 // installer results for consumption by Google Update.
446 AppendCommandLineFlags(configuration, &cmd_line);
448 ProcessExitCode exit_code = SUCCESS_EXIT_CODE;
449 if (success &&
450 (!RunProcessAndWait(exe_path.get(), cmd_line.get(), &exit_code) ||
451 exit_code != SUCCESS_EXIT_CODE)) {
452 success = false;
455 if (!success)
456 DeleteFile(setup_path->get());
458 return success && setup_path->assign(setup_dest_path.get());
461 // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL'
462 // (compressed setup).
463 if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound,
464 reinterpret_cast<LONG_PTR>(&context)) &&
465 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND)
466 return false;
468 if (setup_path->length() > 0) {
469 // Uncompress LZ compressed resource. Setup is packed with 'MSCF'
470 // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy.
471 bool success = mini_installer::Expand(setup_path->get(),
472 setup_dest_path.get());
473 ::DeleteFile(setup_path->get());
474 if (success) {
475 if (!setup_path->assign(setup_dest_path.get())) {
476 ::DeleteFile(setup_dest_path.get());
477 success = false;
481 #if defined(COMPONENT_BUILD)
482 // Extract the (uncompressed) modules required by setup.exe.
483 if (!::EnumResourceNames(module, kBinResourceType, WriteResourceToDirectory,
484 reinterpret_cast<LONG_PTR>(base_path)))
485 return false;
486 #endif
488 return success;
491 // setup.exe still not found. So finally check if it was sent as 'BN'
492 // (uncompressed setup).
493 // TODO(tommi): We don't need BN anymore so let's remove it (and remove
494 // it from create_installer_archive.py).
495 if (!::EnumResourceNames(module, kBinResourceType, OnResourceFound,
496 reinterpret_cast<LONG_PTR>(&context)) &&
497 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND)
498 return false;
500 if (setup_path->length() > 0) {
501 if (setup_path->comparei(setup_dest_path.get()) != 0) {
502 if (!::MoveFileEx(setup_path->get(), setup_dest_path.get(),
503 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) {
504 ::DeleteFile(setup_path->get());
505 setup_path->clear();
506 } else if (!setup_path->assign(setup_dest_path.get())) {
507 ::DeleteFile(setup_dest_path.get());
512 return setup_path->length() > 0;
515 // Executes setup.exe, waits for it to finish and returns the exit code.
516 bool RunSetup(const Configuration& configuration, const wchar_t* archive_path,
517 const wchar_t* setup_path, ProcessExitCode* exit_code) {
518 // There could be three full paths in the command line for setup.exe (path
519 // to exe itself, path to archive and path to log file), so we declare
520 // total size as three + one additional to hold command line options.
521 CommandString cmd_line;
523 // Get the path to setup.exe first.
524 if (::lstrlen(setup_path) > 0) {
525 if (!cmd_line.assign(L"\"") ||
526 !cmd_line.append(setup_path) ||
527 !cmd_line.append(L"\""))
528 return false;
529 } else if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(),
530 cmd_line.capacity())) {
531 return false;
534 // Append the command line param for chrome archive file
535 if (!cmd_line.append(L" --") ||
536 #if defined(COMPONENT_BUILD)
537 // For faster developer turnaround, the component build generates
538 // uncompressed archives.
539 !cmd_line.append(kCmdUncompressedArchive) ||
540 #else
541 !cmd_line.append(kCmdInstallArchive) ||
542 #endif
543 !cmd_line.append(L"=\"") ||
544 !cmd_line.append(archive_path) ||
545 !cmd_line.append(L"\""))
546 return false;
548 // Get any command line option specified for mini_installer and pass them
549 // on to setup.exe
550 AppendCommandLineFlags(configuration, &cmd_line);
552 return RunProcessAndWait(NULL, cmd_line.get(), exit_code);
555 // Deletes given files and working dir.
556 void DeleteExtractedFiles(const wchar_t* base_path,
557 const wchar_t* archive_path,
558 const wchar_t* setup_path) {
559 ::DeleteFile(archive_path);
560 ::DeleteFile(setup_path);
561 // Delete the temp dir (if it is empty, otherwise fail).
562 ::RemoveDirectory(base_path);
565 // Creates a temporary directory under |base_path| and returns the full path
566 // of created directory in |work_dir|. If successful return true, otherwise
567 // false. When successful, the returned |work_dir| will always have a trailing
568 // backslash and this function requires that |base_path| always includes a
569 // trailing backslash as well.
570 // We do not use GetTempFileName here to avoid running into AV software that
571 // might hold on to the temp file as soon as we create it and then we can't
572 // delete it and create a directory in its place. So, we use our own mechanism
573 // for creating a directory with a hopefully-unique name. In the case of a
574 // collision, we retry a few times with a new name before failing.
575 bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) {
576 if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix))
577 return false;
579 // Store the location where we'll append the id.
580 size_t end = work_dir->length();
582 // Check if we'll have enough buffer space to continue.
583 // The name of the directory will use up 11 chars and then we need to append
584 // the trailing backslash and a terminator. We've already added the prefix
585 // to the buffer, so let's just make sure we've got enough space for the rest.
586 if ((work_dir->capacity() - end) < (arraysize("fffff.tmp") + 1))
587 return false;
589 // Generate a unique id. We only use the lowest 20 bits, so take the top
590 // 12 bits and xor them with the lower bits.
591 DWORD id = ::GetTickCount();
592 id ^= (id >> 12);
594 int max_attempts = 10;
595 while (max_attempts--) {
596 // This converts 'id' to a string in the format "78563412" on windows
597 // because of little endianness, but we don't care since it's just
598 // a name.
599 if (!HexEncode(&id, sizeof(id), work_dir->get() + end,
600 work_dir->capacity() - end)) {
601 return false;
604 // We only want the first 5 digits to remain within the 8.3 file name
605 // format (compliant with previous implementation).
606 work_dir->truncate_at(end + 5);
608 // for consistency with the previous implementation which relied on
609 // GetTempFileName, we append the .tmp extension.
610 work_dir->append(L".tmp");
611 if (::CreateDirectory(work_dir->get(), NULL)) {
612 // Yay! Now let's just append the backslash and we're done.
613 return work_dir->append(L"\\");
615 ++id; // Try a different name.
618 return false;
621 // Creates and returns a temporary directory in |work_dir| that can be used to
622 // extract mini_installer payload. |work_dir| ends with a path separator.
623 bool GetWorkDir(HMODULE module, PathString* work_dir) {
624 PathString base_path;
625 DWORD len = ::GetTempPath(base_path.capacity(), base_path.get());
626 if (!len || len >= base_path.capacity() ||
627 !CreateWorkDir(base_path.get(), work_dir)) {
628 // Problem creating the work dir under TEMP path, so try using the
629 // current directory as the base path.
630 len = ::GetModuleFileName(module, base_path.get(), base_path.capacity());
631 if (len >= base_path.capacity() || !len)
632 return false; // Can't even get current directory? Return an error.
634 wchar_t* name = GetNameFromPathExt(base_path.get(), len);
635 if (!name)
636 return false;
638 *name = L'\0';
640 return CreateWorkDir(base_path.get(), work_dir);
642 return true;
645 // Returns true for ".." and "." directories.
646 bool IsCurrentOrParentDirectory(const wchar_t* dir) {
647 return dir &&
648 dir[0] == L'.' &&
649 (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0'));
652 // Best effort directory tree deletion including the directory specified
653 // by |path|, which must not end in a separator.
654 // The |path| argument is writable so that each recursion can use the same
655 // buffer as was originally allocated for the path. The path will be unchanged
656 // upon return.
657 void RecursivelyDeleteDirectory(PathString* path) {
658 // |path| will never have a trailing backslash.
659 size_t end = path->length();
660 if (!path->append(L"\\*.*"))
661 return;
663 WIN32_FIND_DATA find_data = {0};
664 HANDLE find = ::FindFirstFile(path->get(), &find_data);
665 if (find != INVALID_HANDLE_VALUE) {
666 do {
667 // Use the short name if available to make the most of our buffer.
668 const wchar_t* name = find_data.cAlternateFileName[0] ?
669 find_data.cAlternateFileName : find_data.cFileName;
670 if (IsCurrentOrParentDirectory(name))
671 continue;
673 path->truncate_at(end + 1); // Keep the trailing backslash.
674 if (!path->append(name))
675 continue; // Continue in spite of too long names.
677 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
678 RecursivelyDeleteDirectory(path);
679 } else {
680 ::DeleteFile(path->get());
682 } while (::FindNextFile(find, &find_data));
683 ::FindClose(find);
686 // Restore the path and delete the directory before we return.
687 path->truncate_at(end);
688 ::RemoveDirectory(path->get());
691 // Enumerates subdirectories of |parent_dir| and deletes all subdirectories
692 // that match with a given |prefix|. |parent_dir| must have a trailing
693 // backslash.
694 // The process is done on a best effort basis, so conceivably there might
695 // still be matches left when the function returns.
696 void DeleteDirectoriesWithPrefix(const wchar_t* parent_dir,
697 const wchar_t* prefix) {
698 // |parent_dir| is guaranteed to always have a trailing backslash.
699 PathString spec;
700 if (!spec.assign(parent_dir) || !spec.append(prefix) || !spec.append(L"*.*"))
701 return;
703 WIN32_FIND_DATA find_data = {0};
704 HANDLE find = ::FindFirstFileEx(spec.get(), FindExInfoStandard, &find_data,
705 FindExSearchLimitToDirectories, NULL, 0);
706 if (find == INVALID_HANDLE_VALUE)
707 return;
709 PathString path;
710 do {
711 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
712 // Use the short name if available to make the most of our buffer.
713 const wchar_t* name = find_data.cAlternateFileName[0] ?
714 find_data.cAlternateFileName : find_data.cFileName;
715 if (IsCurrentOrParentDirectory(name))
716 continue;
717 if (path.assign(parent_dir) && path.append(name))
718 RecursivelyDeleteDirectory(&path);
720 } while (::FindNextFile(find, &find_data));
721 ::FindClose(find);
724 // Attempts to free up space by deleting temp directories that previous
725 // installer runs have failed to clean up.
726 void DeleteOldChromeTempDirectories() {
727 static const wchar_t* const kDirectoryPrefixes[] = {
728 kTempPrefix,
729 L"chrome_" // Previous installers created directories with this prefix
730 // and there are still some lying around.
733 PathString temp;
734 // GetTempPath always returns a path with a trailing backslash.
735 DWORD len = ::GetTempPath(temp.capacity(), temp.get());
736 // GetTempPath returns 0 or number of chars copied, not including the
737 // terminating '\0'.
738 if (!len || len >= temp.capacity())
739 return;
741 for (int i = 0; i < arraysize(kDirectoryPrefixes); ++i) {
742 DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]);
746 // Checks the command line for specific mini installer flags.
747 // If the function returns true, the command line has been processed and all
748 // required actions taken. The installer must exit and return the returned
749 // |exit_code|.
750 bool ProcessNonInstallOperations(const Configuration& configuration,
751 ProcessExitCode* exit_code) {
752 bool ret = false;
754 switch (configuration.operation()) {
755 case Configuration::CLEANUP:
756 // Cleanup has already taken place in DeleteOldChromeTempDirectories at
757 // this point, so just tell our caller to exit early.
758 *exit_code = SUCCESS_EXIT_CODE;
759 ret = true;
760 break;
762 default: break;
765 return ret;
768 // Returns true if we should delete the temp files we create (default).
769 // Returns false iff the user has manually created a ChromeInstallerCleanup
770 // string value in the registry under HKCU\\Software\\[Google|Chromium]
771 // and set its value to "0". That explicitly forbids the mini installer from
772 // deleting these files.
773 // Support for this has been publicly mentioned in troubleshooting tips so
774 // we continue to support it.
775 bool ShouldDeleteExtractedFiles() {
776 wchar_t value[2] = {0};
777 if (ReadValueFromRegistry(HKEY_CURRENT_USER, kCleanupRegistryKey,
778 kCleanupRegistryValue, value, arraysize(value)) &&
779 value[0] == L'0') {
780 return false;
783 return true;
786 // Main function. First gets a working dir, unpacks the resources and finally
787 // executes setup.exe to do the install/upgrade.
788 ProcessExitCode WMain(HMODULE module) {
789 // Always start with deleting potential leftovers from previous installations.
790 // This can make the difference between success and failure. We've seen
791 // many installations out in the field fail due to out of disk space problems
792 // so this could buy us some space.
793 DeleteOldChromeTempDirectories();
795 // TODO(grt): Make the exit codes more granular so we know where the popular
796 // errors truly are.
797 ProcessExitCode exit_code = GENERIC_INITIALIZATION_FAILURE;
799 // Parse the command line.
800 Configuration configuration;
801 if (!configuration.Initialize())
802 return exit_code;
804 // If the --cleanup switch was specified on the command line, then that means
805 // we should only do the cleanup and then exit.
806 if (ProcessNonInstallOperations(configuration, &exit_code))
807 return exit_code;
809 // First get a path where we can extract payload
810 PathString base_path;
811 if (!GetWorkDir(module, &base_path))
812 return GENERIC_INITIALIZATION_FAILURE;
814 #if defined(GOOGLE_CHROME_BUILD)
815 // Set the magic suffix in registry to try full installer next time. We ignore
816 // any errors here and we try to set the suffix for user level unless
817 // --system-level is on the command line in which case we set it for system
818 // level instead. This only applies to the Google Chrome distribution.
819 SetInstallerFlags(configuration);
820 #endif
822 PathString archive_path;
823 PathString setup_path;
824 if (!UnpackBinaryResources(configuration, module, base_path.get(),
825 &archive_path, &setup_path)) {
826 exit_code = GENERIC_UNPACKING_FAILURE;
827 } else {
828 // While unpacking the binaries, we paged in a whole bunch of memory that
829 // we don't need anymore. Let's give it back to the pool before running
830 // setup.
831 ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1);
832 if (!RunSetup(configuration, archive_path.get(), setup_path.get(),
833 &exit_code)) {
834 exit_code = GENERIC_SETUP_FAILURE;
838 if (ShouldDeleteExtractedFiles())
839 DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get());
841 return exit_code;
844 } // namespace mini_installer
846 int MainEntryPoint() {
847 mini_installer::ProcessExitCode result =
848 mini_installer::WMain(::GetModuleHandle(NULL));
849 ::ExitProcess(result);
852 // VC Express editions don't come with the memset CRT obj file and linking to
853 // the obj files between versions becomes a bit problematic. Therefore,
854 // simply implement memset.
856 // This also avoids having to explicitly set the __sse2_available hack when
857 // linking with both the x64 and x86 obj files which is required when not
858 // linking with the std C lib in certain instances (including Chromium) with
859 // MSVC. __sse2_available determines whether to use SSE2 intructions with
860 // std C lib routines, and is set by MSVC's std C lib implementation normally.
861 extern "C" {
862 #pragma function(memset)
863 void* memset(void* dest, int c, size_t count) {
864 void* start = dest;
865 while (count--) {
866 *reinterpret_cast<char*>(dest) = static_cast<char>(c);
867 dest = reinterpret_cast<char*>(dest) + 1;
869 return start;
871 } // extern "C"