toolkit/mozapps/update/updater/updater.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/update/updater/updater.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,3972 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/**
     1.9 + *  Manifest Format
    1.10 + *  ---------------
    1.11 + *
    1.12 + *  contents = 1*( line )
    1.13 + *  line     = method LWS *( param LWS ) CRLF
    1.14 + *  CRLF     = "\r\n"
    1.15 + *  LWS      = 1*( " " | "\t" )
    1.16 + *
    1.17 + *  Available methods for the manifest file:
    1.18 + *
    1.19 + *  updatev2.manifest
    1.20 + *  -----------------
    1.21 + *  method   = "add" | "add-if" | "patch" | "patch-if" | "remove" |
    1.22 + *             "rmdir" | "rmrfdir" | type
    1.23 + *
    1.24 + *  'type' is the update type (e.g. complete or partial) and when present MUST
    1.25 + *  be the first entry in the update manifest. The type is used to support
    1.26 + *  downgrades by causing the actions defined in precomplete to be performed.
    1.27 + *
    1.28 + *  updatev3.manifest
    1.29 + *  -----------------
    1.30 + *  method   = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
    1.31 + *             "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type
    1.32 + *
    1.33 + *  'add-if-not' adds a file if it doesn't exist.
    1.34 + *
    1.35 + *  precomplete
    1.36 + *  -----------
    1.37 + *  method   = "remove" | "rmdir"
    1.38 + */
    1.39 +#include "bspatch.h"
    1.40 +#include "progressui.h"
    1.41 +#include "archivereader.h"
    1.42 +#include "readstrings.h"
    1.43 +#include "errors.h"
    1.44 +#include "bzlib.h"
    1.45 +
    1.46 +#include <stdio.h>
    1.47 +#include <string.h>
    1.48 +#include <stdlib.h>
    1.49 +#include <stdarg.h>
    1.50 +
    1.51 +#include <sys/types.h>
    1.52 +#include <sys/stat.h>
    1.53 +#include <fcntl.h>
    1.54 +#include <limits.h>
    1.55 +#include <errno.h>
    1.56 +#include <algorithm>
    1.57 +
    1.58 +#include "updatelogging.h"
    1.59 +
    1.60 +#include "mozilla/Compiler.h"
    1.61 +
    1.62 +// Amount of the progress bar to use in each of the 3 update stages,
    1.63 +// should total 100.0.
    1.64 +#define PROGRESS_PREPARE_SIZE 20.0f
    1.65 +#define PROGRESS_EXECUTE_SIZE 75.0f
    1.66 +#define PROGRESS_FINISH_SIZE   5.0f
    1.67 +
    1.68 +// Amount of time in ms to wait for the parent process to close
    1.69 +#define PARENT_WAIT 5000
    1.70 +#define IMMERSIVE_PARENT_WAIT 15000
    1.71 +
    1.72 +#if defined(XP_MACOSX)
    1.73 +// These functions are defined in launchchild_osx.mm
    1.74 +void LaunchChild(int argc, char **argv);
    1.75 +void LaunchMacPostProcess(const char* aAppExe);
    1.76 +#endif
    1.77 +
    1.78 +#ifndef _O_BINARY
    1.79 +# define _O_BINARY 0
    1.80 +#endif
    1.81 +
    1.82 +#ifndef NULL
    1.83 +# define NULL (0)
    1.84 +#endif
    1.85 +
    1.86 +#ifndef SSIZE_MAX
    1.87 +# define SSIZE_MAX LONG_MAX
    1.88 +#endif
    1.89 +
    1.90 +// We want to use execv to invoke the callback executable on platforms where
    1.91 +// we were launched using execv.  See nsUpdateDriver.cpp.
    1.92 +#if defined(XP_UNIX) && !defined(XP_MACOSX)
    1.93 +#define USE_EXECV
    1.94 +#endif
    1.95 +
    1.96 +#if defined(MOZ_WIDGET_GONK)
    1.97 +# include "automounter_gonk.h"
    1.98 +# include <unistd.h>
    1.99 +# include <android/log.h>
   1.100 +# include <linux/ioprio.h>
   1.101 +# include <sys/resource.h>
   1.102 +
   1.103 +// The only header file in bionic which has a function prototype for ioprio_set
   1.104 +// is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts
   1.105 +// badly with unistd.h, so we declare the prototype for ioprio_set directly.
   1.106 +extern "C" int ioprio_set(int which, int who, int ioprio);
   1.107 +
   1.108 +# define MAYBE_USE_HARD_LINKS 1
   1.109 +static bool sUseHardLinks = true;
   1.110 +#else
   1.111 +# define MAYBE_USE_HARD_LINKS 0
   1.112 +#endif
   1.113 +
   1.114 +#ifdef XP_WIN
   1.115 +#include "updatehelper.h"
   1.116 +
   1.117 +// Closes the handle if valid and if the updater is elevated returns with the
   1.118 +// return code specified. This prevents multiple launches of the callback
   1.119 +// application by preventing the elevated process from launching the callback.
   1.120 +#define EXIT_WHEN_ELEVATED(path, handle, retCode) \
   1.121 +  { \
   1.122 +      if (handle != INVALID_HANDLE_VALUE) { \
   1.123 +        CloseHandle(handle); \
   1.124 +      } \
   1.125 +      if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \
   1.126 +        return retCode; \
   1.127 +      } \
   1.128 +  }
   1.129 +#endif
   1.130 +
   1.131 +//-----------------------------------------------------------------------------
   1.132 +
   1.133 +// This variable lives in libbz2.  It's declared in bzlib_private.h, so we just
   1.134 +// declare it here to avoid including that entire header file.
   1.135 +#define BZ2_CRC32TABLE_UNDECLARED
   1.136 +
   1.137 +#if MOZ_IS_GCC
   1.138 +#if MOZ_GCC_VERSION_AT_LEAST(3, 3, 0)
   1.139 +extern "C"  __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256];
   1.140 +#undef BZ2_CRC32TABLE_UNDECLARED
   1.141 +#endif
   1.142 +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
   1.143 +extern "C" __global unsigned int BZ2_crc32Table[256];
   1.144 +#undef BZ2_CRC32TABLE_UNDECLARED
   1.145 +#endif
   1.146 +#if defined(BZ2_CRC32TABLE_UNDECLARED)
   1.147 +extern "C" unsigned int BZ2_crc32Table[256];
   1.148 +#undef BZ2_CRC32TABLE_UNDECLARED
   1.149 +#endif
   1.150 +
   1.151 +static unsigned int
   1.152 +crc32(const unsigned char *buf, unsigned int len)
   1.153 +{
   1.154 +  unsigned int crc = 0xffffffffL;
   1.155 +
   1.156 +  const unsigned char *end = buf + len;
   1.157 +  for (; buf != end; ++buf)
   1.158 +    crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
   1.159 +
   1.160 +  crc = ~crc;
   1.161 +  return crc;
   1.162 +}
   1.163 +
   1.164 +//-----------------------------------------------------------------------------
   1.165 +
   1.166 +// A simple stack based container for a FILE struct that closes the
   1.167 +// file descriptor from its destructor.
   1.168 +class AutoFile
   1.169 +{
   1.170 +public:
   1.171 +  AutoFile(FILE* file = nullptr)
   1.172 +    : mFile(file) {
   1.173 +  }
   1.174 +
   1.175 +  ~AutoFile() {
   1.176 +    if (mFile != nullptr)
   1.177 +      fclose(mFile);
   1.178 +  }
   1.179 +
   1.180 +  AutoFile &operator=(FILE* file) {
   1.181 +    if (mFile != 0)
   1.182 +      fclose(mFile);
   1.183 +    mFile = file;
   1.184 +    return *this;
   1.185 +  }
   1.186 +
   1.187 +  operator FILE*() {
   1.188 +    return mFile;
   1.189 +  }
   1.190 +
   1.191 +  FILE* get() {
   1.192 +    return mFile;
   1.193 +  }
   1.194 +
   1.195 +private:
   1.196 +  FILE* mFile;
   1.197 +};
   1.198 +
   1.199 +struct MARChannelStringTable {
   1.200 +  MARChannelStringTable() 
   1.201 +  {
   1.202 +    MARChannelID[0] = '\0';
   1.203 +  }
   1.204 +
   1.205 +  char MARChannelID[MAX_TEXT_LEN];
   1.206 +};
   1.207 +
   1.208 +//-----------------------------------------------------------------------------
   1.209 +
   1.210 +typedef void (* ThreadFunc)(void *param);
   1.211 +
   1.212 +#ifdef XP_WIN
   1.213 +#include <process.h>
   1.214 +
   1.215 +class Thread
   1.216 +{
   1.217 +public:
   1.218 +  int Run(ThreadFunc func, void *param)
   1.219 +  {
   1.220 +    mThreadFunc = func;
   1.221 +    mThreadParam = param;
   1.222 +
   1.223 +    unsigned int threadID;
   1.224 +
   1.225 +    mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0,
   1.226 +                                      &threadID);
   1.227 +
   1.228 +    return mThread ? 0 : -1;
   1.229 +  }
   1.230 +  int Join()
   1.231 +  {
   1.232 +    WaitForSingleObject(mThread, INFINITE);
   1.233 +    CloseHandle(mThread);
   1.234 +    return 0;
   1.235 +  }
   1.236 +private:
   1.237 +  static unsigned __stdcall ThreadMain(void *p)
   1.238 +  {
   1.239 +    Thread *self = (Thread *) p;
   1.240 +    self->mThreadFunc(self->mThreadParam);
   1.241 +    return 0;
   1.242 +  }
   1.243 +  HANDLE     mThread;
   1.244 +  ThreadFunc mThreadFunc;
   1.245 +  void      *mThreadParam;
   1.246 +};
   1.247 +
   1.248 +#elif defined(XP_UNIX)
   1.249 +#include <pthread.h>
   1.250 +
   1.251 +class Thread
   1.252 +{
   1.253 +public:
   1.254 +  int Run(ThreadFunc func, void *param)
   1.255 +  {
   1.256 +    return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param);
   1.257 +  }
   1.258 +  int Join()
   1.259 +  {
   1.260 +    void *result;
   1.261 +    return pthread_join(thr, &result);
   1.262 +  }
   1.263 +private:
   1.264 +  pthread_t thr;
   1.265 +};
   1.266 +
   1.267 +#else
   1.268 +#error "Unsupported platform"
   1.269 +#endif
   1.270 +
   1.271 +//-----------------------------------------------------------------------------
   1.272 +
   1.273 +static NS_tchar* gSourcePath;
   1.274 +static NS_tchar gDestinationPath[MAXPATHLEN];
   1.275 +static ArchiveReader gArchiveReader;
   1.276 +static bool gSucceeded = false;
   1.277 +static bool sStagedUpdate = false;
   1.278 +static bool sReplaceRequest = false;
   1.279 +static bool sUsingService = false;
   1.280 +static bool sIsOSUpdate = false;
   1.281 +
   1.282 +#ifdef XP_WIN
   1.283 +// The current working directory specified in the command line.
   1.284 +static NS_tchar* gDestPath;
   1.285 +static NS_tchar gCallbackRelPath[MAXPATHLEN];
   1.286 +static NS_tchar gCallbackBackupPath[MAXPATHLEN];
   1.287 +#endif
   1.288 +
   1.289 +static const NS_tchar kWhitespace[] = NS_T(" \t");
   1.290 +static const NS_tchar kNL[] = NS_T("\r\n");
   1.291 +static const NS_tchar kQuote[] = NS_T("\"");
   1.292 +
   1.293 +static inline size_t
   1.294 +mmin(size_t a, size_t b)
   1.295 +{
   1.296 +  return (a > b) ? b : a;
   1.297 +}
   1.298 +
   1.299 +static NS_tchar*
   1.300 +mstrtok(const NS_tchar *delims, NS_tchar **str)
   1.301 +{
   1.302 +  if (!*str || !**str)
   1.303 +    return nullptr;
   1.304 +
   1.305 +  // skip leading "whitespace"
   1.306 +  NS_tchar *ret = *str;
   1.307 +  const NS_tchar *d;
   1.308 +  do {
   1.309 +    for (d = delims; *d != NS_T('\0'); ++d) {
   1.310 +      if (*ret == *d) {
   1.311 +        ++ret;
   1.312 +        break;
   1.313 +      }
   1.314 +    }
   1.315 +  } while (*d);
   1.316 +
   1.317 +  if (!*ret) {
   1.318 +    *str = ret;
   1.319 +    return nullptr;
   1.320 +  }
   1.321 +
   1.322 +  NS_tchar *i = ret;
   1.323 +  do {
   1.324 +    for (d = delims; *d != NS_T('\0'); ++d) {
   1.325 +      if (*i == *d) {
   1.326 +        *i = NS_T('\0');
   1.327 +        *str = ++i;
   1.328 +        return ret;
   1.329 +      }
   1.330 +    }
   1.331 +    ++i;
   1.332 +  } while (*i);
   1.333 +
   1.334 +  *str = nullptr;
   1.335 +  return ret;
   1.336 +}
   1.337 +
   1.338 +#ifdef XP_WIN
   1.339 +/**
   1.340 + * Coverts a relative update path to a full path for Windows.
   1.341 + *
   1.342 + * @param  relpath
   1.343 + *         The relative path to convert to a full path.
   1.344 + * @return valid filesystem full path or nullptr if memory allocation fails.
   1.345 + */
   1.346 +static NS_tchar*
   1.347 +get_full_path(const NS_tchar *relpath)
   1.348 +{
   1.349 +  size_t lendestpath = NS_tstrlen(gDestPath);
   1.350 +  size_t lenrelpath = NS_tstrlen(relpath);
   1.351 +  NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar));
   1.352 +  if (!s)
   1.353 +    return nullptr;
   1.354 +
   1.355 +  NS_tchar *c = s;
   1.356 +
   1.357 +  NS_tstrcpy(c, gDestPath);
   1.358 +  c += lendestpath;
   1.359 +  NS_tstrcat(c, relpath);
   1.360 +  c += lenrelpath;
   1.361 +  *c = NS_T('\0');
   1.362 +  c++;
   1.363 +  return s;
   1.364 +}
   1.365 +#endif
   1.366 +
   1.367 +/**
   1.368 + * Gets the platform specific path and performs simple checks to the path. If
   1.369 + * the path checks don't pass nullptr will be returned.
   1.370 + *
   1.371 + * @param  line
   1.372 + *         The line from the manifest that contains the path.
   1.373 + * @param  isdir
   1.374 + *         Whether the path is a directory path. Defaults to false.
   1.375 + * @param  islinktarget
   1.376 + *         Whether the path is a symbolic link target. Defaults to false.
   1.377 + * @return valid filesystem path or nullptr if the path checks fail.
   1.378 + */
   1.379 +static NS_tchar*
   1.380 +get_valid_path(NS_tchar **line, bool isdir = false, bool islinktarget = false)
   1.381 +{
   1.382 +  NS_tchar *path = mstrtok(kQuote, line);
   1.383 +  if (!path) {
   1.384 +    LOG(("get_valid_path: unable to determine path: " LOG_S, line));
   1.385 +    return nullptr;
   1.386 +  }
   1.387 +
   1.388 +  // All paths must be relative from the current working directory
   1.389 +  if (path[0] == NS_T('/')) {
   1.390 +    LOG(("get_valid_path: path must be relative: " LOG_S, path));
   1.391 +    return nullptr;
   1.392 +  }
   1.393 +
   1.394 +#ifdef XP_WIN
   1.395 +  // All paths must be relative from the current working directory
   1.396 +  if (path[0] == NS_T('\\') || path[1] == NS_T(':')) {
   1.397 +    LOG(("get_valid_path: path must be relative: " LOG_S, path));
   1.398 +    return nullptr;
   1.399 +  }
   1.400 +#endif
   1.401 +
   1.402 +  if (isdir) {
   1.403 +    // Directory paths must have a trailing forward slash.
   1.404 +    if (path[NS_tstrlen(path) - 1] != NS_T('/')) {
   1.405 +      LOG(("get_valid_path: directory paths must have a trailing forward " \
   1.406 +           "slash: " LOG_S, path));
   1.407 +      return nullptr;
   1.408 +    }
   1.409 +
   1.410 +    // Remove the trailing forward slash because stat on Windows will return
   1.411 +    // ENOENT if the path has a trailing slash.
   1.412 +    path[NS_tstrlen(path) - 1] = NS_T('\0');
   1.413 +  }
   1.414 +
   1.415 +  if (!islinktarget) {
   1.416 +    // Don't allow relative paths that resolve to a parent directory.
   1.417 +    if (NS_tstrstr(path, NS_T("..")) != nullptr) {
   1.418 +      LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
   1.419 +      return nullptr;
   1.420 +    }
   1.421 +  }
   1.422 +
   1.423 +  return path;
   1.424 +}
   1.425 +
   1.426 +static NS_tchar*
   1.427 +get_quoted_path(const NS_tchar *path)
   1.428 +{
   1.429 +  size_t lenQuote = NS_tstrlen(kQuote);
   1.430 +  size_t lenPath = NS_tstrlen(path);
   1.431 +  size_t len = lenQuote + lenPath + lenQuote + 1;
   1.432 +
   1.433 +  NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
   1.434 +  if (!s)
   1.435 +    return nullptr;
   1.436 +
   1.437 +  NS_tchar *c = s;
   1.438 +  NS_tstrcpy(c, kQuote);
   1.439 +  c += lenQuote;
   1.440 +  NS_tstrcat(c, path);
   1.441 +  c += lenPath;
   1.442 +  NS_tstrcat(c, kQuote);
   1.443 +  c += lenQuote;
   1.444 +  *c = NS_T('\0');
   1.445 +  c++;
   1.446 +  return s;
   1.447 +}
   1.448 +
   1.449 +static void ensure_write_permissions(const NS_tchar *path)
   1.450 +{
   1.451 +#ifdef XP_WIN
   1.452 +  (void) _wchmod(path, _S_IREAD | _S_IWRITE);
   1.453 +#else
   1.454 +  struct stat fs;
   1.455 +  if (!NS_tlstat(path, &fs) && !S_ISLNK(fs.st_mode)
   1.456 +      && !(fs.st_mode & S_IWUSR)) {
   1.457 +    (void)chmod(path, fs.st_mode | S_IWUSR);
   1.458 +  }
   1.459 +#endif
   1.460 +}
   1.461 +
   1.462 +static int ensure_remove(const NS_tchar *path)
   1.463 +{
   1.464 +  ensure_write_permissions(path);
   1.465 +  int rv = NS_tremove(path);
   1.466 +  if (rv)
   1.467 +    LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
   1.468 +         path, rv, errno));
   1.469 +  return rv;
   1.470 +}
   1.471 +
   1.472 +// Remove the directory pointed to by path and all of its files and sub-directories.
   1.473 +static int ensure_remove_recursive(const NS_tchar *path)
   1.474 +{
   1.475 +  // We use lstat rather than stat here so that we can successfully remove
   1.476 +  // symlinks.
   1.477 +  struct stat sInfo;
   1.478 +  int rv = NS_tlstat(path, &sInfo);
   1.479 +  if (rv) {
   1.480 +    // This error is benign
   1.481 +    return rv;
   1.482 +  }
   1.483 +  if (!S_ISDIR(sInfo.st_mode)) {
   1.484 +    return ensure_remove(path);
   1.485 +  }
   1.486 +
   1.487 +  NS_tDIR *dir;
   1.488 +  NS_tdirent *entry;
   1.489 +
   1.490 +  dir = NS_topendir(path);
   1.491 +  if (!dir) {
   1.492 +    LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   1.493 +         path, rv, errno));
   1.494 +    return rv;
   1.495 +  }
   1.496 +
   1.497 +  while ((entry = NS_treaddir(dir)) != 0) {
   1.498 +    if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
   1.499 +        NS_tstrcmp(entry->d_name, NS_T(".."))) {
   1.500 +      NS_tchar childPath[MAXPATHLEN];
   1.501 +      NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
   1.502 +                   NS_T("%s/%s"), path, entry->d_name);
   1.503 +      rv = ensure_remove_recursive(childPath);
   1.504 +      if (rv) {
   1.505 +        break;
   1.506 +      }
   1.507 +    }
   1.508 +  }
   1.509 +
   1.510 +  NS_tclosedir(dir);
   1.511 +
   1.512 +  if (rv == OK) {
   1.513 +    ensure_write_permissions(path);
   1.514 +    rv = NS_trmdir(path);
   1.515 +    if (rv) {
   1.516 +      LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   1.517 +           path, rv, errno));
   1.518 +    }
   1.519 +  }
   1.520 +  return rv;
   1.521 +}
   1.522 +
   1.523 +static bool is_read_only(const NS_tchar *flags)
   1.524 +{
   1.525 +  size_t length = NS_tstrlen(flags);
   1.526 +  if (length == 0)
   1.527 +    return false;
   1.528 +
   1.529 +  // Make sure the string begins with "r"
   1.530 +  if (flags[0] != NS_T('r'))
   1.531 +    return false;
   1.532 +
   1.533 +  // Look for "r+" or "r+b"
   1.534 +  if (length > 1 && flags[1] == NS_T('+'))
   1.535 +    return false;
   1.536 +
   1.537 +  // Look for "rb+"
   1.538 +  if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
   1.539 +    return false;
   1.540 +
   1.541 +  return true;
   1.542 +}
   1.543 +
   1.544 +static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options)
   1.545 +{
   1.546 +  ensure_write_permissions(path);
   1.547 +  FILE* f = NS_tfopen(path, flags);
   1.548 +  if (is_read_only(flags)) {
   1.549 +    // Don't attempt to modify the file permissions if the file is being opened
   1.550 +    // in read-only mode.
   1.551 +    return f;
   1.552 +  }
   1.553 +  if (NS_tchmod(path, options) != 0) {
   1.554 +    if (f != nullptr) {
   1.555 +      fclose(f);
   1.556 +    }
   1.557 +    return nullptr;
   1.558 +  }
   1.559 +  struct stat ss;
   1.560 +  if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) {
   1.561 +    if (f != nullptr) {
   1.562 +      fclose(f);
   1.563 +    }
   1.564 +    return nullptr;
   1.565 +  }
   1.566 +  return f;
   1.567 +}
   1.568 +
   1.569 +// Ensure that the directory containing this file exists.
   1.570 +static int ensure_parent_dir(const NS_tchar *path)
   1.571 +{
   1.572 +  int rv = OK;
   1.573 +
   1.574 +  NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/'));
   1.575 +  if (slash) {
   1.576 +    *slash = NS_T('\0');
   1.577 +    rv = ensure_parent_dir(path);
   1.578 +    // Only attempt to create the directory if we're not at the root
   1.579 +    if (rv == OK && *path) {
   1.580 +      rv = NS_tmkdir(path, 0755);
   1.581 +      // If the directory already exists, then ignore the error.
   1.582 +      if (rv < 0 && errno != EEXIST) {
   1.583 +        LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \
   1.584 +             "err: %d", path, errno));
   1.585 +        rv = WRITE_ERROR;
   1.586 +      } else {
   1.587 +        rv = OK;
   1.588 +      }
   1.589 +    }
   1.590 +    *slash = NS_T('/');
   1.591 +  }
   1.592 +  return rv;
   1.593 +}
   1.594 +
   1.595 +#ifdef XP_UNIX
   1.596 +static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest)
   1.597 +{
   1.598 +  // Copy symlinks by creating a new symlink to the same target
   1.599 +  NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
   1.600 +  int rv = readlink(path, target, MAXPATHLEN);
   1.601 +  if (rv == -1) {
   1.602 +    LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
   1.603 +         path, errno));
   1.604 +    return READ_ERROR;
   1.605 +  }
   1.606 +  rv = symlink(target, dest);
   1.607 +  if (rv == -1) {
   1.608 +    LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d",
   1.609 +         dest, target, errno));
   1.610 +    return READ_ERROR;
   1.611 +  }
   1.612 +  return 0;
   1.613 +}
   1.614 +#endif
   1.615 +
   1.616 +#if MAYBE_USE_HARD_LINKS
   1.617 +/*
   1.618 + * Creates a hardlink (destFilename) which points to the existing file
   1.619 + * (srcFilename).
   1.620 + *
   1.621 + * @return 0 if successful, an error otherwise
   1.622 + */
   1.623 +
   1.624 +static int
   1.625 +create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename)
   1.626 +{
   1.627 +  if (link(srcFilename, destFilename) < 0) {
   1.628 +    LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno));
   1.629 +    return WRITE_ERROR;
   1.630 +  }
   1.631 +  return OK;
   1.632 +}
   1.633 +#endif
   1.634 +
   1.635 +// Copy the file named path onto a new file named dest.
   1.636 +static int ensure_copy(const NS_tchar *path, const NS_tchar *dest)
   1.637 +{
   1.638 +#ifdef XP_WIN
   1.639 +  // Fast path for Windows
   1.640 +  bool result = CopyFileW(path, dest, false);
   1.641 +  if (!result) {
   1.642 +    LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x",
   1.643 +         path, dest, GetLastError()));
   1.644 +    return WRITE_ERROR;
   1.645 +  }
   1.646 +  return 0;
   1.647 +#else
   1.648 +  struct stat ss;
   1.649 +  int rv = NS_tlstat(path, &ss);
   1.650 +  if (rv) {
   1.651 +    LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
   1.652 +         path, errno));
   1.653 +    return READ_ERROR;
   1.654 +  }
   1.655 +
   1.656 +  if (S_ISLNK(ss.st_mode)) {
   1.657 +    return ensure_copy_symlink(path, dest);
   1.658 +  }
   1.659 +
   1.660 +#if MAYBE_USE_HARD_LINKS
   1.661 +  if (sUseHardLinks) {
   1.662 +    if (!create_hard_link(path, dest)) {
   1.663 +      return OK;
   1.664 +    }
   1.665 +    // Since we failed to create the hard link, fall through and copy the file.
   1.666 +    sUseHardLinks = false;
   1.667 +  }
   1.668 +#endif
   1.669 +
   1.670 +  AutoFile infile = ensure_open(path, NS_T("rb"), ss.st_mode);
   1.671 +  if (!infile) {
   1.672 +    LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
   1.673 +         path, errno));
   1.674 +    return READ_ERROR;
   1.675 +  }
   1.676 +  AutoFile outfile = ensure_open(dest, NS_T("wb"), ss.st_mode);
   1.677 +  if (!outfile) {
   1.678 +    LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
   1.679 +         dest, errno));
   1.680 +    return WRITE_ERROR;
   1.681 +  }
   1.682 +
   1.683 +  // This block size was chosen pretty arbitrarily but seems like a reasonable
   1.684 +  // compromise. For example, the optimal block size on a modern OS X machine
   1.685 +  // is 100k */
   1.686 +  const int blockSize = 32 * 1024;
   1.687 +  void* buffer = malloc(blockSize);
   1.688 +  if (!buffer)
   1.689 +    return UPDATER_MEM_ERROR;
   1.690 +
   1.691 +  while (!feof(infile.get())) {
   1.692 +    size_t read = fread(buffer, 1, blockSize, infile);
   1.693 +    if (ferror(infile.get())) {
   1.694 +      LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d",
   1.695 +           path, errno));
   1.696 +      free(buffer);
   1.697 +      return READ_ERROR;
   1.698 +    }
   1.699 +
   1.700 +    size_t written = 0;
   1.701 +
   1.702 +    while (written < read) {
   1.703 +      size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
   1.704 +      if (chunkWritten <= 0) {
   1.705 +        LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d",
   1.706 +             dest, errno));
   1.707 +        free(buffer);
   1.708 +        return WRITE_ERROR;
   1.709 +      }
   1.710 +
   1.711 +      written += chunkWritten;
   1.712 +    }
   1.713 +  }
   1.714 +
   1.715 +  rv = NS_tchmod(dest, ss.st_mode);
   1.716 +
   1.717 +  free(buffer);
   1.718 +  return rv;
   1.719 +#endif
   1.720 +}
   1.721 +
   1.722 +template <unsigned N>
   1.723 +struct copy_recursive_skiplist {
   1.724 +  NS_tchar paths[N][MAXPATHLEN];
   1.725 +
   1.726 +  void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) {
   1.727 +    NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
   1.728 +  }
   1.729 +  bool find(const NS_tchar *path) {
   1.730 +    for (unsigned i = 0; i < N; ++i) {
   1.731 +      if (!NS_tstricmp(paths[i], path)) {
   1.732 +        return true;
   1.733 +      }
   1.734 +    }
   1.735 +    return false;
   1.736 +  }
   1.737 +};
   1.738 +
   1.739 +// Copy all of the files and subdirectories under path to a new directory named dest.
   1.740 +// The path names in the skiplist will be skipped and will not be copied.
   1.741 +template <unsigned N>
   1.742 +static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest,
   1.743 +                                 copy_recursive_skiplist<N>& skiplist)
   1.744 +{
   1.745 +  struct stat sInfo;
   1.746 +  int rv = NS_tlstat(path, &sInfo);
   1.747 +  if (rv) {
   1.748 +    LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d",
   1.749 +         path, rv, errno));
   1.750 +    return READ_ERROR;
   1.751 +  }
   1.752 +
   1.753 +#ifndef XP_WIN
   1.754 +  if (S_ISLNK(sInfo.st_mode)) {
   1.755 +    return ensure_copy_symlink(path, dest);
   1.756 +  }
   1.757 +#endif
   1.758 +
   1.759 +  if (!S_ISDIR(sInfo.st_mode)) {
   1.760 +    return ensure_copy(path, dest);
   1.761 +  }
   1.762 +
   1.763 +  rv = NS_tmkdir(dest, sInfo.st_mode);
   1.764 +  if (rv < 0 && errno != EEXIST) {
   1.765 +    LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d",
   1.766 +         path, rv, errno));
   1.767 +    return WRITE_ERROR;
   1.768 +  }
   1.769 +
   1.770 +  NS_tDIR *dir;
   1.771 +  NS_tdirent *entry;
   1.772 +
   1.773 +  dir = NS_topendir(path);
   1.774 +  if (!dir) {
   1.775 +    LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   1.776 +         path, rv, errno));
   1.777 +    return READ_ERROR;
   1.778 +  }
   1.779 +
   1.780 +  while ((entry = NS_treaddir(dir)) != 0) {
   1.781 +    if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
   1.782 +        NS_tstrcmp(entry->d_name, NS_T(".."))) {
   1.783 +      NS_tchar childPath[MAXPATHLEN];
   1.784 +      NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
   1.785 +                   NS_T("%s/%s"), path, entry->d_name);
   1.786 +      if (skiplist.find(childPath)) {
   1.787 +        continue;
   1.788 +      }
   1.789 +      NS_tchar childPathDest[MAXPATHLEN];
   1.790 +      NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]),
   1.791 +                   NS_T("%s/%s"), dest, entry->d_name);
   1.792 +      rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
   1.793 +      if (rv) {
   1.794 +        break;
   1.795 +      }
   1.796 +    }
   1.797 +  }
   1.798 +
   1.799 +  return rv;
   1.800 +}
   1.801 +
   1.802 +// Renames the specified file to the new file specified. If the destination file
   1.803 +// exists it is removed.
   1.804 +static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
   1.805 +                       bool allowDirs = false)
   1.806 +{
   1.807 +  int rv = ensure_parent_dir(dpath);
   1.808 +  if (rv)
   1.809 +    return rv;
   1.810 +
   1.811 +  struct stat spathInfo;
   1.812 +  rv = NS_tlstat(spath, &spathInfo);
   1.813 +  if (rv) {
   1.814 +    LOG(("rename_file: failed to read file status info: " LOG_S ", " \
   1.815 +         "err: %d", spath, errno));
   1.816 +    return READ_ERROR;
   1.817 +  }
   1.818 +
   1.819 +#ifdef XP_WIN
   1.820 +  if (!S_ISREG(spathInfo.st_mode))
   1.821 +#else
   1.822 +  if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode))
   1.823 +#endif
   1.824 +  {
   1.825 +    if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
   1.826 +      LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
   1.827 +           spath, errno));
   1.828 +      return UNEXPECTED_FILE_OPERATION_ERROR;
   1.829 +    } else {
   1.830 +      LOG(("rename_file: proceeding to rename the directory"));
   1.831 +    }
   1.832 +  }
   1.833 +
   1.834 +#ifdef XP_WIN
   1.835 +  if (!NS_taccess(dpath, F_OK))
   1.836 +#else
   1.837 +  if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK))
   1.838 +#endif
   1.839 +  {
   1.840 +    if (ensure_remove(dpath)) {
   1.841 +      LOG(("rename_file: destination file exists and could not be " \
   1.842 +           "removed: " LOG_S, dpath));
   1.843 +      return WRITE_ERROR;
   1.844 +    }
   1.845 +  }
   1.846 +
   1.847 +  if (NS_trename(spath, dpath) != 0) {
   1.848 +    LOG(("rename_file: failed to rename file - src: " LOG_S ", " \
   1.849 +         "dst:" LOG_S ", err: %d", spath, dpath, errno));
   1.850 +    return WRITE_ERROR;
   1.851 +  }
   1.852 +
   1.853 +  return OK;
   1.854 +}
   1.855 +
   1.856 +//-----------------------------------------------------------------------------
   1.857 +
   1.858 +// Create a backup of the specified file by renaming it.
   1.859 +static int backup_create(const NS_tchar *path)
   1.860 +{
   1.861 +  NS_tchar backup[MAXPATHLEN];
   1.862 +  NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   1.863 +               NS_T("%s") BACKUP_EXT, path);
   1.864 +
   1.865 +  return rename_file(path, backup);
   1.866 +}
   1.867 +
   1.868 +// Rename the backup of the specified file that was created by renaming it back
   1.869 +// to the original file.
   1.870 +static int backup_restore(const NS_tchar *path)
   1.871 +{
   1.872 +  NS_tchar backup[MAXPATHLEN];
   1.873 +  NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   1.874 +               NS_T("%s") BACKUP_EXT, path);
   1.875 +
   1.876 +  bool isLink = false;
   1.877 +#ifndef XP_WIN
   1.878 +  struct stat linkInfo;
   1.879 +  int rv = NS_tlstat(path, &linkInfo);
   1.880 +  if (!rv) {
   1.881 +    LOG(("backup_restore: cannot get info for backup file: " LOG_S, backup));
   1.882 +    return OK;
   1.883 +  }
   1.884 +  isLink = S_ISLNK(linkInfo.st_mode);
   1.885 +#endif
   1.886 +
   1.887 +  if (!isLink && NS_taccess(backup, F_OK)) {
   1.888 +    LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
   1.889 +    return OK;
   1.890 +  }
   1.891 +
   1.892 +  return rename_file(backup, path);
   1.893 +}
   1.894 +
   1.895 +// Discard the backup of the specified file that was created by renaming it.
   1.896 +static int backup_discard(const NS_tchar *path)
   1.897 +{
   1.898 +  NS_tchar backup[MAXPATHLEN];
   1.899 +  NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   1.900 +               NS_T("%s") BACKUP_EXT, path);
   1.901 +
   1.902 +  bool isLink = false;
   1.903 +#ifndef XP_WIN
   1.904 +  struct stat linkInfo;
   1.905 +  int rv2 = NS_tlstat(backup, &linkInfo);
   1.906 +  if (rv2) {
   1.907 +    return OK;	// File does not exist; nothing to do.
   1.908 +  }
   1.909 +  isLink = S_ISLNK(linkInfo.st_mode);
   1.910 +#endif
   1.911 +
   1.912 +  // Nothing to discard
   1.913 +  if (!isLink && NS_taccess(backup, F_OK)) {
   1.914 +    return OK;
   1.915 +  }
   1.916 +
   1.917 +  int rv = ensure_remove(backup);
   1.918 +#if defined(XP_WIN)
   1.919 +  if (rv && !sStagedUpdate && !sReplaceRequest) {
   1.920 +    LOG(("backup_discard: unable to remove: " LOG_S, backup));
   1.921 +    NS_tchar path[MAXPATHLEN];
   1.922 +    GetTempFileNameW(DELETE_DIR, L"moz", 0, path);
   1.923 +    if (rename_file(backup, path)) {
   1.924 +      LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
   1.925 +           backup, path));
   1.926 +      return WRITE_ERROR;
   1.927 +    }
   1.928 +    // The MoveFileEx call to remove the file on OS reboot will fail if the
   1.929 +    // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
   1.930 +    // but this is ok since the installer / uninstaller will delete the
   1.931 +    // directory containing the file along with its contents after an update is
   1.932 +    // applied, on reinstall, and on uninstall.
   1.933 +    if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
   1.934 +      LOG(("backup_discard: file renamed and will be removed on OS " \
   1.935 +           "reboot: " LOG_S, path));
   1.936 +    } else {
   1.937 +      LOG(("backup_discard: failed to schedule OS reboot removal of " \
   1.938 +           "file: " LOG_S, path));
   1.939 +    }
   1.940 +  }
   1.941 +#else
   1.942 +  if (rv)
   1.943 +    return WRITE_ERROR;
   1.944 +#endif
   1.945 +
   1.946 +  return OK;
   1.947 +}
   1.948 +
   1.949 +// Helper function for post-processing a temporary backup.
   1.950 +static void backup_finish(const NS_tchar *path, int status)
   1.951 +{
   1.952 +  if (status == OK)
   1.953 +    backup_discard(path);
   1.954 +  else
   1.955 +    backup_restore(path);
   1.956 +}
   1.957 +
   1.958 +//-----------------------------------------------------------------------------
   1.959 +
   1.960 +static int DoUpdate();
   1.961 +
   1.962 +class Action
   1.963 +{
   1.964 +public:
   1.965 +  Action() : mProgressCost(1), mNext(nullptr) { }
   1.966 +  virtual ~Action() { }
   1.967 +
   1.968 +  virtual int Parse(NS_tchar *line) = 0;
   1.969 +
   1.970 +  // Do any preprocessing to ensure that the action can be performed.  Execute
   1.971 +  // will be called if this Action and all others return OK from this method.
   1.972 +  virtual int Prepare() = 0;
   1.973 +
   1.974 +  // Perform the operation.  Return OK to indicate success.  After all actions
   1.975 +  // have been executed, Finish will be called.  A requirement of Execute is
   1.976 +  // that its operation be reversable from Finish.
   1.977 +  virtual int Execute() = 0;
   1.978 +
   1.979 +  // Finish is called after execution of all actions.  If status is OK, then
   1.980 +  // all actions were successfully executed.  Otherwise, some action failed.
   1.981 +  virtual void Finish(int status) = 0;
   1.982 +
   1.983 +  int mProgressCost;
   1.984 +private:
   1.985 +  Action* mNext;
   1.986 +
   1.987 +  friend class ActionList;
   1.988 +};
   1.989 +
   1.990 +class RemoveFile : public Action
   1.991 +{
   1.992 +public:
   1.993 +  RemoveFile() : mFile(nullptr), mSkip(0), mIsLink(0) { }
   1.994 +
   1.995 +  int Parse(NS_tchar *line);
   1.996 +  int Prepare();
   1.997 +  int Execute();
   1.998 +  void Finish(int status);
   1.999 +
  1.1000 +private:
  1.1001 +  const NS_tchar *mFile;
  1.1002 +  int mSkip;
  1.1003 +  int mIsLink;
  1.1004 +};
  1.1005 +
  1.1006 +int
  1.1007 +RemoveFile::Parse(NS_tchar *line)
  1.1008 +{
  1.1009 +  // format "<deadfile>"
  1.1010 +
  1.1011 +  mFile = get_valid_path(&line);
  1.1012 +  if (!mFile)
  1.1013 +    return PARSE_ERROR;
  1.1014 +
  1.1015 +  return OK;
  1.1016 +}
  1.1017 +
  1.1018 +int
  1.1019 +RemoveFile::Prepare()
  1.1020 +{
  1.1021 +  int rv;
  1.1022 +#ifndef XP_WIN
  1.1023 +  struct stat linkInfo;
  1.1024 +  rv = NS_tlstat(mFile, &linkInfo);
  1.1025 +  mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode));
  1.1026 +#endif
  1.1027 +
  1.1028 +  if (!mIsLink) {
  1.1029 +    // Skip the file if it already doesn't exist.
  1.1030 +    rv = NS_taccess(mFile, F_OK);
  1.1031 +    if (rv) {
  1.1032 +      mSkip = 1;
  1.1033 +      mProgressCost = 0;
  1.1034 +      return OK;
  1.1035 +    }
  1.1036 +  }
  1.1037 +
  1.1038 +  LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
  1.1039 +
  1.1040 +  if (!mIsLink) {
  1.1041 +    // Make sure that we're actually a file...
  1.1042 +    struct stat fileInfo;
  1.1043 +    rv = NS_tstat(mFile, &fileInfo);
  1.1044 +    if (rv) {
  1.1045 +      LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
  1.1046 +           errno));
  1.1047 +      return READ_ERROR;
  1.1048 +    }
  1.1049 +
  1.1050 +    if (!S_ISREG(fileInfo.st_mode)) {
  1.1051 +      LOG(("path present, but not a file: " LOG_S, mFile));
  1.1052 +      return UNEXPECTED_FILE_OPERATION_ERROR;
  1.1053 +    }
  1.1054 +  }
  1.1055 +
  1.1056 +  NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
  1.1057 +  if (slash) {
  1.1058 +    *slash = NS_T('\0');
  1.1059 +    rv = NS_taccess(mFile, W_OK);
  1.1060 +    *slash = NS_T('/');
  1.1061 +  } else {
  1.1062 +    rv = NS_taccess(NS_T("."), W_OK);
  1.1063 +  }
  1.1064 +
  1.1065 +  if (rv) {
  1.1066 +    LOG(("access failed: %d", errno));
  1.1067 +    return WRITE_ERROR;
  1.1068 +  }
  1.1069 +
  1.1070 +  return OK;
  1.1071 +}
  1.1072 +
  1.1073 +int
  1.1074 +RemoveFile::Execute()
  1.1075 +{
  1.1076 +  if (mSkip)
  1.1077 +    return OK;
  1.1078 +
  1.1079 +  LOG(("EXECUTE REMOVEFILE " LOG_S, mFile));
  1.1080 +
  1.1081 +  // The file is checked for existence here and in Prepare since it might have
  1.1082 +  // been removed by a separate instruction: bug 311099.
  1.1083 +  int rv = 0;
  1.1084 +  if (mIsLink) {
  1.1085 +    struct stat linkInfo;
  1.1086 +    rv = NS_tlstat(mFile, &linkInfo);
  1.1087 +  } else {
  1.1088 +    rv = NS_taccess(mFile, F_OK);
  1.1089 +  }
  1.1090 +  if (rv) {
  1.1091 +    LOG(("file cannot be removed because it does not exist; skipping"));
  1.1092 +    mSkip = 1;
  1.1093 +    return OK;
  1.1094 +  }
  1.1095 +
  1.1096 +  // Rename the old file. It will be removed in Finish.
  1.1097 +  rv = backup_create(mFile);
  1.1098 +  if (rv) {
  1.1099 +    LOG(("backup_create failed: %d", rv));
  1.1100 +    return rv;
  1.1101 +  }
  1.1102 +
  1.1103 +  return OK;
  1.1104 +}
  1.1105 +
  1.1106 +void
  1.1107 +RemoveFile::Finish(int status)
  1.1108 +{
  1.1109 +  if (mSkip)
  1.1110 +    return;
  1.1111 +
  1.1112 +  LOG(("FINISH REMOVEFILE " LOG_S, mFile));
  1.1113 +
  1.1114 +  backup_finish(mFile, status);
  1.1115 +}
  1.1116 +
  1.1117 +class RemoveDir : public Action
  1.1118 +{
  1.1119 +public:
  1.1120 +  RemoveDir() : mDir(nullptr), mSkip(0) { }
  1.1121 +
  1.1122 +  virtual int Parse(NS_tchar *line);
  1.1123 +  virtual int Prepare(); // check that the source dir exists
  1.1124 +  virtual int Execute();
  1.1125 +  virtual void Finish(int status);
  1.1126 +
  1.1127 +private:
  1.1128 +  const NS_tchar *mDir;
  1.1129 +  int mSkip;
  1.1130 +};
  1.1131 +
  1.1132 +int
  1.1133 +RemoveDir::Parse(NS_tchar *line)
  1.1134 +{
  1.1135 +  // format "<deaddir>/"
  1.1136 +
  1.1137 +  mDir = get_valid_path(&line, true);
  1.1138 +  if (!mDir)
  1.1139 +    return PARSE_ERROR;
  1.1140 +
  1.1141 +  return OK;
  1.1142 +}
  1.1143 +
  1.1144 +int
  1.1145 +RemoveDir::Prepare()
  1.1146 +{
  1.1147 +  // We expect the directory to exist if we are to remove it.
  1.1148 +  int rv = NS_taccess(mDir, F_OK);
  1.1149 +  if (rv) {
  1.1150 +    mSkip = 1;
  1.1151 +    mProgressCost = 0;
  1.1152 +    return OK;
  1.1153 +  }
  1.1154 +
  1.1155 +  LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
  1.1156 +
  1.1157 +  // Make sure that we're actually a dir.
  1.1158 +  struct stat dirInfo;
  1.1159 +  rv = NS_tstat(mDir, &dirInfo);
  1.1160 +  if (rv) {
  1.1161 +    LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir,
  1.1162 +         errno));
  1.1163 +    return READ_ERROR;
  1.1164 +  }
  1.1165 +
  1.1166 +  if (!S_ISDIR(dirInfo.st_mode)) {
  1.1167 +    LOG(("path present, but not a directory: " LOG_S, mDir));
  1.1168 +    return UNEXPECTED_FILE_OPERATION_ERROR;
  1.1169 +  }
  1.1170 +
  1.1171 +  rv = NS_taccess(mDir, W_OK);
  1.1172 +  if (rv) {
  1.1173 +    LOG(("access failed: %d, %d", rv, errno));
  1.1174 +    return WRITE_ERROR;
  1.1175 +  }
  1.1176 +
  1.1177 +  return OK;
  1.1178 +}
  1.1179 +
  1.1180 +int
  1.1181 +RemoveDir::Execute()
  1.1182 +{
  1.1183 +  if (mSkip)
  1.1184 +    return OK;
  1.1185 +
  1.1186 +  LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir));
  1.1187 +
  1.1188 +  // The directory is checked for existence at every step since it might have
  1.1189 +  // been removed by a separate instruction: bug 311099.
  1.1190 +  int rv = NS_taccess(mDir, F_OK);
  1.1191 +  if (rv) {
  1.1192 +    LOG(("directory no longer exists; skipping"));
  1.1193 +    mSkip = 1;
  1.1194 +  }
  1.1195 +
  1.1196 +  return OK;
  1.1197 +}
  1.1198 +
  1.1199 +void
  1.1200 +RemoveDir::Finish(int status)
  1.1201 +{
  1.1202 +  if (mSkip || status != OK)
  1.1203 +    return;
  1.1204 +
  1.1205 +  LOG(("FINISH REMOVEDIR " LOG_S "/", mDir));
  1.1206 +
  1.1207 +  // The directory is checked for existence at every step since it might have
  1.1208 +  // been removed by a separate instruction: bug 311099.
  1.1209 +  int rv = NS_taccess(mDir, F_OK);
  1.1210 +  if (rv) {
  1.1211 +    LOG(("directory no longer exists; skipping"));
  1.1212 +    return;
  1.1213 +  }
  1.1214 +
  1.1215 +
  1.1216 +  if (status == OK) {
  1.1217 +    if (NS_trmdir(mDir)) {
  1.1218 +      LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
  1.1219 +           mDir, rv, errno));
  1.1220 +    }
  1.1221 +  }
  1.1222 +}
  1.1223 +
  1.1224 +class AddFile : public Action
  1.1225 +{
  1.1226 +public:
  1.1227 +  AddFile() : mFile(nullptr)
  1.1228 +            , mAdded(false)
  1.1229 +            { }
  1.1230 +
  1.1231 +  virtual int Parse(NS_tchar *line);
  1.1232 +  virtual int Prepare();
  1.1233 +  virtual int Execute();
  1.1234 +  virtual void Finish(int status);
  1.1235 +
  1.1236 +private:
  1.1237 +  const NS_tchar *mFile;
  1.1238 +  bool mAdded;
  1.1239 +};
  1.1240 +
  1.1241 +int
  1.1242 +AddFile::Parse(NS_tchar *line)
  1.1243 +{
  1.1244 +  // format "<newfile>"
  1.1245 +
  1.1246 +  mFile = get_valid_path(&line);
  1.1247 +  if (!mFile)
  1.1248 +    return PARSE_ERROR;
  1.1249 +
  1.1250 +  return OK;
  1.1251 +}
  1.1252 +
  1.1253 +int
  1.1254 +AddFile::Prepare()
  1.1255 +{
  1.1256 +  LOG(("PREPARE ADD " LOG_S, mFile));
  1.1257 +
  1.1258 +  return OK;
  1.1259 +}
  1.1260 +
  1.1261 +int
  1.1262 +AddFile::Execute()
  1.1263 +{
  1.1264 +  LOG(("EXECUTE ADD " LOG_S, mFile));
  1.1265 +
  1.1266 +  int rv;
  1.1267 +
  1.1268 +  // First make sure that we can actually get rid of any existing file.
  1.1269 +  rv = NS_taccess(mFile, F_OK);
  1.1270 +  if (rv == 0) {
  1.1271 +    rv = backup_create(mFile);
  1.1272 +    if (rv)
  1.1273 +      return rv;
  1.1274 +  } else {
  1.1275 +    rv = ensure_parent_dir(mFile);
  1.1276 +    if (rv)
  1.1277 +      return rv;
  1.1278 +  }
  1.1279 +
  1.1280 +#ifdef XP_WIN
  1.1281 +  char sourcefile[MAXPATHLEN];
  1.1282 +  if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN,
  1.1283 +                           nullptr, nullptr)) {
  1.1284 +    LOG(("error converting wchar to utf8: %d", GetLastError()));
  1.1285 +    return STRING_CONVERSION_ERROR;
  1.1286 +  }
  1.1287 +
  1.1288 +  rv = gArchiveReader.ExtractFile(sourcefile, mFile);
  1.1289 +#else
  1.1290 +  rv = gArchiveReader.ExtractFile(mFile, mFile);
  1.1291 +#endif
  1.1292 +  if (!rv) {
  1.1293 +    mAdded = true;
  1.1294 +  }
  1.1295 +  return rv;
  1.1296 +}
  1.1297 +
  1.1298 +void
  1.1299 +AddFile::Finish(int status)
  1.1300 +{
  1.1301 +  LOG(("FINISH ADD " LOG_S, mFile));
  1.1302 +  // When there is an update failure and a file has been added it is removed
  1.1303 +  // here since there might not be a backup to replace it.
  1.1304 +  if (status && mAdded)
  1.1305 +    NS_tremove(mFile);
  1.1306 +  backup_finish(mFile, status);
  1.1307 +}
  1.1308 +
  1.1309 +class PatchFile : public Action
  1.1310 +{
  1.1311 +public:
  1.1312 +  PatchFile() : mPatchIndex(-1), buf(nullptr) { }
  1.1313 +
  1.1314 +  virtual ~PatchFile();
  1.1315 +
  1.1316 +  virtual int Parse(NS_tchar *line);
  1.1317 +  virtual int Prepare(); // should check for patch file and for checksum here
  1.1318 +  virtual int Execute();
  1.1319 +  virtual void Finish(int status);
  1.1320 +
  1.1321 +private:
  1.1322 +  int LoadSourceFile(FILE* ofile);
  1.1323 +
  1.1324 +  static int sPatchIndex;
  1.1325 +
  1.1326 +  const NS_tchar *mPatchFile;
  1.1327 +  const NS_tchar *mFile;
  1.1328 +  int mPatchIndex;
  1.1329 +  MBSPatchHeader header;
  1.1330 +  unsigned char *buf;
  1.1331 +  NS_tchar spath[MAXPATHLEN];
  1.1332 +};
  1.1333 +
  1.1334 +int PatchFile::sPatchIndex = 0;
  1.1335 +
  1.1336 +PatchFile::~PatchFile()
  1.1337 +{
  1.1338 +  // delete the temporary patch file
  1.1339 +  if (spath[0])
  1.1340 +    NS_tremove(spath);
  1.1341 +
  1.1342 +  if (buf)
  1.1343 +    free(buf);
  1.1344 +}
  1.1345 +
  1.1346 +int
  1.1347 +PatchFile::LoadSourceFile(FILE* ofile)
  1.1348 +{
  1.1349 +  struct stat os;
  1.1350 +  int rv = fstat(fileno((FILE *)ofile), &os);
  1.1351 +  if (rv) {
  1.1352 +    LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
  1.1353 +         "err: %d", mFile, errno));
  1.1354 +    return READ_ERROR;
  1.1355 +  }
  1.1356 +
  1.1357 +  if (uint32_t(os.st_size) != header.slen) {
  1.1358 +    LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
  1.1359 +         uint32_t(os.st_size), header.slen));
  1.1360 +    return UNEXPECTED_FILE_OPERATION_ERROR;
  1.1361 +  }
  1.1362 +
  1.1363 +  buf = (unsigned char *) malloc(header.slen);
  1.1364 +  if (!buf)
  1.1365 +    return UPDATER_MEM_ERROR;
  1.1366 +
  1.1367 +  size_t r = header.slen;
  1.1368 +  unsigned char *rb = buf;
  1.1369 +  while (r) {
  1.1370 +    const size_t count = mmin(SSIZE_MAX, r);
  1.1371 +    size_t c = fread(rb, 1, count, ofile);
  1.1372 +    if (c != count) {
  1.1373 +      LOG(("LoadSourceFile: error reading destination file: " LOG_S,
  1.1374 +           mFile));
  1.1375 +      return READ_ERROR;
  1.1376 +    }
  1.1377 +
  1.1378 +    r -= c;
  1.1379 +    rb += c;
  1.1380 +  }
  1.1381 +
  1.1382 +  // Verify that the contents of the source file correspond to what we expect.
  1.1383 +
  1.1384 +  unsigned int crc = crc32(buf, header.slen);
  1.1385 +
  1.1386 +  if (crc != header.scrc32) {
  1.1387 +    LOG(("LoadSourceFile: destination file crc %d does not match expected " \
  1.1388 +         "crc %d", crc, header.scrc32));
  1.1389 +    return CRC_ERROR;
  1.1390 +  }
  1.1391 +
  1.1392 +  return OK;
  1.1393 +}
  1.1394 +
  1.1395 +int
  1.1396 +PatchFile::Parse(NS_tchar *line)
  1.1397 +{
  1.1398 +  // format "<patchfile>" "<filetopatch>"
  1.1399 +
  1.1400 +  // Get the path to the patch file inside of the mar
  1.1401 +  mPatchFile = mstrtok(kQuote, &line);
  1.1402 +  if (!mPatchFile)
  1.1403 +    return PARSE_ERROR;
  1.1404 +
  1.1405 +  // consume whitespace between args
  1.1406 +  NS_tchar *q = mstrtok(kQuote, &line);
  1.1407 +  if (!q)
  1.1408 +    return PARSE_ERROR;
  1.1409 +
  1.1410 +  mFile = get_valid_path(&line);
  1.1411 +  if (!mFile)
  1.1412 +    return PARSE_ERROR;
  1.1413 +
  1.1414 +  return OK;
  1.1415 +}
  1.1416 +
  1.1417 +int
  1.1418 +PatchFile::Prepare()
  1.1419 +{
  1.1420 +  LOG(("PREPARE PATCH " LOG_S, mFile));
  1.1421 +
  1.1422 +  // extract the patch to a temporary file
  1.1423 +  mPatchIndex = sPatchIndex++;
  1.1424 +
  1.1425 +  NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
  1.1426 +               NS_T("%s/updating/%d.patch"), gDestinationPath, mPatchIndex);
  1.1427 +
  1.1428 +  NS_tremove(spath);
  1.1429 +
  1.1430 +  FILE *fp = NS_tfopen(spath, NS_T("wb"));
  1.1431 +  if (!fp)
  1.1432 +    return WRITE_ERROR;
  1.1433 +
  1.1434 +#ifdef XP_WIN
  1.1435 +  char sourcefile[MAXPATHLEN];
  1.1436 +  if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
  1.1437 +                           nullptr, nullptr)) {
  1.1438 +    LOG(("error converting wchar to utf8: %d", GetLastError()));
  1.1439 +    return STRING_CONVERSION_ERROR;
  1.1440 +  }
  1.1441 +
  1.1442 +  int rv = gArchiveReader.ExtractFileToStream(sourcefile, fp);
  1.1443 +#else
  1.1444 +  int rv = gArchiveReader.ExtractFileToStream(mPatchFile, fp);
  1.1445 +#endif
  1.1446 +  fclose(fp);
  1.1447 +  return rv;
  1.1448 +}
  1.1449 +
  1.1450 +int
  1.1451 +PatchFile::Execute()
  1.1452 +{
  1.1453 +  LOG(("EXECUTE PATCH " LOG_S, mFile));
  1.1454 +
  1.1455 +  AutoFile pfile = NS_tfopen(spath, NS_T("rb"));
  1.1456 +  if (pfile == nullptr)
  1.1457 +    return READ_ERROR;
  1.1458 +
  1.1459 +  int rv = MBS_ReadHeader(pfile, &header);
  1.1460 +  if (rv)
  1.1461 +    return rv;
  1.1462 +
  1.1463 +  FILE *origfile = nullptr;
  1.1464 +#ifdef XP_WIN
  1.1465 +  if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) {
  1.1466 +    // Read from the copy of the callback when patching since the callback can't
  1.1467 +    // be opened for reading to prevent the application from being launched.
  1.1468 +    origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
  1.1469 +  } else {
  1.1470 +    origfile = NS_tfopen(mFile, NS_T("rb"));
  1.1471 +  }
  1.1472 +#else
  1.1473 +  origfile = NS_tfopen(mFile, NS_T("rb"));
  1.1474 +#endif
  1.1475 +
  1.1476 +  if (!origfile) {
  1.1477 +    LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
  1.1478 +         errno));
  1.1479 +    return READ_ERROR;
  1.1480 +  }
  1.1481 +
  1.1482 +  rv = LoadSourceFile(origfile);
  1.1483 +  fclose(origfile);
  1.1484 +  if (rv) {
  1.1485 +    LOG(("LoadSourceFile failed"));
  1.1486 +    return rv;
  1.1487 +  }
  1.1488 +
  1.1489 +  // Rename the destination file if it exists before proceeding so it can be
  1.1490 +  // used to restore the file to its original state if there is an error.
  1.1491 +  struct stat ss;
  1.1492 +  rv = NS_tstat(mFile, &ss);
  1.1493 +  if (rv) {
  1.1494 +    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
  1.1495 +         errno));
  1.1496 +    return READ_ERROR;
  1.1497 +  }
  1.1498 +
  1.1499 +  rv = backup_create(mFile);
  1.1500 +  if (rv)
  1.1501 +    return rv;
  1.1502 +
  1.1503 +#if defined(HAVE_POSIX_FALLOCATE)
  1.1504 +  AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1.1505 +  posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
  1.1506 +#elif defined(XP_WIN)
  1.1507 +  bool shouldTruncate = true;
  1.1508 +  // Creating the file, setting the size, and then closing the file handle
  1.1509 +  // lessens fragmentation more than any other method tested. Other methods that
  1.1510 +  // have been tested are:
  1.1511 +  // 1. _chsize / _chsize_s reduced fragmentation but though not completely.
  1.1512 +  // 2. _get_osfhandle and then setting the size reduced fragmentation though
  1.1513 +  //    not completely. There are also reports of _get_osfhandle failing on
  1.1514 +  //    mingw.
  1.1515 +  HANDLE hfile = CreateFileW(mFile,
  1.1516 +                             GENERIC_WRITE,
  1.1517 +                             0,
  1.1518 +                             nullptr,
  1.1519 +                             CREATE_ALWAYS,
  1.1520 +                             FILE_ATTRIBUTE_NORMAL,
  1.1521 +                             nullptr);
  1.1522 +
  1.1523 +  if (hfile != INVALID_HANDLE_VALUE) {
  1.1524 +    if (SetFilePointer(hfile, header.dlen,
  1.1525 +                       nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
  1.1526 +        SetEndOfFile(hfile) != 0) {
  1.1527 +      shouldTruncate = false;
  1.1528 +    }
  1.1529 +    CloseHandle(hfile);
  1.1530 +  }
  1.1531 +
  1.1532 +  AutoFile ofile = ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"), ss.st_mode);
  1.1533 +#elif defined(XP_MACOSX)
  1.1534 +  AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1.1535 +  // Modified code from FileUtils.cpp
  1.1536 +  fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
  1.1537 +  // Try to get a continous chunk of disk space
  1.1538 +  rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
  1.1539 +  if (rv == -1) {
  1.1540 +    // OK, perhaps we are too fragmented, allocate non-continuous
  1.1541 +    store.fst_flags = F_ALLOCATEALL;
  1.1542 +    rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
  1.1543 +  }
  1.1544 +
  1.1545 +  if (rv != -1) {
  1.1546 +    ftruncate(fileno((FILE *)ofile), header.dlen);
  1.1547 +  }
  1.1548 +#else
  1.1549 +  AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1.1550 +#endif
  1.1551 +
  1.1552 +  if (ofile == nullptr) {
  1.1553 +    LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
  1.1554 +    return WRITE_ERROR;
  1.1555 +  }
  1.1556 +
  1.1557 +#ifdef XP_WIN
  1.1558 +  if (!shouldTruncate) {
  1.1559 +    fseek(ofile, 0, SEEK_SET);
  1.1560 +  }
  1.1561 +#endif
  1.1562 +
  1.1563 +  rv = MBS_ApplyPatch(&header, pfile, buf, ofile);
  1.1564 +
  1.1565 +  // Go ahead and do a bit of cleanup now to minimize runtime overhead.
  1.1566 +  // Set pfile to nullptr to make AutoFile close the file so it can be deleted
  1.1567 +  // on Windows.
  1.1568 +  pfile = nullptr;
  1.1569 +  NS_tremove(spath);
  1.1570 +  spath[0] = NS_T('\0');
  1.1571 +  free(buf);
  1.1572 +  buf = nullptr;
  1.1573 +
  1.1574 +  return rv;
  1.1575 +}
  1.1576 +
  1.1577 +void
  1.1578 +PatchFile::Finish(int status)
  1.1579 +{
  1.1580 +  LOG(("FINISH PATCH " LOG_S, mFile));
  1.1581 +
  1.1582 +  backup_finish(mFile, status);
  1.1583 +}
  1.1584 +
  1.1585 +class AddIfFile : public AddFile
  1.1586 +{
  1.1587 +public:
  1.1588 +  AddIfFile() : mTestFile(nullptr) { }
  1.1589 +
  1.1590 +  virtual int Parse(NS_tchar *line);
  1.1591 +  virtual int Prepare();
  1.1592 +  virtual int Execute();
  1.1593 +  virtual void Finish(int status);
  1.1594 +
  1.1595 +protected:
  1.1596 +  const NS_tchar *mTestFile;
  1.1597 +};
  1.1598 +
  1.1599 +int
  1.1600 +AddIfFile::Parse(NS_tchar *line)
  1.1601 +{
  1.1602 +  // format "<testfile>" "<newfile>"
  1.1603 +
  1.1604 +  mTestFile = get_valid_path(&line);
  1.1605 +  if (!mTestFile)
  1.1606 +    return PARSE_ERROR;
  1.1607 +
  1.1608 +  // consume whitespace between args
  1.1609 +  NS_tchar *q = mstrtok(kQuote, &line);
  1.1610 +  if (!q)
  1.1611 +    return PARSE_ERROR;
  1.1612 +
  1.1613 +  return AddFile::Parse(line);
  1.1614 +}
  1.1615 +
  1.1616 +int
  1.1617 +AddIfFile::Prepare()
  1.1618 +{
  1.1619 +  // If the test file does not exist, then skip this action.
  1.1620 +  if (NS_taccess(mTestFile, F_OK)) {
  1.1621 +    mTestFile = nullptr;
  1.1622 +    return OK;
  1.1623 +  }
  1.1624 +
  1.1625 +  return AddFile::Prepare();
  1.1626 +}
  1.1627 +
  1.1628 +int
  1.1629 +AddIfFile::Execute()
  1.1630 +{
  1.1631 +  if (!mTestFile)
  1.1632 +    return OK;
  1.1633 +
  1.1634 +  return AddFile::Execute();
  1.1635 +}
  1.1636 +
  1.1637 +void
  1.1638 +AddIfFile::Finish(int status)
  1.1639 +{
  1.1640 +  if (!mTestFile)
  1.1641 +    return;
  1.1642 +
  1.1643 +  AddFile::Finish(status);
  1.1644 +}
  1.1645 +
  1.1646 +class AddIfNotFile : public AddFile
  1.1647 +{
  1.1648 +public:
  1.1649 +  AddIfNotFile() : mTestFile(NULL) { }
  1.1650 +
  1.1651 +  virtual int Parse(NS_tchar *line);
  1.1652 +  virtual int Prepare();
  1.1653 +  virtual int Execute();
  1.1654 +  virtual void Finish(int status);
  1.1655 +
  1.1656 +protected:
  1.1657 +  const NS_tchar *mTestFile;
  1.1658 +};
  1.1659 +
  1.1660 +int
  1.1661 +AddIfNotFile::Parse(NS_tchar *line)
  1.1662 +{
  1.1663 +  // format "<testfile>" "<newfile>"
  1.1664 +
  1.1665 +  mTestFile = get_valid_path(&line);
  1.1666 +  if (!mTestFile)
  1.1667 +    return PARSE_ERROR;
  1.1668 +
  1.1669 +  // consume whitespace between args
  1.1670 +  NS_tchar *q = mstrtok(kQuote, &line);
  1.1671 +  if (!q)
  1.1672 +    return PARSE_ERROR;
  1.1673 +
  1.1674 +  return AddFile::Parse(line);
  1.1675 +}
  1.1676 +
  1.1677 +int
  1.1678 +AddIfNotFile::Prepare()
  1.1679 +{
  1.1680 +  // If the test file exists, then skip this action.
  1.1681 +  if (!NS_taccess(mTestFile, F_OK)) {
  1.1682 +    mTestFile = NULL;
  1.1683 +    return OK;
  1.1684 +  }
  1.1685 +
  1.1686 +  return AddFile::Prepare();
  1.1687 +}
  1.1688 +
  1.1689 +int
  1.1690 +AddIfNotFile::Execute()
  1.1691 +{
  1.1692 +  if (!mTestFile)
  1.1693 +    return OK;
  1.1694 +
  1.1695 +  return AddFile::Execute();
  1.1696 +}
  1.1697 +
  1.1698 +void
  1.1699 +AddIfNotFile::Finish(int status)
  1.1700 +{
  1.1701 +  if (!mTestFile)
  1.1702 +    return;
  1.1703 +
  1.1704 +  AddFile::Finish(status);
  1.1705 +}
  1.1706 +
  1.1707 +class PatchIfFile : public PatchFile
  1.1708 +{
  1.1709 +public:
  1.1710 +  PatchIfFile() : mTestFile(nullptr) { }
  1.1711 +
  1.1712 +  virtual int Parse(NS_tchar *line);
  1.1713 +  virtual int Prepare(); // should check for patch file and for checksum here
  1.1714 +  virtual int Execute();
  1.1715 +  virtual void Finish(int status);
  1.1716 +
  1.1717 +private:
  1.1718 +  const NS_tchar *mTestFile;
  1.1719 +};
  1.1720 +
  1.1721 +int
  1.1722 +PatchIfFile::Parse(NS_tchar *line)
  1.1723 +{
  1.1724 +  // format "<testfile>" "<patchfile>" "<filetopatch>"
  1.1725 +
  1.1726 +  mTestFile = get_valid_path(&line);
  1.1727 +  if (!mTestFile)
  1.1728 +    return PARSE_ERROR;
  1.1729 +
  1.1730 +  // consume whitespace between args
  1.1731 +  NS_tchar *q = mstrtok(kQuote, &line);
  1.1732 +  if (!q)
  1.1733 +    return PARSE_ERROR;
  1.1734 +
  1.1735 +  return PatchFile::Parse(line);
  1.1736 +}
  1.1737 +
  1.1738 +int
  1.1739 +PatchIfFile::Prepare()
  1.1740 +{
  1.1741 +  // If the test file does not exist, then skip this action.
  1.1742 +  if (NS_taccess(mTestFile, F_OK)) {
  1.1743 +    mTestFile = nullptr;
  1.1744 +    return OK;
  1.1745 +  }
  1.1746 +
  1.1747 +  return PatchFile::Prepare();
  1.1748 +}
  1.1749 +
  1.1750 +int
  1.1751 +PatchIfFile::Execute()
  1.1752 +{
  1.1753 +  if (!mTestFile)
  1.1754 +    return OK;
  1.1755 +
  1.1756 +  return PatchFile::Execute();
  1.1757 +}
  1.1758 +
  1.1759 +void
  1.1760 +PatchIfFile::Finish(int status)
  1.1761 +{
  1.1762 +  if (!mTestFile)
  1.1763 +    return;
  1.1764 +
  1.1765 +  PatchFile::Finish(status);
  1.1766 +}
  1.1767 +
  1.1768 +#ifndef XP_WIN
  1.1769 +class AddSymlink : public Action
  1.1770 +{
  1.1771 +public:
  1.1772 +  AddSymlink() : mLinkName(NULL)
  1.1773 +               , mTarget(NULL)
  1.1774 +               , mAdded(false)
  1.1775 +            { }
  1.1776 +
  1.1777 +  virtual int Parse(NS_tchar *line);
  1.1778 +  virtual int Prepare();
  1.1779 +  virtual int Execute();
  1.1780 +  virtual void Finish(int status);
  1.1781 +
  1.1782 +private:
  1.1783 +  const NS_tchar *mLinkName;
  1.1784 +  const NS_tchar *mTarget;
  1.1785 +  bool mAdded;
  1.1786 +};
  1.1787 +
  1.1788 +int
  1.1789 +AddSymlink::Parse(NS_tchar *line)
  1.1790 +{
  1.1791 +  // format "<linkname>" "target"
  1.1792 +
  1.1793 +  mLinkName = get_valid_path(&line);
  1.1794 +  if (!mLinkName)
  1.1795 +    return PARSE_ERROR;
  1.1796 +
  1.1797 +  // consume whitespace between args
  1.1798 +  NS_tchar *q = mstrtok(kQuote, &line);
  1.1799 +  if (!q)
  1.1800 +    return PARSE_ERROR;
  1.1801 +
  1.1802 +  mTarget = get_valid_path(&line, false, true);
  1.1803 +  if (!mTarget)
  1.1804 +    return PARSE_ERROR;
  1.1805 +
  1.1806 +  return OK;
  1.1807 +}
  1.1808 +
  1.1809 +int
  1.1810 +AddSymlink::Prepare()
  1.1811 +{
  1.1812 +  LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1.1813 +
  1.1814 +  return OK;
  1.1815 +}
  1.1816 +
  1.1817 +int
  1.1818 +AddSymlink::Execute()
  1.1819 +{
  1.1820 +  LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1.1821 +
  1.1822 +  // First make sure that we can actually get rid of any existing file or link.
  1.1823 +  struct stat linkInfo;
  1.1824 +  int rv = NS_tlstat(mLinkName, &linkInfo);
  1.1825 +  if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) {
  1.1826 +    rv = NS_taccess(mLinkName, F_OK);
  1.1827 +  }
  1.1828 +  if (rv == 0) {
  1.1829 +    rv = backup_create(mLinkName);
  1.1830 +    if (rv)
  1.1831 +      return rv;
  1.1832 +  } else {
  1.1833 +    rv = ensure_parent_dir(mLinkName);
  1.1834 +    if (rv)
  1.1835 +      return rv;
  1.1836 +  }
  1.1837 +
  1.1838 +  // Create the link.
  1.1839 +  rv = symlink(mTarget, mLinkName);
  1.1840 +  if (!rv) {
  1.1841 +    mAdded = true;
  1.1842 +  }
  1.1843 +
  1.1844 +  return rv;
  1.1845 +}
  1.1846 +
  1.1847 +void
  1.1848 +AddSymlink::Finish(int status)
  1.1849 +{
  1.1850 +  LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1.1851 +  // When there is an update failure and a link has been added it is removed
  1.1852 +  // here since there might not be a backup to replace it.
  1.1853 +  if (status && mAdded)
  1.1854 +    NS_tremove(mLinkName);
  1.1855 +  backup_finish(mLinkName, status);
  1.1856 +}
  1.1857 +#endif
  1.1858 +
  1.1859 +//-----------------------------------------------------------------------------
  1.1860 +
  1.1861 +#ifdef XP_WIN
  1.1862 +#include "nsWindowsRestart.cpp"
  1.1863 +#include "nsWindowsHelpers.h"
  1.1864 +#include "uachelper.h"
  1.1865 +#include "pathhash.h"
  1.1866 +
  1.1867 +#ifdef MOZ_METRO
  1.1868 +/**
  1.1869 + * Determines if the update came from an Immersive browser
  1.1870 + * @return true if the update came from an immersive browser
  1.1871 + */
  1.1872 +bool
  1.1873 +IsUpdateFromMetro(int argc, NS_tchar **argv)
  1.1874 +{
  1.1875 +  for (int i = 0; i < argc; i++) {
  1.1876 +    if (!wcsicmp(L"-ServerName:DefaultBrowserServer", argv[i])) {
  1.1877 +      return true;
  1.1878 +    }
  1.1879 +  }
  1.1880 +  return false;
  1.1881 +}
  1.1882 +#endif
  1.1883 +#endif
  1.1884 +
  1.1885 +static void
  1.1886 +LaunchCallbackApp(const NS_tchar *workingDir, 
  1.1887 +                  int argc, 
  1.1888 +                  NS_tchar **argv, 
  1.1889 +                  bool usingService)
  1.1890 +{
  1.1891 +  putenv(const_cast<char*>("NO_EM_RESTART="));
  1.1892 +  putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
  1.1893 +
  1.1894 +  // Run from the specified working directory (see bug 312360). This is not
  1.1895 +  // necessary on Windows CE since the application that launches the updater
  1.1896 +  // passes the working directory as an --environ: command line argument.
  1.1897 +  if (NS_tchdir(workingDir) != 0) {
  1.1898 +    LOG(("Warning: chdir failed"));
  1.1899 +  }
  1.1900 +
  1.1901 +#if defined(USE_EXECV)
  1.1902 +  execv(argv[0], argv);
  1.1903 +#elif defined(XP_MACOSX)
  1.1904 +  LaunchChild(argc, argv);
  1.1905 +#elif defined(XP_WIN)
  1.1906 +  // Do not allow the callback to run when running an update through the
  1.1907 +  // service as session 0.  The unelevated updater.exe will do the launching.
  1.1908 +  if (!usingService) {
  1.1909 +#if defined(MOZ_METRO)
  1.1910 +    // If our callback application is the default metro browser, then
  1.1911 +    // launch it now.
  1.1912 +    if (IsUpdateFromMetro(argc, argv)) {
  1.1913 +      LaunchDefaultMetroBrowser();
  1.1914 +      return;
  1.1915 +    }
  1.1916 +#endif
  1.1917 +    WinLaunchChild(argv[0], argc, argv, nullptr);
  1.1918 +  }
  1.1919 +#else
  1.1920 +# warning "Need implementaton of LaunchCallbackApp"
  1.1921 +#endif
  1.1922 +}
  1.1923 +
  1.1924 +static bool
  1.1925 +WriteStatusFile(const char* aStatus)
  1.1926 +{
  1.1927 +  NS_tchar filename[MAXPATHLEN];
  1.1928 +  NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  1.1929 +               NS_T("%s/update.status"), gSourcePath);
  1.1930 +
  1.1931 +  // Make sure that the directory for the update status file exists
  1.1932 +  if (ensure_parent_dir(filename))
  1.1933 +    return false;
  1.1934 +
  1.1935 +  AutoFile file = NS_tfopen(filename, NS_T("wb+"));
  1.1936 +  if (file == nullptr)
  1.1937 +    return false;
  1.1938 +
  1.1939 +  if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
  1.1940 +    return false;
  1.1941 +
  1.1942 +  return true;
  1.1943 +}
  1.1944 +
  1.1945 +static void
  1.1946 +WriteStatusFile(int status)
  1.1947 +{
  1.1948 +  const char *text;
  1.1949 +
  1.1950 +  char buf[32];
  1.1951 +  if (status == OK) {
  1.1952 +    if (sStagedUpdate) {
  1.1953 +      text = "applied\n";
  1.1954 +    } else {
  1.1955 +      text = "succeeded\n";
  1.1956 +    }
  1.1957 +  } else {
  1.1958 +    snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status);
  1.1959 +    text = buf;
  1.1960 +  }
  1.1961 +
  1.1962 +  WriteStatusFile(text);
  1.1963 +}
  1.1964 +
  1.1965 +#ifdef MOZ_MAINTENANCE_SERVICE
  1.1966 +/* 
  1.1967 + * Read the update.status file and sets isPendingService to true if
  1.1968 + * the status is set to pending-service.
  1.1969 + *
  1.1970 + * @param  isPendingService Out parameter for specifying if the status
  1.1971 + *         is set to pending-service or not.
  1.1972 + * @return true if the information was retrieved and it is pending
  1.1973 + *         or pending-service.
  1.1974 +*/
  1.1975 +static bool
  1.1976 +IsUpdateStatusPendingService()
  1.1977 +{
  1.1978 +  NS_tchar filename[MAXPATHLEN];
  1.1979 +  NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  1.1980 +               NS_T("%s/update.status"), gSourcePath);
  1.1981 +
  1.1982 +  AutoFile file = NS_tfopen(filename, NS_T("rb"));
  1.1983 +  if (file == nullptr)
  1.1984 +    return false;
  1.1985 +
  1.1986 +  char buf[32] = { 0 };
  1.1987 +  fread(buf, sizeof(buf), 1, file);
  1.1988 +
  1.1989 +  const char kPendingService[] = "pending-service";
  1.1990 +  const char kAppliedService[] = "applied-service";
  1.1991 +
  1.1992 +  return (strncmp(buf, kPendingService, 
  1.1993 +                  sizeof(kPendingService) - 1) == 0) ||
  1.1994 +         (strncmp(buf, kAppliedService,
  1.1995 +                  sizeof(kAppliedService) - 1) == 0);
  1.1996 +}
  1.1997 +#endif
  1.1998 +
  1.1999 +#ifdef XP_WIN
  1.2000 +/* 
  1.2001 + * Read the update.status file and sets isSuccess to true if
  1.2002 + * the status is set to succeeded.
  1.2003 + *
  1.2004 + * @param  isSucceeded Out parameter for specifying if the status
  1.2005 + *         is set to succeeded or not.
  1.2006 + * @return true if the information was retrieved and it is succeeded.
  1.2007 +*/
  1.2008 +static bool
  1.2009 +IsUpdateStatusSucceeded(bool &isSucceeded)
  1.2010 +{
  1.2011 +  isSucceeded = false;
  1.2012 +  NS_tchar filename[MAXPATHLEN];
  1.2013 +  NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  1.2014 +               NS_T("%s/update.status"), gSourcePath);
  1.2015 +
  1.2016 +  AutoFile file = NS_tfopen(filename, NS_T("rb"));
  1.2017 +  if (file == nullptr)
  1.2018 +    return false;
  1.2019 +
  1.2020 +  char buf[32] = { 0 };
  1.2021 +  fread(buf, sizeof(buf), 1, file);
  1.2022 +
  1.2023 +  const char kSucceeded[] = "succeeded";
  1.2024 +  isSucceeded = strncmp(buf, kSucceeded, 
  1.2025 +                        sizeof(kSucceeded) - 1) == 0;
  1.2026 +  return true;
  1.2027 +}
  1.2028 +#endif
  1.2029 +
  1.2030 +/*
  1.2031 + * Get the application installation directory.
  1.2032 + *
  1.2033 + * @param installDir Out parameter for specifying the installation directory.
  1.2034 + * @return true if successful, false otherwise.
  1.2035 + */
  1.2036 +template <size_t N>
  1.2037 +static bool
  1.2038 +GetInstallationDir(NS_tchar (&installDir)[N])
  1.2039 +{
  1.2040 +  NS_tsnprintf(installDir, N, NS_T("%s"), gDestinationPath);
  1.2041 +  if (!sStagedUpdate && !sReplaceRequest) {
  1.2042 +    // no need to do any further processing
  1.2043 +    return true;
  1.2044 +  }
  1.2045 +
  1.2046 +  NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  1.2047 +  // Make sure we're not looking at a trailing slash
  1.2048 +  if (slash && slash[1] == NS_T('\0')) {
  1.2049 +    *slash = NS_T('\0');
  1.2050 +    slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  1.2051 +  }
  1.2052 +  if (slash) {
  1.2053 +    *slash = NS_T('\0');
  1.2054 +  } else {
  1.2055 +    return false;
  1.2056 +  }
  1.2057 +  return true;
  1.2058 +}
  1.2059 +
  1.2060 +/*
  1.2061 + * Copy the entire contents of the application installation directory to the
  1.2062 + * destination directory for the update process.
  1.2063 + *
  1.2064 + * @return 0 if successful, an error code otherwise.
  1.2065 + */
  1.2066 +static int
  1.2067 +CopyInstallDirToDestDir()
  1.2068 +{
  1.2069 +  // First extract the installation directory from gSourcePath by going two
  1.2070 +  // levels above it.  This is effectively skipping over "updates/0".
  1.2071 +  NS_tchar installDir[MAXPATHLEN];
  1.2072 +  if (!GetInstallationDir(installDir)) {
  1.2073 +    return NO_INSTALLDIR_ERROR;
  1.2074 +  }
  1.2075 +
  1.2076 +  // These files should not be copied over to the updated app
  1.2077 +#ifdef XP_WIN
  1.2078 +  #ifdef TOR_BROWSER_UPDATE
  1.2079 +    #define SKIPLIST_COUNT 5
  1.2080 +  #else
  1.2081 +    #define SKIPLIST_COUNT 3
  1.2082 +  #endif
  1.2083 +#else
  1.2084 +  #ifdef TOR_BROWSER_UPDATE
  1.2085 +    #define SKIPLIST_COUNT 4
  1.2086 +  #else
  1.2087 +    #define SKIPLIST_COUNT 2
  1.2088 +  #endif
  1.2089 +#endif
  1.2090 +  copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
  1.2091 +#ifdef XP_MACOSX
  1.2092 +  skiplist.append(0, installDir, NS_T("Updated.app"));
  1.2093 +  skiplist.append(1, installDir, NS_T("Contents/MacOS/updates/0"));
  1.2094 +#ifdef TOR_BROWSER_UPDATE
  1.2095 +  skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
  1.2096 +  skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock"));
  1.2097 +#endif
  1.2098 +#else
  1.2099 +  skiplist.append(0, installDir, NS_T("updated"));
  1.2100 +  skiplist.append(1, installDir, NS_T("updates/0"));
  1.2101 +#ifdef TOR_BROWSER_UPDATE
  1.2102 +#ifdef XP_UNIX
  1.2103 +  skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
  1.2104 +#else
  1.2105 +  skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/parent.lock"));
  1.2106 +#endif
  1.2107 +  skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock"));
  1.2108 +#endif
  1.2109 +#ifdef XP_WIN
  1.2110 +  skiplist.append(SKIPLIST_COUNT - 1, installDir,
  1.2111 +                  NS_T("updated.update_in_progress.lock"));
  1.2112 +#endif
  1.2113 +#endif
  1.2114 +
  1.2115 +  return ensure_copy_recursive(installDir, gDestinationPath, skiplist);
  1.2116 +}
  1.2117 +
  1.2118 +/*
  1.2119 + * Replace the application installation directory with the destination
  1.2120 + * directory in order to finish a staged update task
  1.2121 + *
  1.2122 + * @return 0 if successful, an error code otherwise.
  1.2123 + */
  1.2124 +static int
  1.2125 +ProcessReplaceRequest()
  1.2126 +{
  1.2127 +  // The replacement algorithm is like this:
  1.2128 +  // 1. Move sourceDir to tmpDir.  In case of failure, abort.
  1.2129 +  // 2. Move newDir to sourceDir.  In case of failure, revert step 1 and abort.
  1.2130 +  // 3. Delete tmpDir (or defer it to the next reboot).
  1.2131 +
  1.2132 +  NS_tchar installDir[MAXPATHLEN];
  1.2133 +  if (!GetInstallationDir(installDir)) {
  1.2134 +    return NO_INSTALLDIR_ERROR;
  1.2135 +  }
  1.2136 +
  1.2137 +#ifdef XP_MACOSX
  1.2138 +  NS_tchar sourceDir[MAXPATHLEN];
  1.2139 +  NS_tsnprintf(sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]),
  1.2140 +               NS_T("%s/Contents"), installDir);
  1.2141 +#elif XP_WIN
  1.2142 +  // Windows preserves the case of the file/directory names.  We use the
  1.2143 +  // GetLongPathName API in order to get the correct case for the directory
  1.2144 +  // name, so that if the user has used a different case when launching the
  1.2145 +  // application, the installation directory's name does not change.
  1.2146 +  NS_tchar sourceDir[MAXPATHLEN];
  1.2147 +  if (!GetLongPathNameW(installDir, sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]))) {
  1.2148 +    return NO_INSTALLDIR_ERROR;
  1.2149 +  }
  1.2150 +#else
  1.2151 +  NS_tchar* sourceDir = installDir;
  1.2152 +#endif
  1.2153 +
  1.2154 +  NS_tchar tmpDir[MAXPATHLEN];
  1.2155 +  NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
  1.2156 +               NS_T("%s.bak"), sourceDir);
  1.2157 +
  1.2158 +  NS_tchar newDir[MAXPATHLEN];
  1.2159 +  NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
  1.2160 +#ifdef XP_MACOSX
  1.2161 +               NS_T("%s/Updated.app/Contents"),
  1.2162 +#else
  1.2163 +               NS_T("%s.bak/updated"),
  1.2164 +#endif
  1.2165 +               installDir);
  1.2166 +
  1.2167 +  // First try to remove the possibly existing temp directory, because if this
  1.2168 +  // directory exists, we will fail to rename sourceDir.
  1.2169 +  // No need to error check here because if this fails, we will fail in the
  1.2170 +  // next step anyways.
  1.2171 +  ensure_remove_recursive(tmpDir);
  1.2172 +
  1.2173 +  LOG(("Begin moving sourceDir (" LOG_S ") to tmpDir (" LOG_S ")",
  1.2174 +       sourceDir, tmpDir));
  1.2175 +  int rv = rename_file(sourceDir, tmpDir, true);
  1.2176 +#ifdef XP_WIN
  1.2177 +  // On Windows, if Firefox is launched using the shortcut, it will hold a handle
  1.2178 +  // to its installation directory open, which might not get released in time.
  1.2179 +  // Therefore we wait a little bit here to see if the handle is released.
  1.2180 +  // If it's not released, we just fail to perform the replace request.
  1.2181 +  const int max_retries = 10;
  1.2182 +  int retries = 0;
  1.2183 +  while (rv == WRITE_ERROR && (retries++ < max_retries)) {
  1.2184 +    LOG(("PerformReplaceRequest: sourceDir rename attempt %d failed. " \
  1.2185 +         "File: " LOG_S ". Last error: %d, err: %d", retries,
  1.2186 +         sourceDir, GetLastError(), rv));
  1.2187 +
  1.2188 +    Sleep(100);
  1.2189 +
  1.2190 +    rv = rename_file(sourceDir, tmpDir, true);
  1.2191 +  }
  1.2192 +#endif
  1.2193 +  if (rv) {
  1.2194 +    LOG(("Moving sourceDir to tmpDir failed, err: %d", rv));
  1.2195 +    return rv;
  1.2196 +  }
  1.2197 +
  1.2198 +  LOG(("Begin moving newDir (" LOG_S ") to sourceDir (" LOG_S ")",
  1.2199 +       newDir, sourceDir));
  1.2200 +  rv = rename_file(newDir, sourceDir, true);
  1.2201 +  if (rv) {
  1.2202 +    LOG(("Moving newDir to sourceDir failed, err: %d", rv));
  1.2203 +    LOG(("Now, try to move tmpDir back to sourceDir"));
  1.2204 +    ensure_remove_recursive(sourceDir);
  1.2205 +    int rv2 = rename_file(tmpDir, sourceDir, true);
  1.2206 +    if (rv2) {
  1.2207 +      LOG(("Moving tmpDir back to sourceDir failed, err: %d", rv2));
  1.2208 +    }
  1.2209 +    return rv;
  1.2210 +  }
  1.2211 +
  1.2212 +  LOG(("Now, remove the tmpDir"));
  1.2213 +  rv = ensure_remove_recursive(tmpDir);
  1.2214 +  if (rv) {
  1.2215 +    LOG(("Removing tmpDir failed, err: %d", rv));
  1.2216 +#ifdef XP_WIN
  1.2217 +    if (MoveFileExW(tmpDir, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
  1.2218 +      LOG(("tmpDir will be removed on OS reboot: " LOG_S, tmpDir));
  1.2219 +    } else {
  1.2220 +      LOG(("Failed to schedule OS reboot removal of directory: " LOG_S,
  1.2221 +           tmpDir));
  1.2222 +    }
  1.2223 +#endif
  1.2224 +  }
  1.2225 +
  1.2226 +#ifdef XP_MACOSX
  1.2227 +  // On OS X, we need to copy anything else left over inside the Updated.app
  1.2228 +  // directory, and then we need to get rid of it as it's no longer going to
  1.2229 +  // be useful.
  1.2230 +  NS_tchar updatedAppDir[MAXPATHLEN];
  1.2231 +  NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
  1.2232 +               NS_T("%s/Updated.app"), installDir);
  1.2233 +  NS_tDIR *dir = NS_topendir(updatedAppDir);
  1.2234 +  if (dir) {
  1.2235 +    NS_tdirent *entry;
  1.2236 +    while ((entry = NS_treaddir(dir)) != 0) {
  1.2237 +      if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
  1.2238 +          NS_tstrcmp(entry->d_name, NS_T(".."))) {
  1.2239 +        NS_tchar childSrcPath[MAXPATHLEN];
  1.2240 +        NS_tsnprintf(childSrcPath, sizeof(childSrcPath)/sizeof(childSrcPath[0]),
  1.2241 +                     NS_T("%s/%s"), updatedAppDir, entry->d_name);
  1.2242 +        NS_tchar childDstPath[MAXPATHLEN];
  1.2243 +        NS_tsnprintf(childDstPath, sizeof(childDstPath)/sizeof(childDstPath[0]),
  1.2244 +                     NS_T("%s/%s"), installDir, entry->d_name);
  1.2245 +        ensure_remove_recursive(childDstPath);
  1.2246 +        rv = rename_file(childSrcPath, childDstPath, true);
  1.2247 +        if (rv) {
  1.2248 +          LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d",
  1.2249 +               childSrcPath, childDstPath, errno));
  1.2250 +        }
  1.2251 +      }
  1.2252 +    }
  1.2253 +
  1.2254 +    NS_tclosedir(dir);
  1.2255 +  } else {
  1.2256 +    LOG(("Updated.app dir can't be found: " LOG_S ", err: %d",
  1.2257 +         updatedAppDir, errno));
  1.2258 +  }
  1.2259 +  ensure_remove_recursive(updatedAppDir);
  1.2260 +
  1.2261 +  LOG(("Moving the precomplete file"));
  1.2262 +
  1.2263 +  // We also need to move the precomplete file too.
  1.2264 +  NS_tchar precompleteSource[MAXPATHLEN];
  1.2265 +  NS_tsnprintf(precompleteSource, sizeof(precompleteSource)/sizeof(precompleteSource[0]),
  1.2266 +               NS_T("%s/precomplete"), installDir);
  1.2267 +
  1.2268 +  NS_tchar precompleteTmp[MAXPATHLEN];
  1.2269 +  NS_tsnprintf(precompleteTmp, sizeof(precompleteTmp)/sizeof(precompleteTmp[0]),
  1.2270 +               NS_T("%s/precomplete.bak"), installDir);
  1.2271 +
  1.2272 +  NS_tchar precompleteNew[MAXPATHLEN];
  1.2273 +  NS_tsnprintf(precompleteNew, sizeof(precompleteNew)/sizeof(precompleteNew[0]),
  1.2274 +               NS_T("%s/Updated.app/precomplete"), installDir);
  1.2275 +
  1.2276 +  ensure_remove(precompleteTmp);
  1.2277 +  LOG(("Begin moving precompleteSrc to precompleteTmp"));
  1.2278 +  rv = rename_file(precompleteSource, precompleteTmp);
  1.2279 +  LOG(("Moved precompleteSrc to precompleteTmp, err: %d", rv));
  1.2280 +  LOG(("Begin moving precompleteNew to precompleteSrc"));
  1.2281 +  int rv2 = rename_file(precompleteNew, precompleteSource);
  1.2282 +  LOG(("Moved precompleteNew to precompleteSrc, err: %d", rv2));
  1.2283 +
  1.2284 +  // If new could not be moved to source, we only want to restore tmp to source
  1.2285 +  // if the first step succeeded.  Note that it is possible for the first
  1.2286 +  // rename to have failed as well, for example if the tmpFile exists and we
  1.2287 +  // race between the ensure_remove call and the first rename call, but there
  1.2288 +  // isn't too much that we can do about that, unfortunately.
  1.2289 +  if (!rv && rv2) {
  1.2290 +    LOG(("Begin trying to recover precompleteSrc"));
  1.2291 +    rv = rename_file(precompleteTmp, precompleteSource);
  1.2292 +    LOG(("Moved precompleteTmp to precompleteSrc, err: %d", rv));
  1.2293 +  }
  1.2294 +
  1.2295 +  LOG(("Finished moving the precomplete file"));
  1.2296 +#endif
  1.2297 +
  1.2298 +  gSucceeded = true;
  1.2299 +
  1.2300 +  return 0;
  1.2301 +}
  1.2302 +
  1.2303 +#ifdef XP_WIN
  1.2304 +static void 
  1.2305 +WaitForServiceFinishThread(void *param)
  1.2306 +{
  1.2307 +  // We wait at most 10 minutes, we already waited 5 seconds previously
  1.2308 +  // before deciding to show this UI.
  1.2309 +  WaitForServiceStop(SVC_NAME, 595);
  1.2310 +  LOG(("calling QuitProgressUI"));
  1.2311 +  QuitProgressUI();
  1.2312 +}
  1.2313 +#endif
  1.2314 +
  1.2315 +#ifdef MOZ_VERIFY_MAR_SIGNATURE
  1.2316 +/**
  1.2317 + * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
  1.2318 + *
  1.2319 + * @param path    The path to the ini file that is to be read
  1.2320 + * @param results A pointer to the location to store the read strings
  1.2321 + * @return OK on success
  1.2322 + */
  1.2323 +static int
  1.2324 +ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
  1.2325 +{
  1.2326 +  const unsigned int kNumStrings = 1;
  1.2327 +  const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
  1.2328 +  char updater_strings[kNumStrings][MAX_TEXT_LEN];
  1.2329 +
  1.2330 +  int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
  1.2331 +                           updater_strings, "Settings");
  1.2332 +
  1.2333 +  strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
  1.2334 +  results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
  1.2335 +
  1.2336 +  return result;
  1.2337 +}
  1.2338 +#endif
  1.2339 +
  1.2340 +static int
  1.2341 +GetUpdateFileName(NS_tchar *fileName, int maxChars)
  1.2342 +{
  1.2343 +#if defined(MOZ_WIDGET_GONK)  // If an update.link file exists, then it will contain the name
  1.2344 +  // of the update file (terminated by a newline).
  1.2345 +
  1.2346 +  NS_tchar linkFileName[MAXPATHLEN];
  1.2347 +  NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]),
  1.2348 +               NS_T("%s/update.link"), gSourcePath);
  1.2349 +  AutoFile linkFile = NS_tfopen(linkFileName, NS_T("rb"));
  1.2350 +  if (linkFile == nullptr) {
  1.2351 +    NS_tsnprintf(fileName, maxChars,
  1.2352 +                 NS_T("%s/update.mar"), gSourcePath);
  1.2353 +    return OK;
  1.2354 +  }
  1.2355 +
  1.2356 +  char dataFileName[MAXPATHLEN];
  1.2357 +  size_t bytesRead;
  1.2358 +
  1.2359 +  if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) {
  1.2360 +    *fileName = NS_T('\0');
  1.2361 +    return READ_ERROR;
  1.2362 +  }
  1.2363 +  if (dataFileName[bytesRead-1] == '\n') {
  1.2364 +    // Strip trailing newline (for \n and \r\n)
  1.2365 +    bytesRead--;
  1.2366 +  }
  1.2367 +  if (dataFileName[bytesRead-1] == '\r') {
  1.2368 +    // Strip trailing CR (for \r, \r\n)
  1.2369 +    bytesRead--;
  1.2370 +  }
  1.2371 +  dataFileName[bytesRead] = '\0';
  1.2372 +
  1.2373 +  strncpy(fileName, dataFileName, maxChars-1);
  1.2374 +  fileName[maxChars-1] = '\0';
  1.2375 +#else
  1.2376 +  // We currently only support update.link files under GONK
  1.2377 +  NS_tsnprintf(fileName, maxChars,
  1.2378 +               NS_T("%s/update.mar"), gSourcePath);
  1.2379 +#endif
  1.2380 +  return OK;
  1.2381 +}
  1.2382 +
  1.2383 +static void
  1.2384 +UpdateThreadFunc(void *param)
  1.2385 +{
  1.2386 +  // open ZIP archive and process...
  1.2387 +  int rv;
  1.2388 +  if (sReplaceRequest) {
  1.2389 +    rv = ProcessReplaceRequest();
  1.2390 +  } else {
  1.2391 +    NS_tchar dataFile[MAXPATHLEN];
  1.2392 +    rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0]));
  1.2393 +    if (rv == OK) {
  1.2394 +      rv = gArchiveReader.Open(dataFile);
  1.2395 +    }
  1.2396 +
  1.2397 +#ifdef MOZ_VERIFY_MAR_SIGNATURE
  1.2398 +    if (rv == OK) {
  1.2399 +      rv = gArchiveReader.VerifySignature();
  1.2400 +    }
  1.2401 +
  1.2402 +    if (rv == OK) {
  1.2403 +      NS_tchar installDir[MAXPATHLEN];
  1.2404 +      if (sStagedUpdate) {
  1.2405 +        if (!GetInstallationDir(installDir)) {
  1.2406 +          rv = NO_INSTALLDIR_ERROR;
  1.2407 +        }
  1.2408 +      } else {
  1.2409 +        NS_tstrcpy(installDir, gDestinationPath);
  1.2410 +      }
  1.2411 +      if (rv == OK) {
  1.2412 +        NS_tchar updateSettingsPath[MAX_TEXT_LEN];
  1.2413 +        NS_tsnprintf(updateSettingsPath,
  1.2414 +                     sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
  1.2415 +                     NS_T("%s/update-settings.ini"), installDir);
  1.2416 +        MARChannelStringTable MARStrings;
  1.2417 +        if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
  1.2418 +          // If we can't read from update-settings.ini then we shouldn't impose
  1.2419 +          // a MAR restriction.  Some installations won't even include this file.
  1.2420 +          MARStrings.MARChannelID[0] = '\0';
  1.2421 +        }
  1.2422 +
  1.2423 +        rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID,
  1.2424 +                                                     MOZ_APP_VERSION);
  1.2425 +      }
  1.2426 +    }
  1.2427 +#endif
  1.2428 +
  1.2429 +    if (rv == OK && sStagedUpdate && !sIsOSUpdate) {
  1.2430 +      rv = CopyInstallDirToDestDir();
  1.2431 +    }
  1.2432 +
  1.2433 +    if (rv == OK) {
  1.2434 +      rv = DoUpdate();
  1.2435 +      gArchiveReader.Close();
  1.2436 +      NS_tchar updatingDir[MAXPATHLEN];
  1.2437 +      NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
  1.2438 +                   NS_T("%s/updating"), gDestinationPath);
  1.2439 +      ensure_remove_recursive(updatingDir);
  1.2440 +    }
  1.2441 +  }
  1.2442 +
  1.2443 +  bool reportRealResults = true;
  1.2444 +  if (sReplaceRequest && rv && !getenv("MOZ_NO_REPLACE_FALLBACK")) {
  1.2445 +    // When attempting to replace the application, we should fall back
  1.2446 +    // to non-staged updates in case of a failure.  We do this by
  1.2447 +    // setting the status to pending, exiting the updater, and
  1.2448 +    // launching the callback application.  The callback application's
  1.2449 +    // startup path will see the pending status, and will start the
  1.2450 +    // updater application again in order to apply the update without
  1.2451 +    // staging.
  1.2452 +    // The MOZ_NO_REPLACE_FALLBACK environment variable is used to
  1.2453 +    // bypass this fallback, and is used in the updater tests.
  1.2454 +    // The only special thing which we should do here is to remove the
  1.2455 +    // staged directory as it won't be useful any more.
  1.2456 +    NS_tchar installDir[MAXPATHLEN];
  1.2457 +    if (GetInstallationDir(installDir)) {
  1.2458 +      NS_tchar stageDir[MAXPATHLEN];
  1.2459 +      NS_tsnprintf(stageDir, sizeof(stageDir)/sizeof(stageDir[0]),
  1.2460 +#ifdef XP_MACOSX
  1.2461 +                   NS_T("%s/Updated.app"),
  1.2462 +#else
  1.2463 +                   NS_T("%s/updated"),
  1.2464 +#endif
  1.2465 +                   installDir);
  1.2466 +
  1.2467 +      ensure_remove_recursive(stageDir);
  1.2468 +      WriteStatusFile(sUsingService ? "pending-service" : "pending");
  1.2469 +      char processUpdates[] = "MOZ_PROCESS_UPDATES=";
  1.2470 +      putenv(processUpdates); // We need to use -process-updates again in the tests
  1.2471 +      reportRealResults = false; // pretend success
  1.2472 +    }
  1.2473 +  }
  1.2474 +
  1.2475 +  if (reportRealResults) {
  1.2476 +    if (rv) {
  1.2477 +      LOG(("failed: %d", rv));
  1.2478 +    }
  1.2479 +    else {
  1.2480 +#ifdef XP_MACOSX
  1.2481 +      // If the update was successful we need to update the timestamp
  1.2482 +      // on the top-level Mac OS X bundle directory so that Mac OS X's
  1.2483 +      // Launch Services picks up any major changes. Here we assume that
  1.2484 +      // the current working directory is the top-level bundle directory.
  1.2485 +      char* cwd = getcwd(nullptr, 0);
  1.2486 +      if (cwd) {
  1.2487 +        if (utimes(cwd, nullptr) != 0) {
  1.2488 +          LOG(("Couldn't set access/modification time on application bundle."));
  1.2489 +        }
  1.2490 +        free(cwd);
  1.2491 +      }
  1.2492 +      else {
  1.2493 +        LOG(("Couldn't get current working directory for setting "
  1.2494 +             "access/modification time on application bundle."));
  1.2495 +      }
  1.2496 +#endif
  1.2497 +
  1.2498 +      LOG(("succeeded"));
  1.2499 +    }
  1.2500 +    WriteStatusFile(rv);
  1.2501 +  }
  1.2502 +
  1.2503 +  LOG(("calling QuitProgressUI"));
  1.2504 +  QuitProgressUI();
  1.2505 +}
  1.2506 +
  1.2507 +int NS_main(int argc, NS_tchar **argv)
  1.2508 +{
  1.2509 +#if defined(MOZ_WIDGET_GONK)
  1.2510 +  if (getenv("LD_PRELOAD")) {
  1.2511 +    // If the updater is launched with LD_PRELOAD set, then we wind up
  1.2512 +    // preloading libmozglue.so. Under some circumstances, this can cause
  1.2513 +    // the remount of /system to fail when going from rw to ro, so if we
  1.2514 +    // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it.
  1.2515 +    // This will cause the offending preloaded library to be closed.
  1.2516 +    //
  1.2517 +    // For a variety of reasons, this is really hard to do in a safe manner
  1.2518 +    // in the parent process, so we do it here.
  1.2519 +    unsetenv("LD_PRELOAD");
  1.2520 +    execv(argv[0], argv);
  1.2521 +    __android_log_print(ANDROID_LOG_INFO, "updater",
  1.2522 +                        "execve failed: errno: %d. Exiting...", errno);
  1.2523 +    _exit(1);
  1.2524 +  }
  1.2525 +#endif
  1.2526 +  InitProgressUI(&argc, &argv);
  1.2527 +
  1.2528 +  // To process an update the updater command line must at a minimum have the
  1.2529 +  // directory path containing the updater.mar file to process as the first argument
  1.2530 +  // and the directory to apply the update to as the second argument. When the
  1.2531 +  // updater is launched by another process the PID of the parent process should be
  1.2532 +  // provided in the optional third argument and the updater will wait on the parent
  1.2533 +  // process to exit if the value is non-zero and the process is present. This is
  1.2534 +  // necessary due to not being able to update files that are in use on Windows. The
  1.2535 +  // optional fourth argument is the callback's working directory and the optional
  1.2536 +  // fifth argument is the callback path. The callback is the application to launch
  1.2537 +  // after  updating and it will be launched when these arguments are provided
  1.2538 +  // whether the update was successful or not. All remaining arguments are optional
  1.2539 +  // and are passed to the callback when it is launched.
  1.2540 +  if (argc < 3) {
  1.2541 +    fprintf(stderr, "Usage: updater update-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
  1.2542 +    return 1;
  1.2543 +  }
  1.2544 +
  1.2545 +  // The directory containing the update information.
  1.2546 +  gSourcePath = argv[1];
  1.2547 +  // The directory we're going to update to.
  1.2548 +  // We copy this string because we need to remove trailing slashes.  The C++
  1.2549 +  // standard says that it's always safe to write to strings pointed to by argv
  1.2550 +  // elements, but I don't necessarily believe it.
  1.2551 +  NS_tstrncpy(gDestinationPath, argv[2], MAXPATHLEN);
  1.2552 +  gDestinationPath[MAXPATHLEN - 1] = NS_T('\0');
  1.2553 +  NS_tchar *slash = NS_tstrrchr(gDestinationPath, NS_SLASH);
  1.2554 +  if (slash && !slash[1]) {
  1.2555 +    *slash = NS_T('\0');
  1.2556 +  }
  1.2557 +
  1.2558 +#ifdef XP_WIN
  1.2559 +  bool useService = false;
  1.2560 +  bool testOnlyFallbackKeyExists = false;
  1.2561 +  bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr;
  1.2562 +  putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
  1.2563 +
  1.2564 +  // We never want the service to be used unless we build with
  1.2565 +  // the maintenance service.
  1.2566 +#ifdef MOZ_MAINTENANCE_SERVICE
  1.2567 +  useService = IsUpdateStatusPendingService();
  1.2568 +  // Our tests run with a different apply directory for each test.
  1.2569 +  // We use this registry key on our test slaves to store the 
  1.2570 +  // allowed name/issuers.
  1.2571 +  testOnlyFallbackKeyExists = DoesFallbackKeyExist();
  1.2572 +#endif
  1.2573 +
  1.2574 +  // Remove everything except close window from the context menu
  1.2575 +  {
  1.2576 +    HKEY hkApp;
  1.2577 +    RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
  1.2578 +                    0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
  1.2579 +                    &hkApp, nullptr);
  1.2580 +    RegCloseKey(hkApp);
  1.2581 +    if (RegCreateKeyExW(HKEY_CURRENT_USER,
  1.2582 +                        L"Software\\Classes\\Applications\\updater.exe",
  1.2583 +                        0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
  1.2584 +                        &hkApp, nullptr) == ERROR_SUCCESS) {
  1.2585 +      RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
  1.2586 +      RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
  1.2587 +      RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
  1.2588 +      RegCloseKey(hkApp);
  1.2589 +    }
  1.2590 +  }
  1.2591 +#endif
  1.2592 +
  1.2593 +  // If there is a PID specified and it is not '0' then wait for the process to exit.
  1.2594 +#ifdef XP_WIN
  1.2595 +  __int64 pid = 0;
  1.2596 +#else
  1.2597 +  int pid = 0;
  1.2598 +#endif
  1.2599 +  if (argc > 3) {
  1.2600 +#ifdef XP_WIN
  1.2601 +    pid = _wtoi64(argv[3]);
  1.2602 +#else
  1.2603 +    pid = atoi(argv[3]);
  1.2604 +#endif
  1.2605 +    if (pid == -1) {
  1.2606 +      // This is a signal from the parent process that the updater should stage
  1.2607 +      // the update.
  1.2608 +      sStagedUpdate = true;
  1.2609 +    } else if (NS_tstrstr(argv[3], NS_T("/replace"))) {
  1.2610 +      // We're processing a request to replace the application with a staged
  1.2611 +      // update.
  1.2612 +      sReplaceRequest = true;
  1.2613 +    }
  1.2614 +  }
  1.2615 +
  1.2616 +  if (getenv("MOZ_OS_UPDATE")) {
  1.2617 +    sIsOSUpdate = true;
  1.2618 +    putenv(const_cast<char*>("MOZ_OS_UPDATE="));
  1.2619 +  }
  1.2620 +
  1.2621 +  if (sReplaceRequest) {
  1.2622 +    // If we're attempting to replace the application, try to append to the
  1.2623 +    // log generated when staging the staged update.
  1.2624 +    NS_tchar installDir[MAXPATHLEN];
  1.2625 +    if (!GetInstallationDir(installDir)) {
  1.2626 +      fprintf(stderr, "Could not get the installation directory\n");
  1.2627 +      return 1;
  1.2628 +    }
  1.2629 +
  1.2630 +#ifdef XP_WIN
  1.2631 +    NS_tchar* logDir = gSourcePath;
  1.2632 +#else
  1.2633 +    NS_tchar logDir[MAXPATHLEN];
  1.2634 +    NS_tsnprintf(logDir, sizeof(logDir)/sizeof(logDir[0]),
  1.2635 +#ifdef XP_MACOSX
  1.2636 +                 NS_T("%s/Updated.app/Contents/MacOS/updates"),
  1.2637 +#else
  1.2638 +                 NS_T("%s/updated/updates"),
  1.2639 +#endif
  1.2640 +                 installDir);
  1.2641 +#endif
  1.2642 +
  1.2643 +    LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log"));
  1.2644 +  } else {
  1.2645 +    LogInit(gSourcePath, NS_T("update.log"));
  1.2646 +  }
  1.2647 +
  1.2648 +  if (!WriteStatusFile("applying")) {
  1.2649 +    LOG(("failed setting status to 'applying'"));
  1.2650 +    return 1;
  1.2651 +  }
  1.2652 +
  1.2653 +  if (sStagedUpdate) {
  1.2654 +    LOG(("Performing a staged update"));
  1.2655 +  } else if (sReplaceRequest) {
  1.2656 +    LOG(("Performing a replace request"));
  1.2657 +  }
  1.2658 +
  1.2659 +#ifdef MOZ_WIDGET_GONK
  1.2660 +  const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
  1.2661 +  if (prioEnv) {
  1.2662 +    int32_t prioVal;
  1.2663 +    int32_t oomScoreAdj;
  1.2664 +    int32_t ioprioClass;
  1.2665 +    int32_t ioprioLevel;
  1.2666 +    if (sscanf(prioEnv, "%d/%d/%d/%d",
  1.2667 +               &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) {
  1.2668 +      LOG(("MOZ_UPDATER_PRIO=%s", prioEnv));
  1.2669 +      if (setpriority(PRIO_PROCESS, 0, prioVal)) {
  1.2670 +        LOG(("setpriority(%d) failed, errno = %d", prioVal, errno));
  1.2671 +      }
  1.2672 +      if (ioprio_set(IOPRIO_WHO_PROCESS, 0,
  1.2673 +                     IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) {
  1.2674 +        LOG(("ioprio_set(%d,%d) failed: errno = %d",
  1.2675 +             ioprioClass, ioprioLevel, errno));
  1.2676 +      }
  1.2677 +      FILE *fs = fopen("/proc/self/oom_score_adj", "w");
  1.2678 +      if (fs) {
  1.2679 +        fprintf(fs, "%d", oomScoreAdj);
  1.2680 +        fclose(fs);
  1.2681 +      } else {
  1.2682 +        LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d", errno));
  1.2683 +      }
  1.2684 +    }
  1.2685 +  }
  1.2686 +#endif
  1.2687 +
  1.2688 +#ifdef XP_WIN
  1.2689 +  if (pid > 0) {
  1.2690 +    HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
  1.2691 +    // May return nullptr if the parent process has already gone away.
  1.2692 +    // Otherwise, wait for the parent process to exit before starting the
  1.2693 +    // update.
  1.2694 +    if (parent) {
  1.2695 +      bool updateFromMetro = false;
  1.2696 +#ifdef MOZ_METRO
  1.2697 +      updateFromMetro = IsUpdateFromMetro(argc, argv);
  1.2698 +#endif
  1.2699 +      DWORD waitTime = updateFromMetro ?
  1.2700 +                       IMMERSIVE_PARENT_WAIT : PARENT_WAIT;
  1.2701 +      DWORD result = WaitForSingleObject(parent, waitTime);
  1.2702 +      CloseHandle(parent);
  1.2703 +      if (result != WAIT_OBJECT_0 && !updateFromMetro)
  1.2704 +        return 1;
  1.2705 +    }
  1.2706 +  }
  1.2707 +#else
  1.2708 +  if (pid > 0)
  1.2709 +    waitpid(pid, nullptr, 0);
  1.2710 +#endif
  1.2711 +
  1.2712 +  if (sReplaceRequest) {
  1.2713 +#ifdef XP_WIN
  1.2714 +    // On Windows, the current working directory of the process should be changed
  1.2715 +    // so that it's not locked.
  1.2716 +    NS_tchar tmpDir[MAXPATHLEN];
  1.2717 +    if (GetTempPathW(MAXPATHLEN, tmpDir)) {
  1.2718 +      NS_tchdir(tmpDir);
  1.2719 +    }
  1.2720 +#endif
  1.2721 +  }
  1.2722 +
  1.2723 +  // The callback is the remaining arguments starting at callbackIndex.
  1.2724 +  // The argument specified by callbackIndex is the callback executable and the
  1.2725 +  // argument prior to callbackIndex is the working directory.
  1.2726 +  const int callbackIndex = 5;
  1.2727 +
  1.2728 +#if defined(XP_WIN)
  1.2729 +  sUsingService = getenv("MOZ_USING_SERVICE") != nullptr;
  1.2730 +  putenv(const_cast<char*>("MOZ_USING_SERVICE="));
  1.2731 +  // lastFallbackError keeps track of the last error for the service not being 
  1.2732 +  // used, in case of an error when fallback is not enabled we write the 
  1.2733 +  // error to the update.status file. 
  1.2734 +  // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
  1.2735 +  // we will instead fallback to not using the service and display a UAC prompt.
  1.2736 +  int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
  1.2737 +
  1.2738 +  // Launch a second instance of the updater with the runas verb on Windows
  1.2739 +  // when write access is denied to the installation directory.
  1.2740 +  HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
  1.2741 +  NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
  1.2742 +  if (!sUsingService &&
  1.2743 +      (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
  1.2744 +    NS_tchar updateLockFilePath[MAXPATHLEN];
  1.2745 +    if (sStagedUpdate) {
  1.2746 +      // When staging an update, the lock file is:
  1.2747 +      // $INSTALLDIR\updated.update_in_progress.lock
  1.2748 +      NS_tsnprintf(updateLockFilePath,
  1.2749 +                   sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  1.2750 +                   NS_T("%s.update_in_progress.lock"), gDestinationPath);
  1.2751 +    } else if (sReplaceRequest) {
  1.2752 +      // When processing a replace request, the lock file is:
  1.2753 +      // $INSTALLDIR\..\moz_update_in_progress.lock
  1.2754 +      NS_tchar installDir[MAXPATHLEN];
  1.2755 +      if (!GetInstallationDir(installDir)) {
  1.2756 +        return 1;
  1.2757 +      }
  1.2758 +      NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  1.2759 +      *slash = NS_T('\0');
  1.2760 +      NS_tsnprintf(updateLockFilePath,
  1.2761 +                   sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  1.2762 +                   NS_T("%s\\moz_update_in_progress.lock"), installDir);
  1.2763 +    } else {
  1.2764 +      // In the non-staging update case, the lock file is:
  1.2765 +      // $INSTALLDIR\$APPNAME.exe.update_in_progress.lock
  1.2766 +      NS_tsnprintf(updateLockFilePath,
  1.2767 +                   sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  1.2768 +                   NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
  1.2769 +    }
  1.2770 +
  1.2771 +    // The update_in_progress.lock file should only exist during an update. In
  1.2772 +    // case it exists attempt to remove it and exit if that fails to prevent
  1.2773 +    // simultaneous updates occurring.
  1.2774 +    if (!_waccess(updateLockFilePath, F_OK) &&
  1.2775 +        NS_tremove(updateLockFilePath) != 0) {
  1.2776 +      // Try to fall back to the old way of doing updates if a staged
  1.2777 +      // update fails.
  1.2778 +      if (sStagedUpdate || sReplaceRequest) {
  1.2779 +        // Note that this could fail, but if it does, there isn't too much we
  1.2780 +        // can do in order to recover anyways.
  1.2781 +        WriteStatusFile("pending");
  1.2782 +      }
  1.2783 +      LOG(("Update already in progress! Exiting"));
  1.2784 +      return 1;
  1.2785 +    }
  1.2786 +
  1.2787 +    updateLockFileHandle = CreateFileW(updateLockFilePath,
  1.2788 +                                       GENERIC_READ | GENERIC_WRITE,
  1.2789 +                                       0,
  1.2790 +                                       nullptr,
  1.2791 +                                       OPEN_ALWAYS,
  1.2792 +                                       FILE_FLAG_DELETE_ON_CLOSE,
  1.2793 +                                       nullptr);
  1.2794 +
  1.2795 +    NS_tsnprintf(elevatedLockFilePath,
  1.2796 +                 sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
  1.2797 +                 NS_T("%s/update_elevated.lock"), gSourcePath);
  1.2798 +
  1.2799 +
  1.2800 +    // Even if a file has no sharing access, you can still get its attributes
  1.2801 +    bool startedFromUnelevatedUpdater =
  1.2802 +      GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
  1.2803 +    
  1.2804 +    // If we're running from the service, then we were started with the same
  1.2805 +    // token as the service so the permissions are already dropped.  If we're
  1.2806 +    // running from an elevated updater that was started from an unelevated 
  1.2807 +    // updater, then we drop the permissions here. We do not drop the 
  1.2808 +    // permissions on the originally called updater because we use its token
  1.2809 +    // to start the callback application.
  1.2810 +    if(startedFromUnelevatedUpdater) {
  1.2811 +      // Disable every privilege we don't need. Processes started using
  1.2812 +      // CreateProcess will use the same token as this process.
  1.2813 +      UACHelper::DisablePrivileges(nullptr);
  1.2814 +    }
  1.2815 +
  1.2816 +    if (updateLockFileHandle == INVALID_HANDLE_VALUE || 
  1.2817 +        (useService && testOnlyFallbackKeyExists && noServiceFallback)) {
  1.2818 +      if (!_waccess(elevatedLockFilePath, F_OK) &&
  1.2819 +          NS_tremove(elevatedLockFilePath) != 0) {
  1.2820 +        fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
  1.2821 +        return 1;
  1.2822 +      }
  1.2823 +
  1.2824 +      HANDLE elevatedFileHandle;
  1.2825 +      elevatedFileHandle = CreateFileW(elevatedLockFilePath,
  1.2826 +                                       GENERIC_READ | GENERIC_WRITE,
  1.2827 +                                       0,
  1.2828 +                                       nullptr,
  1.2829 +                                       OPEN_ALWAYS,
  1.2830 +                                       FILE_FLAG_DELETE_ON_CLOSE,
  1.2831 +                                       nullptr);
  1.2832 +
  1.2833 +      if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
  1.2834 +        LOG(("Unable to create elevated lock file! Exiting"));
  1.2835 +        return 1;
  1.2836 +      }
  1.2837 +
  1.2838 +      wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
  1.2839 +      if (!cmdLine) {
  1.2840 +        CloseHandle(elevatedFileHandle);
  1.2841 +        return 1;
  1.2842 +      }
  1.2843 +
  1.2844 +      NS_tchar installDir[MAXPATHLEN];
  1.2845 +      if (!GetInstallationDir(installDir)) {
  1.2846 +        return 1;
  1.2847 +      }
  1.2848 +
  1.2849 +      // Make sure the path to the updater to use for the update is on local.
  1.2850 +      // We do this check to make sure that file locking is available for
  1.2851 +      // race condition security checks.
  1.2852 +      if (useService) {
  1.2853 +        BOOL isLocal = FALSE;
  1.2854 +        useService = IsLocalFile(argv[0], isLocal) && isLocal;
  1.2855 +      }
  1.2856 +
  1.2857 +      // If we have unprompted elevation we should NOT use the service
  1.2858 +      // for the update. Service updates happen with the SYSTEM account
  1.2859 +      // which has more privs than we need to update with.
  1.2860 +      // Windows 8 provides a user interface so users can configure this
  1.2861 +      // behavior and it can be configured in the registry in all Windows
  1.2862 +      // versions that support UAC.
  1.2863 +      if (useService) {
  1.2864 +        BOOL unpromptedElevation;
  1.2865 +        if (IsUnpromptedElevation(unpromptedElevation)) {
  1.2866 +          useService = !unpromptedElevation;
  1.2867 +        }
  1.2868 +      }
  1.2869 +
  1.2870 +      // Make sure the service registry entries for the instsallation path
  1.2871 +      // are available.  If not don't use the service.
  1.2872 +      if (useService) {
  1.2873 +        WCHAR maintenanceServiceKey[MAX_PATH + 1];
  1.2874 +        if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) {
  1.2875 +          HKEY baseKey;
  1.2876 +          if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, 
  1.2877 +                            maintenanceServiceKey, 0, 
  1.2878 +                            KEY_READ | KEY_WOW64_64KEY, 
  1.2879 +                            &baseKey) == ERROR_SUCCESS) {
  1.2880 +            RegCloseKey(baseKey);
  1.2881 +          } else {
  1.2882 +            useService = testOnlyFallbackKeyExists;
  1.2883 +            if (!useService) {
  1.2884 +              lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
  1.2885 +            }
  1.2886 +          }
  1.2887 +        } else {
  1.2888 +          useService = false;
  1.2889 +          lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
  1.2890 +        }
  1.2891 +      }
  1.2892 +
  1.2893 +      // Originally we used to write "pending" to update.status before
  1.2894 +      // launching the service command.  This is no longer needed now
  1.2895 +      // since the service command is launched from updater.exe.  If anything
  1.2896 +      // fails in between, we can fall back to using the normal update process
  1.2897 +      // on our own.
  1.2898 +
  1.2899 +      // If we still want to use the service try to launch the service 
  1.2900 +      // comamnd for the update.
  1.2901 +      if (useService) {
  1.2902 +        // If the update couldn't be started, then set useService to false so
  1.2903 +        // we do the update the old way.
  1.2904 +        DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
  1.2905 +        useService = (ret == ERROR_SUCCESS);
  1.2906 +        // If the command was launched then wait for the service to be done.
  1.2907 +        if (useService) {
  1.2908 +          bool showProgressUI = false;
  1.2909 +          // Never show the progress UI when staging updates.
  1.2910 +          if (!sStagedUpdate) {
  1.2911 +            // We need to call this separately instead of allowing ShowProgressUI
  1.2912 +            // to initialize the strings because the service will move the
  1.2913 +            // ini file out of the way when running updater.
  1.2914 +            showProgressUI = !InitProgressUIStrings();
  1.2915 +          }
  1.2916 +
  1.2917 +          // Wait for the service to stop for 5 seconds.  If the service
  1.2918 +          // has still not stopped then show an indeterminate progress bar.
  1.2919 +          DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
  1.2920 +          if (lastState != SERVICE_STOPPED) {
  1.2921 +            Thread t1;
  1.2922 +            if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 && 
  1.2923 +                showProgressUI) {
  1.2924 +              ShowProgressUI(true, false);
  1.2925 +            }
  1.2926 +            t1.Join();
  1.2927 +          }
  1.2928 +
  1.2929 +          lastState = WaitForServiceStop(SVC_NAME, 1);
  1.2930 +          if (lastState != SERVICE_STOPPED) {
  1.2931 +            // If the service doesn't stop after 10 minutes there is
  1.2932 +            // something seriously wrong.
  1.2933 +            lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
  1.2934 +            useService = false;
  1.2935 +          }
  1.2936 +        } else {
  1.2937 +          lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
  1.2938 +        }
  1.2939 +      }
  1.2940 +
  1.2941 +      // If the service can't be used when staging and update, make sure that
  1.2942 +      // the UAC prompt is not shown! In this case, just set the status to
  1.2943 +      // pending and the update will be applied during the next startup.
  1.2944 +      if (!useService && sStagedUpdate) {
  1.2945 +        if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
  1.2946 +          CloseHandle(updateLockFileHandle);
  1.2947 +        }
  1.2948 +        WriteStatusPending(gSourcePath);
  1.2949 +        return 0;
  1.2950 +      }
  1.2951 +
  1.2952 +      // If we started the service command, and it finished, check the
  1.2953 +      // update.status file to make sure it succeeded, and if it did
  1.2954 +      // we need to manually start the PostUpdate process from the
  1.2955 +      // current user's session of this unelevated updater.exe the
  1.2956 +      // current process is running as.
  1.2957 +      // Note that we don't need to do this if we're just staging the update,
  1.2958 +      // as the PostUpdate step runs when performing the replacing in that case.
  1.2959 +      if (useService && !sStagedUpdate) {
  1.2960 +        bool updateStatusSucceeded = false;
  1.2961 +        if (IsUpdateStatusSucceeded(updateStatusSucceeded) && 
  1.2962 +            updateStatusSucceeded) {
  1.2963 +          if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) {
  1.2964 +            fprintf(stderr, "The post update process which runs as the user"
  1.2965 +                    " for service update could not be launched.");
  1.2966 +          }
  1.2967 +        }
  1.2968 +      }
  1.2969 +
  1.2970 +      // If we didn't want to use the service at all, or if an update was 
  1.2971 +      // already happening, or launching the service command failed, then 
  1.2972 +      // launch the elevated updater.exe as we do without the service.
  1.2973 +      // We don't launch the elevated updater in the case that we did have
  1.2974 +      // write access all along because in that case the only reason we're
  1.2975 +      // using the service is because we are testing. 
  1.2976 +      if (!useService && !noServiceFallback && 
  1.2977 +          updateLockFileHandle == INVALID_HANDLE_VALUE) {
  1.2978 +        SHELLEXECUTEINFO sinfo;
  1.2979 +        memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
  1.2980 +        sinfo.cbSize       = sizeof(SHELLEXECUTEINFO);
  1.2981 +        sinfo.fMask        = SEE_MASK_FLAG_NO_UI |
  1.2982 +                             SEE_MASK_FLAG_DDEWAIT |
  1.2983 +                             SEE_MASK_NOCLOSEPROCESS;
  1.2984 +        sinfo.hwnd         = nullptr;
  1.2985 +        sinfo.lpFile       = argv[0];
  1.2986 +        sinfo.lpParameters = cmdLine;
  1.2987 +        sinfo.lpVerb       = L"runas";
  1.2988 +        sinfo.nShow        = SW_SHOWNORMAL;
  1.2989 +
  1.2990 +        bool result = ShellExecuteEx(&sinfo);
  1.2991 +        free(cmdLine);
  1.2992 +
  1.2993 +        if (result) {
  1.2994 +          WaitForSingleObject(sinfo.hProcess, INFINITE);
  1.2995 +          CloseHandle(sinfo.hProcess);
  1.2996 +        } else {
  1.2997 +          WriteStatusFile(ELEVATION_CANCELED);
  1.2998 +        }
  1.2999 +      }
  1.3000 +
  1.3001 +      if (argc > callbackIndex) {
  1.3002 +        LaunchCallbackApp(argv[4], argc - callbackIndex,
  1.3003 +                          argv + callbackIndex, sUsingService);
  1.3004 +      }
  1.3005 +
  1.3006 +      CloseHandle(elevatedFileHandle);
  1.3007 +
  1.3008 +      if (!useService && !noServiceFallback &&
  1.3009 +          INVALID_HANDLE_VALUE == updateLockFileHandle) {
  1.3010 +        // We didn't use the service and we did run the elevated updater.exe.
  1.3011 +        // The elevated updater.exe is responsible for writing out the
  1.3012 +        // update.status file.
  1.3013 +        return 0;
  1.3014 +      } else if(useService) {
  1.3015 +        // The service command was launched.  The service is responsible for 
  1.3016 +        // writing out the update.status file.
  1.3017 +        if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
  1.3018 +          CloseHandle(updateLockFileHandle);
  1.3019 +        }
  1.3020 +        return 0;
  1.3021 +      } else {
  1.3022 +        // Otherwise the service command was not launched at all.
  1.3023 +        // We are only reaching this code path because we had write access
  1.3024 +        // all along to the directory and a fallback key existed, and we
  1.3025 +        // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
  1.3026 +        // We only currently use this env var from XPCShell tests.
  1.3027 +        CloseHandle(updateLockFileHandle);
  1.3028 +        WriteStatusFile(lastFallbackError);
  1.3029 +        return 0;
  1.3030 +      }
  1.3031 +    }
  1.3032 +  }
  1.3033 +#endif
  1.3034 +
  1.3035 +#if defined(MOZ_WIDGET_GONK)
  1.3036 +  // In gonk, the master b2g process sets its umask to 0027 because
  1.3037 +  // there's no reason for it to ever create world-readable files.
  1.3038 +  // The updater binary, however, needs to do this, and it inherits
  1.3039 +  // the master process's cautious umask.  So we drop down a bit here.
  1.3040 +  umask(0022);
  1.3041 +
  1.3042 +  // Remount the /system partition as read-write for gonk. The destructor will
  1.3043 +  // remount /system as read-only. We add an extra level of scope here to avoid
  1.3044 +  // calling LogFinish() before the GonkAutoMounter destructor has a chance
  1.3045 +  // to be called
  1.3046 +  {
  1.3047 +    GonkAutoMounter mounter;
  1.3048 +    if (mounter.GetAccess() != MountAccess::ReadWrite) {
  1.3049 +      WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR);
  1.3050 +      return 1;
  1.3051 +    }
  1.3052 +#endif
  1.3053 +
  1.3054 +  if (sStagedUpdate) {
  1.3055 +    // When staging updates, blow away the old installation directory and create
  1.3056 +    // it from scratch.
  1.3057 +    ensure_remove_recursive(gDestinationPath);
  1.3058 +  }
  1.3059 +  if (!sReplaceRequest) {
  1.3060 +    // Change current directory to the directory where we need to apply the update.
  1.3061 +    if (NS_tchdir(gDestinationPath) != 0) {
  1.3062 +      // Try to create the destination directory if it doesn't exist
  1.3063 +      int rv = NS_tmkdir(gDestinationPath, 0755);
  1.3064 +      if (rv == OK && errno != EEXIST) {
  1.3065 +        // Try changing the current directory again
  1.3066 +        if (NS_tchdir(gDestinationPath) != 0) {
  1.3067 +          // OK, time to give up!
  1.3068 +          return 1;
  1.3069 +        }
  1.3070 +      } else {
  1.3071 +        // Failed to create the directory, bail out
  1.3072 +        return 1;
  1.3073 +      }
  1.3074 +    }
  1.3075 +  }
  1.3076 +
  1.3077 +  LOG(("SOURCE DIRECTORY " LOG_S, gSourcePath));
  1.3078 +  LOG(("DESTINATION DIRECTORY " LOG_S, gDestinationPath));
  1.3079 +
  1.3080 +#ifdef XP_WIN
  1.3081 +  // For replace requests, we don't need to do any real updates, so this is not
  1.3082 +  // necessary.
  1.3083 +  if (!sReplaceRequest) {
  1.3084 +    // Allocate enough space for the length of the path an optional additional
  1.3085 +    // trailing slash and null termination.
  1.3086 +    NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gDestinationPath) + 2) * sizeof(NS_tchar));
  1.3087 +    if (!destpath)
  1.3088 +      return 1;
  1.3089 +
  1.3090 +    NS_tchar *c = destpath;
  1.3091 +    NS_tstrcpy(c, gDestinationPath);
  1.3092 +    c += NS_tstrlen(gDestinationPath);
  1.3093 +    if (gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('/') &&
  1.3094 +        gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('\\')) {
  1.3095 +      NS_tstrcat(c, NS_T("/"));
  1.3096 +      c += NS_tstrlen(NS_T("/"));
  1.3097 +    }
  1.3098 +    *c = NS_T('\0');
  1.3099 +    c++;
  1.3100 +
  1.3101 +    gDestPath = destpath;
  1.3102 +  }
  1.3103 +
  1.3104 +  NS_tchar applyDirLongPath[MAXPATHLEN];
  1.3105 +  if (!GetLongPathNameW(gDestinationPath, applyDirLongPath,
  1.3106 +                        sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) {
  1.3107 +    LOG(("NS_main: unable to find apply to dir: " LOG_S, gDestinationPath));
  1.3108 +    LogFinish();
  1.3109 +    WriteStatusFile(WRITE_ERROR);
  1.3110 +    EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  1.3111 +    if (argc > callbackIndex) {
  1.3112 +      LaunchCallbackApp(argv[4], argc - callbackIndex,
  1.3113 +                        argv + callbackIndex, sUsingService);
  1.3114 +    }
  1.3115 +    return 1;
  1.3116 +  }
  1.3117 +
  1.3118 +  HANDLE callbackFile = INVALID_HANDLE_VALUE;
  1.3119 +  if (argc > callbackIndex) {
  1.3120 +    // If the callback executable is specified it must exist for a successful
  1.3121 +    // update.  It is important we null out the whole buffer here because later
  1.3122 +    // we make the assumption that the callback application is inside the
  1.3123 +    // apply-to dir.  If we don't have a fully null'ed out buffer it can lead
  1.3124 +    // to stack corruption which causes crashes and other problems.
  1.3125 +    NS_tchar callbackLongPath[MAXPATHLEN];
  1.3126 +    ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
  1.3127 +    NS_tchar *targetPath = argv[callbackIndex];
  1.3128 +    NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
  1.3129 +    size_t bufferLeft = MAXPATHLEN * 2;
  1.3130 +    if (sReplaceRequest) {
  1.3131 +      // In case of replace requests, we should look for the callback file in
  1.3132 +      // the destination directory.
  1.3133 +      size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
  1.3134 +                                                    gDestinationPath,
  1.3135 +                                                    nullptr);
  1.3136 +      NS_tchar *p = buffer;
  1.3137 +      NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
  1.3138 +      p += commonPrefixLength;
  1.3139 +      bufferLeft -= commonPrefixLength;
  1.3140 +      NS_tstrncpy(p, gDestinationPath + commonPrefixLength, bufferLeft);
  1.3141 +
  1.3142 +      size_t len = NS_tstrlen(gDestinationPath + commonPrefixLength);
  1.3143 +      p += len;
  1.3144 +      bufferLeft -= len;
  1.3145 +      *p = NS_T('\\');
  1.3146 +      ++p;
  1.3147 +      bufferLeft--;
  1.3148 +      *p = NS_T('\0');
  1.3149 +      NS_tchar installDir[MAXPATHLEN];
  1.3150 +      if (!GetInstallationDir(installDir))
  1.3151 +        return 1;
  1.3152 +      size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
  1.3153 +                                                      installDir,
  1.3154 +                                                      nullptr);
  1.3155 +      NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, commonPrefixLength), bufferLeft);
  1.3156 +      targetPath = buffer;
  1.3157 +    }
  1.3158 +    if (!GetLongPathNameW(targetPath, callbackLongPath,
  1.3159 +                          sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) {
  1.3160 +      LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
  1.3161 +      LogFinish();
  1.3162 +      WriteStatusFile(WRITE_ERROR);
  1.3163 +      EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  1.3164 +      if (argc > callbackIndex) {
  1.3165 +        LaunchCallbackApp(argv[4], 
  1.3166 +                          argc - callbackIndex, 
  1.3167 +                          argv + callbackIndex, 
  1.3168 +                          sUsingService);
  1.3169 +      }
  1.3170 +      return 1;
  1.3171 +    }
  1.3172 +
  1.3173 +    // Doing this is only necessary when we're actually applying a patch.
  1.3174 +    if (!sReplaceRequest) {
  1.3175 +      int len = NS_tstrlen(applyDirLongPath);
  1.3176 +      NS_tchar *s = callbackLongPath;
  1.3177 +      NS_tchar *d = gCallbackRelPath;
  1.3178 +      // advance to the apply to directory and advance past the trailing backslash
  1.3179 +      // if present.
  1.3180 +      s += len;
  1.3181 +      if (*s == NS_T('\\'))
  1.3182 +        ++s;
  1.3183 +
  1.3184 +      // Copy the string and replace backslashes with forward slashes along the
  1.3185 +      // way.
  1.3186 +      do {
  1.3187 +        if (*s == NS_T('\\'))
  1.3188 +          *d = NS_T('/');
  1.3189 +        else
  1.3190 +          *d = *s;
  1.3191 +        ++s;
  1.3192 +        ++d;
  1.3193 +      } while (*s);
  1.3194 +      *d = NS_T('\0');
  1.3195 +      ++d;
  1.3196 +
  1.3197 +      // Make a copy of the callback executable so it can be read when patching.
  1.3198 +      NS_tsnprintf(gCallbackBackupPath,
  1.3199 +                   sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
  1.3200 +                   NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
  1.3201 +      NS_tremove(gCallbackBackupPath);
  1.3202 +      CopyFileW(argv[callbackIndex], gCallbackBackupPath, false);
  1.3203 +
  1.3204 +      // Since the process may be signaled as exited by WaitForSingleObject before
  1.3205 +      // the release of the executable image try to lock the main executable file
  1.3206 +      // multiple times before giving up.  If we end up giving up, we won't
  1.3207 +      // fail the update.
  1.3208 +      const int max_retries = 10;
  1.3209 +      int retries = 1;
  1.3210 +      DWORD lastWriteError = 0;
  1.3211 +      do {
  1.3212 +        // By opening a file handle wihout FILE_SHARE_READ to the callback
  1.3213 +        // executable, the OS will prevent launching the process while it is
  1.3214 +        // being updated.
  1.3215 +        callbackFile = CreateFileW(targetPath,
  1.3216 +                                   DELETE | GENERIC_WRITE,
  1.3217 +                                   // allow delete, rename, and write
  1.3218 +                                   FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  1.3219 +                                   nullptr, OPEN_EXISTING, 0, nullptr);
  1.3220 +        if (callbackFile != INVALID_HANDLE_VALUE)
  1.3221 +          break;
  1.3222 +
  1.3223 +        lastWriteError = GetLastError();
  1.3224 +        LOG(("NS_main: callback app file open attempt %d failed. " \
  1.3225 +             "File: " LOG_S ". Last error: %d", retries,
  1.3226 +             targetPath, lastWriteError));
  1.3227 +
  1.3228 +        Sleep(100);
  1.3229 +      } while (++retries <= max_retries);
  1.3230 +
  1.3231 +      // CreateFileW will fail if the callback executable is already in use.
  1.3232 +      if (callbackFile == INVALID_HANDLE_VALUE) {
  1.3233 +        // Only fail the update if the last error was not a sharing violation.
  1.3234 +        if (lastWriteError != ERROR_SHARING_VIOLATION) {
  1.3235 +          LOG(("NS_main: callback app file in use, failed to exclusively open " \
  1.3236 +               "executable file: " LOG_S, argv[callbackIndex]));
  1.3237 +          LogFinish();
  1.3238 +          if (lastWriteError == ERROR_ACCESS_DENIED) {
  1.3239 +            WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
  1.3240 +          } else {
  1.3241 +            WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
  1.3242 +          }
  1.3243 +
  1.3244 +          NS_tremove(gCallbackBackupPath);
  1.3245 +          EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  1.3246 +          LaunchCallbackApp(argv[4],
  1.3247 +                            argc - callbackIndex,
  1.3248 +                            argv + callbackIndex,
  1.3249 +                            sUsingService);
  1.3250 +          return 1;
  1.3251 +        }
  1.3252 +        LOG(("NS_main: callback app file in use, continuing without " \
  1.3253 +             "exclusive access for executable file: " LOG_S,
  1.3254 +             argv[callbackIndex]));
  1.3255 +      }
  1.3256 +    }
  1.3257 +  }
  1.3258 +
  1.3259 +  // DELETE_DIR is not required when staging an update.
  1.3260 +  if (!sStagedUpdate && !sReplaceRequest) {
  1.3261 +    // The directory to move files that are in use to on Windows. This directory
  1.3262 +    // will be deleted after the update is finished or on OS reboot using
  1.3263 +    // MoveFileEx if it contains files that are in use.
  1.3264 +    if (NS_taccess(DELETE_DIR, F_OK)) {
  1.3265 +      NS_tmkdir(DELETE_DIR, 0755);
  1.3266 +    }
  1.3267 +  }
  1.3268 +#endif /* XP_WIN */
  1.3269 +
  1.3270 +  // Run update process on a background thread.  ShowProgressUI may return
  1.3271 +  // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
  1.3272 +  // terminate.  Avoid showing the progress UI when staging an update.
  1.3273 +  Thread t;
  1.3274 +  if (t.Run(UpdateThreadFunc, nullptr) == 0) {
  1.3275 +    if (!sStagedUpdate && !sReplaceRequest) {
  1.3276 +      ShowProgressUI();
  1.3277 +    }
  1.3278 +  }
  1.3279 +  t.Join();
  1.3280 +
  1.3281 +#ifdef XP_WIN
  1.3282 +  if (argc > callbackIndex && !sReplaceRequest) {
  1.3283 +    if (callbackFile != INVALID_HANDLE_VALUE) {
  1.3284 +      CloseHandle(callbackFile);
  1.3285 +    }
  1.3286 +    // Remove the copy of the callback executable.
  1.3287 +    NS_tremove(gCallbackBackupPath);
  1.3288 +  }
  1.3289 +
  1.3290 +  if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) {
  1.3291 +    LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
  1.3292 +         DELETE_DIR, errno));
  1.3293 +    // The directory probably couldn't be removed due to it containing files
  1.3294 +    // that are in use and will be removed on OS reboot. The call to remove the
  1.3295 +    // directory on OS reboot is done after the calls to remove the files so the
  1.3296 +    // files are removed first on OS reboot since the directory must be empty
  1.3297 +    // for the directory removal to be successful. The MoveFileEx call to remove
  1.3298 +    // the directory on OS reboot will fail if the process doesn't have write
  1.3299 +    // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
  1.3300 +    // installer / uninstaller will delete the directory along with its contents
  1.3301 +    // after an update is applied, on reinstall, and on uninstall.
  1.3302 +    if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
  1.3303 +      LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
  1.3304 +           DELETE_DIR));
  1.3305 +    } else {
  1.3306 +      LOG(("NS_main: failed to schedule OS reboot removal of " \
  1.3307 +           "directory: " LOG_S, DELETE_DIR));
  1.3308 +    }
  1.3309 +  }
  1.3310 +#endif /* XP_WIN */
  1.3311 +
  1.3312 +#if defined(MOZ_WIDGET_GONK)
  1.3313 +  } // end the extra level of scope for the GonkAutoMounter
  1.3314 +#endif
  1.3315 +
  1.3316 +  LogFinish();
  1.3317 +
  1.3318 +  if (argc > callbackIndex) {
  1.3319 +#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE)
  1.3320 +    if (gSucceeded) {
  1.3321 +      // The service update will only be executed if it is already installed.
  1.3322 +      // For first time installs of the service, the install will happen from
  1.3323 +      // the PostUpdate process. We do the service update process here 
  1.3324 +      // because it's possible we are updating with updater.exe without the 
  1.3325 +      // service if the service failed to apply the update. We want to update
  1.3326 +      // the service to a newer version in that case. If we are not running
  1.3327 +      // through the service, then MOZ_USING_SERVICE will not exist.
  1.3328 +      if (!sUsingService) {
  1.3329 +        NS_tchar installDir[MAXPATHLEN];
  1.3330 +        if (GetInstallationDir(installDir)) {
  1.3331 +          if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) {
  1.3332 +            LOG(("NS_main: The post update process could not be launched."));
  1.3333 +          }
  1.3334 +
  1.3335 +          StartServiceUpdate(installDir);
  1.3336 +        }
  1.3337 +      }
  1.3338 +    }
  1.3339 +    EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
  1.3340 +#endif /* XP_WIN */
  1.3341 +#ifdef XP_MACOSX
  1.3342 +    if (gSucceeded) {
  1.3343 +      LaunchMacPostProcess(argv[callbackIndex]);
  1.3344 +    }
  1.3345 +#endif /* XP_MACOSX */
  1.3346 +
  1.3347 +    if (getenv("MOZ_PROCESS_UPDATES") == nullptr) {
  1.3348 +      LaunchCallbackApp(argv[4], 
  1.3349 +                        argc - callbackIndex, 
  1.3350 +                        argv + callbackIndex, 
  1.3351 +                        sUsingService);
  1.3352 +    }
  1.3353 +  }
  1.3354 +
  1.3355 +  return gSucceeded ? 0 : 1;
  1.3356 +}
  1.3357 +
  1.3358 +class ActionList
  1.3359 +{
  1.3360 +public:
  1.3361 +  ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
  1.3362 +  ~ActionList();
  1.3363 +
  1.3364 +  void Append(Action* action);
  1.3365 +  int Prepare();
  1.3366 +  int Execute();
  1.3367 +  void Finish(int status);
  1.3368 +
  1.3369 +private:
  1.3370 +  Action *mFirst;
  1.3371 +  Action *mLast;
  1.3372 +  int     mCount;
  1.3373 +};
  1.3374 +
  1.3375 +ActionList::~ActionList()
  1.3376 +{
  1.3377 +  Action* a = mFirst;
  1.3378 +  while (a) {
  1.3379 +    Action *b = a;
  1.3380 +    a = a->mNext;
  1.3381 +    delete b;
  1.3382 +  }
  1.3383 +}
  1.3384 +
  1.3385 +void
  1.3386 +ActionList::Append(Action *action)
  1.3387 +{
  1.3388 +  if (mLast)
  1.3389 +    mLast->mNext = action;
  1.3390 +  else
  1.3391 +    mFirst = action;
  1.3392 +
  1.3393 +  mLast = action;
  1.3394 +  mCount++;
  1.3395 +}
  1.3396 +
  1.3397 +int
  1.3398 +ActionList::Prepare()
  1.3399 +{
  1.3400 +  // If the action list is empty then we should fail in order to signal that
  1.3401 +  // something has gone wrong. Otherwise we report success when nothing is
  1.3402 +  // actually done. See bug 327140.
  1.3403 +  if (mCount == 0) {
  1.3404 +    LOG(("empty action list"));
  1.3405 +    return UNEXPECTED_MAR_ERROR;
  1.3406 +  }
  1.3407 +
  1.3408 +  Action *a = mFirst;
  1.3409 +  int i = 0;
  1.3410 +  while (a) {
  1.3411 +    int rv = a->Prepare();
  1.3412 +    if (rv)
  1.3413 +      return rv;
  1.3414 +
  1.3415 +    float percent = float(++i) / float(mCount);
  1.3416 +    UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
  1.3417 +
  1.3418 +    a = a->mNext;
  1.3419 +  }
  1.3420 +
  1.3421 +  return OK;
  1.3422 +}
  1.3423 +
  1.3424 +int
  1.3425 +ActionList::Execute()
  1.3426 +{
  1.3427 +  int currentProgress = 0, maxProgress = 0;
  1.3428 +  Action *a = mFirst;
  1.3429 +  while (a) {
  1.3430 +    maxProgress += a->mProgressCost;
  1.3431 +    a = a->mNext;
  1.3432 +  }
  1.3433 +
  1.3434 +  a = mFirst;
  1.3435 +  while (a) {
  1.3436 +    int rv = a->Execute();
  1.3437 +    if (rv) {
  1.3438 +      LOG(("### execution failed"));
  1.3439 +      return rv;
  1.3440 +    }
  1.3441 +
  1.3442 +    currentProgress += a->mProgressCost;
  1.3443 +    float percent = float(currentProgress) / float(maxProgress);
  1.3444 +    UpdateProgressUI(PROGRESS_PREPARE_SIZE +
  1.3445 +                     PROGRESS_EXECUTE_SIZE * percent);
  1.3446 +
  1.3447 +    a = a->mNext;
  1.3448 +  }
  1.3449 +
  1.3450 +  return OK;
  1.3451 +}
  1.3452 +
  1.3453 +void
  1.3454 +ActionList::Finish(int status)
  1.3455 +{
  1.3456 +  Action *a = mFirst;
  1.3457 +  int i = 0;
  1.3458 +  while (a) {
  1.3459 +    a->Finish(status);
  1.3460 +
  1.3461 +    float percent = float(++i) / float(mCount);
  1.3462 +    UpdateProgressUI(PROGRESS_PREPARE_SIZE +
  1.3463 +                     PROGRESS_EXECUTE_SIZE +
  1.3464 +                     PROGRESS_FINISH_SIZE * percent);
  1.3465 +
  1.3466 +    a = a->mNext;
  1.3467 +  }
  1.3468 +
  1.3469 +  if (status == OK)
  1.3470 +    gSucceeded = true;
  1.3471 +}
  1.3472 +
  1.3473 +
  1.3474 +#ifdef XP_WIN
  1.3475 +int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  1.3476 +{
  1.3477 +  int rv = OK;
  1.3478 +  WIN32_FIND_DATAW finddata;
  1.3479 +  HANDLE hFindFile;
  1.3480 +  NS_tchar searchspec[MAXPATHLEN];
  1.3481 +  NS_tchar foundpath[MAXPATHLEN];
  1.3482 +
  1.3483 +  NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
  1.3484 +               NS_T("%s*"), dirpath);
  1.3485 +  const NS_tchar *pszSpec = get_full_path(searchspec);
  1.3486 +
  1.3487 +  hFindFile = FindFirstFileW(pszSpec, &finddata);
  1.3488 +  if (hFindFile != INVALID_HANDLE_VALUE) {
  1.3489 +    do {
  1.3490 +      // Don't process the current or parent directory.
  1.3491 +      if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
  1.3492 +          NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
  1.3493 +        continue;
  1.3494 +
  1.3495 +      NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3496 +                   NS_T("%s%s"), dirpath, finddata.cFileName);
  1.3497 +      if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  1.3498 +        NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3499 +                     NS_T("%s/"), foundpath);
  1.3500 +        // Recurse into the directory.
  1.3501 +        rv = add_dir_entries(foundpath, list);
  1.3502 +        if (rv) {
  1.3503 +          LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
  1.3504 +          return rv;
  1.3505 +        }
  1.3506 +      } else {
  1.3507 +        // Add the file to be removed to the ActionList.
  1.3508 +        NS_tchar *quotedpath = get_quoted_path(foundpath);
  1.3509 +        if (!quotedpath)
  1.3510 +          return PARSE_ERROR;
  1.3511 +
  1.3512 +        Action *action = new RemoveFile();
  1.3513 +        rv = action->Parse(quotedpath);
  1.3514 +        if (rv) {
  1.3515 +          LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", quotedpath, rv));
  1.3516 +          return rv;
  1.3517 +        }
  1.3518 +
  1.3519 +        list->Append(action);
  1.3520 +      }
  1.3521 +    } while (FindNextFileW(hFindFile, &finddata) != 0);
  1.3522 +
  1.3523 +    FindClose(hFindFile);
  1.3524 +    {
  1.3525 +      // Add the directory to be removed to the ActionList.
  1.3526 +      NS_tchar *quotedpath = get_quoted_path(dirpath);
  1.3527 +      if (!quotedpath)
  1.3528 +        return PARSE_ERROR;
  1.3529 +
  1.3530 +      Action *action = new RemoveDir();
  1.3531 +      rv = action->Parse(quotedpath);
  1.3532 +      if (rv)
  1.3533 +        LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", quotedpath, rv));
  1.3534 +      else
  1.3535 +        list->Append(action);
  1.3536 +    }
  1.3537 +  }
  1.3538 +
  1.3539 +  return rv;
  1.3540 +}
  1.3541 +
  1.3542 +#elif defined(SOLARIS)
  1.3543 +int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  1.3544 +{
  1.3545 +  int rv = OK;
  1.3546 +  NS_tchar searchpath[MAXPATHLEN];
  1.3547 +  NS_tchar foundpath[MAXPATHLEN];
  1.3548 +  struct {
  1.3549 +    dirent dent_buffer;
  1.3550 +    char chars[MAXNAMLEN];
  1.3551 +  } ent_buf;
  1.3552 +  struct dirent* ent;
  1.3553 +
  1.3554 +
  1.3555 +  NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
  1.3556 +               dirpath);
  1.3557 +  // Remove the trailing slash so the paths don't contain double slashes. The
  1.3558 +  // existence of the slash has already been checked in DoUpdate.
  1.3559 +  searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
  1.3560 +
  1.3561 +  DIR* dir = opendir(searchpath);
  1.3562 +  if (!dir) {
  1.3563 +    LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath,
  1.3564 +         errno));
  1.3565 +    return UNEXPECTED_FILE_OPERATION_ERROR;
  1.3566 +  }
  1.3567 +
  1.3568 +  while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) {
  1.3569 +    if ((strcmp(ent->d_name, ".") == 0) ||
  1.3570 +        (strcmp(ent->d_name, "..") == 0))
  1.3571 +      continue;
  1.3572 +
  1.3573 +    NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3574 +                 NS_T("%s%s"), dirpath, ent->d_name);
  1.3575 +    struct stat64 st_buf;
  1.3576 +    int test = stat64(foundpath, &st_buf);
  1.3577 +    if (test) {
  1.3578 +      closedir(dir);
  1.3579 +      return UNEXPECTED_FILE_OPERATION_ERROR;
  1.3580 +    }
  1.3581 +    if (S_ISDIR(st_buf.st_mode)) {
  1.3582 +      NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3583 +                   NS_T("%s/"), foundpath);
  1.3584 +      // Recurse into the directory.
  1.3585 +      rv = add_dir_entries(foundpath, list);
  1.3586 +      if (rv) {
  1.3587 +        LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
  1.3588 +        closedir(dir);
  1.3589 +        return rv;
  1.3590 +      }
  1.3591 +    } else {
  1.3592 +      // Add the file to be removed to the ActionList.
  1.3593 +      NS_tchar *quotedpath = get_quoted_path(foundpath);
  1.3594 +      if (!quotedpath) {
  1.3595 +        closedir(dir);
  1.3596 +        return PARSE_ERROR;
  1.3597 +      }
  1.3598 +
  1.3599 +      Action *action = new RemoveFile();
  1.3600 +      rv = action->Parse(quotedpath);
  1.3601 +      if (rv) {
  1.3602 +        LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
  1.3603 +             quotedpath, rv));
  1.3604 +        closedir(dir);
  1.3605 +        return rv;
  1.3606 +      }
  1.3607 +
  1.3608 +      list->Append(action);
  1.3609 +    }
  1.3610 +  }
  1.3611 +  closedir(dir);
  1.3612 +
  1.3613 +  // Add the directory to be removed to the ActionList.
  1.3614 +  NS_tchar *quotedpath = get_quoted_path(dirpath);
  1.3615 +  if (!quotedpath)
  1.3616 +    return PARSE_ERROR;
  1.3617 +
  1.3618 +  Action *action = new RemoveDir();
  1.3619 +  rv = action->Parse(quotedpath);
  1.3620 +  if (rv) {
  1.3621 +    LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
  1.3622 +         quotedpath, rv));
  1.3623 +  }
  1.3624 +  else {
  1.3625 +    list->Append(action);
  1.3626 +  }
  1.3627 +
  1.3628 +  return rv;
  1.3629 +}
  1.3630 +
  1.3631 +#else
  1.3632 +
  1.3633 +int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  1.3634 +{
  1.3635 +  int rv = OK;
  1.3636 +  FTS *ftsdir;
  1.3637 +  FTSENT *ftsdirEntry;
  1.3638 +  NS_tchar searchpath[MAXPATHLEN];
  1.3639 +
  1.3640 +  NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
  1.3641 +               dirpath);
  1.3642 +  // Remove the trailing slash so the paths don't contain double slashes. The
  1.3643 +  // existence of the slash has already been checked in DoUpdate.
  1.3644 +  searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
  1.3645 +  char* const pathargv[] = {searchpath, nullptr};
  1.3646 +
  1.3647 +  // FTS_NOCHDIR is used so relative paths from the destination directory are
  1.3648 +  // returned.
  1.3649 +  if (!(ftsdir = fts_open(pathargv,
  1.3650 +                          FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
  1.3651 +                          nullptr)))
  1.3652 +    return UNEXPECTED_FILE_OPERATION_ERROR;
  1.3653 +
  1.3654 +  while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
  1.3655 +    NS_tchar foundpath[MAXPATHLEN];
  1.3656 +    NS_tchar *quotedpath;
  1.3657 +    Action *action = nullptr;
  1.3658 +
  1.3659 +    switch (ftsdirEntry->fts_info) {
  1.3660 +      // Filesystem objects that shouldn't be in the application's directories
  1.3661 +      case FTS_SL:
  1.3662 +      case FTS_SLNONE:
  1.3663 +      case FTS_DEFAULT:
  1.3664 +        LOG(("add_dir_entries: found a non-standard file: " LOG_S,
  1.3665 +             ftsdirEntry->fts_path));
  1.3666 +        // Fall through and try to remove as a file
  1.3667 +
  1.3668 +      // Files
  1.3669 +      case FTS_F:
  1.3670 +      case FTS_NSOK:
  1.3671 +        // Add the file to be removed to the ActionList.
  1.3672 +        NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3673 +                     NS_T("%s"), ftsdirEntry->fts_accpath);
  1.3674 +        quotedpath = get_quoted_path(foundpath);
  1.3675 +        if (!quotedpath) {
  1.3676 +          rv = UPDATER_QUOTED_PATH_MEM_ERROR;
  1.3677 +          break;
  1.3678 +        }
  1.3679 +        action = new RemoveFile();
  1.3680 +        rv = action->Parse(quotedpath);
  1.3681 +        if (!rv)
  1.3682 +          list->Append(action);
  1.3683 +        break;
  1.3684 +
  1.3685 +      // Directories
  1.3686 +      case FTS_DP:
  1.3687 +        rv = OK;
  1.3688 +        // Add the directory to be removed to the ActionList.
  1.3689 +        NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  1.3690 +                     NS_T("%s/"), ftsdirEntry->fts_accpath);
  1.3691 +        quotedpath = get_quoted_path(foundpath);
  1.3692 +        if (!quotedpath) {
  1.3693 +          rv = UPDATER_QUOTED_PATH_MEM_ERROR;
  1.3694 +          break;
  1.3695 +        }
  1.3696 +
  1.3697 +        action = new RemoveDir();
  1.3698 +        rv = action->Parse(quotedpath);
  1.3699 +        if (!rv)
  1.3700 +          list->Append(action);
  1.3701 +        break;
  1.3702 +
  1.3703 +      // Errors
  1.3704 +      case FTS_DNR:
  1.3705 +      case FTS_NS:
  1.3706 +        // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
  1.3707 +        // we're racing with ourselves. Though strange, the entry will be
  1.3708 +        // removed anyway.
  1.3709 +        if (ENOENT == ftsdirEntry->fts_errno) {
  1.3710 +          rv = OK;
  1.3711 +          break;
  1.3712 +        }
  1.3713 +        // Fall through
  1.3714 +
  1.3715 +      case FTS_ERR:
  1.3716 +        rv = UNEXPECTED_FILE_OPERATION_ERROR;
  1.3717 +        LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
  1.3718 +             ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
  1.3719 +        break;
  1.3720 +
  1.3721 +      case FTS_DC:
  1.3722 +        rv = UNEXPECTED_FILE_OPERATION_ERROR;
  1.3723 +        LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
  1.3724 +             ftsdirEntry->fts_path));
  1.3725 +        break;
  1.3726 +
  1.3727 +      default:
  1.3728 +        // FTS_D is ignored and FTS_DP is used instead (post-order).
  1.3729 +        rv = OK;
  1.3730 +        break;
  1.3731 +    }
  1.3732 +
  1.3733 +    if (rv != OK)
  1.3734 +      break;
  1.3735 +  }
  1.3736 +
  1.3737 +  fts_close(ftsdir);
  1.3738 +
  1.3739 +  return rv;
  1.3740 +}
  1.3741 +#endif
  1.3742 +
  1.3743 +static NS_tchar*
  1.3744 +GetManifestContents(const NS_tchar *manifest)
  1.3745 +{
  1.3746 +  AutoFile mfile = NS_tfopen(manifest, NS_T("rb"));
  1.3747 +  if (mfile == nullptr) {
  1.3748 +    LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
  1.3749 +    return nullptr;
  1.3750 +  }
  1.3751 +
  1.3752 +  struct stat ms;
  1.3753 +  int rv = fstat(fileno((FILE *)mfile), &ms);
  1.3754 +  if (rv) {
  1.3755 +    LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
  1.3756 +    return nullptr;
  1.3757 +  }
  1.3758 +
  1.3759 +  char *mbuf = (char *) malloc(ms.st_size + 1);
  1.3760 +  if (!mbuf)
  1.3761 +    return nullptr;
  1.3762 +
  1.3763 +  size_t r = ms.st_size;
  1.3764 +  char *rb = mbuf;
  1.3765 +  while (r) {
  1.3766 +    const size_t count = mmin(SSIZE_MAX, r);
  1.3767 +    size_t c = fread(rb, 1, count, mfile);
  1.3768 +    if (c != count) {
  1.3769 +      LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
  1.3770 +      return nullptr;
  1.3771 +    }
  1.3772 +
  1.3773 +    r -= c;
  1.3774 +    rb += c;
  1.3775 +  }
  1.3776 +  mbuf[ms.st_size] = '\0';
  1.3777 +  rb = mbuf;
  1.3778 +
  1.3779 +#ifndef XP_WIN
  1.3780 +  return rb;
  1.3781 +#else
  1.3782 +  NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
  1.3783 +  if (!wrb)
  1.3784 +    return nullptr;
  1.3785 +
  1.3786 +  if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
  1.3787 +                           ms.st_size + 1)) {
  1.3788 +    LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
  1.3789 +    free(mbuf);
  1.3790 +    free(wrb);
  1.3791 +    return nullptr;
  1.3792 +  }
  1.3793 +  free(mbuf);
  1.3794 +
  1.3795 +  return wrb;
  1.3796 +#endif
  1.3797 +}
  1.3798 +
  1.3799 +int AddPreCompleteActions(ActionList *list)
  1.3800 +{
  1.3801 +  if (sIsOSUpdate) {
  1.3802 +    return OK;
  1.3803 +  }
  1.3804 +
  1.3805 +  NS_tchar *rb = GetManifestContents(NS_T("precomplete"));
  1.3806 +  if (rb == nullptr) {
  1.3807 +    LOG(("AddPreCompleteActions: error getting contents of precomplete " \
  1.3808 +         "manifest"));
  1.3809 +    // Applications aren't required to have a precomplete manifest. The mar
  1.3810 +    // generation scripts enforce the presence of a precomplete manifest.
  1.3811 +    return OK;
  1.3812 +  }
  1.3813 +
  1.3814 +  int rv;
  1.3815 +  NS_tchar *line;
  1.3816 +  while((line = mstrtok(kNL, &rb)) != 0) {
  1.3817 +    // skip comments
  1.3818 +    if (*line == NS_T('#'))
  1.3819 +      continue;
  1.3820 +
  1.3821 +    NS_tchar *token = mstrtok(kWhitespace, &line);
  1.3822 +    if (!token) {
  1.3823 +      LOG(("AddPreCompleteActions: token not found in manifest"));
  1.3824 +      return PARSE_ERROR;
  1.3825 +    }
  1.3826 +
  1.3827 +    Action *action = nullptr;
  1.3828 +    if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
  1.3829 +      action = new RemoveFile();
  1.3830 +    }
  1.3831 +    else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported
  1.3832 +      continue;
  1.3833 +    }
  1.3834 +    else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if  empty
  1.3835 +      action = new RemoveDir();
  1.3836 +    }
  1.3837 +    else {
  1.3838 +      LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
  1.3839 +      return PARSE_ERROR;
  1.3840 +    }
  1.3841 +
  1.3842 +    if (!action)
  1.3843 +      return BAD_ACTION_ERROR;
  1.3844 +
  1.3845 +    rv = action->Parse(line);
  1.3846 +    if (rv)
  1.3847 +      return rv;
  1.3848 +
  1.3849 +    list->Append(action);
  1.3850 +  }
  1.3851 +
  1.3852 +  return OK;
  1.3853 +}
  1.3854 +
  1.3855 +int DoUpdate()
  1.3856 +{
  1.3857 +  NS_tchar manifest[MAXPATHLEN];
  1.3858 +  NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
  1.3859 +               NS_T("%s/updating/update.manifest"), gDestinationPath);
  1.3860 +  ensure_parent_dir(manifest);
  1.3861 +
  1.3862 +  // extract the manifest
  1.3863 +  int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest);
  1.3864 +  if (rv) {
  1.3865 +    rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest);
  1.3866 +    if (rv) {
  1.3867 +      LOG(("DoUpdate: error extracting manifest file"));
  1.3868 +      return rv;
  1.3869 +    }
  1.3870 +  }
  1.3871 +
  1.3872 +  NS_tchar *rb = GetManifestContents(manifest);
  1.3873 +  NS_tremove(manifest);
  1.3874 +  if (rb == nullptr) {
  1.3875 +    LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
  1.3876 +    return READ_ERROR;
  1.3877 +  }
  1.3878 +
  1.3879 +
  1.3880 +  ActionList list;
  1.3881 +  NS_tchar *line;
  1.3882 +  bool isFirstAction = true;
  1.3883 +
  1.3884 +  while((line = mstrtok(kNL, &rb)) != 0) {
  1.3885 +    // skip comments
  1.3886 +    if (*line == NS_T('#'))
  1.3887 +      continue;
  1.3888 +
  1.3889 +    NS_tchar *token = mstrtok(kWhitespace, &line);
  1.3890 +    if (!token) {
  1.3891 +      LOG(("DoUpdate: token not found in manifest"));
  1.3892 +      return PARSE_ERROR;
  1.3893 +    }
  1.3894 +
  1.3895 +    if (isFirstAction) {
  1.3896 +      isFirstAction = false;
  1.3897 +      // The update manifest isn't required to have a type declaration. The mar
  1.3898 +      // generation scripts enforce the presence of the type declaration.
  1.3899 +      if (NS_tstrcmp(token, NS_T("type")) == 0) {
  1.3900 +        const NS_tchar *type = mstrtok(kQuote, &line);
  1.3901 +        LOG(("UPDATE TYPE " LOG_S, type));
  1.3902 +        if (NS_tstrcmp(type, NS_T("complete")) == 0) {
  1.3903 +          rv = AddPreCompleteActions(&list);
  1.3904 +          if (rv)
  1.3905 +            return rv;
  1.3906 +        }
  1.3907 +        continue;
  1.3908 +      }
  1.3909 +    }
  1.3910 +
  1.3911 +    Action *action = nullptr;
  1.3912 +    if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
  1.3913 +      action = new RemoveFile();
  1.3914 +    }
  1.3915 +    else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
  1.3916 +      action = new RemoveDir();
  1.3917 +    }
  1.3918 +    else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive
  1.3919 +      const NS_tchar *reldirpath = mstrtok(kQuote, &line);
  1.3920 +      if (!reldirpath)
  1.3921 +        return PARSE_ERROR;
  1.3922 +
  1.3923 +      if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
  1.3924 +        return PARSE_ERROR;
  1.3925 +
  1.3926 +      rv = add_dir_entries(reldirpath, &list);
  1.3927 +      if (rv)
  1.3928 +        return rv;
  1.3929 +
  1.3930 +      continue;
  1.3931 +    }
  1.3932 +    else if (NS_tstrcmp(token, NS_T("add")) == 0) {
  1.3933 +      action = new AddFile();
  1.3934 +    }
  1.3935 +    else if (NS_tstrcmp(token, NS_T("patch")) == 0) {
  1.3936 +      action = new PatchFile();
  1.3937 +    }
  1.3938 +    else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists
  1.3939 +      action = new AddIfFile();
  1.3940 +    }
  1.3941 +    else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists
  1.3942 +      action = new AddIfNotFile();
  1.3943 +    }
  1.3944 +    else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists
  1.3945 +      action = new PatchIfFile();
  1.3946 +    }
  1.3947 +#ifndef XP_WIN
  1.3948 +    else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) {
  1.3949 +      action = new AddSymlink();
  1.3950 +    }
  1.3951 +#endif
  1.3952 +    else {
  1.3953 +      LOG(("DoUpdate: unknown token: " LOG_S, token));
  1.3954 +      return PARSE_ERROR;
  1.3955 +    }
  1.3956 +
  1.3957 +    if (!action)
  1.3958 +      return BAD_ACTION_ERROR;
  1.3959 +
  1.3960 +    rv = action->Parse(line);
  1.3961 +    if (rv)
  1.3962 +      return rv;
  1.3963 +
  1.3964 +    list.Append(action);
  1.3965 +  }
  1.3966 +
  1.3967 +  rv = list.Prepare();
  1.3968 +  if (rv)
  1.3969 +    return rv;
  1.3970 +
  1.3971 +  rv = list.Execute();
  1.3972 +
  1.3973 +  list.Finish(rv);
  1.3974 +  return rv;
  1.3975 +}

mercurial