Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/toolkit/mozapps/update/updater/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 158 kB image not shown  

Quelle  updater.cpp   Sprache: C

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/**
 *  Manifest Format
 *  ---------------
 *
 *  contents = 1*( line )
 *  line     = method LWS *( param LWS ) CRLF
 *  CRLF     = "\r\n"
 *  LWS      = 1*( " " | "\t" )
 *
 *  Available methods for the manifest file:
 *
 *  updatev3.manifest
 *  -----------------
 *  method   = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
 *             "remove" | "rmdir" | "rmrfdir" | type
 *
 *  'add-if-not' adds a file if it doesn't exist.
 *
 *  'type' is the update type (e.g. complete or partial) and when present MUST
 *  be the first entry in the update manifest. The type is used to support
 *  removing files that no longer exist when when applying a complete update by
 *  causing the actions defined in the precomplete file to be performed.
 *
 *  precomplete
 *  -----------
 *  method   = "remove" | "rmdir"
 */

#include "bspatch.h"
#include "progressui.h"
#include "archivereader.h"
#include "readstrings.h"
#include "updatererrors.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>

#include "updatecommon.h"
#ifdef XP_MACOSX
#  include "UpdateSettingsUtil.h"
#  include "updaterfileutils_osx.h"
#endif  // XP_MACOSX

#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/UniquePtr.h"
#ifdef XP_WIN
#  include "mozilla/Maybe.h"
#  include "mozilla/WinHeaderOnlyUtils.h"
#  include <climits>
#endif  // XP_WIN

// Amount of the progress bar to use in each of the 3 update stages,
// should total 100.0.
#define PROGRESS_PREPARE_SIZE 20.0f
#define PROGRESS_EXECUTE_SIZE 75.0f
#define PROGRESS_FINISH_SIZE 5.0f

// Maximum amount of time in ms to wait for the parent process to close. The 30
// seconds is rather long but there have been bug reports where the parent
// process has exited after 10 seconds and it is better to give it a chance.
#define PARENT_WAIT 30000

#if defined(XP_MACOSX)
// These functions are defined in launchchild_osx.mm
void CleanupElevatedMacUpdate(bool aFailureOccurred);
bool IsOwnedByGroupAdmin(const char* aAppBundle);
bool IsRecursivelyWritable(const char* aPath);
void LaunchMacApp(int argc, const char** argv);
void LaunchMacPostProcess(const char* aAppBundle);
bool ObtainUpdaterArguments(int* aArgc, char*** aArgv,
                            MARChannelStringTable* aMARStrings);
bool ServeElevatedUpdate(int aArgc, const char** aArgv,
                         const char* aMARChannelID);
void SetGroupOwnershipAndPermissions(const char* aAppBundle);
bool PerformInstallationFromDMG(int argc, char** argv);
struct UpdateServerThreadArgs {
  int argc;
  const NS_tchar** argv;
  const char* marChannelID;
};
#endif

#ifndef _O_BINARY
#  define _O_BINARY 0
#endif

#ifndef NULL
#  define NULL (0)
#endif

#ifndef SSIZE_MAX
#  define SSIZE_MAX LONG_MAX
#endif

// We want to use execv to invoke the callback executable on platforms where
// we were launched using execv.  See nsUpdateDriver.cpp.
#if defined(XP_UNIX) && !defined(XP_MACOSX)
#  define USE_EXECV
#endif

#if defined(XP_OPENBSD)
#  define stat64 stat
#endif

#if defined(MOZ_VERIFY_MAR_SIGNATURE) && defined(MAR_NSS)
#  include "nss.h"
#  include "prerror.h"
#endif

#include "crctable.h"

#ifdef XP_WIN
#  ifdef MOZ_MAINTENANCE_SERVICE
#    include "registrycertificates.h"
#  endif
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
                            LPCWSTR newFileName);
#  include "updatehelper.h"

// Closes the handle if valid and if the updater is elevated returns with the
// return code specified. This prevents multiple launches of the callback
// application by preventing the elevated process from launching the callback.
#  define EXIT_WHEN_ELEVATED(path, handle, retCode) \
    {                                               \
      if (handle != INVALID_HANDLE_VALUE) {         \
        CloseHandle(handle);                        \
      }                                             \
      if (NS_tremove(path) && errno != ENOENT) {    \
        return retCode;                             \
      }                                             \
    }
#endif

//-----------------------------------------------------------------------------

// This BZ2_crc32Table variable lives in libbz2. We just took the
// data structure from bz2 and created crctables.h

static unsigned int crc32(const unsigned char* buf, unsigned int len) {
  unsigned int crc = 0xffffffffL;

  const unsigned char* end = buf + len;
  for (; buf != end; ++buf)
    crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];

  crc = ~crc;
  return crc;
}

//-----------------------------------------------------------------------------

// A simple stack based container for a FILE struct that closes the
// file descriptor from its destructor.
class AutoFile {
 public:
  explicit AutoFile(FILE* file = nullptr) : mFile(file) {}

  ~AutoFile() { close(); }

  AutoFile& operator=(FILE* file) {
    close();
    mFile = file;
    return *this;
  }

  operator FILE*() { return mFile; }

  FILE* get() { return mFile; }

 private:
  FILE* mFile;

  void close() {
    if (mFile != nullptr) {
      int rv = fclose(mFile);
      if (rv != 0) {
        LOG(("File close did not execute successfully"));
      }
      mFile = nullptr;
    }
  }
};

//-----------------------------------------------------------------------------

#ifdef XP_MACOSX

// Just a simple class that sets a umask value in its constructor and resets
// it in its destructor.
class UmaskContext {
 public:
  explicit UmaskContext(mode_t umaskToSet);
  ~UmaskContext();

 private:
  mode_t mPreviousUmask;
};

UmaskContext::UmaskContext(mode_t umaskToSet) {
  mPreviousUmask = umask(umaskToSet);
}

UmaskContext::~UmaskContext() { umask(mPreviousUmask); }

#endif

//-----------------------------------------------------------------------------

typedef void (*ThreadFunc)(void* param);

#ifdef XP_WIN
#  include <process.h>

class Thread {
 public:
  int Run(ThreadFunc func, void* param) {
    mThreadFunc = func;
    mThreadParam = param;

    unsigned int threadID;

    mThread =
        (HANDLE)_beginthreadex(nullptr, 0, ThreadMain, this, 0, &threadID);

    return mThread ? 0 : -1;
  }
  int Join() {
    WaitForSingleObject(mThread, INFINITE);
    CloseHandle(mThread);
    return 0;
  }

 private:
  static unsigned __stdcall ThreadMain(void* p) {
    Thread* self = (Thread*)p;
    self->mThreadFunc(self->mThreadParam);
    return 0;
  }
  HANDLE mThread;
  ThreadFunc mThreadFunc;
  void* mThreadParam;
};

#elif defined(XP_UNIX)
#  include <pthread.h>

class Thread {
 public:
  int Run(ThreadFunc func, void* param) {
    return pthread_create(&thr, nullptr, (void* (*)(void*))func, param);
  }
  int Join() {
    void* result;
    return pthread_join(thr, &result);
  }

 private:
  pthread_t thr;
};

#else
#  error "Unsupported platform"
#endif

//-----------------------------------------------------------------------------

static NS_tchar gPatchDirPath[MAXPATHLEN];
static NS_tchar gInstallDirPath[MAXPATHLEN];
static NS_tchar gWorkingDirPath[MAXPATHLEN];
MOZ_RUNINIT static ArchiveReader gArchiveReader;
static bool gSucceeded = false;
static bool sStagedUpdate = false;
static bool sReplaceRequest = false;
static bool sUsingService = false;
// The updater binary can potentially run twice. It will always initially run
// with `gIsElevated == false`. If it is run an additional time with elevation,
// that iteration will run with `gIsElevated == true`.
static bool gIsElevated = false;

// This string contains the MAR channel IDs that are later extracted by one of
// the `ReadMARChannelIDsFrom` variants.
MOZ_RUNINIT static MARChannelStringTable gMARStrings;

