Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / installer / mini_installer / mini_installer.cc
blob3ecd6e5fa90c156117f7d6d00e846d551a1a1003
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>
26 #include <stdlib.h>
28 #include "chrome/installer/mini_installer/appid.h"
29 #include "chrome/installer/mini_installer/configuration.h"
30 #include "chrome/installer/mini_installer/decompress.h"
31 #include "chrome/installer/mini_installer/exit_code.h"
32 #include "chrome/installer/mini_installer/mini_installer_constants.h"
33 #include "chrome/installer/mini_installer/mini_string.h"
34 #include "chrome/installer/mini_installer/pe_resource.h"
35 #include "chrome/installer/mini_installer/regkey.h"
37 namespace mini_installer {
39 typedef StackString<MAX_PATH> PathString;
40 typedef StackString<MAX_PATH * 4> CommandString;
42 struct ProcessExitResult {
43 DWORD exit_code;
44 DWORD windows_error;
46 explicit ProcessExitResult(DWORD exit) : exit_code(exit), windows_error(0) {}
47 ProcessExitResult(DWORD exit, DWORD win)
48 : exit_code(exit), windows_error(win) {
51 bool IsSuccess() {
52 return exit_code == SUCCESS_EXIT_CODE;
56 // This structure passes data back and forth for the processing
57 // of resource callbacks.
58 struct Context {
59 // Input to the call back method. Specifies the dir to save resources.
60 const wchar_t* base_path;
61 // First output from call back method. Full path of Chrome archive.
62 PathString* chrome_resource_path;
63 // Second output from call back method. Full path of Setup archive/exe.
64 PathString* setup_resource_path;
68 // Opens the Google Update ClientState key for the current install
69 // configuration. This includes locating the correct key in the face of
70 // multi-install. The flag will by default be written to HKCU, but if
71 // --system-level is included in the command line, it will be written to
72 // HKLM instead.
73 bool OpenInstallStateKey(const Configuration& configuration, RegKey* key) {
74 const HKEY root_key =
75 configuration.is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
76 const wchar_t* app_guid = configuration.chrome_app_guid();
77 const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE;
79 return OpenClientStateKey(root_key, app_guid, key_access, key);
82 // Writes install results into registry where it is read by Google Update.
83 // Don't write anything if there is already a result present, likely
84 // written by setup.exe.
85 void WriteInstallResults(const Configuration& configuration,
86 ProcessExitResult result) {
87 #if defined(GOOGLE_CHROME_BUILD)
88 // Calls to setup.exe will write a "success" result if everything was good
89 // so we don't need to write anything from here.
90 if (result.IsSuccess())
91 return;
93 RegKey key;
94 DWORD value;
95 if (OpenInstallStateKey(configuration, &key)) {
96 if (key.ReadDWValue(kInstallerResultRegistryValue, &value)
97 != ERROR_SUCCESS || value == 0) {
98 key.WriteDWValue(kInstallerResultRegistryValue,
99 result.exit_code ? 1 /* FAILED_CUSTOM_ERROR */
100 : 0 /* SUCCESS */);
101 key.WriteDWValue(kInstallerErrorRegistryValue, result.exit_code);
102 key.WriteDWValue(kInstallerExtraCode1RegistryValue, result.windows_error);
104 key.Close();
106 #endif
109 // This function sets the flag in registry to indicate that Google Update
110 // should try full installer next time. If the current installer works, this
111 // flag is cleared by setup.exe at the end of install.
112 void SetInstallerFlags(const Configuration& configuration) {
113 RegKey key;
114 StackString<128> value;
115 LONG ret = ERROR_SUCCESS;
117 if (!OpenInstallStateKey(configuration, &key))
118 return;
120 ret = key.ReadSZValue(kApRegistryValue, value.get(), value.capacity());
122 // The conditions below are handling two cases:
123 // 1. When ap value is present, we want to add the required tag only if it is
124 // not present.
125 // 2. When ap value is missing, we are going to create it with the required
126 // tag.
127 if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) {
128 if (ret == ERROR_FILE_NOT_FOUND)
129 value.clear();
131 if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
132 value.append(kFullInstallerSuffix)) {
133 key.WriteSZValue(kApRegistryValue, value.get());
138 // Gets the setup.exe path from Registry by looking at the value of Uninstall
139 // string. |size| is measured in wchar_t units.
140 ProcessExitResult GetSetupExePathForAppGuid(bool system_level,
141 const wchar_t* app_guid,
142 const wchar_t* previous_version,
143 wchar_t* path,
144 size_t size) {
145 const HKEY root_key = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
146 RegKey key;
147 if (!OpenClientStateKey(root_key, app_guid, KEY_QUERY_VALUE, &key))
148 return ProcessExitResult(UNABLE_TO_FIND_REGISTRY_KEY);
149 DWORD result = key.ReadSZValue(kUninstallRegistryValue, path, size);
150 if (result != ERROR_SUCCESS)
151 return ProcessExitResult(UNABLE_TO_FIND_REGISTRY_KEY, result);
153 // Check that the path to the existing installer includes the expected
154 // version number. It's not necessary for accuracy to verify before/after
155 // delimiters.
156 if (!SearchStringI(path, previous_version))
157 return ProcessExitResult(PATCH_NOT_FOR_INSTALLED_VERSION);
159 return ProcessExitResult(SUCCESS_EXIT_CODE);
162 // Gets the path to setup.exe of the previous version. The overall path is found
163 // in the Uninstall string in the registry. A previous version number specified
164 // in |configuration| is used if available. |size| is measured in wchar_t units.
165 ProcessExitResult GetPreviousSetupExePath(const Configuration& configuration,
166 wchar_t* path,
167 size_t size) {
168 bool system_level = configuration.is_system_level();
169 const wchar_t* previous_version = configuration.previous_version();
170 ProcessExitResult exit_code = ProcessExitResult(GENERIC_ERROR);
172 // If this is a multi install, first try looking in the binaries for the path.
173 if (configuration.is_multi_install()) {
174 exit_code = GetSetupExePathForAppGuid(
175 system_level, google_update::kMultiInstallAppGuid, previous_version,
176 path, size);
179 // Failing that, look in Chrome Frame's client state key if --chrome-frame was
180 // specified.
181 if (!exit_code.IsSuccess() && configuration.has_chrome_frame()) {
182 exit_code = GetSetupExePathForAppGuid(
183 system_level, google_update::kChromeFrameAppGuid, previous_version,
184 path, size);
187 // Make a last-ditch effort to look in the Chrome client state key.
188 if (!exit_code.IsSuccess()) {
189 exit_code = GetSetupExePathForAppGuid(
190 system_level, configuration.chrome_app_guid(), previous_version,
191 path, size);
194 return exit_code;
197 // Calls CreateProcess with good default parameters and waits for the process to
198 // terminate returning the process exit code. |exit_code|, if non-NULL, is
199 // populated with the process exit code.
200 ProcessExitResult RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline) {
201 STARTUPINFOW si = {sizeof(si)};
202 PROCESS_INFORMATION pi = {0};
203 if (!::CreateProcess(exe_path, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW,
204 NULL, NULL, &si, &pi)) {
205 return ProcessExitResult(COULD_NOT_CREATE_PROCESS, ::GetLastError());
208 ::CloseHandle(pi.hThread);
210 DWORD exit_code = SUCCESS_EXIT_CODE;
211 DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE);
212 if (WAIT_OBJECT_0 != wr || !::GetExitCodeProcess(pi.hProcess, &exit_code)) {
213 // Note: We've assumed that WAIT_OBJCT_0 != wr means a failure. The call
214 // could return a different object but since we never spawn more than one
215 // sub-process at a time that case should never happen.
216 return ProcessExitResult(WAIT_FOR_PROCESS_FAILED, ::GetLastError());
219 ::CloseHandle(pi.hProcess);
221 return ProcessExitResult(exit_code);
224 // Appends any command line params passed to mini_installer to the given buffer
225 // so that they can be passed on to setup.exe.
226 // |buffer| is unchanged in case of error.
227 void AppendCommandLineFlags(const Configuration& configuration,
228 CommandString* buffer) {
229 PathString full_exe_path;
230 size_t len = ::GetModuleFileName(
231 NULL, full_exe_path.get(), static_cast<DWORD>(full_exe_path.capacity()));
232 if (!len || len >= full_exe_path.capacity())
233 return;
235 const wchar_t* exe_name =
236 GetNameFromPathExt(full_exe_path.get(), static_cast<DWORD>(len));
238 // - configuration.program() returns the first command line argument
239 // passed into the program (that the user probably typed in this case).
240 // "mini_installer.exe"
241 // "mini_installer"
242 // "out\Release\mini_installer"
243 // - |exe_name| is the executable file of the current process.
244 // "mini_installer.exe"
246 // Note that there are three possibilities to handle here.
247 // Receive a cmdline containing:
248 // 1) executable name WITH extension
249 // 2) executable name with NO extension
250 // 3) NO executable name as part of cmdline
251 const wchar_t* cmd_to_append = L"";
252 const wchar_t* arg0 = configuration.program();
253 if (!arg0)
254 return;
255 const wchar_t* arg0_base_name = GetNameFromPathExt(arg0, ::lstrlen(arg0));
256 if (!StrStartsWith(exe_name, arg0_base_name)) {
257 // State 3: NO executable name as part of cmdline.
258 buffer->append(L" ");
259 cmd_to_append = configuration.command_line();
260 } else if (configuration.argument_count() > 1) {
261 // State 1 or 2: Executable name is in cmdline.
262 // - Append everything AFTER the executable name.
263 // (Using arg0_base_name here to make sure to match with or without
264 // extension. Then move to the space following the token.)
265 const wchar_t* tmp = SearchStringI(configuration.command_line(),
266 arg0_base_name);
267 tmp = SearchStringI(tmp, L" ");
268 cmd_to_append = tmp;
271 buffer->append(cmd_to_append);
275 // Windows defined callback used in the EnumResourceNames call. For each
276 // matching resource found, the callback is invoked and at this point we write
277 // it to disk. We expect resource names to start with 'chrome' or 'setup'. Any
278 // other name is treated as an error.
279 BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type,
280 wchar_t* name, LONG_PTR context) {
281 if (NULL == context)
282 return FALSE;
284 Context* ctx = reinterpret_cast<Context*>(context);
286 PEResource resource(name, type, module);
287 if ((!resource.IsValid()) ||
288 (resource.Size() < 1) ||
289 (resource.Size() > kMaxResourceSize)) {
290 return FALSE;
293 PathString full_path;
294 if (!full_path.assign(ctx->base_path) ||
295 !full_path.append(name) ||
296 !resource.WriteToDisk(full_path.get()))
297 return FALSE;
299 if (StrStartsWith(name, kChromeArchivePrefix)) {
300 if (!ctx->chrome_resource_path->assign(full_path.get()))
301 return FALSE;
302 } else if (StrStartsWith(name, kSetupPrefix)) {
303 if (!ctx->setup_resource_path->assign(full_path.get()))
304 return FALSE;
305 } else {
306 // Resources should either start with 'chrome' or 'setup'. We don't handle
307 // anything else.
308 return FALSE;
311 return TRUE;
314 #if defined(COMPONENT_BUILD)
315 // An EnumResNameProc callback that writes the resource |name| to disk in the
316 // directory |base_path_ptr| (which must end with a path separator).
317 BOOL CALLBACK WriteResourceToDirectory(HMODULE module,
318 const wchar_t* type,
319 wchar_t* name,
320 LONG_PTR base_path_ptr) {
321 const wchar_t* base_path = reinterpret_cast<const wchar_t*>(base_path_ptr);
322 PathString full_path;
324 PEResource resource(name, type, module);
325 return (resource.IsValid() &&
326 full_path.assign(base_path) &&
327 full_path.append(name) &&
328 resource.WriteToDisk(full_path.get()));
330 #endif
332 // Finds and writes to disk resources of various types. Returns false
333 // if there is a problem in writing any resource to disk. setup.exe resource
334 // can come in one of three possible forms:
335 // - Resource type 'B7', compressed using LZMA (*.7z)
336 // - Resource type 'BL', compressed using LZ (*.ex_)
337 // - Resource type 'BN', uncompressed (*.exe)
338 // If setup.exe is present in more than one form, the precedence order is
339 // BN < BL < B7
340 // For more details see chrome/tools/build/win/create_installer_archive.py.
341 // For component builds (where setup.ex_ is always used), all files stored as
342 // uncompressed 'BN' resources are also extracted. This is generally the set of
343 // DLLs/resources needed by setup.exe to run.
344 ProcessExitResult UnpackBinaryResources(const Configuration& configuration,
345 HMODULE module, const wchar_t* base_path,
346 PathString* archive_path,
347 PathString* setup_path) {
348 // Generate the setup.exe path where we patch/uncompress setup resource.
349 PathString setup_dest_path;
350 if (!setup_dest_path.assign(base_path) ||
351 !setup_dest_path.append(kSetupExe))
352 return ProcessExitResult(PATH_STRING_OVERFLOW);
354 // Prepare the input to OnResourceFound method that needs a location where
355 // it will write all the resources.
356 Context context = {
357 base_path,
358 archive_path,
359 setup_path,
362 // Get the resources of type 'B7' (7zip archive).
363 // We need a chrome archive to do the installation. So if there
364 // is a problem in fetching B7 resource, just return an error.
365 if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
366 reinterpret_cast<LONG_PTR>(&context))) {
367 return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE,
368 ::GetLastError());
370 if (archive_path->length() == 0) {
371 return ProcessExitResult(UNABLE_TO_EXTRACT_CHROME_ARCHIVE);
374 ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
376 // If we found setup 'B7' resource (used for differential updates), handle
377 // it. Note that this is only for Chrome; Chromium installs are always
378 // "full" installs.
379 if (setup_path->length() > 0) {
380 CommandString cmd_line;
381 PathString exe_path;
382 // Get the path to setup.exe first.
383 exit_code = GetPreviousSetupExePath(configuration, exe_path.get(),
384 exe_path.capacity());
385 if (exit_code.IsSuccess()) {
386 if (!cmd_line.append(exe_path.get()) ||
387 !cmd_line.append(L" --") ||
388 !cmd_line.append(kCmdUpdateSetupExe) ||
389 !cmd_line.append(L"=\"") ||
390 !cmd_line.append(setup_path->get()) ||
391 !cmd_line.append(L"\" --") ||
392 !cmd_line.append(kCmdNewSetupExe) ||
393 !cmd_line.append(L"=\"") ||
394 !cmd_line.append(setup_dest_path.get()) ||
395 !cmd_line.append(L"\"")) {
396 exit_code = ProcessExitResult(COMMAND_STRING_OVERFLOW);
400 // Get any command line option specified for mini_installer and pass them
401 // on to setup.exe. This is important since switches such as
402 // --multi-install and --chrome-frame affect where setup.exe will write
403 // installer results for consumption by Google Update.
404 AppendCommandLineFlags(configuration, &cmd_line);
406 if (exit_code.IsSuccess())
407 exit_code = RunProcessAndWait(exe_path.get(), cmd_line.get());
409 if (!exit_code.IsSuccess())
410 DeleteFile(setup_path->get());
411 else if (!setup_path->assign(setup_dest_path.get()))
412 exit_code = ProcessExitResult(PATH_STRING_OVERFLOW);
414 return exit_code;
417 // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL'
418 // (compressed setup).
419 if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound,
420 reinterpret_cast<LONG_PTR>(&context)) &&
421 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) {
422 return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_B7, ::GetLastError());
425 if (setup_path->length() > 0) {
426 // Uncompress LZ compressed resource. Setup is packed with 'MSCF'
427 // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy.
428 bool success = mini_installer::Expand(setup_path->get(),
429 setup_dest_path.get());
430 ::DeleteFile(setup_path->get());
431 if (success) {
432 if (!setup_path->assign(setup_dest_path.get())) {
433 ::DeleteFile(setup_dest_path.get());
434 exit_code = ProcessExitResult(PATH_STRING_OVERFLOW);
436 } else {
437 exit_code = ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_EXE);
440 #if defined(COMPONENT_BUILD)
441 // Extract the (uncompressed) modules required by setup.exe.
442 if (!::EnumResourceNames(module, kBinResourceType, WriteResourceToDirectory,
443 reinterpret_cast<LONG_PTR>(base_path))) {
444 return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP, ::GetLastError());
446 #endif
448 return exit_code;
451 // setup.exe still not found. So finally check if it was sent as 'BN'
452 // (uncompressed setup).
453 // TODO(tommi): We don't need BN anymore so let's remove it (and remove
454 // it from create_installer_archive.py).
455 if (!::EnumResourceNames(module, kBinResourceType, OnResourceFound,
456 reinterpret_cast<LONG_PTR>(&context)) &&
457 ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) {
458 return ProcessExitResult(UNABLE_TO_EXTRACT_SETUP_BN, ::GetLastError());
461 if (setup_path->length() > 0) {
462 if (setup_path->comparei(setup_dest_path.get()) != 0) {
463 if (!::MoveFileEx(setup_path->get(), setup_dest_path.get(),
464 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) {
465 ::DeleteFile(setup_path->get());
466 setup_path->clear();
467 } else if (!setup_path->assign(setup_dest_path.get())) {
468 ::DeleteFile(setup_dest_path.get());
473 if (setup_path->length() == 0)
474 exit_code = ProcessExitResult(UNABLE_TO_EXTRACT_SETUP);
476 return exit_code;
479 // Executes setup.exe, waits for it to finish and returns the exit code.
480 ProcessExitResult RunSetup(const Configuration& configuration,
481 const wchar_t* archive_path,
482 const wchar_t* setup_path) {
483 // There could be three full paths in the command line for setup.exe (path
484 // to exe itself, path to archive and path to log file), so we declare
485 // total size as three + one additional to hold command line options.
486 CommandString cmd_line;
488 // Get the path to setup.exe first.
489 if (::lstrlen(setup_path) > 0) {
490 if (!cmd_line.assign(L"\"") ||
491 !cmd_line.append(setup_path) ||
492 !cmd_line.append(L"\""))
493 return ProcessExitResult(COMMAND_STRING_OVERFLOW);
494 } else {
495 ProcessExitResult exit_code = GetPreviousSetupExePath(
496 configuration, cmd_line.get(), cmd_line.capacity());
497 if (!exit_code.IsSuccess())
498 return exit_code;
501 // Append the command line param for chrome archive file.
502 if (!cmd_line.append(L" --") ||
503 #if defined(COMPONENT_BUILD)
504 // For faster developer turnaround, the component build generates
505 // uncompressed archives.
506 !cmd_line.append(kCmdUncompressedArchive) ||
507 #else
508 !cmd_line.append(kCmdInstallArchive) ||
509 #endif
510 !cmd_line.append(L"=\"") ||
511 !cmd_line.append(archive_path) ||
512 !cmd_line.append(L"\""))
513 return ProcessExitResult(COMMAND_STRING_OVERFLOW);
515 // Append the command line param for chrome previous version.
516 if (configuration.previous_version() &&
517 (!cmd_line.append(L" --") ||
518 !cmd_line.append(kCmdPreviousVersion) ||
519 !cmd_line.append(L"=\"") ||
520 !cmd_line.append(configuration.previous_version()) ||
521 !cmd_line.append(L"\""))) {
522 return ProcessExitResult(COMMAND_STRING_OVERFLOW);
525 // Get any command line option specified for mini_installer and pass them
526 // on to setup.exe
527 AppendCommandLineFlags(configuration, &cmd_line);
529 return RunProcessAndWait(NULL, cmd_line.get());
532 // Deletes given files and working dir.
533 void DeleteExtractedFiles(const wchar_t* base_path,
534 const wchar_t* archive_path,
535 const wchar_t* setup_path) {
536 ::DeleteFile(archive_path);
537 ::DeleteFile(setup_path);
538 // Delete the temp dir (if it is empty, otherwise fail).
539 ::RemoveDirectory(base_path);
542 // Creates a temporary directory under |base_path| and returns the full path
543 // of created directory in |work_dir|. If successful return true, otherwise
544 // false. When successful, the returned |work_dir| will always have a trailing
545 // backslash and this function requires that |base_path| always includes a
546 // trailing backslash as well.
547 // We do not use GetTempFileName here to avoid running into AV software that
548 // might hold on to the temp file as soon as we create it and then we can't
549 // delete it and create a directory in its place. So, we use our own mechanism
550 // for creating a directory with a hopefully-unique name. In the case of a
551 // collision, we retry a few times with a new name before failing.
552 bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) {
553 if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix))
554 return false;
556 // Store the location where we'll append the id.
557 size_t end = work_dir->length();
559 // Check if we'll have enough buffer space to continue.
560 // The name of the directory will use up 11 chars and then we need to append
561 // the trailing backslash and a terminator. We've already added the prefix
562 // to the buffer, so let's just make sure we've got enough space for the rest.
563 if ((work_dir->capacity() - end) < (_countof("fffff.tmp") + 1))
564 return false;
566 // Generate a unique id. We only use the lowest 20 bits, so take the top
567 // 12 bits and xor them with the lower bits.
568 DWORD id = ::GetTickCount();
569 id ^= (id >> 12);
571 int max_attempts = 10;
572 while (max_attempts--) {
573 // This converts 'id' to a string in the format "78563412" on windows
574 // because of little endianness, but we don't care since it's just
575 // a name.
576 if (!HexEncode(&id, sizeof(id), work_dir->get() + end,
577 work_dir->capacity() - end)) {
578 return false;
581 // We only want the first 5 digits to remain within the 8.3 file name
582 // format (compliant with previous implementation).
583 work_dir->truncate_at(end + 5);
585 // for consistency with the previous implementation which relied on
586 // GetTempFileName, we append the .tmp extension.
587 work_dir->append(L".tmp");
588 if (::CreateDirectory(work_dir->get(), NULL)) {
589 // Yay! Now let's just append the backslash and we're done.
590 return work_dir->append(L"\\");
592 ++id; // Try a different name.
595 return false;
598 // Creates and returns a temporary directory in |work_dir| that can be used to
599 // extract mini_installer payload. |work_dir| ends with a path separator.
600 bool GetWorkDir(HMODULE module, PathString* work_dir) {
601 PathString base_path;
602 DWORD len = ::GetTempPath(static_cast<DWORD>(base_path.capacity()),
603 base_path.get());
604 if (!len || len >= base_path.capacity() ||
605 !CreateWorkDir(base_path.get(), work_dir)) {
606 // Problem creating the work dir under TEMP path, so try using the
607 // current directory as the base path.
608 len = ::GetModuleFileName(module, base_path.get(),
609 static_cast<DWORD>(base_path.capacity()));
610 if (len >= base_path.capacity() || !len)
611 return false; // Can't even get current directory? Return an error.
613 wchar_t* name = GetNameFromPathExt(base_path.get(), len);
614 if (name == base_path.get())
615 return false; // There was no directory in the string! Bail out.
617 *name = L'\0';
619 return CreateWorkDir(base_path.get(), work_dir);
621 return true;
624 // Returns true for ".." and "." directories.
625 bool IsCurrentOrParentDirectory(const wchar_t* dir) {
626 return dir &&
627 dir[0] == L'.' &&
628 (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0'));
631 // Best effort directory tree deletion including the directory specified
632 // by |path|, which must not end in a separator.
633 // The |path| argument is writable so that each recursion can use the same
634 // buffer as was originally allocated for the path. The path will be unchanged
635 // upon return.
636 void RecursivelyDeleteDirectory(PathString* path) {
637 // |path| will never have a trailing backslash.
638 size_t end = path->length();
639 if (!path->append(L"\\*.*"))
640 return;
642 WIN32_FIND_DATA find_data = {0};
643 HANDLE find = ::FindFirstFile(path->get(), &find_data);
644 if (find != INVALID_HANDLE_VALUE) {
645 do {
646 // Use the short name if available to make the most of our buffer.
647 const wchar_t* name = find_data.cAlternateFileName[0] ?
648 find_data.cAlternateFileName : find_data.cFileName;
649 if (IsCurrentOrParentDirectory(name))
650 continue;
652 path->truncate_at(end + 1); // Keep the trailing backslash.
653 if (!path->append(name))
654 continue; // Continue in spite of too long names.
656 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
657 RecursivelyDeleteDirectory(path);
658 } else {
659 ::DeleteFile(path->get());
661 } while (::FindNextFile(find, &find_data));
662 ::FindClose(find);
665 // Restore the path and delete the directory before we return.
666 path->truncate_at(end);
667 ::RemoveDirectory(path->get());
670 // Enumerates subdirectories of |parent_dir| and deletes all subdirectories
671 // that match with a given |prefix|. |parent_dir| must have a trailing
672 // backslash.
673 // The process is done on a best effort basis, so conceivably there might
674 // still be matches left when the function returns.
675 void DeleteDirectoriesWithPrefix(const wchar_t* parent_dir,
676 const wchar_t* prefix) {
677 // |parent_dir| is guaranteed to always have a trailing backslash.
678 PathString spec;
679 if (!spec.assign(parent_dir) || !spec.append(prefix) || !spec.append(L"*.*"))
680 return;
682 WIN32_FIND_DATA find_data = {0};
683 HANDLE find = ::FindFirstFileEx(spec.get(), FindExInfoStandard, &find_data,
684 FindExSearchLimitToDirectories, NULL, 0);
685 if (find == INVALID_HANDLE_VALUE)
686 return;
688 PathString path;
689 do {
690 if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
691 // Use the short name if available to make the most of our buffer.
692 const wchar_t* name = find_data.cAlternateFileName[0] ?
693 find_data.cAlternateFileName : find_data.cFileName;
694 if (IsCurrentOrParentDirectory(name))
695 continue;
696 if (path.assign(parent_dir) && path.append(name))
697 RecursivelyDeleteDirectory(&path);
699 } while (::FindNextFile(find, &find_data));
700 ::FindClose(find);
703 // Attempts to free up space by deleting temp directories that previous
704 // installer runs have failed to clean up.
705 void DeleteOldChromeTempDirectories() {
706 static const wchar_t* const kDirectoryPrefixes[] = {
707 kTempPrefix,
708 L"chrome_" // Previous installers created directories with this prefix
709 // and there are still some lying around.
712 PathString temp;
713 // GetTempPath always returns a path with a trailing backslash.
714 DWORD len = ::GetTempPath(static_cast<DWORD>(temp.capacity()), temp.get());
715 // GetTempPath returns 0 or number of chars copied, not including the
716 // terminating '\0'.
717 if (!len || len >= temp.capacity())
718 return;
720 for (int i = 0; i < _countof(kDirectoryPrefixes); ++i) {
721 DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]);
725 // Checks the command line for specific mini installer flags.
726 // If the function returns true, the command line has been processed and all
727 // required actions taken. The installer must exit and return the returned
728 // |exit_code|.
729 bool ProcessNonInstallOperations(const Configuration& configuration,
730 ProcessExitResult* exit_code) {
731 switch (configuration.operation()) {
732 case Configuration::CLEANUP:
733 // Cleanup has already taken place in DeleteOldChromeTempDirectories at
734 // this point, so just tell our caller to exit early.
735 *exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
736 return true;
738 default:
739 return false;
743 // Returns true if we should delete the temp files we create (default).
744 // Returns false iff the user has manually created a ChromeInstallerCleanup
745 // string value in the registry under HKCU\\Software\\[Google|Chromium]
746 // and set its value to "0". That explicitly forbids the mini installer from
747 // deleting these files.
748 // Support for this has been publicly mentioned in troubleshooting tips so
749 // we continue to support it.
750 bool ShouldDeleteExtractedFiles() {
751 wchar_t value[2] = {0};
752 if (RegKey::ReadSZValue(HKEY_CURRENT_USER, kCleanupRegistryKey,
753 kCleanupRegistryValue, value, _countof(value)) &&
754 value[0] == L'0') {
755 return false;
758 return true;
761 // Main function. First gets a working dir, unpacks the resources and finally
762 // executes setup.exe to do the install/upgrade.
763 ProcessExitResult WMain(HMODULE module) {
764 // Always start with deleting potential leftovers from previous installations.
765 // This can make the difference between success and failure. We've seen
766 // many installations out in the field fail due to out of disk space problems
767 // so this could buy us some space.
768 DeleteOldChromeTempDirectories();
770 ProcessExitResult exit_code = ProcessExitResult(SUCCESS_EXIT_CODE);
772 // Parse configuration from the command line and resources.
773 Configuration configuration;
774 if (!configuration.Initialize(module))
775 return ProcessExitResult(GENERIC_INITIALIZATION_FAILURE);
777 // If the --cleanup switch was specified on the command line, then that means
778 // we should only do the cleanup and then exit.
779 if (ProcessNonInstallOperations(configuration, &exit_code))
780 return exit_code;
782 // First get a path where we can extract payload
783 PathString base_path;
784 if (!GetWorkDir(module, &base_path))
785 return ProcessExitResult(UNABLE_TO_GET_WORK_DIRECTORY);
787 #if defined(GOOGLE_CHROME_BUILD)
788 // Set the magic suffix in registry to try full installer next time. We ignore
789 // any errors here and we try to set the suffix for user level unless
790 // --system-level is on the command line in which case we set it for system
791 // level instead. This only applies to the Google Chrome distribution.
792 SetInstallerFlags(configuration);
793 #endif
795 PathString archive_path;
796 PathString setup_path;
797 exit_code = UnpackBinaryResources(configuration, module, base_path.get(),
798 &archive_path, &setup_path);
800 // While unpacking the binaries, we paged in a whole bunch of memory that
801 // we don't need anymore. Let's give it back to the pool before running
802 // setup.
803 ::SetProcessWorkingSetSize(::GetCurrentProcess(), (SIZE_T)-1, (SIZE_T)-1);
805 if (exit_code.IsSuccess())
806 exit_code = RunSetup(configuration, archive_path.get(), setup_path.get());
808 if (ShouldDeleteExtractedFiles())
809 DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get());
811 WriteInstallResults(configuration, exit_code);
812 return exit_code;
815 } // namespace mini_installer
817 int MainEntryPoint() {
818 mini_installer::ProcessExitResult result =
819 mini_installer::WMain(::GetModuleHandle(NULL));
821 ::ExitProcess(result.exit_code);
824 // VC Express editions don't come with the memset CRT obj file and linking to
825 // the obj files between versions becomes a bit problematic. Therefore,
826 // simply implement memset.
828 // This also avoids having to explicitly set the __sse2_available hack when
829 // linking with both the x64 and x86 obj files which is required when not
830 // linking with the std C lib in certain instances (including Chromium) with
831 // MSVC. __sse2_available determines whether to use SSE2 intructions with
832 // std C lib routines, and is set by MSVC's std C lib implementation normally.
833 extern "C" {
834 #pragma function(memset)
835 void* memset(void* dest, int c, size_t count) {
836 void* start = dest;
837 while (count--) {
838 *reinterpret_cast<char*>(dest) = static_cast<char>(c);
839 dest = reinterpret_cast<char*>(dest) + 1;
841 return start;
843 } // extern "C"