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