// Normally, we run updates as a result of user action (the user started Firefox
// or clicked a "Restart to Update" button). But there are some cases when
// we are not:
// a) The callback app is a background task. If true then the updater is
//    likely being run as part of a background task.
//    The updater could be run with no callback, but this only happens
//    when performing a staged update (see calls to ProcessUpdates), and there
//    are already checks for sStagedUpdate when showing UI or elevating.
// b) The environment variable MOZ_APP_SILENT_START is set and not empty. This
//    is set, for instance, on macOS when Firefox had no windows open for a
//    while and restarted to apply updates.
//
// In these cases, the update should be installed silently, so we shouldn't:
// a) show progress UI
// b) prompt for elevation
static bool sUpdateSilently = false;

#ifdef XP_WIN
static NS_tchar gCallbackRelPath[MAXPATHLEN];
static NS_tchar gCallbackBackupPath[MAXPATHLEN];
static NS_tchar gDeleteDirPath[MAXPATHLEN];

// Whether to copy the update-elevated.log and update.status file to the update
// patch directory from a secure directory.
static bool gCopyOutputFiles = false;
// Whether to write the update-elevated.log and update.status file to a secure
// directory.
static bool gUseSecureOutputPath = false;
#endif

static const NS_tchar kWhitespace[] = NS_T(" \t");
static const NS_tchar kNL[] = NS_T("\r\n");
static const NS_tchar kQuote[] = NS_T("\"");

static inline size_t mmin(size_t a, size_t b) { return (a > b) ? b : a; }

static NS_tchar* mstrtok(const NS_tchar* delims, NS_tchar** str) {
  if (!*str || !**str) {
    *str = nullptr;
    return nullptr;
  }

  // skip leading "whitespace"
  NS_tchar* ret = *str;
  const NS_tchar* d;
  do {
    for (d = delims; *d != NS_T('\0'); ++d) {
      if (*ret == *d) {
        ++ret;
        break;
      }
    }
  } while (*d);

  if (!*ret) {
    *str = ret;
    return nullptr;
  }

  NS_tchar* i = ret;
  do {
    for (d = delims; *d != NS_T('\0'); ++d) {
      if (*i == *d) {
        *i = NS_T('\0');
        *str = ++i;
        return ret;
      }
    }
    ++i;
  } while (*i);

  *str = nullptr;
  return ret;
}

#if defined(TEST_UPDATER) || defined(XP_WIN) || defined(XP_MACOSX)
static bool EnvHasValue(const char* name) {
  const char* val = getenv(name);
  return (val && *val);
}
#endif

static const NS_tchar* UpdateLogFilename() {
  if (gIsElevated) {
    return NS_T("update-elevated.log");
  }
  return NS_T("update.log");
}

#ifdef XP_WIN
/**
 * Obtains the update ID from the secure id file located in secure output
 * directory.
 *
 * @param  outBuf
 *         A buffer of size UUID_LEN (e.g. 37) to store the result. The uuid is
 *         36 characters in length and 1 more for null termination.
 * @return true if successful
 */

bool GetSecureID(char* outBuf) {
  NS_tchar idFilePath[MAX_PATH + 1] = {L'\0'};
  if (!GetSecureOutputFilePath(gPatchDirPath, L".id", idFilePath)) {
    return false;
  }

  AutoFile idFile(NS_tfopen(idFilePath, NS_T("rb")));
  if (idFile == nullptr) {
    return false;
  }

  size_t read = fread(outBuf, UUID_LEN - 1, 1, idFile);
  if (read != 1) {
    return false;
  }

  outBuf[UUID_LEN - 1] = '\0';
  return true;
}
#endif

/**
 * Calls LogFinish for the update log. On Windows, the unelevated updater copies
 * the update status file and the update log file that were written by the
 * elevated updater from the secure directory to the update patch directory.
 *
 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
 *       because this function copies the update status file for the elevated
 *       updater and writing the status file after calling output_finish will
 *       overwrite it.
 */

static void output_finish() {
  LogFinish();
#ifdef XP_WIN
  if (gCopyOutputFiles) {
    NS_tchar srcStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
    if (GetSecureOutputFilePath(gPatchDirPath, L".status", srcStatusPath)) {
      NS_tchar dstStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
      NS_tsnprintf(dstStatusPath,
                   sizeof(dstStatusPath) / sizeof(dstStatusPath[0]),
                   NS_T("%s\\update.status"), gPatchDirPath);
      CopyFileW(srcStatusPath, dstStatusPath, false);
    }

    NS_tchar srcLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
    if (GetSecureOutputFilePath(gPatchDirPath, L".log", srcLogPath)) {
      NS_tchar dstLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
      // Unconditionally use "update-elevated.log" here rather than
      // `UpdateLogFilename` since (a) secure output files are only created by
      // elevated instances and (b) the copying of the secure output file is
      // done by the unelevated instance, so `UpdateLogFilename` will return
      // the wrong thing for this.
      NS_tsnprintf(dstLogPath, sizeof(dstLogPath) / sizeof(dstLogPath[0]),
                   NS_T("%s\\update-elevated.log"), gPatchDirPath);
      CopyFileW(srcLogPath, dstLogPath, false);
    }
  }
#endif
}

/**
 * Coverts a relative update path to a full path.
 *
 * @param  relpath
 *         The relative path to convert to a full path.
 * @return valid filesystem full path or nullptr if memory allocation fails.
 */

static NS_tchar* get_full_path(const NS_tchar* relpath) {
  NS_tchar* destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
  size_t lendestpath = NS_tstrlen(destpath);
  size_t lenrelpath = NS_tstrlen(relpath);
  NS_tchar* s = new NS_tchar[lendestpath + lenrelpath + 2];

  NS_tchar* c = s;

  NS_tstrcpy(c, destpath);
  c += lendestpath;
  NS_tstrcat(c, NS_T("/"));
  c++;

  NS_tstrcat(c, relpath);
  c += lenrelpath;
  *c = NS_T('\0');
  return s;
}

/**
 * Converts a full update path into a relative path; reverses get_full_path.
 *
 * @param  fullpath
 *         The absolute path to convert into a relative path.
 * return pointer to the location within fullpath where the relative path starts
 *        or fullpath itself if it already looks relative.
 */

#ifndef XP_WIN
static const NS_tchar* get_relative_path(const NS_tchar* fullpath) {
  if (fullpath[0] != '/') {
    return fullpath;
  }

  NS_tchar* prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;

  // If the path isn't long enough to be absolute, return it as-is.
  if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) {
    return fullpath;
  }

  return fullpath + NS_tstrlen(prefix) + 1;
}
#endif

/**
 * Gets the platform specific path and performs simple checks to the path. If
 * the path checks don't pass nullptr will be returned.
 *
 * @param  line
 *         The line from the manifest that contains the path.
 * @param  isdir
 *         Whether the path is a directory path. Defaults to false.
 * @return valid filesystem path or nullptr if the path checks fail.
 */

static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
  NS_tchar* path = mstrtok(kQuote, line);
  if (!path) {
    LOG(("get_valid_path: unable to determine path: " LOG_S, *line));
    return nullptr;
  }

  // All paths must be relative from the current working directory
  if (path[0] == NS_T('/')) {
    LOG(("get_valid_path: path must be relative: " LOG_S, path));
    return nullptr;
  }

#ifdef XP_WIN
  // All paths must be relative from the current working directory
  if (path[0] == NS_T('\\') || path[1] == NS_T(':')) {
    LOG(("get_valid_path: path must be relative: " LOG_S, path));
    return nullptr;
  }
#endif

  if (isdir) {
    // Directory paths must have a trailing forward slash.
    if (path[NS_tstrlen(path) - 1] != NS_T('/')) {
      LOG(
          ("get_valid_path: directory paths must have a trailing forward "
           "slash: " LOG_S,
           path));
      return nullptr;
    }

    // Remove the trailing forward slash because stat on Windows will return
    // ENOENT if the path has a trailing slash.
    path[NS_tstrlen(path) - 1] = NS_T('\0');
  }

  // Don't allow relative paths that resolve to a parent directory.
  if (NS_tstrstr(path, NS_T("..")) != nullptr) {
    LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
    return nullptr;
  }

  return path;
}

