mini_installer.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
35namespace mini_installer {
36
37typedef DWORD ProcessExitCode;
38typedef StackString<MAX_PATH> PathString;
39typedef StackString<MAX_PATH * 4> CommandString;
40
41// This structure passes data back and forth for the processing
42// of resource callbacks.
43struct 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.
54class 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
88LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
89  Close();
90  return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_);
91}
92
93LONG 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
116LONG 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
122void 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.
132bool 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.
145bool 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.
161void 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.
230bool 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.
242bool 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.
277bool 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.
305void 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.
336BOOL 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.
380bool 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    PathString exe_path;
409    // Get the path to setup.exe first.
410    bool success = true;
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"\"")) {
423      success = false;
424    }
425
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);
431
432    ProcessExitCode exit_code = SUCCESS_EXIT_CODE;
433    if (success &&
434        (!RunProcessAndWait(exe_path.get(), cmd_line.get(), &exit_code) ||
435         exit_code != SUCCESS_EXIT_CODE)) {
436      success = false;
437    }
438
439    if (!success)
440      DeleteFile(setup_path->get());
441
442    return success && setup_path->assign(setup_dest_path.get());
443  }
444
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)
450    return false;
451
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());
458    if (success) {
459      if (!setup_path->assign(setup_dest_path.get())) {
460        ::DeleteFile(setup_dest_path.get());
461        success = false;
462      }
463    }
464
465    return success;
466  }
467
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)
475    return false;
476
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());
482        setup_path->clear();
483      } else if (!setup_path->assign(setup_dest_path.get())) {
484        ::DeleteFile(setup_dest_path.get());
485      }
486    }
487  }
488
489  return setup_path->length() > 0;
490}
491
492// Executes setup.exe, waits for it to finish and returns the exit code.
493bool 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;
499
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"\""))
505      return false;
506  } else if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(),
507                                          cmd_line.capacity())) {
508    return false;
509  }
510
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"\""))
517    return false;
518
519  // Get any command line option specified for mini_installer and pass them
520  // on to setup.exe
521  AppendCommandLineFlags(configuration, &cmd_line);
522
523  return RunProcessAndWait(NULL, cmd_line.get(), exit_code);
524}
525
526// Deletes given files and working dir.
527void 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);
534}
535
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.
546bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) {
547  if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix))
548    return false;
549
550  // Store the location where we'll append the id.
551  size_t end = work_dir->length();
552
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))
558    return false;
559
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();
563  id ^= (id >> 12);
564
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
569    // a name.
570    if (!HexEncode(&id, sizeof(id), work_dir->get() + end,
571                   work_dir->capacity() - end)) {
572      return false;
573    }
574
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);
578
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"\\");
585    }
586    ++id;  // Try a different name.
587  }
588
589  return false;
590}
591
592// Creates and returns a temporary directory that can be used to extract
593// mini_installer payload.
594bool 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.
604
605    wchar_t* name = GetNameFromPathExt(base_path.get(), len);
606    if (!name)
607      return false;
608
609    *name = L'\0';
610
611    return CreateWorkDir(base_path.get(), work_dir);
612  }
613  return true;
614}
615
616// Returns true for ".." and "." directories.
617bool IsCurrentOrParentDirectory(const wchar_t* dir) {
618  return dir &&
619         dir[0] == L'.' &&
620         (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0'));
621}
622
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
627// upon return.
628void RecursivelyDeleteDirectory(PathString* path) {
629  // |path| will never have a trailing backslash.
630  size_t end = path->length();
631  if (!path->append(L"\\*.*"))
632    return;
633
634  WIN32_FIND_DATA find_data = {0};
635  HANDLE find = ::FindFirstFile(path->get(), &find_data);
636  if (find != INVALID_HANDLE_VALUE) {
637    do {
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))
642        continue;
643
644      path->truncate_at(end + 1);  // Keep the trailing backslash.
645      if (!path->append(name))
646        continue;  // Continue in spite of too long names.
647
648      if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
649        RecursivelyDeleteDirectory(path);
650      } else {
651        ::DeleteFile(path->get());
652      }
653    } while (::FindNextFile(find, &find_data));
654    ::FindClose(find);
655  }
656
657  // Restore the path and delete the directory before we return.
658  path->truncate_at(end);
659  ::RemoveDirectory(path->get());
660}
661
662// Enumerates subdirectories of |parent_dir| and deletes all subdirectories
663// that match with a given |prefix|.  |parent_dir| must have a trailing
664// backslash.
665// The process is done on a best effort basis, so conceivably there might
666// still be matches left when the function returns.
667void DeleteDirectoriesWithPrefix(const wchar_t* parent_dir,
668                                 const wchar_t* prefix) {
669  // |parent_dir| is guaranteed to always have a trailing backslash.
670  PathString spec;
671  if (!spec.assign(parent_dir) || !spec.append(prefix) || !spec.append(L"*.*"))
672    return;
673
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)
678    return;
679
680  PathString path;
681  do {
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))
687        continue;
688      if (path.assign(parent_dir) && path.append(name))
689        RecursivelyDeleteDirectory(&path);
690    }
691  } while (::FindNextFile(find, &find_data));
692  ::FindClose(find);
693}
694
695// Attempts to free up space by deleting temp directories that previous
696// installer runs have failed to clean up.
697void DeleteOldChromeTempDirectories() {
698  static const wchar_t* const kDirectoryPrefixes[] = {
699    kTempPrefix,
700    L"chrome_"  // Previous installers created directories with this prefix
701                // and there are still some lying around.
702  };
703
704  PathString temp;
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
708  // terminating '\0'.
709  if (!len || len >= temp.capacity())
710    return;
711
712  for (int i = 0; i < arraysize(kDirectoryPrefixes); ++i) {
713    DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]);
714  }
715}
716
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
720// |exit_code|.
721bool ProcessNonInstallOperations(const Configuration& configuration,
722                                 ProcessExitCode* exit_code) {
723  bool ret = false;
724
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;
730      ret = true;
731      break;
732
733    default: break;
734  }
735
736  return ret;
737}
738
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.
746bool ShouldDeleteExtractedFiles() {
747  wchar_t value[2] = {0};
748  if (ReadValueFromRegistry(HKEY_CURRENT_USER, kCleanupRegistryKey,
749                            kCleanupRegistryValue, value, arraysize(value)) &&
750      value[0] == L'0') {
751    return false;
752  }
753
754  return true;
755}
756
757// Main function. First gets a working dir, unpacks the resources and finally
758// executes setup.exe to do the install/upgrade.
759ProcessExitCode 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;
768  }
769#endif
770
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();
776
777  // TODO(grt): Make the exit codes more granular so we know where the popular
778  // errors truly are.
779  ProcessExitCode exit_code = GENERIC_INITIALIZATION_FAILURE;
780
781  // Parse the command line.
782  Configuration configuration;
783  if (!configuration.Initialize())
784    return exit_code;
785
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;
792#else
793    return GENERIC_ERROR;
794#endif
795  }
796
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))
800    return exit_code;
801
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;
806
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);
813#endif
814
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;
820  } else {
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
823    // setup.
824    ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1);
825    if (!RunSetup(configuration, archive_path.get(), setup_path.get(),
826                  &exit_code)) {
827      exit_code = GENERIC_SETUP_FAILURE;
828    }
829  }
830
831  if (ShouldDeleteExtractedFiles())
832    DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get());
833
834  return exit_code;
835}
836
837}  // namespace mini_installer
838
839int MainEntryPoint() {
840  mini_installer::ProcessExitCode result =
841      mini_installer::WMain(::GetModuleHandle(NULL));
842  ::ExitProcess(result);
843}
844
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.
848//
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.
854extern "C" {
855#pragma function(memset)
856void* memset(void* dest, int c, size_t count) {
857  void* start = dest;
858  while (count--) {
859    *reinterpret_cast<char*>(dest) = static_cast<char>(c);
860    dest = reinterpret_cast<char*>(dest) + 1;
861  }
862  return start;
863}
864}  // extern "C"
865