• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
34 namespace mini_installer {
35 
36 typedef StackString<MAX_PATH> PathString;
37 typedef StackString<MAX_PATH * 4> CommandString;
38 
39 // This structure passes data back and forth for the processing
40 // of resource callbacks.
41 struct 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.
52 class RegKey {
53  public:
RegKey()54   RegKey() : key_(NULL) { }
~RegKey()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.
is_valid() const62   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 
Open(HKEY key,const wchar_t * sub_key,REGSAM access)86 LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) {
87   Close();
88   return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_);
89 }
90 
ReadValue(const wchar_t * value_name,wchar_t * value,size_t value_size) const91 LONG 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 
WriteValue(const wchar_t * value_name,const wchar_t * value)114 LONG 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 
Close()120 void 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.
ReadValueFromRegistry(HKEY root_key,const wchar_t * sub_key,const wchar_t * value_name,wchar_t * value,size_t size)130 bool 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.
OpenClientStateKey(HKEY root_key,const wchar_t * app_guid,REGSAM access,RegKey * key)143 bool 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.
SetInstallerFlags(const Configuration & configuration)159 void 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.
GetSetupExePathForGuidFromRegistry(bool system_level,const wchar_t * app_guid,wchar_t * path,size_t size)228 bool 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.
GetSetupExePathFromRegistry(const Configuration & configuration,wchar_t * path,size_t size)240 bool 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.
RunProcessAndWait(const wchar_t * exe_path,wchar_t * cmdline,int * exit_code)274 bool 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.
AppendCommandLineFlags(const Configuration & configuration,CommandString * buffer)304 void 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.
OnResourceFound(HMODULE module,const wchar_t * type,wchar_t * name,LONG_PTR context)335 BOOL 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.
UnpackBinaryResources(const Configuration & configuration,HMODULE module,const wchar_t * base_path,PathString * archive_path,PathString * setup_path)379 bool 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.
RunSetup(const Configuration & configuration,const wchar_t * archive_path,const wchar_t * setup_path,int * exit_code)489 bool 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.
DeleteExtractedFiles(const wchar_t * base_path,const wchar_t * archive_path,const wchar_t * setup_path)522 void 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.
CreateWorkDir(const wchar_t * base_path,PathString * work_dir)541 bool 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.
GetWorkDir(HMODULE module,PathString * work_dir)589 bool 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.
IsCurrentOrParentDirectory(const wchar_t * dir)612 bool 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.
RecursivelyDeleteDirectory(PathString * path)623 void 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.
DeleteDirectoriesWithPrefix(const wchar_t * parent_dir,const wchar_t * prefix)662 void 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.
DeleteOldChromeTempDirectories()692 void 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|.
ProcessNonInstallOperations(const Configuration & configuration,int * exit_code)716 bool 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.
ShouldDeleteExtractedFiles()741 bool 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.
WMain(HMODULE module)755 int 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 
MainEntryPoint()835 int 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.
849 extern "C" {
850 #pragma function(memset)
memset(void * dest,int c,size_t count)851 void* 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