/*
 * Gets a quoted path. The return value is malloc'd and it is the responsibility
 * of the caller to free it.
 *
 * @param  path
 *         The path to quote.
 * @return On success the quoted path and nullptr otherwise.
 */

static NS_tchar* get_quoted_path(const NS_tchar* path) {
  size_t lenQuote = NS_tstrlen(kQuote);
  size_t lenPath = NS_tstrlen(path);
  size_t len = lenQuote + lenPath + lenQuote + 1;

  NS_tchar* s = (NS_tchar*)malloc(len * sizeof(NS_tchar));
  if (!s) {
    return nullptr;
  }

  NS_tchar* c = s;
  NS_tstrcpy(c, kQuote);
  c += lenQuote;
  NS_tstrcat(c, path);
  c += lenPath;
  NS_tstrcat(c, kQuote);
  c += lenQuote;
  *c = NS_T('\0');
  return s;
}

static void ensure_write_permissions(const NS_tchar* path) {
#ifdef XP_WIN
  (void)_wchmod(path, _S_IREAD | _S_IWRITE);
#else
  struct stat fs;
  if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) {
    (void)chmod(path, fs.st_mode | S_IWUSR);
  }
#endif
}

static int ensure_remove(const NS_tchar* path) {
  ensure_write_permissions(path);
  int rv = NS_tremove(path);
  if (rv) {
    LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
         path, rv, errno));
  }
  return rv;
}

// Remove the directory pointed to by path and all of its files and
// sub-directories.
static int ensure_remove_recursive(const NS_tchar* path,
                                   bool continueEnumOnFailure = false) {
  // We use lstat rather than stat here so that we can successfully remove
  // symlinks.
  struct NS_tstat_t sInfo;
  int rv = NS_tlstat(path, &sInfo);
  if (rv) {
    // This error is benign
    return rv;
  }
  if (!S_ISDIR(sInfo.st_mode)) {
    return ensure_remove(path);
  }

  NS_tDIR* dir;
  NS_tdirent* entry;

  dir = NS_topendir(path);
  if (!dir) {
    LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
         ", rv: %d, err: %d",
         path, rv, errno));
    return rv;
  }

  while ((entry = NS_treaddir(dir)) != 0) {
    if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
        NS_tstrcmp(entry->d_name, NS_T(".."))) {
      NS_tchar childPath[MAXPATHLEN];
      NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
                   NS_T("%s/%s"), path, entry->d_name);
      rv = ensure_remove_recursive(childPath);
      if (rv && !continueEnumOnFailure) {
        break;
      }
    }
  }

  NS_tclosedir(dir);

  if (rv == OK) {
    ensure_write_permissions(path);
    rv = NS_trmdir(path);
    if (rv) {
      LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
           ", rv: %d, err: %d",
           path, rv, errno));
    }
  }
  return rv;
}

static bool is_read_only(const NS_tchar* flags) {
  size_t length = NS_tstrlen(flags);
  if (length == 0) {
    return false;
  }

  // Make sure the string begins with "r"
  if (flags[0] != NS_T('r')) {
    return false;
  }

  // Look for "r+" or "r+b"
  if (length > 1 && flags[1] == NS_T('+')) {
    return false;
  }

  // Look for "rb+"
  if (NS_tstrcmp(flags, NS_T("rb+")) == 0) {
    return false;
  }

  return true;
}

static FILE* ensure_open(const NS_tchar* path, const NS_tchar* flags,
                         unsigned int options) {
  ensure_write_permissions(path);
  FILE* f = NS_tfopen(path, flags);
  if (is_read_only(flags)) {
    // Don't attempt to modify the file permissions if the file is being opened
    // in read-only mode.
    return f;
  }
  if (NS_tchmod(path, options) != 0) {
    if (f != nullptr) {
      fclose(f);
    }
    return nullptr;
  }
  struct NS_tstat_t ss;
  if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) {
    if (f != nullptr) {
      fclose(f);
    }
    return nullptr;
  }
  return f;
}

// Ensure that the directory containing this file exists.
static int ensure_parent_dir(const NS_tchar* path) {
  int rv = OK;

  NS_tchar* slash = (NS_tchar*)NS_tstrrchr(path, NS_T('/'));
  if (slash) {
    *slash = NS_T('\0');
    rv = ensure_parent_dir(path);
    // Only attempt to create the directory if we're not at the root
    if (rv == OK && *path) {
      rv = NS_tmkdir(path, 0755);
      // If the directory already exists, then ignore the error.
      if (rv < 0 && errno != EEXIST) {
        LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", "
             "err: %d",
             path, errno));
        rv = WRITE_ERROR;
      } else {
        rv = OK;
      }
    }
    *slash = NS_T('/');
  }
  return rv;
}

#ifdef XP_UNIX
static int ensure_copy_symlink(const NS_tchar* path, const NS_tchar* dest) {
  // Copy symlinks by creating a new symlink to the same target
  NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
  int rv = readlink(path, target, MAXPATHLEN);
  if (rv == -1) {
    LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
         path, errno));
    return READ_ERROR;
  }
  rv = symlink(target, dest);
  if (rv == -1) {
    LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S
         ", target: " LOG_S " err: %d",
         dest, target, errno));
    return READ_ERROR;
  }
  return 0;
}
#endif

// Copy the file named path onto a new file named dest.
static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) {
#ifdef XP_WIN
  // Fast path for Windows
  bool result = CopyFileW(path, dest, false);
  if (!result) {
    LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S
         ", lasterr: %lx",
         path, dest, GetLastError()));
    return WRITE_ERROR_FILE_COPY;
  }
  return OK;
#else
  struct NS_tstat_t ss;
  int rv = NS_tlstat(path, &ss);
  if (rv) {
    LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
         path, errno));
    return READ_ERROR;
  }

#  ifdef XP_UNIX
  if (S_ISLNK(ss.st_mode)) {
    return ensure_copy_symlink(path, dest);
  }
#  endif

  AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
  if (!infile) {
    LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
         path, errno));
    return READ_ERROR;
  }
  AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode));
  if (!outfile) {
    LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
         dest, errno));
    return WRITE_ERROR;
  }

  // This block size was chosen pretty arbitrarily but seems like a reasonable
  // compromise. For example, the optimal block size on a modern OS X machine
  // is 100k */
  const int blockSize = 32 * 1024;
  void* buffer = malloc(blockSize);
  if (!buffer) {
    return UPDATER_MEM_ERROR;
  }

  while (!feof(infile.get())) {
    size_t read = fread(buffer, 1, blockSize, infile);
    if (ferror(infile.get())) {
      LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", path,
           errno));
      free(buffer);
      return READ_ERROR;
    }

    size_t written = 0;

    while (written < read) {
      size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
      if (chunkWritten <= 0) {
        LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", dest,
             errno));
        free(buffer);
        return WRITE_ERROR_FILE_COPY;
      }

      written += chunkWritten;
    }
  }

  rv = NS_tchmod(dest, ss.st_mode);

  free(buffer);
  return rv;
#endif
}

template <unsigned N>
struct copy_recursive_skiplist {
  NS_tchar paths[N][MAXPATHLEN];

  void append(unsigned index, const NS_tchar* path, const NS_tchar* suffix) {
    NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
  }

  bool find(const NS_tchar* path) {
    for (int i = 0; i < static_cast<int>(N); ++i) {
      if (!NS_tstricmp(paths[i], path)) {
        return true;
      }
    }
    return false;
  }
};

// Copy all of the files and subdirectories under path to a new directory named
// dest. The path names in the skiplist will be skipped and will not be copied.
template <unsigned N>
static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest,
                                 copy_recursive_skiplist<N>& skiplist) {
  struct NS_tstat_t sInfo;
  int rv = NS_tlstat(path, &sInfo);
  if (rv) {
    LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S
         ", rv: %d, err: %d",
         path, rv, errno));
    return READ_ERROR;
  }

#ifdef XP_UNIX
  if (S_ISLNK(sInfo.st_mode)) {
    return ensure_copy_symlink(path, dest);
  }
