Home | History | Annotate | Download | only in mini_installer
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // mini_installer.exe is the first exe that is run when chrome is being
      6 // installed or upgraded. It is designed to be extremely small (~5KB with no
      7 // extra resources linked) and it has two main jobs:
      8 //   1) unpack the resources (possibly decompressing some)
      9 //   2) run the real installer (setup.exe) with appropriate flags.
     10 //
     11 // In order to be really small the app doesn't link against the CRT and
     12 // defines the following compiler/linker flags:
     13 //   EnableIntrinsicFunctions="true" compiler: /Oi
     14 //   BasicRuntimeChecks="0"
     15 //   BufferSecurityCheck="false" compiler: /GS-
     16 //   EntryPointSymbol="MainEntryPoint" linker: /ENTRY
     17 //   IgnoreAllDefaultLibraries="true" linker: /NODEFAULTLIB
     18 //   OptimizeForWindows98="1"  liker: /OPT:NOWIN98
     19 //   linker: /SAFESEH:NO
     20 
     21 // have the linker merge the sections, saving us ~500 bytes.
     22 #pragma comment(linker, "/MERGE:.rdata=.text")
     23 
     24 #include <windows.h>
     25 #include <shellapi.h>
     26 
     27 #include "chrome/installer/mini_installer/appid.h"
     28 #include "chrome/installer/mini_installer/configuration.h"
     29 #include "chrome/installer/mini_installer/decompress.h"
     30 #include "chrome/installer/mini_installer/exit_code.h"
     31 #include "chrome/installer/mini_installer/mini_installer_constants.h"
     32 #include "chrome/installer/mini_installer/mini_string.h"
     33 #include "chrome/installer/mini_installer/pe_resource.h"
     34 
     35 namespace mini_installer {
     36 
     37 typedef DWORD ProcessExitCode;
     38 typedef StackString<MAX_PATH> PathString;
     39 typedef StackString<MAX_PATH * 4> CommandString;
     40 
     41 // This structure passes data back and forth for the processing
     42 // of resource callbacks.
     43 struct Context {
     44   // Input to the call back method. Specifies the dir to save resources.
     45   const wchar_t* base_path;
     46   // First output from call back method. Full path of Chrome archive.
     47   PathString* chrome_resource_path;
     48   // Second output from call back method. Full path of Setup archive/exe.
     49   PathString* setup_resource_path;
     50 };
     51 
     52 // A helper class used to manipulate the Windows registry.  Typically, members
     53 // return Windows last-error codes a la the Win32 registry API.
     54 class RegKey {
     55  public:
     56   RegKey() : key_(NULL) { }
     57   ~RegKey() { Close(); }
     58 
     59   // Opens the key named |sub_key| with given |access| rights.  Returns
     60   // ERROR_SUCCESS or some other error.
     61   LONG Open(HKEY key, const wchar_t* sub_key, REGSAM access);
     62 
     63   // Returns true if a key is open.
     64   bool is_valid() const { return key_ != NULL; }
     65 
     66   // Read a REG_SZ value from the registry into the memory indicated by |value|
     67   // (of |value_size| wchar_t units).  Returns ERROR_SUCCESS,
     68   // ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA, or some other error.  |value| is
     69   // guaranteed to be null-terminated on success.
     70   LONG ReadValue(const wchar_t* value_name,
     71                  wchar_t* value,
     72                  size_t value_size) const;
     73 
     74   // Write a REG_SZ value to the registry.  |value| must be null-terminated.
     75   // Returns ERROR_SUCCESS or an error code.
     76   LONG WriteValue(const wchar_t* value_name, const wchar_t* value);
     77 
     78   // Closes the key if it was open.
     79   void Close();
     80 
     81  private:
     82   RegKey(const RegKey&);
     83   RegKey& operator=(const RegKey&);
     84 
     85   HKEY key_;
     86 };  // class RegKey
     87 
     88 LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
     89   Close();
     90   return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_);
     91 }
     92 
     93 LONG RegKey::ReadValue(const wchar_t* value_name,
     94                        wchar_t* value,
     95                        size_t value_size) const {
     96   DWORD type;
     97   DWORD byte_length = static_cast<DWORD>(value_size * sizeof(wchar_t));
     98   LONG result = ::RegQueryValueEx(key_, value_name, NULL, &type,
     99                                   reinterpret_cast<BYTE*>(value),
    100                                   &byte_length);
    101   if (result == ERROR_SUCCESS) {
    102     if (type != REG_SZ) {
    103       result = ERROR_NOT_SUPPORTED;
    104     } else if (byte_length == 0) {
    105       *value = L'\0';
    106     } else if (value[byte_length/sizeof(wchar_t) - 1] != L'\0') {
    107       if ((byte_length / sizeof(wchar_t)) < value_size)
    108         value[byte_length / sizeof(wchar_t)] = L'\0';
    109       else
    110         result = ERROR_MORE_DATA;
    111     }
    112   }
    113   return result;
    114 }
    115 
    116 LONG RegKey::WriteValue(const wchar_t* value_name, const wchar_t* value) {
    117   return ::RegSetValueEx(key_, value_name, 0, REG_SZ,
    118                          reinterpret_cast<const BYTE*>(value),
    119                          (lstrlen(value) + 1) * sizeof(wchar_t));
    120 }
    121 
    122 void RegKey::Close() {
    123   if (key_ != NULL) {
    124     ::RegCloseKey(key_);
    125     key_ = NULL;
    126   }
    127 }
    128 
    129 // Helper function to read a value from registry. Returns true if value
    130 // is read successfully and stored in parameter value. Returns false otherwise.
    131 // |size| is measured in wchar_t units.
    132 bool ReadValueFromRegistry(HKEY root_key, const wchar_t *sub_key,
    133                            const wchar_t *value_name, wchar_t *value,
    134                            size_t size) {
    135   RegKey key;
    136 
    137   if (key.Open(root_key, sub_key, KEY_QUERY_VALUE) == ERROR_SUCCESS &&
    138       key.ReadValue(value_name, value, size) == ERROR_SUCCESS) {
    139     return true;
    140   }
    141   return false;
    142 }
    143 
    144 // Opens the Google Update ClientState key for a product.
    145 bool OpenClientStateKey(HKEY root_key, const wchar_t* app_guid, REGSAM access,
    146                         RegKey* key) {
    147   PathString client_state_key;
    148   return client_state_key.assign(kClientStateKeyBase) &&
    149          client_state_key.append(app_guid) &&
    150          (key->Open(root_key,
    151                     client_state_key.get(),
    152                     access | KEY_WOW64_32KEY) == ERROR_SUCCESS);
    153 }
    154 
    155 // This function sets the flag in registry to indicate that Google Update
    156 // should try full installer next time. If the current installer works, this
    157 // flag is cleared by setup.exe at the end of install. The flag will by default
    158 // be written to HKCU, but if --system-level is included in the command line,
    159 // it will be written to HKLM instead.
    160 // TODO(grt): Write a unit test for this that uses registry virtualization.
    161 void SetInstallerFlags(const Configuration& configuration) {
    162   RegKey key;
    163   const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE;
    164   const HKEY root_key =
    165       configuration.is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    166   // This is ignored if multi-install is true.
    167   const wchar_t* app_guid =
    168       configuration.has_chrome_frame() ?
    169           google_update::kChromeFrameAppGuid :
    170           configuration.chrome_app_guid();
    171   StackString<128> value;
    172   LONG ret;
    173 
    174   // When multi_install is true, we are potentially:
    175   // 1. Performing a multi-install of some product(s) on a clean machine.
    176   //    Neither the product(s) nor the multi-installer will have a ClientState
    177   //    key in the registry, so there is nothing to be done.
    178   // 2. Upgrading an existing multi-install.  The multi-installer will have a
    179   //    ClientState key in the registry.  Only it need be modified.
    180   // 3. Migrating a single-install into a multi-install.  The product will have
    181   //    a ClientState key in the registry.  Only it need be modified.
    182   // To handle all cases, we inspect the product's ClientState to see if it
    183   // exists and its "ap" value does not contain "-multi".  This is case 3, so we
    184   // modify the product's ClientState.  Otherwise, we check the
    185   // multi-installer's ClientState and modify it if it exists.
    186   if (configuration.is_multi_install()) {
    187     if (OpenClientStateKey(root_key, app_guid, key_access, &key)) {
    188       // The product has a client state key.  See if it's a single-install.
    189       ret = key.ReadValue(kApRegistryValue, value.get(), value.capacity());
    190       if (ret != ERROR_FILE_NOT_FOUND &&
    191           (ret != ERROR_SUCCESS ||
    192            FindTagInStr(value.get(), kMultiInstallTag, NULL))) {
    193         // Error or case 2: modify the multi-installer's value.
    194         key.Close();
    195         app_guid = google_update::kMultiInstallAppGuid;
    196       }  // else case 3: modify this value.
    197     } else {
    198       // case 1 or 2: modify the multi-installer's value.
    199       key.Close();
    200       app_guid = google_update::kMultiInstallAppGuid;
    201     }
    202   }
    203 
    204   if (!key.is_valid()) {
    205     if (!OpenClientStateKey(root_key, app_guid, key_access, &key))
    206       return;
    207 
    208     value.clear();
    209     ret = key.ReadValue(kApRegistryValue, value.get(), value.capacity());
    210   }
    211 
    212   // The conditions below are handling two cases:
    213   // 1. When ap value is present, we want to add the required tag only if it is
    214   //    not present.
    215   // 2. When ap value is missing, we are going to create it with the required
    216   //    tag.
    217   if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) {
    218     if (ret == ERROR_FILE_NOT_FOUND)
    219       value.clear();
    220 
    221     if (!StrEndsWith(value.get(), kFullInstallerSuffix) &&
    222         value.append(kFullInstallerSuffix)) {
    223       key.WriteValue(kApRegistryValue, value.get());
    224     }
    225   }
    226 }
    227 
    228 // Gets the setup.exe path from Registry by looking the value of Uninstall
    229 // string.  |size| is measured in wchar_t units.
    230 bool GetSetupExePathForGuidFromRegistry(bool system_level,
    231                                         const wchar_t* app_guid,
    232                                         wchar_t* path,
    233                                         size_t size) {
    234   const HKEY root_key = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    235   RegKey key;
    236   return OpenClientStateKey(root_key, app_guid, KEY_QUERY_VALUE, &key) &&
    237       (key.ReadValue(kUninstallRegistryValue, path, size) == ERROR_SUCCESS);
    238 }
    239 
    240 // Gets the setup.exe path from Registry by looking the value of Uninstall
    241 // string.  |size| is measured in wchar_t units.
    242 bool GetSetupExePathFromRegistry(const Configuration& configuration,
    243                                  wchar_t* path,
    244                                  size_t size) {
    245   bool system_level = configuration.is_system_level();
    246 
    247   // If this is a multi install, first try looking in the binaries for the path.
    248   if (configuration.is_multi_install() && GetSetupExePathForGuidFromRegistry(
    249           system_level, google_update::kMultiInstallAppGuid, path, size)) {
    250     return true;
    251   }
    252 
    253   // Failing that, look in Chrome Frame's client state key if --chrome-frame was
    254   // specified.
    255   if (configuration.has_chrome_frame() && GetSetupExePathForGuidFromRegistry(
    256           system_level, google_update::kChromeFrameAppGuid, path, size)) {
    257     return true;
    258   }
    259 
    260   // Make a last-ditch effort to look in the Chrome and App Host client state
    261   // keys.
    262   if (GetSetupExePathForGuidFromRegistry(
    263           system_level, configuration.chrome_app_guid(), path, size)) {
    264     return true;
    265   }
    266   if (configuration.has_app_host() && GetSetupExePathForGuidFromRegistry(
    267           system_level, google_update::kChromeAppHostAppGuid, path, size)) {
    268     return true;
    269   }
    270 
    271   return false;
    272 }
    273 
    274 // Calls CreateProcess with good default parameters and waits for the process to
    275 // terminate returning the process exit code. |exit_code|, if non-NULL, is
    276 // populated with the process exit code.
    277 bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline,
    278                        ProcessExitCode* exit_code) {
    279   STARTUPINFOW si = {sizeof(si)};
    280   PROCESS_INFORMATION pi = {0};
    281   if (!::CreateProcess(exe_path, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW,
    282                        NULL, NULL, &si, &pi)) {
    283     return false;
    284   }
    285 
    286   ::CloseHandle(pi.hThread);
    287 
    288   bool ret = true;
    289   DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE);
    290   if (WAIT_OBJECT_0 != wr) {
    291     ret = false;
    292   } else if (exit_code) {
    293     if (!::GetExitCodeProcess(pi.hProcess, exit_code))
    294       ret = false;
    295   }
    296 
    297   ::CloseHandle(pi.hProcess);
    298 
    299   return ret;
    300 }
    301 
    302 // Append any command line params passed to mini_installer to the given buffer
    303 // so that they can be passed on to setup.exe. We do not return any error from
    304 // this method and simply skip making any changes in case of error.
    305 void AppendCommandLineFlags(const Configuration& configuration,
    306                             CommandString* buffer) {
    307   PathString full_exe_path;
    308   size_t len = ::GetModuleFileName(NULL, full_exe_path.get(),
    309                                    full_exe_path.capacity());
    310   if (!len || len >= full_exe_path.capacity())
    311     return;
    312 
    313   const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len);
    314   if (exe_name == NULL)
    315     return;
    316 
    317   const wchar_t* cmd_to_append = L"";
    318   if (!StrEndsWith(configuration.program(), exe_name)) {
    319     // Current executable name not in the command line so just append
    320     // the whole command line.
    321     cmd_to_append = configuration.command_line();
    322   } else if (configuration.argument_count() > 1) {
    323     const wchar_t* tmp = SearchStringI(configuration.command_line(), exe_name);
    324     tmp = SearchStringI(tmp, L" ");
    325     cmd_to_append = tmp;
    326   }
    327 
    328   buffer->append(cmd_to_append);
    329 }
    330 
    331 
    332 // Windows defined callback used in the EnumResourceNames call. For each
    333 // matching resource found, the callback is invoked and at this point we write
    334 // it to disk. We expect resource names to start with 'chrome' or 'setup'. Any
    335 // other name is treated as an error.
    336 BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type,
    337                               wchar_t* name, LONG_PTR context) {
    338   if (NULL == context)
    339     return FALSE;
    340 
    341   Context* ctx = reinterpret_cast<Context*>(context);
    342 
    343   PEResource resource(name, type, module);
    344   if ((!resource.IsValid()) ||
    345       (resource.Size() < 1) ||
    346       (resource.Size() > kMaxResourceSize)) {
    347     return FALSE;
    348   }
    349 
    350   PathString full_path;
    351   if (!full_path.assign(ctx->base_path) ||
    352       !full_path.append(name) ||
    353       !resource.WriteToDisk(full_path.get()))
    354     return FALSE;
    355 
    356   if (StrStartsWith(name, kChromeArchivePrefix)) {
    357     if (!ctx->chrome_resource_path->assign(full_path.get()))
    358       return FALSE;
    359   } else if (StrStartsWith(name, kSetupPrefix)) {
    360     if (!ctx->setup_resource_path->assign(full_path.get()))
    361       return FALSE;
    362   } else {
    363     // Resources should either start with 'chrome' or 'setup'. We don't handle
    364     // anything else.
    365     return FALSE;
    366   }
    367 
    368   return TRUE;
    369 }
    370 
    371 // Finds and writes to disk resources of various types. Returns false
    372 // if there is a problem in writing any resource to disk. setup.exe resource
    373 // can come in one of three possible forms:
    374 // - Resource type 'B7', compressed using LZMA (*.7z)
    375 // - Resource type 'BL', compressed using LZ (*.ex_)
    376 // - Resource type 'BN', uncompressed (*.exe)
    377 // If setup.exe is present in more than one form, the precedence order is
    378 // BN < BL < B7
    379 // For more details see chrome/tools/build/win/create_installer_archive.py.
    380 bool UnpackBinaryResources(const Configuration& configuration, HMODULE module,
    381                            const wchar_t* base_path, PathString* archive_path,
    382                            PathString* setup_path) {
    383   // Generate the setup.exe path where we patch/uncompress setup resource.
    384   PathString setup_dest_path;
    385   if (!setup_dest_path.assign(base_path) ||
    386       !setup_dest_path.append(kSetupExe))
    387     return false;
    388 
    389   // Prepare the input to OnResourceFound method that needs a location where
    390   // it will write all the resources.
    391   Context context = {
    392     base_path,
    393     archive_path,
    394     setup_path,
    395   };
    396 
    397   // Get the resources of type 'B7' (7zip archive).
    398   // We need a chrome archive to do the installation. So if there
    399   // is a problem in fetching B7 resource, just return an error.
    400   if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound,
    401                            reinterpret_cast<LONG_PTR>(&context)) ||
    402       archive_path->length() == 0)
    403     return false;
    404 
    405   // If we found setup 'B7' resource, handle it.
    406   if (setup_path->length() > 0) {
    407     CommandString cmd_line;
    408     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.
    493 bool 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.
    527 void 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.
    546 bool 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.
    594 bool 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.
    617 bool 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.
    628 void 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.
    667 void 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.
    697 void 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|.
    721 bool 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.
    746 bool 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.
    759 ProcessExitCode 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 
    839 int 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.
    854 extern "C" {
    855 #pragma function(memset)
    856 void* 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