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