#endif

  if (!S_ISDIR(sInfo.st_mode)) {
    return ensure_copy(path, dest);
  }

  rv = NS_tmkdir(dest, sInfo.st_mode);
  if (rv < 0 && errno != EEXIST) {
    LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S
         ", rv: %d, err: %d",
         path, rv, errno));
    return WRITE_ERROR;
  }

  NS_tDIR* dir;
  NS_tdirent* entry;

  dir = NS_topendir(path);
  if (!dir) {
    LOG(("ensure_copy_recursive: path is not a directory: " LOG_S
         ", rv: %d, err: %d",
         path, rv, errno));
    return READ_ERROR;
  }

  while ((entry = NS_treaddir(dir)) != 0) {
    if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
        NS_tstrcmp(entry->d_name, NS_T(".."))) {
      NS_tchar childPath[MAXPATHLEN];
      NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
                   NS_T("%s/%s"), path, entry->d_name);
      if (skiplist.find(childPath)) {
        continue;
      }
      NS_tchar childPathDest[MAXPATHLEN];
      NS_tsnprintf(childPathDest,
                   sizeof(childPathDest) / sizeof(childPathDest[0]),
                   NS_T("%s/%s"), dest, entry->d_name);
      rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
      if (rv) {
        break;
      }
    }
  }
  NS_tclosedir(dir);
  return rv;
}

// Renames the specified file to the new file specified. If the destination file
// exists it is removed.
static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
                       bool allowDirs = false) {
  int rv = ensure_parent_dir(dpath);
  if (rv) {
    return rv;
  }

  struct NS_tstat_t spathInfo;
  rv = NS_tstat(spath, &spathInfo);
  if (rv) {
    LOG(("rename_file: failed to read file status info: " LOG_S ", "
         "err: %d",
         spath, errno));
    return READ_ERROR;
  }

  if (!S_ISREG(spathInfo.st_mode)) {
    if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
      LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
           spath, errno));
      return RENAME_ERROR_EXPECTED_FILE;
    }
    LOG(("rename_file: proceeding to rename the directory"));
  }

  if (!NS_taccess(dpath, F_OK)) {
    if (ensure_remove(dpath)) {
      LOG(
          ("rename_file: destination file exists and could not be "
           "removed: " LOG_S,
           dpath));
      return WRITE_ERROR_DELETE_FILE;
    }
  }

  if (NS_trename(spath, dpath) != 0) {
    LOG(("rename_file: failed to rename file - src: " LOG_S ", "
         "dst:" LOG_S ", err: %d",
         spath, dpath, errno));
    return WRITE_ERROR;
  }

  return OK;
}

#ifdef XP_WIN
// Remove the directory pointed to by path and all of its files and
// sub-directories. If a file is in use move it to the tobedeleted directory
// and attempt to schedule removal of the file on reboot
static int remove_recursive_on_reboot(const NS_tchar* path,
                                      const NS_tchar* deleteDir) {
  struct NS_tstat_t sInfo;
  int rv = NS_tlstat(path, &sInfo);
  if (rv) {
    // This error is benign
    return rv;
  }

  if (!S_ISDIR(sInfo.st_mode)) {
    NS_tchar tmpDeleteFile[MAXPATHLEN + 1];
    GetUUIDTempFilePath(deleteDir, L"rep", tmpDeleteFile);
    if (NS_tremove(tmpDeleteFile) && errno != ENOENT) {
      LOG(("remove_recursive_on_reboot: failed to remove temporary file: " LOG_S
           ", err: %d",
           tmpDeleteFile, errno));
    }
    rv = rename_file(path, tmpDeleteFile, false);
    if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr,
                   MOVEFILE_DELAY_UNTIL_REBOOT)) {
      LOG(
          ("remove_recursive_on_reboot: file will be removed on OS "
           "reboot: " LOG_S,
           rv ? path : tmpDeleteFile));
    } else {
      LOG((
          "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
          "file: " LOG_S,
          rv ? path : tmpDeleteFile));
    }
    return rv;
  }

  NS_tDIR* dir;
  NS_tdirent* entry;

  dir = NS_topendir(path);
  if (!dir) {
    LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
         ", rv: %d, err: %d",
         path, rv, errno));
    return rv;
  }

  while ((entry = NS_treaddir(dir)) != 0) {
    if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
        NS_tstrcmp(entry->d_name, NS_T(".."))) {
      NS_tchar childPath[MAXPATHLEN];
      NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
                   NS_T("%s/%s"), path, entry->d_name);
      // There is no need to check the return value of this call since this
      // function is only called after an update is successful and there is not
      // much that can be done to recover if it isn't successful. There is also
      // no need to log the value since it will have already been logged.
      remove_recursive_on_reboot(childPath, deleteDir);
    }
  }

  NS_tclosedir(dir);

  if (rv == OK) {
    ensure_write_permissions(path);
    rv = NS_trmdir(path);
    if (rv) {
      LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
           ", rv: %d, err: %d",
           path, rv, errno));
    }
  }
  return rv;
}
#endif

//-----------------------------------------------------------------------------

// Create a backup of the specified file by renaming it.
static int backup_create(const NS_tchar* path) {
  NS_tchar backup[MAXPATHLEN];
  NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
               NS_T("%s") BACKUP_EXT, path);

  return rename_file(path, backup);
}

// Rename the backup of the specified file that was created by renaming it back
// to the original file.
static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) {
  NS_tchar backup[MAXPATHLEN];
  NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
               NS_T("%s") BACKUP_EXT, path);

  NS_tchar relBackup[MAXPATHLEN];
  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
               NS_T("%s") BACKUP_EXT, relPath);

  if (NS_taccess(backup, F_OK)) {
    LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
    return OK;
  }

  return rename_file(backup, path);
}

// Discard the backup of the specified file that was created by renaming it.
static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
  NS_tchar backup[MAXPATHLEN];
  NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
               NS_T("%s") BACKUP_EXT, path);

  NS_tchar relBackup[MAXPATHLEN];
  NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
               NS_T("%s") BACKUP_EXT, relPath);

  // Nothing to discard
  if (NS_taccess(backup, F_OK)) {
    return OK;
  }

  int rv = ensure_remove(backup);
#if defined(XP_WIN)
  if (rv && !sStagedUpdate && !sReplaceRequest) {
    LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
    NS_tchar path[MAXPATHLEN + 1];
    GetUUIDTempFilePath(gDeleteDirPath, L"moz", path);
    if (rename_file(backup, path)) {
      LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
           relBackup, relPath));
      return WRITE_ERROR_DELETE_BACKUP;
    }
    // The MoveFileEx call to remove the file on OS reboot will fail if the
    // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
    // but this is ok since the installer / uninstaller will delete the
    // directory containing the file along with its contents after an update is
    // applied, on reinstall, and on uninstall.
    if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
      LOG(
          ("backup_discard: file renamed and will be removed on OS "
           "reboot: " LOG_S,
           relPath));
    } else {
      LOG(
          ("backup_discard: failed to schedule OS reboot removal of "
           "file: " LOG_S,
           relPath));
    }
  }
#else
  if (rv) {
    return WRITE_ERROR_DELETE_BACKUP;
  }
#endif

  return OK;
}

// Helper function for post-processing a temporary backup.
static void backup_finish(const NS_tchar* path, const NS_tchar* relPath,
                          int status) {
  if (status == OK) {
    backup_discard(path, relPath);
  } else {
    backup_restore(path, relPath);
  }
}

//-----------------------------------------------------------------------------

static int DoUpdate();

class Action {
 public:
  Action() : mProgressCost(1), mNext(nullptr) {}
  virtual ~Action() = default;

  virtual int Parse(NS_tchar* line) = 0;

  // Do any preprocessing to ensure that the action can be performed.  Execute
  // will be called if this Action and all others return OK from this method.
  virtual int Prepare() = 0;

  // Perform the operation.  Return OK to indicate success.  After all actions
  // have been executed, Finish will be called.  A requirement of Execute is
  // that its operation be reversable from Finish.
  virtual int Execute() = 0;

