Add a function to create a bookmark app from a WebApplicationInfo.
[chromium-blink-merge.git] / chrome / installer / mini_installer / mini_installer.cc
blob28a8057627e67353dc4fb8d737337f467ea22a56
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/mini_installer.h"
31 #include "chrome/installer/mini_installer/mini_string.h"
32 #include "chrome/installer/mini_installer/pe_resource.h"
34 namespace mini_installer {
36 typedef StackString<MAX_PATH> PathString;
37 typedef StackString<MAX_PATH * 4> CommandString;
39 // This structure passes data back and forth for the processing
40 // of resource callbacks.
41 struct 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;
50 // A helper class used to manipulate the Windows registry. Typically, members
51 // return Windows last-error codes a la the Win32 registry API.
52 class RegKey {
53 public:
54 RegKey() : key_(NULL) { }
55 ~RegKey() { Close(); }
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);
61 // Returns true if a key is open.
62 bool is_valid() const { return key_ != NULL; }
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;
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);
76 // Closes the key if it was open.
77 void Close();
79 private:
80 RegKey(const RegKey&);
81 RegKey& operator=(const RegKey&);
83 HKEY key_;
84 }; // class RegKey
86 LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
87 Close();
88 return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_);
91 LONG 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;
111 return result;
114 LONG 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));
120 void RegKey::Close() {
121 if (key_ != NULL) {
122 ::RegCloseKey(key_);
123 key_ = NULL;
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.
130 bool 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;
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;
139 return false;
142 // Opens the Google Update ClientState key for a product.
143 bool 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);
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.
157 void 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;
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;
200 if (!key.is_valid()) {
201 if (!OpenClientStateKey(root_key, app_guid, key_access, &key))
202 return;
204 value.clear();
205 ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity());
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();
217 if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
218 value.append(kFullInstallerSuffix)) {
219 key.WriteValue(kApRegistryValueName, value.get());
224 // Gets the setup.exe path from Registry by looking the value of Uninstall
225 // string. |size| is measured in wchar_t units.
226 bool 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);
236 // Gets the setup.exe path from Registry by looking the value of Uninstall
237 // string. |size| is measured in wchar_t units.
238 bool GetSetupExePathFromRegistry(const Configuration& configuration,
239 wchar_t* path,
240 size_t size) {
241 bool system_level = configuration.is_system_level();
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;
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;
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;
262 if (configuration.has_app_host() && GetSetupExePathForGuidFromRegistry(
263 system_level, google_update::kChromeAppHostAppGuid, path, size)) {
264 return true;
267 return false;
270 // Calls CreateProcess with good default parameters and waits for the process
271 // to terminate returning the process exit code.
272 bool 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;
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,
289 reinterpret_cast<DWORD*>(exit_code))) {
290 ret = false;
294 ::CloseHandle(pi.hProcess);
296 return ret;
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.
302 void 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;
310 const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len);
311 if (exe_name == NULL)
312 return;
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;
325 buffer->append(cmd_to_append);
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.
333 BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type,
334 wchar_t* name, LONG_PTR context) {
335 if (NULL == context)
336 return FALSE;
338 Context* ctx = reinterpret_cast<Context*>(context);
340 PEResource resource(name, type, module);
341 if ((!resource.IsValid()) ||
342 (resource.Size() < 1) ||
343 (resource.Size() > kMaxResourceSize)) {
344 return FALSE;
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;
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;
365 return TRUE;
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.
377 bool 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;
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,
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;
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;
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);
426 int exit_code = 0;
427 if (success &&
428 (!RunProcessAndWait(NULL, cmd_line.get(), &exit_code) ||
429 exit_code != ERROR_SUCCESS)) {
430 success = false;
433 if (!success)
434 DeleteFile(setup_path->get());
436 return success && setup_path->assign(setup_dest_path.get());
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;
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;
459 return success;
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;
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());
483 return setup_path->length() > 0;
486 // Executes setup.exe, waits for it to finish and returns the exit code.
487 bool 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;
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;
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;
512 // Get any command line option specified for mini_installer and pass them
513 // on to setup.exe
514 AppendCommandLineFlags(configuration, &cmd_line);
516 return RunProcessAndWait(NULL, cmd_line.get(), exit_code);
519 // Deletes given files and working dir.
520 void 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);
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.
539 bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) {
540 if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix))
541 return false;
543 // Store the location where we'll append the id.
544 size_t end = work_dir->length();
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;
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);
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;
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);
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"\\");
579 ++id; // Try a different name.
582 return false;
585 // Creates and returns a temporary directory that can be used to extract
586 // mini_installer payload.
587 bool 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.
598 wchar_t* name = GetNameFromPathExt(base_path.get(), len);
599 if (!name)
600 return false;
602 *name = L'\0';
604 return CreateWorkDir(base_path.get(), work_dir);
606 return true;
609 // Returns true for ".." and "." directories.
610 bool IsCurrentOrParentDirectory(const wchar_t* dir) {
611 return dir &&
612 dir[0] == L'.' &&
613 (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0'));
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.
621 void RecursivelyDeleteDirectory(PathString* path) {
622 // |path| will never have a trailing backslash.
623 size_t end = path->length();
624 if (!path->append(L"\\*.*"))
625 return;
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;
637 path->truncate_at(end + 1); // Keep the trailing backslash.
638 if (!path->append(name))
639 continue; // Continue in spite of too long names.
641 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
642 RecursivelyDeleteDirectory(path);
643 } else {
644 ::DeleteFile(path->get());
646 } while (::FindNextFile(find, &find_data));
647 ::FindClose(find);
650 // Restore the path and delete the directory before we return.
651 path->truncate_at(end);
652 ::RemoveDirectory(path->get());
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.
660 void 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;
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;
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);
684 } while (::FindNextFile(find, &find_data));
685 ::FindClose(find);
688 // Attempts to free up space by deleting temp directories that previous
689 // installer runs have failed to clean up.
690 void 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.
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;
705 for (int i = 0; i < arraysize(kDirectoryPrefixes); ++i) {
706 DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]);
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|.
714 bool ProcessNonInstallOperations(const Configuration& configuration,
715 int* exit_code) {
716 bool ret = false;
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;
726 default: break;
729 return ret;
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.
739 bool 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;
748 return true;
751 // Main function. First gets a working dir, unpacks the resources and finally
752 // executes setup.exe to do the install/upgrade.
753 int WMain(HMODULE module) {
754 #if defined(COMPONENT_BUILD)
755 static const wchar_t kComponentBuildIncompatibleMessage[] =
756 L"mini_installer.exe is incompatible with the component build, please run"
757 L" setup.exe with the same command line instead. See"
758 L" http://crbug.com/127233#c17 for details.";
759 ::MessageBox(NULL, kComponentBuildIncompatibleMessage, NULL, MB_ICONERROR);
760 return 1;
761 #endif
763 // Always start with deleting potential leftovers from previous installations.
764 // This can make the difference between success and failure. We've seen
765 // many installations out in the field fail due to out of disk space problems
766 // so this could buy us some space.
767 DeleteOldChromeTempDirectories();
769 // TODO(grt): Make the exit codes more granular so we know where the popular
770 // errors truly are.
771 int exit_code = 101;
773 // Parse the command line.
774 Configuration configuration;
775 if (!configuration.Initialize())
776 return exit_code;
778 // If the --cleanup switch was specified on the command line, then that means
779 // we should only do the cleanup and then exit.
780 if (ProcessNonInstallOperations(configuration, &exit_code))
781 return exit_code;
783 // First get a path where we can extract payload
784 PathString base_path;
785 if (!GetWorkDir(module, &base_path))
786 return 101;
788 #if defined(GOOGLE_CHROME_BUILD)
789 // Set the magic suffix in registry to try full installer next time. We ignore
790 // any errors here and we try to set the suffix for user level unless
791 // --system-level is on the command line in which case we set it for system
792 // level instead. This only applies to the Google Chrome distribution.
793 SetInstallerFlags(configuration);
794 #endif
796 PathString archive_path;
797 PathString setup_path;
798 if (!UnpackBinaryResources(configuration, module, base_path.get(),
799 &archive_path, &setup_path)) {
800 exit_code = 102;
801 } else {
802 // While unpacking the binaries, we paged in a whole bunch of memory that
803 // we don't need anymore. Let's give it back to the pool before running
804 // setup.
805 ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1);
806 if (!RunSetup(configuration, archive_path.get(), setup_path.get(),
807 &exit_code)) {
808 exit_code = 103;
812 if (ShouldDeleteExtractedFiles())
813 DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get());
815 return exit_code;
818 } // namespace mini_installer
820 int MainEntryPoint() {
821 int result = mini_installer::WMain(::GetModuleHandle(NULL));
822 ::ExitProcess(result);
825 // VC Express editions don't come with the memset CRT obj file and linking to
826 // the obj files between versions becomes a bit problematic. Therefore,
827 // simply implement memset.
829 // This also avoids having to explicitly set the __sse2_available hack when
830 // linking with both the x64 and x86 obj files which is required when not
831 // linking with the std C lib in certain instances (including Chromium) with
832 // MSVC. __sse2_available determines whether to use SSE2 intructions with
833 // std C lib routines, and is set by MSVC's std C lib implementation normally.
834 extern "C" {
835 #pragma function(memset)
836 void* memset(void* dest, int c, size_t count) {
837 // Simplistic 32-bit memset C implementation which assumes properly aligned
838 // memory; performance hit on memory that isn't properly aligned, but still
839 // better overall then a 8-bit implementation.
840 size_t adjcount = count >> 2;
841 UINT32 fill = (c << 24 | c << 16 | c << 8 | c);
842 UINT32* dest32 = reinterpret_cast<UINT32*>(dest);
843 UINT8* dest8 = reinterpret_cast<UINT8*>(dest);
845 // Take care of the ending 0-3 bytes (binary 11 = 3). The lack of breaks is
846 // deliberate; it falls through for each byte. Think of it a simplified for
847 // loop.
848 switch (count - (adjcount << 2)) {
849 case 3:
850 dest8[count - 3] = c;
851 case 2:
852 dest8[count - 2] = c;
853 case 1:
854 dest8[count - 1] = c;
857 while (adjcount-- > 0) // Copy the rest, 4 bytes/32 bits at a time
858 *(dest32++) = fill;
860 return dest;
862 } // extern "C"