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")
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.
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.
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
,
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.
82 RegKey(const RegKey
&);
83 RegKey
& operator=(const RegKey
&);
88 LONG
RegKey::Open(HKEY key
, const wchar_t* sub_key
, REGSAM access
) {
90 return ::RegOpenKeyEx(key
, sub_key
, NULL
, access
, &key_
);
93 LONG
RegKey::ReadValue(const wchar_t* value_name
,
95 size_t value_size
) const {
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
),
101 if (result
== ERROR_SUCCESS
) {
102 if (type
!= REG_SZ
) {
103 result
= ERROR_NOT_SUPPORTED
;
104 } else if (byte_length
== 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';
110 result
= ERROR_MORE_DATA
;
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() {
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
,
137 if (key
.Open(root_key
, sub_key
, KEY_QUERY_VALUE
) == ERROR_SUCCESS
&&
138 key
.ReadValue(value_name
, value
, size
) == ERROR_SUCCESS
) {
144 // Opens the Google Update ClientState key for a product.
145 bool OpenClientStateKey(HKEY root_key
, const wchar_t* app_guid
, REGSAM access
,
147 PathString client_state_key
;
148 return client_state_key
.assign(kClientStateKeyBase
) &&
149 client_state_key
.append(app_guid
) &&
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
) {
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
;
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.
195 app_guid
= google_update::kMultiInstallAppGuid
;
196 } // else case 3: modify this value.
198 // case 1 or 2: modify the multi-installer's value.
200 app_guid
= google_update::kMultiInstallAppGuid
;
204 if (!key
.is_valid()) {
205 if (!OpenClientStateKey(root_key
, app_guid
, key_access
, &key
))
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
215 // 2. When ap value is missing, we are going to create it with the required
217 if ((ret
== ERROR_SUCCESS
) || (ret
== ERROR_FILE_NOT_FOUND
)) {
218 if (ret
== ERROR_FILE_NOT_FOUND
)
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
,
234 const HKEY root_key
= system_level
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
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
,
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
)) {
253 // Failing that, look in Chrome Frame's client state key if --chrome-frame was
255 if (configuration
.has_chrome_frame() && GetSetupExePathForGuidFromRegistry(
256 system_level
, google_update::kChromeFrameAppGuid
, path
, size
)) {
260 // Make a last-ditch effort to look in the Chrome and App Host client state
262 if (GetSetupExePathForGuidFromRegistry(
263 system_level
, configuration
.chrome_app_guid(), path
, size
)) {
266 if (configuration
.has_app_host() && GetSetupExePathForGuidFromRegistry(
267 system_level
, google_update::kChromeAppHostAppGuid
, path
, size
)) {
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
)) {
286 ::CloseHandle(pi
.hThread
);
289 DWORD wr
= ::WaitForSingleObject(pi
.hProcess
, INFINITE
);
290 if (WAIT_OBJECT_0
!= wr
) {
292 } else if (exit_code
) {
293 if (!::GetExitCodeProcess(pi
.hProcess
, exit_code
))
297 ::CloseHandle(pi
.hProcess
);
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())
313 const wchar_t* exe_name
= GetNameFromPathExt(full_exe_path
.get(), len
);
314 if (exe_name
== NULL
)
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
" ");
328 buffer
->append(cmd_to_append
);
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
) {
341 Context
* ctx
= reinterpret_cast<Context
*>(context
);
343 PEResource
resource(name
, type
, module
);
344 if ((!resource
.IsValid()) ||
345 (resource
.Size() < 1) ||
346 (resource
.Size() > kMaxResourceSize
)) {
350 PathString full_path
;
351 if (!full_path
.assign(ctx
->base_path
) ||
352 !full_path
.append(name
) ||
353 !resource
.WriteToDisk(full_path
.get()))
356 if (StrStartsWith(name
, kChromeArchivePrefix
)) {
357 if (!ctx
->chrome_resource_path
->assign(full_path
.get()))
359 } else if (StrStartsWith(name
, kSetupPrefix
)) {
360 if (!ctx
->setup_resource_path
->assign(full_path
.get()))
363 // Resources should either start with 'chrome' or 'setup'. We don't handle
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
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
))
389 // Prepare the input to OnResourceFound method that needs a location where
390 // it will write all the resources.
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)
405 // If we found setup 'B7' resource, handle it.
406 if (setup_path
->length() > 0) {
407 CommandString cmd_line
;
409 // Get the path to setup.exe first.
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
"\"")) {
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
);
432 ProcessExitCode exit_code
= SUCCESS_EXIT_CODE
;
434 (!RunProcessAndWait(exe_path
.get(), cmd_line
.get(), &exit_code
) ||
435 exit_code
!= SUCCESS_EXIT_CODE
)) {
440 DeleteFile(setup_path
->get());
442 return success
&& setup_path
->assign(setup_dest_path
.get());
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
)
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());
459 if (!setup_path
->assign(setup_dest_path
.get())) {
460 ::DeleteFile(setup_dest_path
.get());
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
)
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());
483 } else if (!setup_path
->assign(setup_dest_path
.get())) {
484 ::DeleteFile(setup_dest_path
.get());
489 return setup_path
->length() > 0;
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
;
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
"\""))
506 } else if (!GetSetupExePathFromRegistry(configuration
, cmd_line
.get(),
507 cmd_line
.capacity())) {
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
"\""))
519 // Get any command line option specified for mini_installer and pass them
521 AppendCommandLineFlags(configuration
, &cmd_line
);
523 return RunProcessAndWait(NULL
, cmd_line
.get(), exit_code
);
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
);
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
))
550 // Store the location where we'll append the id.
551 size_t end
= work_dir
->length();
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))
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();
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
570 if (!HexEncode(&id
, sizeof(id
), work_dir
->get() + end
,
571 work_dir
->capacity() - end
)) {
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);
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
"\\");
586 ++id
; // Try a different name.
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.
605 wchar_t* name
= GetNameFromPathExt(base_path
.get(), len
);
611 return CreateWorkDir(base_path
.get(), work_dir
);
616 // Returns true for ".." and "." directories.
617 bool IsCurrentOrParentDirectory(const wchar_t* dir
) {
620 (dir
[1] == L
'\0' || (dir
[1] == L
'.' && dir
[2] == L
'\0'));
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
628 void RecursivelyDeleteDirectory(PathString
* path
) {
629 // |path| will never have a trailing backslash.
630 size_t end
= path
->length();
631 if (!path
->append(L
"\\*.*"))
634 WIN32_FIND_DATA find_data
= {0};
635 HANDLE find
= ::FindFirstFile(path
->get(), &find_data
);
636 if (find
!= INVALID_HANDLE_VALUE
) {
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
))
644 path
->truncate_at(end
+ 1); // Keep the trailing backslash.
645 if (!path
->append(name
))
646 continue; // Continue in spite of too long names.
648 if (find_data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
649 RecursivelyDeleteDirectory(path
);
651 ::DeleteFile(path
->get());
653 } while (::FindNextFile(find
, &find_data
));
657 // Restore the path and delete the directory before we return.
658 path
->truncate_at(end
);
659 ::RemoveDirectory(path
->get());
662 // Enumerates subdirectories of |parent_dir| and deletes all subdirectories
663 // that match with a given |prefix|. |parent_dir| must have a trailing
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.
671 if (!spec
.assign(parent_dir
) || !spec
.append(prefix
) || !spec
.append(L
"*.*"))
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
)
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
))
688 if (path
.assign(parent_dir
) && path
.append(name
))
689 RecursivelyDeleteDirectory(&path
);
691 } while (::FindNextFile(find
, &find_data
));
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
[] = {
700 L
"chrome_" // Previous installers created directories with this prefix
701 // and there are still some lying around.
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
709 if (!len
|| len
>= temp
.capacity())
712 for (int i
= 0; i
< arraysize(kDirectoryPrefixes
); ++i
) {
713 DeleteDirectoriesWithPrefix(temp
.get(), kDirectoryPrefixes
[i
]);
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
721 bool ProcessNonInstallOperations(const Configuration
& configuration
,
722 ProcessExitCode
* exit_code
) {
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
;
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
)) &&
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
;
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();
777 // TODO(grt): Make the exit codes more granular so we know where the popular
779 ProcessExitCode exit_code
= GENERIC_INITIALIZATION_FAILURE
;
781 // Parse the command line.
782 Configuration configuration
;
783 if (!configuration
.Initialize())
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
;
793 return GENERIC_ERROR
;
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
))
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
;
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
);
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
;
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
824 ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1);
825 if (!RunSetup(configuration
, archive_path
.get(), setup_path
.get(),
827 exit_code
= GENERIC_SETUP_FAILURE
;
831 if (ShouldDeleteExtractedFiles())
832 DeleteExtractedFiles(base_path
.get(), archive_path
.get(), setup_path
.get());
837 } // namespace mini_installer
839 int MainEntryPoint() {
840 mini_installer::ProcessExitCode result
=
841 mini_installer::WMain(::GetModuleHandle(NULL
));
842 ::ExitProcess(result
);
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.
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.
855 #pragma function(memset)
856 void* memset(void* dest
, int c
, size_t count
) {
859 *reinterpret_cast<char*>(dest
) = static_cast<char>(c
);
860 dest
= reinterpret_cast<char*>(dest
) + 1;