  // Finish is called after execution of all actions.  If status is OK, then
  // all actions were successfully executed.  Otherwise, some action failed.
  virtual void Finish(int status) = 0;

  int mProgressCost;

 private:
  Action* mNext;

  friend class ActionList;
};

class RemoveFile : public Action {
 public:
  RemoveFile() : mSkip(0) {}

  int Parse(NS_tchar* line) override;
  int Prepare() override;
  int Execute() override;
  void Finish(int status) override;

 private:
  mozilla::UniquePtr<NS_tchar[]> mFile;
  mozilla::UniquePtr<NS_tchar[]> mRelPath;
  int mSkip;
};

int RemoveFile::Parse(NS_tchar* line) {
  // format "<deadfile>"

  NS_tchar* validPath = get_valid_path(&line);
  if (!validPath) {
    return PARSE_ERROR;
  }

  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mRelPath.get(), validPath);

  mFile.reset(get_full_path(validPath));
  if (!mFile) {
    return PARSE_ERROR;
  }

  return OK;
}

int RemoveFile::Prepare() {
  // Skip the file if it already doesn't exist.
  int rv = NS_taccess(mFile.get(), F_OK);
  if (rv) {
    mSkip = 1;
    mProgressCost = 0;
    return OK;
  }

  LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));

  // Make sure that we're actually a file...
  struct NS_tstat_t fileInfo;
  rv = NS_tstat(mFile.get(), &fileInfo);
  if (rv) {
    LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
         errno));
    return READ_ERROR;
  }

  if (!S_ISREG(fileInfo.st_mode)) {
    LOG(("path present, but not a file: " LOG_S, mFile.get()));
    return DELETE_ERROR_EXPECTED_FILE;
  }

  NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/'));
  if (slash) {
    *slash = NS_T('\0');
    rv = NS_taccess(mFile.get(), W_OK);
    *slash = NS_T('/');
  } else {
    rv = NS_taccess(NS_T("."), W_OK);
  }

  if (rv) {
    LOG(("access failed: %d", errno));
    return WRITE_ERROR_FILE_ACCESS_DENIED;
  }

  return OK;
}

int RemoveFile::Execute() {
  if (mSkip) {
    return OK;
  }

  LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));

  // The file is checked for existence here and in Prepare since it might have
  // been removed by a separate instruction: bug 311099.
  int rv = NS_taccess(mFile.get(), F_OK);
  if (rv) {
    LOG(("file cannot be removed because it does not exist; skipping"));
    mSkip = 1;
    return OK;
  }

  if (sStagedUpdate) {
    // Staged updates don't need backup files so just remove it.
    rv = ensure_remove(mFile.get());
    if (rv) {
      return rv;
    }
  } else {
    // Rename the old file. It will be removed in Finish.
    rv = backup_create(mFile.get());
    if (rv) {
      LOG(("backup_create failed: %d", rv));
      return rv;
    }
  }

  return OK;
}

void RemoveFile::Finish(int status) {
  if (mSkip) {
    return;
  }

  LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));

  // Staged updates don't create backup files.
  if (!sStagedUpdate) {
    backup_finish(mFile.get(), mRelPath.get(), status);
  }
}

class RemoveDir : public Action {
 public:
  RemoveDir() : mSkip(0) {}

  int Parse(NS_tchar* line) override;
  int Prepare() override;  // check that the source dir exists
  int Execute() override;
  void Finish(int status) override;

 private:
  mozilla::UniquePtr<NS_tchar[]> mDir;
  mozilla::UniquePtr<NS_tchar[]> mRelPath;
  int mSkip;
};

int RemoveDir::Parse(NS_tchar* line) {
  // format "<deaddir>/"

  NS_tchar* validPath = get_valid_path(&line, true);
  if (!validPath) {
    return PARSE_ERROR;
  }

  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mRelPath.get(), validPath);

  mDir.reset(get_full_path(validPath));
  if (!mDir) {
    return PARSE_ERROR;
  }

  return OK;
}

int RemoveDir::Prepare() {
  // We expect the directory to exist if we are to remove it.
  int rv = NS_taccess(mDir.get(), F_OK);
  if (rv) {
    mSkip = 1;
    mProgressCost = 0;
    return OK;
  }

  LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));

  // Make sure that we're actually a dir.
  struct NS_tstat_t dirInfo;
  rv = NS_tstat(mDir.get(), &dirInfo);
  if (rv) {
    LOG(("failed to read directory status info: " LOG_S ", err: %d",
         mRelPath.get(), errno));
    return READ_ERROR;
  }

  if (!S_ISDIR(dirInfo.st_mode)) {
    LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
    return DELETE_ERROR_EXPECTED_DIR;
  }

  rv = NS_taccess(mDir.get(), W_OK);
  if (rv) {
    LOG(("access failed: %d, %d", rv, errno));
    return WRITE_ERROR_DIR_ACCESS_DENIED;
  }

  return OK;
}

int RemoveDir::Execute() {
  if (mSkip) {
    return OK;
  }

  LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));

  // The directory is checked for existence at every step since it might have
  // been removed by a separate instruction: bug 311099.
  int rv = NS_taccess(mDir.get(), F_OK);
  if (rv) {
    LOG(("directory no longer exists; skipping"));
    mSkip = 1;
  }

  return OK;
}

void RemoveDir::Finish(int status) {
  if (mSkip || status != OK) {
    return;
  }

  LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));

  // The directory is checked for existence at every step since it might have
  // been removed by a separate instruction: bug 311099.
  int rv = NS_taccess(mDir.get(), F_OK);
  if (rv) {
    LOG(("directory no longer exists; skipping"));
    return;
  }

  if (status == OK) {
    if (NS_trmdir(mDir.get())) {
      LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
           mRelPath.get(), rv, errno));
    }
  }
}

class AddFile : public Action {
 public:
  AddFile() : mAdded(false) {}

  int Parse(NS_tchar* line) override;
  int Prepare() override;
  int Execute() override;
  void Finish(int status) override;

 private:
  mozilla::UniquePtr<NS_tchar[]> mFile;
  mozilla::UniquePtr<NS_tchar[]> mRelPath;
  bool mAdded;
};

int AddFile::Parse(NS_tchar* line) {
  // format "<newfile>"

  NS_tchar* validPath = get_valid_path(&line);
  if (!validPath) {
    return PARSE_ERROR;
  }

  mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mRelPath.get(), validPath);

  mFile.reset(get_full_path(validPath));
  if (!mFile) {
    return PARSE_ERROR;
  }

  return OK;
}

int AddFile::Prepare() {
  LOG(("PREPARE ADD " LOG_S, mRelPath.get()));

  return OK;
}

int AddFile::Execute() {
  LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));

  int rv;

  // First make sure that we can actually get rid of any existing file.
  rv = NS_taccess(mFile.get(), F_OK);
  if (rv == 0) {
    if (sStagedUpdate) {
      // Staged updates don't need backup files so just remove it.
      rv = ensure_remove(mFile.get());
    } else {
      rv = backup_create(mFile.get());
    }
    if (rv) {
      return rv;
    }
  } else {
    rv = ensure_parent_dir(mFile.get());
    if (rv) {
      return rv;
    }
  }

#ifdef XP_WIN
  char sourcefile[MAXPATHLEN];
  if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
                           MAXPATHLEN, nullptr, nullptr)) {
    LOG(("error converting wchar to utf8: %lu", GetLastError()));
    return STRING_CONVERSION_ERROR;
  }

  rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
#else
  rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
#endif
  if (!rv) {
    mAdded = true;
  }
  return rv;
}

void AddFile::Finish(int status) {
  LOG(("FINISH ADD " LOG_S, mRelPath.get()));
  // Staged updates don't create backup files.
  if (!sStagedUpdate) {
    // When there is an update failure and a file has been added it is removed
    // here since there might not be a backup to replace it.
    if (status && mAdded) {
      if (NS_tremove(mFile.get()) && errno != ENOENT) {
        LOG(("non-fatal error after update failure removing added file: " LOG_S
             ", err: %d",
             mFile.get(), errno));
      }
    }
    backup_finish(mFile.get(), mRelPath.get(), status);
  }
}

