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