class PatchFile : public Action {
 public:
  PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) {}

  ~PatchFile() override;

  int Parse(NS_tchar* line) override;
  int Prepare() override;  // should check for patch file and for checksum here
  int Execute() override;
  void Finish(int status) override;

 private:
  int LoadSourceFile(FILE* ofile);

  static int sPatchIndex;

  const NS_tchar* mPatchFile;
  mozilla::UniquePtr<NS_tchar[]> mFile;
  mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
  int mPatchIndex;
  MBSPatchHeader header;
  unsigned char* buf;
  NS_tchar spath[MAXPATHLEN];
  AutoFile mPatchStream;
};

int PatchFile::sPatchIndex = 0;

PatchFile::~PatchFile() {
  // Make sure mPatchStream gets unlocked on Windows; the system will do that,
  // but not until some indeterminate future time, and we want determinism.
  // Normally this happens at the end of Execute, when we close the stream;
  // this call is here in case Execute errors out.
#ifdef XP_WIN
  if (mPatchStream) {
    UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
  }
#endif
  // Patch files are written to the <working_dir>/updating directory which is
  // removed after the update has finished so don't delete patch files here.

  if (buf) {
    free(buf);
  }
}

int PatchFile::LoadSourceFile(FILE* ofile) {
  struct stat os;
  int rv = fstat(fileno((FILE*)ofile), &os);
  if (rv) {
    LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", "
         "err: %d",
         mFileRelPath.get(), errno));
    return READ_ERROR;
  }

  if (uint32_t(os.st_size) != header.slen) {
    LOG(
        ("LoadSourceFile: destination file size %d does not match expected "
         "size %d",
         uint32_t(os.st_size), header.slen));
    return LOADSOURCE_ERROR_WRONG_SIZE;
  }

  buf = (unsigned char*)malloc(header.slen);
  if (!buf) {
    return UPDATER_MEM_ERROR;
  }

  size_t r = header.slen;
  unsigned char* rb = buf;
  while (r) {
    const size_t count = mmin(SSIZE_MAX, r);
    size_t c = fread(rb, 1, count, ofile);
    if (c != count) {
      LOG(("LoadSourceFile: error reading destination file: " LOG_S,
           mFileRelPath.get()));
      return READ_ERROR;
    }

    r -= c;
    rb += c;
  }

  // Verify that the contents of the source file correspond to what we expect.

  unsigned int crc = crc32(buf, header.slen);

  if (crc != header.scrc32) {
    LOG(
        ("LoadSourceFile: destination file crc %d does not match expected "
         "crc %d",
         crc, header.scrc32));
    return CRC_ERROR;
  }

  return OK;
}

int PatchFile::Parse(NS_tchar* line) {
  // format "<patchfile>" "<filetopatch>"

  // Get the path to the patch file inside of the mar
  mPatchFile = mstrtok(kQuote, &line);
  if (!mPatchFile) {
    return PARSE_ERROR;
  }

  // consume whitespace between args
  NS_tchar* q = mstrtok(kQuote, &line);
  if (!q) {
    return PARSE_ERROR;
  }

  NS_tchar* validPath = get_valid_path(&line);
  if (!validPath) {
    return PARSE_ERROR;
  }

  mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
  NS_tstrcpy(mFileRelPath.get(), validPath);

  mFile.reset(get_full_path(validPath));
  if (!mFile) {
    return PARSE_ERROR;
  }

  return OK;
}

int PatchFile::Prepare() {
  LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));

  // extract the patch to a temporary file
  mPatchIndex = sPatchIndex++;

  NS_tsnprintf(spath, sizeof(spath) / sizeof(spath[0]),
               NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);

  // The removal of pre-existing patch files here is in case a previous update
  // crashed and left these files behind.
  if (NS_tremove(spath) && errno != ENOENT) {
    LOG(("failure removing pre-existing patch file: " LOG_S ", err: %d", spath,
         errno));
    return WRITE_ERROR;
  }

  mPatchStream = NS_tfopen(spath, NS_T("wb+"));
  if (!mPatchStream) {
    return WRITE_ERROR;
  }

#ifdef XP_WIN
  // Lock the patch file, so it can't be messed with between
  // when we're done creating it and when we go to apply it.
  if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1)) {
    LOG(("Couldn't lock patch file: %lu", GetLastError()));
    return LOCK_ERROR_PATCH_FILE;
  }

  char sourcefile[MAXPATHLEN];
  if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
                           nullptr, nullptr)) {
    LOG(("error converting wchar to utf8: %lu", GetLastError()));
    return STRING_CONVERSION_ERROR;
  }

  int rv = gArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
#else
  int rv = gArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
#endif

  return rv;
}

int PatchFile::Execute() {
  LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));

  fseek(mPatchStream, 0, SEEK_SET);

  int rv = MBS_ReadHeader(mPatchStream, &header);
  if (rv) {
    return rv;
  }

  FILE* origfile = nullptr;
#ifdef XP_WIN
  if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
    // Read from the copy of the callback when patching since the callback can't
    // be opened for reading to prevent the application from being launched.
    origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
  } else {
    origfile = NS_tfopen(mFile.get(), NS_T("rb"));
  }
#else
  origfile = NS_tfopen(mFile.get(), NS_T("rb"));
#endif

  if (!origfile) {
    LOG(("unable to open destination file: " LOG_S ", err: %d",
         mFileRelPath.get(), errno));
    return READ_ERROR;
  }

  rv = LoadSourceFile(origfile);
  fclose(origfile);
  if (rv) {
    LOG(("LoadSourceFile failed"));
    return rv;
  }

  // Rename the destination file if it exists before proceeding so it can be
  // used to restore the file to its original state if there is an error.
  struct NS_tstat_t ss;
  rv = NS_tstat(mFile.get(), &ss);
  if (rv) {
    LOG(("failed to read file status info: " LOG_S ", err: %d",
         mFileRelPath.get(), errno));
    return READ_ERROR;
  }

  // Staged updates don't need backup files.
  if (!sStagedUpdate) {
    rv = backup_create(mFile.get());
    if (rv) {
      return rv;
    }
  }

#if defined(HAVE_POSIX_FALLOCATE)
  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
  posix_fallocate(fileno((FILE*)ofile), 0, header.dlen);
#elif defined(XP_WIN)
  bool shouldTruncate = true;
  // Creating the file, setting the size, and then closing the file handle
  // lessens fragmentation more than any other method tested. Other methods that
  // have been tested are:
  // 1. _chsize / _chsize_s reduced fragmentation though not completely.
  // 2. _get_osfhandle and then setting the size reduced fragmentation though
  //    not completely. There are also reports of _get_osfhandle failing on
  //    mingw.
  HANDLE hfile = CreateFileW(mFile.get(), GENERIC_WRITE, 0, nullptr,
                             CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

  if (hfile != INVALID_HANDLE_VALUE) {
    if (SetFilePointer(hfile, header.dlen, nullptr, FILE_BEGIN) !=
            INVALID_SET_FILE_POINTER &&
        SetEndOfFile(hfile) != 0) {
      shouldTruncate = false;
    }
    CloseHandle(hfile);
  }

  AutoFile ofile(ensure_open(
      mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"), ss.st_mode));
#elif defined(XP_MACOSX)
  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
  // Modified code from FileUtils.cpp
  fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
  // Try to get a continous chunk of disk space
  rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
  if (rv == -1) {
    // OK, perhaps we are too fragmented, allocate non-continuous
    store.fst_flags = F_ALLOCATEALL;
    rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
  }

  if (rv != -1) {
    ftruncate(fileno((FILE*)ofile), header.dlen);
  }
#else
  AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
#endif

  if (ofile == nullptr) {
    LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
         errno));
    return WRITE_ERROR_OPEN_PATCH_FILE;
  }

#ifdef XP_WIN
  if (!shouldTruncate) {
    fseek(ofile, 0, SEEK_SET);
  }
#endif

  rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);

  // Go ahead and do a bit of cleanup now to minimize runtime overhead.
  // Make sure mPatchStream gets unlocked on Windows; the system will do that,
  // but not until some indeterminate future time, and we want determinism.
#ifdef XP_WIN
  UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
#endif
  // Set mPatchStream to nullptr to make AutoFile close the file,
  // so it can be deleted on Windows.
  mPatchStream = nullptr;
  // Patch files are written to the <working_dir>/updating directory which is
  // removed after the update has finished so don't delete patch files here.
  spath[0] = NS_T('\0');
  free(buf);
  buf = nullptr;

  return rv;
}

void PatchFile::Finish(int status) {
  LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));

  // Staged updates don't create backup files.
  if (!sStagedUpdate) {
    backup_finish(mFile.get(), mFileRelPath.get(), status);
  }
}

class AddIfFile : public AddFile {
 public:
  int Parse(NS_tchar* line) override;
  int Prepare() override;
  int Execute() override;
  void Finish(int status) override;

 protected:
  mozilla::UniquePtr<NS_tchar[]> mTestFile;
};

int AddIfFile::Parse(NS_tchar* line) {
  // format "<testfile>" "<newfile>"

  mTestFile.reset(get_full_path(get_valid_path(&line)));
  if (!mTestFile) {
    return PARSE_ERROR;
  }

  // consume whitespace between args
  NS_tchar* q = mstrtok(kQuote, &line);
  if (!q) {
    return PARSE_ERROR;
  }

  return AddFile::Parse(line);
}

int AddIfFile::Prepare() {
  // If the test file does not exist, then skip this action.
  if (NS_taccess(mTestFile.get(), F_OK)) {
    mTestFile = nullptr;
    return OK;
  }

  return AddFile::Prepare();
}

int AddIfFile::Execute() {
  if (!mTestFile) {
    return OK;
  }

  return AddFile::Execute();
}

void AddIfFile::Finish(int status) {
  if (!mTestFile) {
    return;
  }

  AddFile::Finish(status);
}

class AddIfNotFile : public AddFile {
 public:
  int Parse(NS_tchar* line) override;
  int Prepare() override;
  int Execute() override;
  void Finish(int status) override;

 protected:
  mozilla::UniquePtr<NS_tchar[]> mTestFile;
};

int AddIfNotFile::Parse(NS_tchar* line) {
  // format "<testfile>" "<newfile>"

  mTestFile.reset(get_full_path(get_valid_path(&line)));
  if (!mTestFile) {
    return PARSE_ERROR;
  }

  // consume whitespace between args
  NS_tchar* q = mstrtok(kQuote, &line);
  if (!q) {
    return PARSE_ERROR;
  }

  return AddFile::Parse(line);
}

int AddIfNotFile::Prepare() {
  // If the test file exists, then skip this action.
  if (!NS_taccess(mTestFile.get(), F_OK)) {
    mTestFile = NULL;
    return OK;
  }

  return AddFile::Prepare();
}

int AddIfNotFile::Execute() {
  if (!mTestFile) {
    return OK;
  }

  return AddFile::Execute();
}

void AddIfNotFile::Finish(int status) {
  if (!mTestFile) {
    return;
  }

  AddFile::Finish(status);
}

class PatchIfFile : public PatchFile {
 public:
  int Parse(NS_tchar* line) override;
  int Prepare() override;  // should check for patch file and for checksum here
  int Execute() override;
  void Finish(int status) override;

 private:
  mozilla::UniquePtr<NS_tchar[]> mTestFile;
};

int PatchIfFile::Parse(NS_tchar* line) {
  // format "<testfile>" "<patchfile>" "<filetopatch>"

  mTestFile.reset(get_full_path(get_valid_path(&line)));
  if (!mTestFile) {
    return PARSE_ERROR;
  }

  // consume whitespace between args
  NS_tchar* q = mstrtok(kQuote, &line);
  if (!q) {
    return PARSE_ERROR;
  }

  return PatchFile::Parse(line);
}

int PatchIfFile::Prepare() {
  // If the test file does not exist, then skip this action.
  if (NS_taccess(mTestFile.get(), F_OK)) {
    mTestFile = nullptr;
    return OK;
  }

  return PatchFile::Prepare();
}

int PatchIfFile::Execute() {
  if (!mTestFile) {
    return OK;
  }

  return PatchFile::Execute();
}

void PatchIfFile::Finish(int status) {
  if (!mTestFile) {
    return;
  }

  PatchFile::Finish(status);
}

//-----------------------------------------------------------------------------

#ifdef XP_WIN
#  include "nsWindowsRestart.cpp"
#  include "nsWindowsHelpers.h"
#  include "uachelper.h"
#  ifdef MOZ_MAINTENANCE_SERVICE
#    include "pathhash.h"
#  endif

/**
 * Launch the post update application (helper.exe). It takes in the path of the
 * callback application to calculate the path of helper.exe. For service updates
 * this is called from both the system account and the current user account.
 *
 * @param  installationDir The path to the callback application binary.
 * @param  updateInfoDir   The directory where update info is stored.
 * @return true if there was no error starting the process.
 */

bool LaunchWinPostProcess(const WCHAR* installationDir,
                          const WCHAR* updateInfoDir) {
  WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'};
  wcsncpy(workingDirectory, installationDir, MAX_PATH);

  // Launch helper.exe to perform post processing (e.g. registry and log file
  // modifications) for the update.
  WCHAR inifile[MAX_PATH + 1] = {L'\0'};
  wcsncpy(inifile, installationDir, MAX_PATH);
  if (!PathAppendSafe(inifile, L"updater.ini")) {
    LOG(
        ("LaunchWinPostProcess failed because PathAppendSafe failed when "
         "getting INI path"));
    return false;
  }

  WCHAR exefile[MAX_PATH + 1];
  WCHAR exearg[MAX_PATH + 1];
  if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
                                exefile, MAX_PATH + 1, inifile)) {
    LOG(("LaunchWinPostProcess failed due to failure to retrieve ExeRelPath"));
    return false;
  }

  if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
                                MAX_PATH + 1, inifile)) {
    LOG(("LaunchWinPostProcess failed due to failure to retrieve ExeArg"));
    return false;
  }

  // The relative path must not contain directory traversals, current directory,
  // or colons.
  if (wcsstr(exefile, L"..") != nullptr || wcsstr(exefile, L"./") != nullptr ||
      wcsstr(exefile, L".\\") != nullptr || wcsstr(exefile, L":") != nullptr) {
    LOG(
        ("LaunchWinPostProcess failed because executable path contains "
         "disallowed characters"));
    return false;
  }

  // The relative path must not start with a decimal point, backslash, or
  // forward slash.
  if (exefile[0] == L'.' || exefile[0] == L'\\' || exefile[0] == L'/') {
    LOG(("LaunchWinPostProcess failed because first character is invalid"));
    return false;
  }

  WCHAR exefullpath[MAX_PATH + 1] = {L'\0'};
  wcsncpy(exefullpath, installationDir, MAX_PATH);
  if (!PathAppendSafe(exefullpath, exefile)) {
    LOG(
        ("LaunchWinPostProcess failed because PathAppendSafe failed when "
         "getting full executable path"));
    return false;
  }

  if (!IsValidFullPath(exefullpath)) {
    LOG(
        ("LaunchWinPostProcess failed because executable path is not a valid, "
         "full path"));
    return false;
  }

#  if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE)
  if (sUsingService &&
      !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
    LOG(
        ("LaunchWinPostProcess failed because the binary doesn't match the "
         "allowed certificates"));
    return false;
  }
#  endif

  WCHAR dlogFile[MAX_PATH + 1];
  if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
    LOG(("LaunchWinPostProcess failed because dlogFile path is unavailable"));
    return false;
  }

  WCHAR slogFile[MAX_PATH + 1] = {L'\0'};
  if (gCopyOutputFiles) {
    if (!GetSecureOutputFilePath(gPatchDirPath, L".log", slogFile)) {
      LOG(
          ("LaunchWinPostProcess failed because a secure slogFile path is "
           "unavailable"));
      return false;
    }
  } else {
    wcsncpy(slogFile, updateInfoDir, MAX_PATH);
    if (!PathAppendSafe(slogFile, UpdateLogFilename())) {
      LOG(("LaunchWinPostProcess failed because slogFile path is unavailable"));
      return false;
    }
  }

  WCHAR dummyArg[14] = {L'\0'};
  wcsncpy(dummyArg, L"argv0ignored ",
          sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);

  size_t len = wcslen(exearg) + wcslen(dummyArg);
  WCHAR* cmdline = (WCHAR*)malloc((len + 1) * sizeof(WCHAR));
  if (!cmdline) {
    LOG(
        ("LaunchWinPostProcess failed due to failure to allocate %zu wchars "
         "for cmdline",
         len + 1));
    return false;
  }

  wcsncpy(cmdline, dummyArg, len);
  wcscat(cmdline, exearg);

  // We want to launch the post update helper app to update the Windows
  // registry even if there is a failure with removing the uninstall.update
  // file or copying the update.log file.
  CopyFileW(slogFile, dlogFile, false);

  STARTUPINFOW si = {sizeof(si), 0};
  si.lpDesktop = const_cast<LPWSTR>(L"");  // -Wwritable-strings
  PROCESS_INFORMATION pi = {0};

  // Invoke post-update with a minimal environment to avoid environment
  // variables intended to relaunch Firefox impacting post-update operations, in
  // particular background tasks.  The updater will invoke the callback
  // application with the current (non-minimal) environment.
  //
  // N.b.: two null terminating characters!  The first terminates a non-existent
  // key-value pair, the second (automatically added) terminates the block of
  // key-value pairs.
  const WCHAR* emptyEnvironment = L"\0";

  bool ok =
      CreateProcessW(exefullpath, cmdline,
                     nullptr,  // no special security attributes
                     nullptr,  // no special thread attributes
                     false,    // don't inherit filehandles
                     0,        // No special process creation flags
                     (LPVOID)emptyEnvironment, workingDirectory, &si, &pi);
  free(cmdline);
  if (ok) {
    LOG(("LaunchWinPostProcess - Waiting for process to complete"));
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    LOG(("LaunchWinPostProcess - Process completed"));
  } else {
    LOG(("LaunchWinPostProcess - CreateProcessW failed: %lu", GetLastError()));
  }
  return ok;
}

#endif

static void LaunchCallbackApp(const NS_tchar* workingDir, int argc,
                              NS_tchar** argv, bool usingService) {
  putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));

  // Run from the specified working directory (see bug 312360).
  if (NS_tchdir(workingDir) != 0) {
    LOG(("Warning: chdir failed"));
  }

#if defined(USE_EXECV)
  execv(argv[0], argv);
#elif defined(XP_MACOSX)
  LaunchMacApp(argc, (const char**)argv);
#elif defined(XP_WIN)
  // Do not allow the callback to run when running an update through the
  // service as session 0.  The unelevated updater.exe will do the launching.
  if (!usingService) {
    HANDLE hProcess;
    if (WinLaunchChild(argv[0], argc, argv, nullptr, &hProcess)) {
      // Keep the current process around until the callback process has created
      // its message queue, to avoid the launched process's windows being forced
      // into the background.
      mozilla::WaitForInputIdle(hProcess);
      CloseHandle(hProcess);
    }
  }
#else
#  warning "Need implementaton of LaunchCallbackApp"
#endif
}

static bool WriteToFile(const NS_tchar* aFilename, const char* aStatus) {
  LOG(("Writing status to file: %s", aStatus));

  NS_tchar statusFilePath[MAXPATHLEN + 1] = {NS_T('\0')};
#if defined(XP_WIN)
  if (gUseSecureOutputPath) {
    if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
      LOG(("WriteToFile failed to get secure output path"));
      return false;
    }
  } else {
    NS_tsnprintf(statusFilePath,
                 sizeof(statusFilePath) / sizeof(statusFilePath[0]),
                 NS_T("%s\\%s"), gPatchDirPath, aFilename);
  }
#else
  NS_tsnprintf(statusFilePath,
               sizeof(statusFilePath) / sizeof(statusFilePath[0]),
               NS_T("%s/%s"), gPatchDirPath, aFilename);
  // Make sure that the directory for the update status file exists
  if (ensure_parent_dir(statusFilePath)) {
    LOG(("WriteToFile failed to ensure parent directory's existence"));
    return false;
  }
#endif

  AutoFile statusFile(NS_tfopen(statusFilePath, NS_T("wb+")));
  if (statusFile == nullptr) {
    LOG(("WriteToFile failed to open status file: %d", errno));
    return false;
  }

  if (fwrite(aStatus, strlen(aStatus), 1, statusFile) != 1) {
    LOG(("WriteToFile failed to write to status file: %d", errno));
    return false;
  }

#if defined(XP_WIN)
  if (gUseSecureOutputPath) {
    // This is done after the update status file has been written so if the
    // write to the update status file fails an existing update status file
    // won't be used.
    if (!WriteSecureIDFile(gPatchDirPath)) {
      LOG(("WriteToFile failed to write secure ID file"));
      return false;
    }
  }
#endif

  return true;
}

/**
 * Writes a string to the update.status file.
 *
 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
 *       because the output_finish function copies the update status file for
 *       the elevated updater and writing the status file after calling
 *       output_finish will overwrite it.
 *
 * @param  aStatus
 *         The string to write to the update.status file.
 * @return true on success.
 */

static bool WriteStatusFile(const char* aStatus) {
  return WriteToFile(NS_T("update.status"), aStatus);
}

/**
 * Writes a string to the update.status file based on the status param.
 *
 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
 *       because the output_finish function copies the update status file for
 *       the elevated updater and writing the status file after calling
 *       output_finish will overwrite it.
 *
 * @param  status
 *         A status code used to determine what string to write to the
 *         update.status file (see code).
 */

static void WriteStatusFile(int status) {
  const char* text;

  char buf[32];
  if (status == OK) {
    if (sStagedUpdate) {
      text = "applied\n";
    } else {
      text = "succeeded\n";
    }
  } else {
    snprintf(buf, sizeof(buf) / sizeof(buf[0]), "failed: %d\n", status);
    text = buf;
  }

  WriteStatusFile(text);
}

#if defined(XP_WIN)
/*
 * Parses the passed contents of an update status file and checks if the
 * contained status matches the expected status.
 *
 * @param  statusString The status file contents.
 * @param  expectedStatus The status to compare the update status file's
 *                        contents against.
 * @param  errorCode Optional out parameter. If a pointer is passed and the
 *                   update status file contains an error code, the code
 *                   will be returned via the out parameter. If a pointer is
 *                   passed and the update status file does not contain an error
 *                   code, or any error code after the status could not be
 *                   parsed, mozilla::Nothing will be returned via this
 *                   parameter.
 * @return true if the status is set to the value indicated by expectedStatus.
 */

static bool UpdateStatusIs(const char* statusString, const char* expectedStatus,
                           mozilla::Maybe<int>* errorCode = nullptr) {
  if (errorCode) {
    *errorCode = mozilla::Nothing();
  }

  // Parse the update status file. Expected format is:
  //   Update status string
  //   Optionally followed by:
  //     Colon character (':')
  //     Space character (' ')
  //     Integer error code
  //   Newline character
  const char* statusEnd = strchr(statusString, ':');
  if (statusEnd == nullptr) {
    statusEnd = strchr(statusString, '\n');
  }
  if (statusEnd == nullptr) {
    statusEnd = strchr(statusString, '\0');
  }
  size_t statusLen = statusEnd - statusString;
  size_t expectedStatusLen = strlen(expectedStatus);

  bool statusMatch =
      statusLen == expectedStatusLen &&
      strncmp(statusString, expectedStatus, expectedStatusLen) == 0;

  // We only need to continue parsing if (a) there is a place to store the error
  // code if we parse it, and (b) there is a status code to parse. If the status
  // string didn't end with a ':', there won't be an error code after it.
  if (!errorCode || *statusEnd != ':') {
    return statusMatch;
  }

  const char* errorCodeStart = statusEnd + 1;
  char* errorCodeEnd = nullptr;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=98 G=94

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.