summaryrefslogtreecommitdiff
path: root/onlineupdate/source/update/updater
diff options
context:
space:
mode:
authorJan Holesovsky <kendy@collabora.com>2015-06-16 11:12:17 +0200
committerJan Holesovsky <kendy@collabora.com>2015-06-29 17:43:30 +0200
commitdba90c047e4d4b743175d1b6d7355b49aa60c474 (patch)
treec158b4dec3b5ba39216491c067a913ee298e6bd9 /onlineupdate/source/update/updater
parentdc1dd18d0650fb0e054791381062b33a5609c424 (diff)
online update: MAR-based online update - initial import from Mozilla.
This commit copies several source files around the Mozilla's online update from the Mozilla sources to LibreOffice. The hope is that we will be able to modify it so that LibreOffice can use the same update mechanism as Firefox, including downloading the packs on background, and applying them on the next start. changeset: 248917:ce863f9d8864 The following locations in the Mozzila sources were copied: firefox/modules/libmar -> onlineupdate/source/libmar firefox/toolkit/mozapps/update -> onlineupdate/source/update JavaScript parts were omitted. Change-Id: I0c92dc0bf734bfd5d8746822f674e162d64fa62f
Diffstat (limited to 'onlineupdate/source/update/updater')
-rw-r--r--onlineupdate/source/update/updater/Makefile.in61
-rw-r--r--onlineupdate/source/update/updater/archivereader.cpp324
-rw-r--r--onlineupdate/source/update/updater/archivereader.h41
-rw-r--r--onlineupdate/source/update/updater/automounter_gonk.cpp251
-rw-r--r--onlineupdate/source/update/updater/automounter_gonk.h48
-rw-r--r--onlineupdate/source/update/updater/bspatch.cpp187
-rw-r--r--onlineupdate/source/update/updater/bspatch.h93
-rw-r--r--onlineupdate/source/update/updater/dep1.derbin0 -> 671 bytes
-rw-r--r--onlineupdate/source/update/updater/dep2.derbin0 -> 671 bytes
-rw-r--r--onlineupdate/source/update/updater/gen_cert_header.py25
-rw-r--r--onlineupdate/source/update/updater/launchchild_osx.mm138
-rw-r--r--onlineupdate/source/update/updater/loaddlls.cpp103
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Info.plist35
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/PkgInfo1
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in7
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib19
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib22
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 5567 bytes
-rw-r--r--onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icnsbin0 -> 55969 bytes
-rw-r--r--onlineupdate/source/update/updater/module.ver1
-rw-r--r--onlineupdate/source/update/updater/moz.build13
-rw-r--r--onlineupdate/source/update/updater/nightly_aurora_level3_primary.derbin0 -> 679 bytes
-rw-r--r--onlineupdate/source/update/updater/nightly_aurora_level3_secondary.derbin0 -> 679 bytes
-rw-r--r--onlineupdate/source/update/updater/progressui.h37
-rw-r--r--onlineupdate/source/update/updater/progressui_gonk.cpp53
-rw-r--r--onlineupdate/source/update/updater/progressui_gtk.cpp131
-rw-r--r--onlineupdate/source/update/updater/progressui_null.cpp25
-rw-r--r--onlineupdate/source/update/updater/progressui_osx.mm141
-rw-r--r--onlineupdate/source/update/updater/progressui_win.cpp319
-rw-r--r--onlineupdate/source/update/updater/release_primary.derbin0 -> 709 bytes
-rw-r--r--onlineupdate/source/update/updater/release_secondary.derbin0 -> 713 bytes
-rw-r--r--onlineupdate/source/update/updater/resource.h29
-rw-r--r--onlineupdate/source/update/updater/updater-common.build130
-rw-r--r--onlineupdate/source/update/updater/updater-xpcshell/Makefile.in42
-rw-r--r--onlineupdate/source/update/updater/updater-xpcshell/moz.build13
-rw-r--r--onlineupdate/source/update/updater/updater.cpp3847
-rw-r--r--onlineupdate/source/update/updater/updater.exe.comctl32.manifest38
-rw-r--r--onlineupdate/source/update/updater/updater.exe.manifest26
-rw-r--r--onlineupdate/source/update/updater/updater.icobin0 -> 92854 bytes
-rw-r--r--onlineupdate/source/update/updater/updater.pngbin0 -> 4030 bytes
-rw-r--r--onlineupdate/source/update/updater/updater.rc137
-rw-r--r--onlineupdate/source/update/updater/win_dirent.cpp78
-rw-r--r--onlineupdate/source/update/updater/xpcshellCertificate.derbin0 -> 677 bytes
43 files changed, 6415 insertions, 0 deletions
diff --git a/onlineupdate/source/update/updater/Makefile.in b/onlineupdate/source/update/updater/Makefile.in
new file mode 100644
index 000000000000..b465031b2832
--- /dev/null
+++ b/onlineupdate/source/update/updater/Makefile.in
@@ -0,0 +1,61 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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/.
+
+# For changes here, also consider ./updater-xpcshell/Makefile.in
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+ifneq (,$(filter beta release esr,$(MOZ_UPDATE_CHANNEL)))
+ PRIMARY_CERT = release_primary.der
+ SECONDARY_CERT = release_secondary.der
+else ifneq (,$(filter nightly aurora nightly-elm nightly-profiling nightly-oak nightly-ux,$(MOZ_UPDATE_CHANNEL)))
+ PRIMARY_CERT = nightly_aurora_level3_primary.der
+ SECONDARY_CERT = nightly_aurora_level3_secondary.der
+else
+ PRIMARY_CERT = dep1.der
+ SECONDARY_CERT = dep2.der
+endif
+
+CERT_HEADERS := primaryCert.h secondaryCert.h xpcshellCert.h
+
+export:: $(CERT_HEADERS)
+
+primaryCert.h: $(PRIMARY_CERT)
+secondaryCert.h: $(SECONDARY_CERT)
+
+# This is how the xpcshellCertificate.der file is generated, in case we ever
+# have to regenerate it.
+# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der
+xpcshellCert.h: xpcshellCertificate.der
+
+$(CERT_HEADERS): gen_cert_header.py
+ $(PYTHON) $< $(@:.h=Data) $(filter-out $<,$^) > $@
+
+ifdef MOZ_WIDGET_GTK
+libs:: updater.png
+ $(NSINSTALL) -D $(DIST)/bin/icons
+ $(INSTALL) $(IFLAGS1) $^ $(DIST)/bin/icons
+endif
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/updater.app
+ rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS
+ $(NSINSTALL) $(DIST)/bin/updater $(DIST)/bin/updater.app/Contents/MacOS
+ rm -f $(DIST)/bin/updater
+endif
+
+CXXFLAGS += $(MOZ_BZ2_CFLAGS)
diff --git a/onlineupdate/source/update/updater/archivereader.cpp b/onlineupdate/source/update/updater/archivereader.cpp
new file mode 100644
index 000000000000..6aa1f49fde9d
--- /dev/null
+++ b/onlineupdate/source/update/updater/archivereader.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include "bzlib.h"
+#include "archivereader.h"
+#include "errors.h"
+#ifdef XP_WIN
+#include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp
+#include "updatehelper.h"
+#endif
+
+// These are generated at compile time based on the DER file for the channel
+// being used
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+#ifdef TEST_UPDATER
+#include "../xpcshellCert.h"
+#else
+#include "primaryCert.h"
+#include "secondaryCert.h"
+#endif
+#endif
+
+#define UPDATER_NO_STRING_GLUE_STL
+#include "nsVersionComparator.cpp"
+#undef UPDATER_NO_STRING_GLUE_STL
+
+#if defined(XP_UNIX)
+# include <sys/types.h>
+#elif defined(XP_WIN)
+# include <io.h>
+#endif
+
+static int inbuf_size = 262144;
+static int outbuf_size = 262144;
+static char *inbuf = nullptr;
+static char *outbuf = nullptr;
+
+/**
+ * Performs a verification on the opened MAR file with the passed in
+ * certificate name ID and type ID.
+ *
+ * @param archive The MAR file to verify the signature on.
+ * @param certData The certificate data.
+ * @return OK on success, CERT_VERIFY_ERROR on failure.
+*/
+template<uint32_t SIZE>
+int
+VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE])
+{
+ (void)archive;
+ (void)certData;
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+ const uint32_t size = SIZE;
+ const uint8_t* const data = &certData[0];
+ if (mar_verify_signatures(archive, &data, &size, 1)) {
+ return CERT_VERIFY_ERROR;
+ }
+#endif
+
+ return OK;
+}
+
+/**
+ * Performs a verification on the opened MAR file. Both the primary and backup
+ * keys stored are stored in the current process and at least the primary key
+ * will be tried. Success will be returned as long as one of the two
+ * signatures verify.
+ *
+ * @return OK on success
+*/
+int
+ArchiveReader::VerifySignature()
+{
+ if (!mArchive) {
+ return ARCHIVE_NOT_OPEN;
+ }
+
+#ifndef MOZ_VERIFY_MAR_SIGNATURE
+ return OK;
+#else
+#ifdef TEST_UPDATER
+ int rv = VerifyLoadedCert(mArchive, xpcshellCertData);
+#else
+ int rv = VerifyLoadedCert(mArchive, primaryCertData);
+ if (rv != OK) {
+ rv = VerifyLoadedCert(mArchive, secondaryCertData);
+ }
+#endif
+ return rv;
+#endif
+}
+
+/**
+ * Verifies that the MAR file matches the current product, channel, and version
+ *
+ * @param MARChannelID The MAR channel name to use, only updates from MARs
+ * with a matching MAR channel name will succeed.
+ * If an empty string is passed, no check will be done
+ * for the channel name in the product information block.
+ * If a comma separated list of values is passed then
+ * one value must match.
+ * @param appVersion The application version to use, only MARs with an
+ * application version >= to appVersion will be applied.
+ * @return OK on success
+ * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block
+ * could not be read.
+ * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR
+ * channel ID doesn't match the MAR
+ * file's MAR channel ID.
+ * VERSION_DOWNGRADE_ERROR if the application version for
+ * this updater is newer than the
+ * one in the MAR.
+ */
+int
+ArchiveReader::VerifyProductInformation(const char *MARChannelID,
+ const char *appVersion)
+{
+ if (!mArchive) {
+ return ARCHIVE_NOT_OPEN;
+ }
+
+ ProductInformationBlock productInfoBlock;
+ int rv = mar_read_product_info_block(mArchive,
+ &productInfoBlock);
+ if (rv != OK) {
+ return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR;
+ }
+
+ // Only check the MAR channel name if specified, it should be passed in from
+ // the update-settings.ini file.
+ if (MARChannelID && strlen(MARChannelID)) {
+ // Check for at least one match in the comma separated list of values.
+ const char *delimiter = " ,\t";
+ // Make a copy of the string in case a read only memory buffer
+ // was specified. strtok modifies the input buffer.
+ char channelCopy[512] = { 0 };
+ strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1);
+ char *channel = strtok(channelCopy, delimiter);
+ rv = MAR_CHANNEL_MISMATCH_ERROR;
+ while(channel) {
+ if (!strcmp(channel, productInfoBlock.MARChannelID)) {
+ rv = OK;
+ break;
+ }
+ channel = strtok(nullptr, delimiter);
+ }
+ }
+
+ if (rv == OK) {
+ /* Compare both versions to ensure we don't have a downgrade
+ -1 if appVersion is older than productInfoBlock.productVersion
+ 1 if appVersion is newer than productInfoBlock.productVersion
+ 0 if appVersion is the same as productInfoBlock.productVersion
+ This even works with strings like:
+ - 12.0a1 being older than 12.0a2
+ - 12.0a2 being older than 12.0b1
+ - 12.0a1 being older than 12.0
+ - 12.0 being older than 12.1a1 */
+ int versionCompareResult =
+ mozilla::CompareVersions(appVersion, productInfoBlock.productVersion);
+ if (1 == versionCompareResult) {
+ rv = VERSION_DOWNGRADE_ERROR;
+ }
+ }
+
+ free((void *)productInfoBlock.MARChannelID);
+ free((void *)productInfoBlock.productVersion);
+ return rv;
+}
+
+int
+ArchiveReader::Open(const NS_tchar *path)
+{
+ if (mArchive)
+ Close();
+
+ if (!inbuf) {
+ inbuf = (char *)malloc(inbuf_size);
+ if (!inbuf) {
+ // Try again with a smaller buffer.
+ inbuf_size = 1024;
+ inbuf = (char *)malloc(inbuf_size);
+ if (!inbuf)
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+
+ if (!outbuf) {
+ outbuf = (char *)malloc(outbuf_size);
+ if (!outbuf) {
+ // Try again with a smaller buffer.
+ outbuf_size = 1024;
+ outbuf = (char *)malloc(outbuf_size);
+ if (!outbuf)
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+
+#ifdef XP_WIN
+ mArchive = mar_wopen(path);
+#else
+ mArchive = mar_open(path);
+#endif
+ if (!mArchive)
+ return READ_ERROR;
+
+ return OK;
+}
+
+void
+ArchiveReader::Close()
+{
+ if (mArchive) {
+ mar_close(mArchive);
+ mArchive = nullptr;
+ }
+
+ if (inbuf) {
+ free(inbuf);
+ inbuf = nullptr;
+ }
+
+ if (outbuf) {
+ free(outbuf);
+ outbuf = nullptr;
+ }
+}
+
+int
+ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest)
+{
+ const MarItem *item = mar_find_item(mArchive, name);
+ if (!item)
+ return READ_ERROR;
+
+#ifdef XP_WIN
+ FILE* fp = _wfopen(dest, L"wb+");
+#else
+ int fd = creat(dest, item->flags);
+ if (fd == -1)
+ return WRITE_ERROR;
+
+ FILE *fp = fdopen(fd, "wb");
+#endif
+ if (!fp)
+ return WRITE_ERROR;
+
+ int rv = ExtractItemToStream(item, fp);
+
+ fclose(fp);
+ return rv;
+}
+
+int
+ArchiveReader::ExtractFileToStream(const char *name, FILE *fp)
+{
+ const MarItem *item = mar_find_item(mArchive, name);
+ if (!item)
+ return READ_ERROR;
+
+ return ExtractItemToStream(item, fp);
+}
+
+int
+ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp)
+{
+ /* decompress the data chunk by chunk */
+
+ bz_stream strm;
+ int offset, inlen, outlen, ret = OK;
+
+ memset(&strm, 0, sizeof(strm));
+ if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK)
+ return UNEXPECTED_BZIP_ERROR;
+
+ offset = 0;
+ for (;;) {
+ if (!item->length) {
+ ret = UNEXPECTED_MAR_ERROR;
+ break;
+ }
+
+ if (offset < (int) item->length && strm.avail_in == 0) {
+ inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size);
+ if (inlen <= 0)
+ return READ_ERROR;
+ offset += inlen;
+ strm.next_in = inbuf;
+ strm.avail_in = inlen;
+ }
+
+ strm.next_out = outbuf;
+ strm.avail_out = outbuf_size;
+
+ ret = BZ2_bzDecompress(&strm);
+ if (ret != BZ_OK && ret != BZ_STREAM_END) {
+ ret = UNEXPECTED_BZIP_ERROR;
+ break;
+ }
+
+ outlen = outbuf_size - strm.avail_out;
+ if (outlen) {
+ if (fwrite(outbuf, outlen, 1, fp) != 1) {
+ ret = WRITE_ERROR_EXTRACT;
+ break;
+ }
+ }
+
+ if (ret == BZ_STREAM_END) {
+ ret = OK;
+ break;
+ }
+ }
+
+ BZ2_bzDecompressEnd(&strm);
+ return ret;
+}
diff --git a/onlineupdate/source/update/updater/archivereader.h b/onlineupdate/source/update/updater/archivereader.h
new file mode 100644
index 000000000000..6dccd8983eb5
--- /dev/null
+++ b/onlineupdate/source/update/updater/archivereader.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef ArchiveReader_h__
+#define ArchiveReader_h__
+
+#include <stdio.h>
+#include "mar.h"
+
+#ifdef XP_WIN
+ typedef WCHAR NS_tchar;
+#else
+ typedef char NS_tchar;
+#endif
+
+// This class provides an API to extract files from an update archive.
+class ArchiveReader
+{
+public:
+ ArchiveReader() : mArchive(nullptr) {}
+ ~ArchiveReader() { Close(); }
+
+ int Open(const NS_tchar *path);
+ int VerifySignature();
+ int VerifyProductInformation(const char *MARChannelID,
+ const char *appVersion);
+ void Close();
+
+ int ExtractFile(const char *item, const NS_tchar *destination);
+ int ExtractFileToStream(const char *item, FILE *fp);
+
+private:
+ int ExtractItemToStream(const MarItem *item, FILE *fp);
+
+ MarFile *mArchive;
+};
+
+#endif // ArchiveReader_h__
diff --git a/onlineupdate/source/update/updater/automounter_gonk.cpp b/onlineupdate/source/update/updater/automounter_gonk.cpp
new file mode 100644
index 000000000000..3dff2a1337a1
--- /dev/null
+++ b/onlineupdate/source/update/updater/automounter_gonk.cpp
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include <android/log.h>
+#include <cutils/android_reboot.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "automounter_gonk.h"
+#include "updatedefines.h"
+#include "updatelogging.h"
+
+#define LOG_TAG "GonkAutoMounter"
+
+#define GONK_LOG(level, format, ...) \
+ LOG((LOG_TAG ": " format "\n", ##__VA_ARGS__)); \
+ __android_log_print(level, LOG_TAG, format, ##__VA_ARGS__)
+
+#define LOGI(format, ...) GONK_LOG(ANDROID_LOG_INFO, format, ##__VA_ARGS__)
+#define LOGE(format, ...) GONK_LOG(ANDROID_LOG_ERROR, format, ##__VA_ARGS__)
+
+const char *kGonkMountsPath = "/proc/mounts";
+const char *kGonkSystemPath = "/system";
+
+GonkAutoMounter::GonkAutoMounter() : mDevice(nullptr), mAccess(Unknown)
+{
+ if (!RemountSystem(ReadWrite)) {
+ LOGE("Could not remount %s as read-write.", kGonkSystemPath);
+ }
+}
+
+GonkAutoMounter::~GonkAutoMounter()
+{
+ bool result = RemountSystem(ReadOnly);
+ free(mDevice);
+
+ if (!result) {
+ // Don't take any chances when remounting as read-only fails, just reboot.
+ Reboot();
+ }
+}
+
+void
+GonkAutoMounter::Reboot()
+{
+ // The android_reboot wrapper provides more safety, doing fancier read-only
+ // remounting and attempting to sync() the filesystem first. If this fails
+ // our only hope is to force a reboot directly without these protections.
+ // For more, see system/core/libcutils/android_reboot.c
+ LOGE("Could not remount %s as read-only, forcing a system reboot.",
+ kGonkSystemPath);
+ LogFlush();
+
+ if (android_reboot(ANDROID_RB_RESTART, 0, nullptr) != 0) {
+ LOGE("Safe system reboot failed, attempting to force");
+ LogFlush();
+
+ if (reboot(RB_AUTOBOOT) != 0) {
+ LOGE("CRITICAL: Failed to force restart");
+ }
+ }
+}
+
+static const char *
+MountAccessToString(MountAccess access)
+{
+ switch (access) {
+ case ReadOnly: return "read-only";
+ case ReadWrite: return "read-write";
+ default: return "unknown";
+ }
+}
+
+bool
+GonkAutoMounter::RemountSystem(MountAccess access)
+{
+ if (!UpdateMountStatus()) {
+ return false;
+ }
+
+ if (mAccess == access) {
+ return true;
+ }
+
+ unsigned long flags = MS_REMOUNT;
+ if (access == ReadOnly) {
+ flags |= MS_RDONLY;
+ // Give the system a chance to flush file buffers
+ sync();
+ }
+
+ if (!MountSystem(flags)) {
+ return false;
+ }
+
+ // Check status again to verify /system has been properly remounted
+ if (!UpdateMountStatus()) {
+ return false;
+ }
+
+ if (mAccess != access) {
+ LOGE("Updated mount status %s should be %s",
+ MountAccessToString(mAccess),
+ MountAccessToString(access));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+GonkAutoMounter::UpdateMountStatus()
+{
+ FILE *mountsFile = NS_tfopen(kGonkMountsPath, "r");
+
+ if (mountsFile == nullptr) {
+ LOGE("Error opening %s: %s", kGonkMountsPath, strerror(errno));
+ return false;
+ }
+
+ // /proc/mounts returns a 0 size from fstat, so we use the same
+ // pre-allocated buffer size that ADB does here
+ const int mountsMaxSize = 4096;
+ char mountData[mountsMaxSize];
+ size_t read = fread(mountData, 1, mountsMaxSize - 1, mountsFile);
+ mountData[read + 1] = '\0';
+
+ if (ferror(mountsFile)) {
+ LOGE("Error reading %s, %s", kGonkMountsPath, strerror(errno));
+ fclose(mountsFile);
+ return false;
+ }
+
+ char *token, *tokenContext;
+ bool foundSystem = false;
+
+ for (token = strtok_r(mountData, "\n", &tokenContext);
+ token;
+ token = strtok_r(nullptr, "\n", &tokenContext))
+ {
+ if (ProcessMount(token)) {
+ foundSystem = true;
+ break;
+ }
+ }
+
+ fclose(mountsFile);
+
+ if (!foundSystem) {
+ LOGE("Couldn't find %s mount in %s", kGonkSystemPath, kGonkMountsPath);
+ }
+ return foundSystem;
+}
+
+bool
+GonkAutoMounter::ProcessMount(const char *mount)
+{
+ const int strSize = 256;
+ char mountDev[strSize];
+ char mountDir[strSize];
+ char mountAccess[strSize];
+
+ int rv = sscanf(mount, "%255s %255s %*s %255s %*d %*d\n",
+ mountDev, mountDir, mountAccess);
+ mountDev[strSize - 1] = '\0';
+ mountDir[strSize - 1] = '\0';
+ mountAccess[strSize - 1] = '\0';
+
+ if (rv != 3) {
+ return false;
+ }
+
+ if (strcmp(kGonkSystemPath, mountDir) != 0) {
+ return false;
+ }
+
+ free(mDevice);
+ mDevice = strdup(mountDev);
+ mAccess = Unknown;
+
+ char *option, *optionContext;
+ for (option = strtok_r(mountAccess, ",", &optionContext);
+ option;
+ option = strtok_r(nullptr, ",", &optionContext))
+ {
+ if (strcmp("ro", option) == 0) {
+ mAccess = ReadOnly;
+ break;
+ } else if (strcmp("rw", option) == 0) {
+ mAccess = ReadWrite;
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Mark the given block device as read-write or read-only, using the BLKROSET
+ * ioctl.
+ */
+static void SetBlockReadWriteStatus(const char *blockdev, bool setReadOnly) {
+ int fd;
+ int roMode = setReadOnly ? 1 : 0;
+
+ fd = open(blockdev, O_RDONLY);
+ if (fd < 0) {
+ return;
+ }
+
+ if (ioctl(fd, BLKROSET, &roMode) == -1) {
+ LOGE("Error setting read-only mode on %s to %s: %s", blockdev,
+ setReadOnly ? "true": "false", strerror(errno));
+ }
+ close(fd);
+}
+
+
+bool
+GonkAutoMounter::MountSystem(unsigned long flags)
+{
+ if (!mDevice) {
+ LOGE("No device was found for %s", kGonkSystemPath);
+ return false;
+ }
+
+ // Without setting the block device ro mode to false, we get a permission
+ // denied error while trying to remount it in read-write.
+ SetBlockReadWriteStatus(mDevice, (flags & MS_RDONLY));
+
+ const char *readOnly = flags & MS_RDONLY ? "read-only" : "read-write";
+ int result = mount(mDevice, kGonkSystemPath, "none", flags, nullptr);
+
+ if (result != 0) {
+ LOGE("Error mounting %s as %s: %s", kGonkSystemPath, readOnly,
+ strerror(errno));
+ return false;
+ }
+
+ LOGI("Mounted %s partition as %s", kGonkSystemPath, readOnly);
+ return true;
+}
diff --git a/onlineupdate/source/update/updater/automounter_gonk.h b/onlineupdate/source/update/updater/automounter_gonk.h
new file mode 100644
index 000000000000..1300d39003aa
--- /dev/null
+++ b/onlineupdate/source/update/updater/automounter_gonk.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef AUTOMOUNTER_GONK_H__
+#define AUTOMOUNTER_GONK_H__
+
+typedef enum {
+ ReadOnly,
+ ReadWrite,
+ Unknown
+} MountAccess;
+
+/**
+ * This class will remount the /system partition as read-write in Gonk to allow
+ * the updater write access. Upon destruction, /system will be remounted back to
+ * read-only. If something causes /system to remain read-write, this class will
+ * reboot the device and allow the system to mount as read-only.
+ *
+ * Code inspired from AOSP system/core/adb/remount_service.c
+ */
+class GonkAutoMounter
+{
+public:
+ GonkAutoMounter();
+ ~GonkAutoMounter();
+
+ const MountAccess GetAccess()
+ {
+ return mAccess;
+ }
+
+private:
+ bool RemountSystem(MountAccess access);
+ bool ForceRemountReadOnly();
+ bool UpdateMountStatus();
+ bool ProcessMount(const char *mount);
+ bool MountSystem(unsigned long flags);
+ void Reboot();
+
+private:
+ char *mDevice;
+ MountAccess mAccess;
+};
+
+#endif // AUTOMOUNTER_GONK_H__
diff --git a/onlineupdate/source/update/updater/bspatch.cpp b/onlineupdate/source/update/updater/bspatch.cpp
new file mode 100644
index 000000000000..e4b5706ef2de
--- /dev/null
+++ b/onlineupdate/source/update/updater/bspatch.cpp
@@ -0,0 +1,187 @@
+/*-
+ * Copyright 2003,2004 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Changelog:
+ * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to
+ * the header, and make all the types 32-bit.
+ * --Benjamin Smedberg <benjamin@smedbergs.us>
+ */
+
+#include "bspatch.h"
+#include "errors.h"
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+
+#if defined(XP_WIN)
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+
+#ifdef XP_WIN
+# include <winsock2.h>
+#else
+# include <arpa/inet.h>
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX LONG_MAX
+#endif
+
+int
+MBS_ReadHeader(FILE* file, MBSPatchHeader *header)
+{
+ size_t s = fread(header, 1, sizeof(MBSPatchHeader), file);
+ if (s != sizeof(MBSPatchHeader))
+ return READ_ERROR;
+
+ header->slen = ntohl(header->slen);
+ header->scrc32 = ntohl(header->scrc32);
+ header->dlen = ntohl(header->dlen);
+ header->cblen = ntohl(header->cblen);
+ header->difflen = ntohl(header->difflen);
+ header->extralen = ntohl(header->extralen);
+
+ struct stat hs;
+ s = fstat(fileno(file), &hs);
+ if (s)
+ return READ_ERROR;
+
+ if (memcmp(header->tag, "MBDIFF10", 8) != 0)
+ return UNEXPECTED_BSPATCH_ERROR;
+
+ if (sizeof(MBSPatchHeader) +
+ header->cblen +
+ header->difflen +
+ header->extralen != uint32_t(hs.st_size))
+ return UNEXPECTED_BSPATCH_ERROR;
+
+ return OK;
+}
+
+int
+MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile,
+ unsigned char *fbuffer, FILE* file)
+{
+ unsigned char *fbufend = fbuffer + header->slen;
+
+ unsigned char *buf = (unsigned char*) malloc(header->cblen +
+ header->difflen +
+ header->extralen);
+ if (!buf)
+ return BSPATCH_MEM_ERROR;
+
+ int rv = OK;
+
+ size_t r = header->cblen + header->difflen + header->extralen;
+ unsigned char *wb = buf;
+ while (r) {
+ const size_t count = (r > SSIZE_MAX) ? SSIZE_MAX : r;
+ size_t c = fread(wb, 1, count, patchFile);
+ if (c != count) {
+ rv = READ_ERROR;
+ goto end;
+ }
+
+ r -= c;
+ wb += c;
+ }
+
+ {
+ MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf;
+ unsigned char *diffsrc = buf + header->cblen;
+ unsigned char *extrasrc = diffsrc + header->difflen;
+
+ MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc;
+ unsigned char *diffend = extrasrc;
+ unsigned char *extraend = extrasrc + header->extralen;
+
+ do {
+ ctrlsrc->x = ntohl(ctrlsrc->x);
+ ctrlsrc->y = ntohl(ctrlsrc->y);
+ ctrlsrc->z = ntohl(ctrlsrc->z);
+
+#ifdef DEBUG_bsmedberg
+ printf("Applying block:\n"
+ " x: %u\n"
+ " y: %u\n"
+ " z: %i\n",
+ ctrlsrc->x,
+ ctrlsrc->y,
+ ctrlsrc->z);
+#endif
+
+ /* Add x bytes from oldfile to x bytes from the diff block */
+
+ if (fbuffer + ctrlsrc->x > fbufend ||
+ diffsrc + ctrlsrc->x > diffend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ for (uint32_t i = 0; i < ctrlsrc->x; ++i) {
+ diffsrc[i] += fbuffer[i];
+ }
+ if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) {
+ rv = WRITE_ERROR_PATCH_FILE;
+ goto end;
+ }
+ fbuffer += ctrlsrc->x;
+ diffsrc += ctrlsrc->x;
+
+ /* Copy y bytes from the extra block */
+
+ if (extrasrc + ctrlsrc->y > extraend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) {
+ rv = WRITE_ERROR_PATCH_FILE;
+ goto end;
+ }
+ extrasrc += ctrlsrc->y;
+
+ /* "seek" forwards in oldfile by z bytes */
+
+ if (fbuffer + ctrlsrc->z > fbufend) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ fbuffer += ctrlsrc->z;
+
+ /* and on to the next control block */
+
+ ++ctrlsrc;
+ } while (ctrlsrc < ctrlend);
+ }
+
+end:
+ free(buf);
+ return rv;
+}
diff --git a/onlineupdate/source/update/updater/bspatch.h b/onlineupdate/source/update/updater/bspatch.h
new file mode 100644
index 000000000000..2b5fb338726f
--- /dev/null
+++ b/onlineupdate/source/update/updater/bspatch.h
@@ -0,0 +1,93 @@
+/*-
+ * Copyright 2003,2004 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Changelog:
+ * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to
+ * the header, and make all the types 32-bit.
+ * --Benjamin Smedberg <benjamin@smedbergs.us>
+ */
+
+#ifndef bspatch_h__
+#define bspatch_h__
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct MBSPatchHeader_ {
+ /* "MBDIFF10" */
+ char tag[8];
+
+ /* Length of the file to be patched */
+ uint32_t slen;
+
+ /* CRC32 of the file to be patched */
+ uint32_t scrc32;
+
+ /* Length of the result file */
+ uint32_t dlen;
+
+ /* Length of the control block in bytes */
+ uint32_t cblen;
+
+ /* Length of the diff block in bytes */
+ uint32_t difflen;
+
+ /* Length of the extra block in bytes */
+ uint32_t extralen;
+
+ /* Control block (MBSPatchTriple[]) */
+ /* Diff block (binary data) */
+ /* Extra block (binary data) */
+} MBSPatchHeader;
+
+/**
+ * Read the header of a patch file into the MBSPatchHeader structure.
+ *
+ * @param fd Must have been opened for reading, and be at the beginning
+ * of the file.
+ */
+int MBS_ReadHeader(FILE* file, MBSPatchHeader *header);
+
+/**
+ * Apply a patch. This method does not validate the checksum of the original
+ * file: client code should validate the checksum before calling this method.
+ *
+ * @param patchfd Must have been processed by MBS_ReadHeader
+ * @param fbuffer The original file read into a memory buffer of length
+ * header->slen.
+ * @param filefd Must have been opened for writing. Should be truncated
+ * to header->dlen if it is an existing file. The offset
+ * should be at the beginning of the file.
+ */
+int MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile,
+ unsigned char *fbuffer, FILE* file);
+
+typedef struct MBSPatchTriple_ {
+ uint32_t x; /* add x bytes from oldfile to x bytes from the diff block */
+ uint32_t y; /* copy y bytes from the extra block */
+ int32_t z; /* seek forwards in oldfile by z bytes */
+} MBSPatchTriple;
+
+#endif // bspatch_h__
diff --git a/onlineupdate/source/update/updater/dep1.der b/onlineupdate/source/update/updater/dep1.der
new file mode 100644
index 000000000000..95b4ef38c6bf
--- /dev/null
+++ b/onlineupdate/source/update/updater/dep1.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/dep2.der b/onlineupdate/source/update/updater/dep2.der
new file mode 100644
index 000000000000..a460d6a16dd1
--- /dev/null
+++ b/onlineupdate/source/update/updater/dep2.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/gen_cert_header.py b/onlineupdate/source/update/updater/gen_cert_header.py
new file mode 100644
index 000000000000..182e98b64443
--- /dev/null
+++ b/onlineupdate/source/update/updater/gen_cert_header.py
@@ -0,0 +1,25 @@
+import sys
+import binascii
+
+def file_byte_generator(filename, block_size = 512):
+ with open(filename, "rb") as f:
+ while True:
+ block = f.read(block_size)
+ if block:
+ for byte in block:
+ yield byte
+ else:
+ break
+
+def create_header(array_name, in_filename):
+ hexified = ["0x" + binascii.hexlify(byte) for byte in file_byte_generator(in_filename)]
+ print "const uint8_t " + array_name + "[] = {"
+ print ", ".join(hexified)
+ print "};"
+ return 0
+
+if __name__ == '__main__':
+ if len(sys.argv) < 3:
+ print 'ERROR: usage: gen_cert_header.py array_name in_filename'
+ sys.exit(1);
+ sys.exit(create_header(sys.argv[1], sys.argv[2]))
diff --git a/onlineupdate/source/update/updater/launchchild_osx.mm b/onlineupdate/source/update/updater/launchchild_osx.mm
new file mode 100644
index 000000000000..9e4e08d372fe
--- /dev/null
+++ b/onlineupdate/source/update/updater/launchchild_osx.mm
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include <Cocoa/Cocoa.h>
+#include <CoreServices/CoreServices.h>
+#include <crt_externs.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <spawn.h>
+#include "readstrings.h"
+
+// Prefer the currently running architecture (this is the same as the
+// architecture that launched the updater) and fallback to CPU_TYPE_ANY if it
+// is no longer available after the update.
+static cpu_type_t pref_cpu_types[2] = {
+#if defined(__i386__)
+ CPU_TYPE_X86,
+#elif defined(__x86_64__)
+ CPU_TYPE_X86_64,
+#elif defined(__ppc__)
+ CPU_TYPE_POWERPC,
+#endif
+ CPU_TYPE_ANY };
+
+void LaunchChild(int argc, char **argv)
+{
+ // Initialize spawn attributes.
+ posix_spawnattr_t spawnattr;
+ if (posix_spawnattr_init(&spawnattr) != 0) {
+ printf("Failed to init posix spawn attribute.");
+ return;
+ }
+
+ // Set spawn attributes.
+ size_t attr_count = 2;
+ size_t attr_ocount = 0;
+ if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 ||
+ attr_ocount != attr_count) {
+ printf("Failed to set binary preference on posix spawn attribute.");
+ posix_spawnattr_destroy(&spawnattr);
+ return;
+ }
+
+ // "posix_spawnp" uses null termination for arguments rather than a count.
+ // Note that we are not duplicating the argument strings themselves.
+ char** argv_copy = (char**)malloc((argc + 1) * sizeof(char*));
+ if (!argv_copy) {
+ printf("Failed to allocate memory for arguments.");
+ posix_spawnattr_destroy(&spawnattr);
+ return;
+ }
+ for (int i = 0; i < argc; i++) {
+ argv_copy[i] = argv[i];
+ }
+ argv_copy[argc] = NULL;
+
+ // Pass along our environment.
+ char** envp = NULL;
+ char*** cocoaEnvironment = _NSGetEnviron();
+ if (cocoaEnvironment) {
+ envp = *cocoaEnvironment;
+ }
+
+ int result = posix_spawnp(NULL, argv_copy[0], NULL, &spawnattr, argv_copy, envp);
+
+ free(argv_copy);
+ posix_spawnattr_destroy(&spawnattr);
+
+ if (result != 0) {
+ printf("Process spawn failed with code %d!", result);
+ }
+}
+
+void
+LaunchMacPostProcess(const char* aAppBundle)
+{
+ // Launch helper to perform post processing for the update; this is the Mac
+ // analogue of LaunchWinPostProcess (PostUpdateWin).
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ NSString* iniPath = [NSString stringWithUTF8String:aAppBundle];
+ iniPath =
+ [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"];
+
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:iniPath]) {
+ // the file does not exist; there is nothing to run
+ [pool release];
+ return;
+ }
+
+ int readResult;
+ char values[2][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeRelPath\0ExeArg\0",
+ 2,
+ values,
+ "PostUpdateMac");
+ if (readResult) {
+ [pool release];
+ return;
+ }
+
+ NSString *exeRelPath = [NSString stringWithUTF8String:values[0]];
+ NSString *exeArg = [NSString stringWithUTF8String:values[1]];
+ if (!exeArg || !exeRelPath) {
+ [pool release];
+ return;
+ }
+
+ NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle];
+ exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath];
+
+ char optVals[1][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeAsync\0",
+ 1,
+ optVals,
+ "PostUpdateMac");
+
+ NSTask *task = [[NSTask alloc] init];
+ [task setLaunchPath:exeFullPath];
+ [task setArguments:[NSArray arrayWithObject:exeArg]];
+ [task launch];
+ if (!readResult) {
+ NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]];
+ if ([exeAsync isEqualToString:@"false"]) {
+ [task waitUntilExit];
+ }
+ }
+ // ignore the return value of the task, there's nothing we can do with it
+ [task release];
+
+ [pool release];
+}
diff --git a/onlineupdate/source/update/updater/loaddlls.cpp b/onlineupdate/source/update/updater/loaddlls.cpp
new file mode 100644
index 000000000000..b4291a5df6bf
--- /dev/null
+++ b/onlineupdate/source/update/updater/loaddlls.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <windows.h>
+
+// Delayed load libraries are loaded when the first symbol is used.
+// The following ensures that we load the delayed loaded libraries from the
+// system directory.
+struct AutoLoadSystemDependencies
+{
+ AutoLoadSystemDependencies()
+ {
+ // Remove the current directory from the search path for dynamically loaded
+ // DLLs as a precaution. This call has no effect for delay load DLLs.
+ SetDllDirectory(L"");
+
+ HMODULE module = ::GetModuleHandleW(L"kernel32.dll");
+ if (module) {
+ // SetDefaultDllDirectories is always available on Windows 8 and above. It
+ // is also available on Windows Vista, Windows Server 2008, and
+ // Windows 7 when MS KB2533623 has been applied.
+ decltype(SetDefaultDllDirectories)* setDefaultDllDirectories =
+ (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories");
+ if (setDefaultDllDirectories) {
+ setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);
+ return;
+ }
+ }
+
+ // When SetDefaultDllDirectories is not available, fallback to preloading
+ // dlls. The order that these are loaded does not matter since they are
+ // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag.
+#ifdef HAVE_64BIT_BUILD
+ // DLLs for Firefox x64 on Windows 7 (x64).
+ // Note: dwmapi.dll is preloaded since a crash will try to load it from the
+ // application's directory.
+ static LPCWSTR delayDLLs[] = { L"apphelp.dll",
+ L"cryptbase.dll",
+ L"cryptsp.dll",
+ L"dwmapi.dll",
+ L"mpr.dll",
+ L"ntmarta.dll",
+ L"profapi.dll",
+ L"propsys.dll",
+ L"sspicli.dll",
+ L"wsock32.dll" };
+
+#else
+ // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64).
+ // Note: dwmapi.dll is preloaded since a crash will try to load it from the
+ // application's directory.
+ static LPCWSTR delayDLLs[] = { L"apphelp.dll",
+ L"crypt32.dll",
+ L"cryptbase.dll",
+ L"cryptsp.dll",
+ L"dwmapi.dll",
+ L"mpr.dll",
+ L"msasn1.dll",
+ L"ntmarta.dll",
+ L"profapi.dll",
+ L"propsys.dll",
+ L"psapi.dll",
+ L"secur32.dll",
+ L"sspicli.dll",
+ L"userenv.dll",
+ L"uxtheme.dll",
+ L"ws2_32.dll",
+ L"ws2help.dll",
+ L"wsock32.dll" };
+#endif
+
+ WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' };
+ // If GetSystemDirectory fails we accept that we'll load the DLLs from the
+ // normal search path.
+ GetSystemDirectoryW(systemDirectory, MAX_PATH + 1);
+ size_t systemDirLen = wcslen(systemDirectory);
+
+ // Make the system directory path terminate with a slash
+ if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) {
+ systemDirectory[systemDirLen] = L'\\';
+ ++systemDirLen;
+ // No need to re-null terminate
+ }
+
+ // For each known DLL ensure it is loaded from the system32 directory
+ for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) {
+ size_t fileLen = wcslen(delayDLLs[i]);
+ wcsncpy(systemDirectory + systemDirLen, delayDLLs[i],
+ MAX_PATH - systemDirLen);
+ if (systemDirLen + fileLen <= MAX_PATH) {
+ systemDirectory[systemDirLen + fileLen] = L'\0';
+ } else {
+ systemDirectory[MAX_PATH] = L'\0';
+ }
+ LPCWSTR fullModulePath = systemDirectory; // just for code readability
+ // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for
+ // dependencies and is only available on Win 7 and below.
+ LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
+ }
+ }
+} loadDLLs;
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Info.plist b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist
new file mode 100644
index 000000000000..f104b55b9920
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>updater</string>
+ <key>CFBundleIconFile</key>
+ <string>updater.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.mozilla.updater</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.5</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>i386</key>
+ <string>10.5.0</string>
+ <key>x86_64</key>
+ <string>10.6.0</string>
+ </dict>
+</dict>
+</plist>
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo
new file mode 100644
index 000000000000..bd04210fb49f
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 000000000000..bca4022e755a
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,7 @@
+/* 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/. */
+
+/* Localized versions of Info.plist keys */
+
+CFBundleName = "%APP_NAME% Software Update";
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 000000000000..6cfb50406bcf
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,19 @@
+{
+ IBClasses = (
+ {
+ CLASS = FirstResponder;
+ LANGUAGE = ObjC;
+ SUPERCLASS = NSObject;
+},
+ {
+ CLASS = UpdaterUI;
+ LANGUAGE = ObjC;
+ OUTLETS = {
+ progressBar = NSProgressIndicator;
+ progressTextField = NSTextField;
+ };
+ SUPERCLASS = NSObject;
+}
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 000000000000..15091783707b
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>111 162 356 240 0 0 1440 878 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>106 299 84 44 0 0 1440 878 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>489.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>21</integer>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>10J567</string>
+</dict>
+</plist>
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 000000000000..61ff026009dc
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns
new file mode 100644
index 000000000000..d7499c6692db
--- /dev/null
+++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns
Binary files differ
diff --git a/onlineupdate/source/update/updater/module.ver b/onlineupdate/source/update/updater/module.ver
new file mode 100644
index 000000000000..771416bb115d
--- /dev/null
+++ b/onlineupdate/source/update/updater/module.ver
@@ -0,0 +1 @@
+WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ Software Updater
diff --git a/onlineupdate/source/update/updater/moz.build b/onlineupdate/source/update/updater/moz.build
new file mode 100644
index 000000000000..d5b1d5046062
--- /dev/null
+++ b/onlineupdate/source/update/updater/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Program('updater')
+
+updater_rel_path = ''
+include('updater-common.build')
+if CONFIG['ENABLE_TESTS']:
+ DIRS += ['updater-xpcshell']
+FAIL_ON_WARNINGS = True
diff --git a/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der b/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der
new file mode 100644
index 000000000000..b22124798d43
--- /dev/null
+++ b/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der b/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der
new file mode 100644
index 000000000000..2dffbd02da0f
--- /dev/null
+++ b/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/progressui.h b/onlineupdate/source/update/updater/progressui.h
new file mode 100644
index 000000000000..6dc20e06bcc7
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef PROGRESSUI_H__
+#define PROGRESSUI_H__
+
+#include "updatedefines.h"
+
+#if defined(XP_WIN)
+ typedef WCHAR NS_tchar;
+ #define NS_main wmain
+#else
+ typedef char NS_tchar;
+ #define NS_main main
+#endif
+
+// Called to perform any initialization of the widget toolkit
+int InitProgressUI(int *argc, NS_tchar ***argv);
+
+#if defined(XP_WIN)
+ // Called on the main thread at startup
+ int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true);
+ int InitProgressUIStrings();
+#else
+ // Called on the main thread at startup
+ int ShowProgressUI();
+#endif
+// May be called from any thread
+void QuitProgressUI();
+
+// May be called from any thread: progress is a number between 0 and 100
+void UpdateProgressUI(float progress);
+
+#endif // PROGRESSUI_H__
diff --git a/onlineupdate/source/update/updater/progressui_gonk.cpp b/onlineupdate/source/update/updater/progressui_gonk.cpp
new file mode 100644
index 000000000000..f77d0af6338a
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_gonk.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "android/log.h"
+
+#include "progressui.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args)
+
+using namespace std;
+
+int InitProgressUI(int *argc, char ***argv)
+{
+ return 0;
+}
+
+int ShowProgressUI()
+{
+ LOG("Starting to apply update ...\n");
+ return 0;
+}
+
+void QuitProgressUI()
+{
+ LOG("Finished applying update\n");
+}
+
+void UpdateProgressUI(float progress)
+{
+ assert(0.0f <= progress && progress <= 100.0f);
+
+ static const size_t kProgressBarLength = 50;
+ static size_t sLastNumBars;
+ size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f);
+ if (numBars == sLastNumBars) {
+ return;
+ }
+ sLastNumBars = numBars;
+
+ size_t numSpaces = kProgressBarLength - numBars;
+ string bars(numBars, '=');
+ string spaces(numSpaces, ' ');
+ LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str());
+}
diff --git a/onlineupdate/source/update/updater/progressui_gtk.cpp b/onlineupdate/source/update/updater/progressui_gtk.cpp
new file mode 100644
index 000000000000..f3c537047f25
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_gtk.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include <stdio.h>
+#include <gtk/gtk.h>
+#include <unistd.h>
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_INTERVAL 100
+
+static float sProgressVal; // between 0 and 100
+static gboolean sQuit = FALSE;
+static gboolean sEnableUI;
+static guint sTimerID;
+
+static GtkWidget *sWin;
+static GtkWidget *sLabel;
+static GtkWidget *sProgressBar;
+
+static const char *sProgramPath;
+
+static gboolean
+UpdateDialog(gpointer data)
+{
+ if (sQuit)
+ {
+ gtk_widget_hide(sWin);
+ gtk_main_quit();
+ }
+
+ float progress = sProgressVal;
+
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar),
+ progress / 100.0);
+
+ return TRUE;
+}
+
+static gboolean
+OnDeleteEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ return TRUE;
+}
+
+int
+InitProgressUI(int *pargc, char ***pargv)
+{
+ sProgramPath = (*pargv)[0];
+
+ sEnableUI = gtk_init_check(pargc, pargv);
+ return 0;
+}
+
+int
+ShowProgressUI()
+{
+ if (!sEnableUI)
+ return -1;
+
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ usleep(500000);
+
+ if (sQuit || sProgressVal > 70.0f)
+ return 0;
+
+ char ini_path[PATH_MAX];
+ snprintf(ini_path, sizeof(ini_path), "%s.ini", sProgramPath);
+
+ StringTable strings;
+ if (ReadStrings(ini_path, &strings) != OK)
+ return -1;
+
+ sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if (!sWin)
+ return -1;
+
+ static GdkPixbuf *pixbuf;
+ char icon_path[PATH_MAX];
+ snprintf(icon_path, sizeof(icon_path), "%s.png", sProgramPath);
+
+ g_signal_connect(G_OBJECT(sWin), "delete_event",
+ G_CALLBACK(OnDeleteEvent), nullptr);
+
+ gtk_window_set_title(GTK_WINDOW(sWin), strings.title);
+ gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS);
+ gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE);
+ gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE);
+ pixbuf = gdk_pixbuf_new_from_file (icon_path, nullptr);
+ gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf);
+ g_object_unref(pixbuf);
+
+ GtkWidget *vbox = gtk_vbox_new(TRUE, 6);
+ sLabel = gtk_label_new(strings.info);
+ gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f);
+ sProgressBar = gtk_progress_bar_new();
+
+ gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0);
+
+ sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr);
+
+ gtk_container_set_border_width(GTK_CONTAINER(sWin), 10);
+ gtk_container_add(GTK_CONTAINER(sWin), vbox);
+ gtk_widget_show_all(sWin);
+
+ gtk_main();
+ return 0;
+}
+
+// Called on a background thread
+void
+QuitProgressUI()
+{
+ sQuit = TRUE;
+}
+
+// Called on a background thread
+void
+UpdateProgressUI(float progress)
+{
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/onlineupdate/source/update/updater/progressui_null.cpp b/onlineupdate/source/update/updater/progressui_null.cpp
new file mode 100644
index 000000000000..cb3ac6369593
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_null.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "progressui.h"
+
+int InitProgressUI(int *argc, char ***argv)
+{
+ return 0;
+}
+
+int ShowProgressUI()
+{
+ return 0;
+}
+
+void QuitProgressUI()
+{
+}
+
+void UpdateProgressUI(float progress)
+{
+}
diff --git a/onlineupdate/source/update/updater/progressui_osx.mm b/onlineupdate/source/update/updater/progressui_osx.mm
new file mode 100644
index 000000000000..8e3ce01eb836
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_osx.mm
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_INTERVAL 0.2
+
+static float sProgressVal; // between 0 and 100
+static BOOL sQuit = FALSE;
+static StringTable sLabels;
+static const char *sUpdatePath;
+
+@interface UpdaterUI : NSObject
+{
+ IBOutlet NSProgressIndicator *progressBar;
+ IBOutlet NSTextField *progressTextField;
+}
+@end
+
+@implementation UpdaterUI
+
+-(void)awakeFromNib
+{
+ NSWindow *w = [progressBar window];
+
+ [w setTitle:[NSString stringWithUTF8String:sLabels.title]];
+ [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]];
+
+ NSRect origTextFrame = [progressTextField frame];
+ [progressTextField sizeToFit];
+
+ int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width;
+
+ if (widthAdjust > 0) {
+ NSRect f;
+ f.size.width = w.frame.size.width + widthAdjust;
+ f.size.height = w.frame.size.height;
+ [w setFrame:f display:YES];
+ }
+
+ [w center];
+
+ [progressBar setIndeterminate:NO];
+ [progressBar setDoubleValue:0.0];
+
+ [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self
+ selector:@selector(updateProgressUI:)
+ userInfo:nil repeats:YES] retain];
+
+ // Make sure we are on top initially
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+// called when the timer goes off
+-(void)updateProgressUI:(NSTimer *)aTimer
+{
+ if (sQuit) {
+ [aTimer invalidate];
+ [aTimer release];
+
+ // It seems to be necessary to activate and hide ourselves before we stop,
+ // otherwise the "run" method will not return until the user focuses some
+ // other app. The activate step is necessary if we are not the active app.
+ // This is a big hack, but it seems to do the trick.
+ [NSApp activateIgnoringOtherApps:YES];
+ [NSApp hide:self];
+ [NSApp stop:self];
+ }
+
+ float progress = sProgressVal;
+
+ [progressBar setDoubleValue:(double)progress];
+}
+
+// leave this as returning a BOOL instead of NSApplicationTerminateReply
+// for backward compatibility
+- (BOOL)applicationShouldTerminate:(NSApplication *)sender
+{
+ return sQuit;
+}
+
+@end
+
+int
+InitProgressUI(int *pargc, char ***pargv)
+{
+ sUpdatePath = (*pargv)[1];
+
+ return 0;
+}
+
+int
+ShowProgressUI()
+{
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ usleep(500000);
+
+ if (sQuit || sProgressVal > 70.0f)
+ return 0;
+
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/updater.ini", sUpdatePath);
+ if (ReadStrings(path, &sLabels) != OK)
+ return -1;
+
+ // Continue the update without showing the Progress UI if any of the supplied
+ // strings are larger than MAX_TEXT_LEN (Bug 628829).
+ if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 &&
+ strlen(sLabels.info) < MAX_TEXT_LEN - 1))
+ return -1;
+
+ [NSApplication sharedApplication];
+ [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+ [NSApp run];
+
+ return 0;
+}
+
+// Called on a background thread
+void
+QuitProgressUI()
+{
+ sQuit = TRUE;
+}
+
+// Called on a background thread
+void
+UpdateProgressUI(float progress)
+{
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/onlineupdate/source/update/updater/progressui_win.cpp b/onlineupdate/source/update/updater/progressui_win.cpp
new file mode 100644
index 000000000000..66232902a4e8
--- /dev/null
+++ b/onlineupdate/source/update/updater/progressui_win.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include <stdio.h>
+#include <windows.h>
+#include <commctrl.h>
+#include <process.h>
+#include <io.h>
+
+#include "resource.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_ID 1
+#define TIMER_INTERVAL 100
+
+#define RESIZE_WINDOW(hwnd, extrax, extray) \
+ { \
+ RECT windowSize; \
+ GetWindowRect(hwnd, &windowSize); \
+ SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \
+ windowSize.bottom - windowSize.top + extray, \
+ SWP_NOMOVE | SWP_NOZORDER); \
+ }
+
+#define MOVE_WINDOW(hwnd, dx, dy) \
+ { \
+ RECT rc; \
+ POINT pt; \
+ GetWindowRect(hwnd, &rc); \
+ pt.x = rc.left; \
+ pt.y = rc.top; \
+ ScreenToClient(GetParent(hwnd), &pt); \
+ SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \
+ SWP_NOSIZE | SWP_NOZORDER); \
+ }
+
+static float sProgress; // between 0 and 100
+static BOOL sQuit = FALSE;
+static BOOL sIndeterminate = FALSE;
+static StringTable sUIStrings;
+
+static BOOL
+GetStringsFile(WCHAR filename[MAX_PATH])
+{
+ if (!GetModuleFileNameW(nullptr, filename, MAX_PATH))
+ return FALSE;
+
+ WCHAR *dot = wcsrchr(filename, '.');
+ if (!dot || wcsicmp(dot + 1, L"exe"))
+ return FALSE;
+
+ wcscpy(dot + 1, L"ini");
+ return TRUE;
+}
+
+static void
+UpdateDialog(HWND hDlg)
+{
+ int pos = int(sProgress + 0.5f);
+ HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
+ SendMessage(hWndPro, PBM_SETPOS, pos, 0L);
+}
+
+// The code in this function is from MSDN:
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp
+static void
+CenterDialog(HWND hDlg)
+{
+ RECT rc, rcOwner, rcDlg;
+
+ // Get the owner window and dialog box rectangles.
+ HWND desktop = GetDesktopWindow();
+
+ GetWindowRect(desktop, &rcOwner);
+ GetWindowRect(hDlg, &rcDlg);
+ CopyRect(&rc, &rcOwner);
+
+ // Offset the owner and dialog box rectangles so that
+ // right and bottom values represent the width and
+ // height, and then offset the owner again to discard
+ // space taken up by the dialog box.
+
+ OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
+ OffsetRect(&rc, -rc.left, -rc.top);
+ OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
+
+ // The new position is the sum of half the remaining
+ // space and the owner's original position.
+
+ SetWindowPos(hDlg,
+ HWND_TOP,
+ rcOwner.left + (rc.right / 2),
+ rcOwner.top + (rc.bottom / 2),
+ 0, 0, // ignores size arguments
+ SWP_NOSIZE);
+}
+
+static void
+InitDialog(HWND hDlg)
+{
+ WCHAR szwTitle[MAX_TEXT_LEN];
+ WCHAR szwInfo[MAX_TEXT_LEN];
+
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle,
+ sizeof(szwTitle)/sizeof(szwTitle[0]));
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo,
+ sizeof(szwInfo)/sizeof(szwInfo[0]));
+
+ SetWindowTextW(hDlg, szwTitle);
+ SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo);
+
+ // Set dialog icon
+ HICON hIcon = LoadIcon(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDI_DIALOG));
+ if (hIcon)
+ SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
+
+ HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
+ SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+ if (sIndeterminate) {
+ LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE);
+ SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE);
+ SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 );
+ }
+
+ // Resize the dialog to fit all of the text if necessary.
+ RECT infoSize, textSize;
+ HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO);
+
+ // Get the control's font for calculating the new size for the control
+ HDC hDCInfo = GetDC(hWndInfo);
+ HFONT hInfoFont, hOldFont;
+ hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0);
+
+ if (hInfoFont)
+ hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont);
+
+ // Measure the space needed for the text on a single line. DT_CALCRECT means
+ // nothing is drawn.
+ if (DrawText(hDCInfo, szwInfo, -1, &textSize,
+ DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) {
+ GetClientRect(hWndInfo, &infoSize);
+ SIZE extra;
+ // Calculate the additional space needed for the text by subtracting from
+ // the rectangle returned by DrawText the existing client rectangle's width
+ // and height.
+ extra.cx = (textSize.right - textSize.left) - \
+ (infoSize.right - infoSize.left);
+ extra.cy = (textSize.bottom - textSize.top) - \
+ (infoSize.bottom - infoSize.top);
+ if (extra.cx < 0)
+ extra.cx = 0;
+ if (extra.cy < 0)
+ extra.cy = 0;
+ if ((extra.cx > 0) || (extra.cy > 0)) {
+ RESIZE_WINDOW(hDlg, extra.cx, extra.cy);
+ RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy);
+ RESIZE_WINDOW(hWndPro, extra.cx, 0);
+ MOVE_WINDOW(hWndPro, 0, extra.cy);
+ }
+ }
+
+ if (hOldFont)
+ SelectObject(hDCInfo, hOldFont);
+
+ ReleaseDC(hWndInfo, hDCInfo);
+
+ CenterDialog(hDlg); // make dialog appear in the center of the screen
+
+ SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr);
+}
+
+// Message handler for update dialog.
+static LRESULT CALLBACK
+DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_INITDIALOG:
+ InitDialog(hDlg);
+ return TRUE;
+
+ case WM_TIMER:
+ if (sQuit) {
+ EndDialog(hDlg, 0);
+ } else {
+ UpdateDialog(hDlg);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+InitProgressUI(int *argc, WCHAR ***argv)
+{
+ return 0;
+}
+
+/**
+ * Initializes the progress UI strings
+ *
+ * @return 0 on success, -1 on error
+*/
+int
+InitProgressUIStrings() {
+ // If we do not have updater.ini, then we should not bother showing UI.
+ WCHAR filename[MAX_PATH];
+ if (!GetStringsFile(filename)) {
+ return -1;
+ }
+
+ if (_waccess(filename, 04)) {
+ return -1;
+ }
+
+ // If the updater.ini doesn't have the required strings, then we should not
+ // bother showing UI.
+ if (ReadStrings(filename, &sUIStrings) != OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+ShowProgressUI(bool indeterminate, bool initUIStrings)
+{
+ sIndeterminate = indeterminate;
+ if (!indeterminate) {
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ Sleep(500);
+
+ if (sQuit || sProgress > 70.0f)
+ return 0;
+ }
+
+ // Don't load the UI if there's an <exe_name>.Local directory for redirection.
+ WCHAR appPath[MAX_PATH + 1] = { L'\0' };
+ if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
+ return -1;
+ }
+
+ if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) {
+ return -1;
+ }
+
+ wcscat(appPath, L".Local");
+
+ if (!_waccess(appPath, 04)) {
+ return -1;
+ }
+
+ // Don't load the UI if the strings for the UI are not provided.
+ if (initUIStrings && InitProgressUIStrings() == -1) {
+ return -1;
+ }
+
+ if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
+ return -1;
+ }
+
+ // Use an activation context that supports visual styles for the controls.
+ ACTCTXW actx = {0};
+ actx.cbSize = sizeof(ACTCTXW);
+ actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID;
+ actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest
+ // This is needed only for Win XP but doesn't cause a problem with other
+ // versions of Windows.
+ actx.lpSource = appPath;
+ actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST);
+
+ HANDLE hactx = INVALID_HANDLE_VALUE;
+ hactx = CreateActCtxW(&actx);
+ ULONG_PTR actxCookie = NULL;
+ if (hactx != INVALID_HANDLE_VALUE) {
+ // Push the specified activation context to the top of the activation stack.
+ ActivateActCtx(hactx, &actxCookie);
+ }
+
+ INITCOMMONCONTROLSEX icc = {
+ sizeof(INITCOMMONCONTROLSEX),
+ ICC_PROGRESS_CLASS
+ };
+ InitCommonControlsEx(&icc);
+
+ DialogBox(GetModuleHandle(nullptr),
+ MAKEINTRESOURCE(IDD_DIALOG), nullptr,
+ (DLGPROC) DialogProc);
+
+ if (hactx != INVALID_HANDLE_VALUE) {
+ // Deactivate the context now that the comctl32.dll is loaded.
+ DeactivateActCtx(0, actxCookie);
+ }
+
+ return 0;
+}
+
+void
+QuitProgressUI()
+{
+ sQuit = TRUE;
+}
+
+void
+UpdateProgressUI(float progress)
+{
+ sProgress = progress; // 32-bit writes are atomic
+}
diff --git a/onlineupdate/source/update/updater/release_primary.der b/onlineupdate/source/update/updater/release_primary.der
new file mode 100644
index 000000000000..11417c35e7ff
--- /dev/null
+++ b/onlineupdate/source/update/updater/release_primary.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/release_secondary.der b/onlineupdate/source/update/updater/release_secondary.der
new file mode 100644
index 000000000000..16a7ef6d91d9
--- /dev/null
+++ b/onlineupdate/source/update/updater/release_secondary.der
Binary files differ
diff --git a/onlineupdate/source/update/updater/resource.h b/onlineupdate/source/update/updater/resource.h
new file mode 100644
index 000000000000..6b6091c7b8bb
--- /dev/null
+++ b/onlineupdate/source/update/updater/resource.h
@@ -0,0 +1,29 @@
+/* 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/. */
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by updater.rc
+//
+#define IDD_DIALOG 101
+#define IDC_PROGRESS 1000
+#define IDC_INFO 1002
+#define IDI_DIALOG 1003
+#define TYPE_CERT 512
+#define IDR_PRIMARY_CERT 1004
+#define IDR_BACKUP_CERT 1005
+#define IDS_UPDATER_IDENTITY 1006
+#define IDR_XPCSHELL_CERT 1007
+#define IDR_COMCTL32_MANIFEST 17
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1008
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/onlineupdate/source/update/updater/updater-common.build b/onlineupdate/source/update/updater/updater-common.build
new file mode 100644
index 000000000000..ae1d680a630b
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater-common.build
@@ -0,0 +1,130 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+srcs = [
+ 'archivereader.cpp',
+ 'bspatch.cpp',
+ 'updater.cpp',
+]
+
+have_progressui = 0
+
+if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+ USE_LIBS += [
+ 'verifymar',
+ ]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ have_progressui = 1
+ srcs += [
+ 'loaddlls.cpp',
+ 'progressui_win.cpp',
+ 'win_dirent.cpp',
+ ]
+ RCINCLUDE = '%supdater.rc' % updater_rel_path
+ DEFINES['UNICODE'] = True
+ DEFINES['_UNICODE'] = True
+ DEFINES['NOMINMAX'] = True
+ USE_STATIC_LIBS = True
+
+ # Pick up nsWindowsRestart.cpp
+ LOCAL_INCLUDES += [
+ '/toolkit/xre',
+ ]
+ USE_LIBS += [
+ 'updatecommon-standalone',
+ ]
+ OS_LIBS += [
+ 'comctl32',
+ 'ws2_32',
+ 'shell32',
+ 'shlwapi',
+ 'crypt32',
+ 'advapi32',
+ ]
+elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+ USE_LIBS += [
+ 'nss',
+ 'signmar',
+ 'updatecommon',
+ ]
+ OS_LIBS += CONFIG['NSPR_LIBS']
+else:
+ USE_LIBS += [
+ 'updatecommon',
+ ]
+
+USE_LIBS += [
+ 'mar',
+]
+
+if CONFIG['MOZ_NATIVE_BZ2']:
+ OS_LIBS += CONFIG['MOZ_BZ2_LIBS']
+else:
+ USE_LIBS += [
+ 'bz2',
+ ]
+
+if CONFIG['MOZ_ENABLE_GTK']:
+ have_progressui = 1
+ srcs += [
+ 'progressui_gtk.cpp',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ have_progressui = 1
+ srcs += [
+ 'launchchild_osx.mm',
+ 'progressui_osx.mm',
+ ]
+ OS_LIBS += [
+ '-framework Cocoa',
+ '-framework Security',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ have_progressui = 1
+ srcs += [
+ 'automounter_gonk.cpp',
+ 'progressui_gonk.cpp',
+ ]
+ DISABLE_STL_WRAPPING = True
+ OS_LIBS += [
+ 'cutils',
+ 'sysutils',
+ ]
+
+if have_progressui == 0:
+ srcs += [
+ 'progressui_null.cpp',
+ ]
+
+SOURCES += sorted(srcs)
+
+DEFINES['NS_NO_XPCOM'] = True
+DISABLE_STL_WRAPPING = True
+for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+LOCAL_INCLUDES += [
+ '/toolkit/mozapps/update/common',
+ '/xpcom/glue',
+]
+
+DELAYLOAD_DLLS += [
+ 'crypt32.dll',
+ 'comctl32.dll',
+ 'userenv.dll',
+ 'wsock32.dll',
+]
+
+if CONFIG['_MSC_VER']:
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+elif CONFIG['OS_ARCH'] == 'WINNT':
+ WIN32_EXE_LDFLAGS += ['-municode']
+
+if CONFIG['MOZ_WIDGET_GTK']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+ OS_LIBS += CONFIG['TK_LIBS']
diff --git a/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in
new file mode 100644
index 000000000000..13b4e7f35d5a
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in
@@ -0,0 +1,42 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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/.
+
+# For changes here, also consider ../Makefile.in
+
+XPCSHELLTESTROOT = $(abspath $(DEPTH))/_tests/xpcshell/toolkit/mozapps/update/tests
+MOCHITESTROOT = $(abspath $(DEPTH))/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests
+
+include $(topsrcdir)/config/rules.mk
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+libs::
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ # Copy for xpcshell tests
+ $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
+ rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell
+ $(NSINSTALL) $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS
+ rm -f $(PROGRAM)
+ rm -Rf $(XPCSHELLTESTROOT)/data/updater.app
+ mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app
+ mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater
+
+ # Copy for mochitest chrome tests
+ rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/updater.app
+else
+ cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX)
+ cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX)
+endif
+
+CXXFLAGS += $(MOZ_BZ2_CFLAGS)
diff --git a/onlineupdate/source/update/updater/updater-xpcshell/moz.build b/onlineupdate/source/update/updater/updater-xpcshell/moz.build
new file mode 100644
index 000000000000..ba5c844cfc10
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater-xpcshell/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Program('updater-xpcshell')
+
+updater_rel_path = '../'
+DIST_INSTALL = False
+DEFINES['TEST_UPDATER'] = True
+include('../updater-common.build')
+FAIL_ON_WARNINGS = True
diff --git a/onlineupdate/source/update/updater/updater.cpp b/onlineupdate/source/update/updater/updater.cpp
new file mode 100644
index 000000000000..62237ade7bd1
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.cpp
@@ -0,0 +1,3847 @@
+/* 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:
+ *
+ * updatev2.manifest
+ * -----------------
+ * method = "add" | "add-if" | "patch" | "patch-if" | "remove" |
+ * "rmdir" | "rmrfdir" | type
+ *
+ * '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
+ * downgrades by causing the actions defined in precomplete to be performed.
+ *
+ * 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.
+ *
+ * precomplete
+ * -----------
+ * method = "remove" | "rmdir"
+ */
+#include "bspatch.h"
+#include "progressui.h"
+#include "archivereader.h"
+#include "readstrings.h"
+#include "errors.h"
+#include "bzlib.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <algorithm>
+
+#include "updatelogging.h"
+
+#include "mozilla/Compiler.h"
+#include "mozilla/Types.h"
+
+// 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
+
+// Amount of time in ms to wait for the parent process to close
+#define PARENT_WAIT 5000
+
+#if defined(XP_MACOSX)
+// These functions are defined in launchchild_osx.mm
+void LaunchChild(int argc, char **argv);
+void LaunchMacPostProcess(const char* aAppBundle);
+#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(MOZ_WIDGET_GONK)
+# include "automounter_gonk.h"
+# include <unistd.h>
+# include <android/log.h>
+# include <linux/ioprio.h>
+# include <sys/resource.h>
+
+#if ANDROID_VERSION < 21
+// The only header file in bionic which has a function prototype for ioprio_set
+// is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts
+// badly with unistd.h, so we declare the prototype for ioprio_set directly.
+extern "C" MOZ_EXPORT int ioprio_set(int which, int who, int ioprio);
+#else
+# include <sys/syscall.h>
+static int ioprio_set(int which, int who, int ioprio) {
+ return syscall(__NR_ioprio_set, which, who, ioprio);
+}
+#endif
+
+# define MAYBE_USE_HARD_LINKS 1
+static bool sUseHardLinks = true;
+#else
+# define MAYBE_USE_HARD_LINKS 0
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
+#include "nss.h"
+#include "prerror.h"
+#endif
+
+#ifdef XP_WIN
+#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 (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \
+ return retCode; \
+ } \
+ }
+#endif
+
+//-----------------------------------------------------------------------------
+
+// This variable lives in libbz2. It's declared in bzlib_private.h, so we just
+// declare it here to avoid including that entire header file.
+#define BZ2_CRC32TABLE_UNDECLARED
+
+#if MOZ_IS_GCC
+extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
+extern "C" __global unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#endif
+#if defined(BZ2_CRC32TABLE_UNDECLARED)
+extern "C" unsigned int BZ2_crc32Table[256];
+#undef BZ2_CRC32TABLE_UNDECLARED
+#endif
+
+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() {
+ if (mFile != nullptr)
+ fclose(mFile);
+ }
+
+ AutoFile &operator=(FILE* file) {
+ if (mFile != 0)
+ fclose(mFile);
+ mFile = file;
+ return *this;
+ }
+
+ operator FILE*() {
+ return mFile;
+ }
+
+ FILE* get() {
+ return mFile;
+ }
+
+private:
+ FILE* mFile;
+};
+
+struct MARChannelStringTable {
+ MARChannelStringTable()
+ {
+ MARChannelID[0] = '\0';
+ }
+
+ char MARChannelID[MAX_TEXT_LEN];
+};
+
+//-----------------------------------------------------------------------------
+
+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;
+static NS_tchar gInstallDirPath[MAXPATHLEN];
+static NS_tchar gWorkingDirPath[MAXPATHLEN];
+static ArchiveReader gArchiveReader;
+static bool gSucceeded = false;
+static bool sStagedUpdate = false;
+static bool sReplaceRequest = false;
+static bool sUsingService = false;
+static bool sIsOSUpdate = false;
+
+#ifdef XP_WIN
+// The current working directory specified in the command line.
+static NS_tchar* gDestPath;
+static NS_tchar gCallbackRelPath[MAXPATHLEN];
+static NS_tchar gCallbackBackupPath[MAXPATHLEN];
+#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;
+}
+
+static bool
+EnvHasValue(const char *name)
+{
+ const char *val = getenv(name);
+ return (val && *val);
+}
+
+#ifdef XP_WIN
+/**
+ * Coverts a relative update path to a full path for Windows.
+ *
+ * @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)
+{
+ size_t lendestpath = NS_tstrlen(gDestPath);
+ size_t lenrelpath = NS_tstrlen(relpath);
+ NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar));
+ if (!s)
+ return nullptr;
+
+ NS_tchar *c = s;
+
+ NS_tstrcpy(c, gDestPath);
+ c += lendestpath;
+ NS_tstrcat(c, relpath);
+ c += lenrelpath;
+ *c = NS_T('\0');
+ c++;
+ return s;
+}
+#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;
+}
+
+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');
+ c++;
+ 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
+
+#if MAYBE_USE_HARD_LINKS
+/*
+ * Creates a hardlink (destFilename) which points to the existing file
+ * (srcFilename).
+ *
+ * @return 0 if successful, an error otherwise
+ */
+
+static int
+create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename)
+{
+ if (link(srcFilename, destFilename) < 0) {
+ LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno));
+ return WRITE_ERROR;
+ }
+ return OK;
+}
+#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: %x",
+ 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
+
+#if MAYBE_USE_HARD_LINKS
+ if (sUseHardLinks) {
+ if (!create_hard_link(path, dest)) {
+ return OK;
+ }
+ // Since we failed to create the hard link, fall through and copy the file.
+ sUseHardLinks = false;
+ }
+#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;
+ } else {
+ 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];
+ GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile);
+ NS_tremove(tmpDeleteFile);
+ 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)
+{
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ if (NS_taccess(backup, F_OK)) {
+ LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
+ 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)
+{
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ // 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, backup));
+ NS_tchar path[MAXPATHLEN];
+ GetTempFileNameW(DELETE_DIR, L"moz", 0, path);
+ if (rename_file(backup, path)) {
+ LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
+ backup, path));
+ 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, path));
+ } else {
+ LOG(("backup_discard: failed to schedule OS reboot removal of " \
+ "file: " LOG_S, path));
+ }
+ }
+#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, int status)
+{
+ if (status == OK)
+ backup_discard(path);
+ else
+ backup_restore(path);
+}
+
+//-----------------------------------------------------------------------------
+
+static int DoUpdate();
+
+class Action
+{
+public:
+ Action() : mProgressCost(1), mNext(nullptr) { }
+ virtual ~Action() { }
+
+ 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() : mFile(nullptr), mSkip(0) { }
+
+ int Parse(NS_tchar *line);
+ int Prepare();
+ int Execute();
+ void Finish(int status);
+
+private:
+ const NS_tchar *mFile;
+ int mSkip;
+};
+
+int
+RemoveFile::Parse(NS_tchar *line)
+{
+ // format "<deadfile>"
+
+ mFile = get_valid_path(&line);
+ if (!mFile)
+ return PARSE_ERROR;
+
+ return OK;
+}
+
+int
+RemoveFile::Prepare()
+{
+ // Skip the file if it already doesn't exist.
+ int rv = NS_taccess(mFile, F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
+
+ // Make sure that we're actually a file...
+ struct NS_tstat_t fileInfo;
+ rv = NS_tstat(mFile, &fileInfo);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
+ errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISREG(fileInfo.st_mode)) {
+ LOG(("path present, but not a file: " LOG_S, mFile));
+ return DELETE_ERROR_EXPECTED_FILE;
+ }
+
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
+ if (slash) {
+ *slash = NS_T('\0');
+ rv = NS_taccess(mFile, 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, mFile));
+
+ // 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, F_OK);
+ if (rv) {
+ LOG(("file cannot be removed because it does not exist; skipping"));
+ mSkip = 1;
+ return OK;
+ }
+
+ // Rename the old file. It will be removed in Finish.
+ rv = backup_create(mFile);
+ 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, mFile));
+
+ backup_finish(mFile, status);
+}
+
+class RemoveDir : public Action
+{
+public:
+ RemoveDir() : mDir(nullptr), mSkip(0) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // check that the source dir exists
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ const NS_tchar *mDir;
+ int mSkip;
+};
+
+int
+RemoveDir::Parse(NS_tchar *line)
+{
+ // format "<deaddir>/"
+
+ mDir = get_valid_path(&line, true);
+ 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, F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
+
+ // Make sure that we're actually a dir.
+ struct NS_tstat_t dirInfo;
+ rv = NS_tstat(mDir, &dirInfo);
+ if (rv) {
+ LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir,
+ errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISDIR(dirInfo.st_mode)) {
+ LOG(("path present, but not a directory: " LOG_S, mDir));
+ return DELETE_ERROR_EXPECTED_DIR;
+ }
+
+ rv = NS_taccess(mDir, 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 "/", mDir));
+
+ // 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, 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 "/", mDir));
+
+ // 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, F_OK);
+ if (rv) {
+ LOG(("directory no longer exists; skipping"));
+ return;
+ }
+
+
+ if (status == OK) {
+ if (NS_trmdir(mDir)) {
+ LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
+ mDir, rv, errno));
+ }
+ }
+}
+
+class AddFile : public Action
+{
+public:
+ AddFile() : mFile(nullptr)
+ , mAdded(false)
+ { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ const NS_tchar *mFile;
+ bool mAdded;
+};
+
+int
+AddFile::Parse(NS_tchar *line)
+{
+ // format "<newfile>"
+
+ mFile = get_valid_path(&line);
+ if (!mFile)
+ return PARSE_ERROR;
+
+ return OK;
+}
+
+int
+AddFile::Prepare()
+{
+ LOG(("PREPARE ADD " LOG_S, mFile));
+
+ return OK;
+}
+
+int
+AddFile::Execute()
+{
+ LOG(("EXECUTE ADD " LOG_S, mFile));
+
+ int rv;
+
+ // First make sure that we can actually get rid of any existing file.
+ rv = NS_taccess(mFile, F_OK);
+ if (rv == 0) {
+ rv = backup_create(mFile);
+ if (rv)
+ return rv;
+ } else {
+ rv = ensure_parent_dir(mFile);
+ if (rv)
+ return rv;
+ }
+
+#ifdef XP_WIN
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN,
+ nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %d", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ rv = gArchiveReader.ExtractFile(sourcefile, mFile);
+#else
+ rv = gArchiveReader.ExtractFile(mFile, mFile);
+#endif
+ if (!rv) {
+ mAdded = true;
+ }
+ return rv;
+}
+
+void
+AddFile::Finish(int status)
+{
+ LOG(("FINISH ADD " LOG_S, mFile));
+ // 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)
+ NS_tremove(mFile);
+ backup_finish(mFile, status);
+}
+
+class PatchFile : public Action
+{
+public:
+ PatchFile() : mPatchIndex(-1), buf(nullptr) { }
+
+ virtual ~PatchFile();
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // should check for patch file and for checksum here
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ int LoadSourceFile(FILE* ofile);
+
+ static int sPatchIndex;
+
+ const NS_tchar *mPatchFile;
+ const NS_tchar *mFile;
+ int mPatchIndex;
+ MBSPatchHeader header;
+ unsigned char *buf;
+ NS_tchar spath[MAXPATHLEN];
+};
+
+int PatchFile::sPatchIndex = 0;
+
+PatchFile::~PatchFile()
+{
+ // delete the temporary patch file
+ if (spath[0])
+ NS_tremove(spath);
+
+ 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", mFile, 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,
+ mFile));
+ 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;
+
+ mFile = get_valid_path(&line);
+ if (!mFile)
+ return PARSE_ERROR;
+
+ return OK;
+}
+
+int
+PatchFile::Prepare()
+{
+ LOG(("PREPARE PATCH " LOG_S, mFile));
+
+ // 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);
+
+ NS_tremove(spath);
+
+ FILE *fp = NS_tfopen(spath, NS_T("wb"));
+ if (!fp)
+ return WRITE_ERROR;
+
+#ifdef XP_WIN
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
+ nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %d", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ int rv = gArchiveReader.ExtractFileToStream(sourcefile, fp);
+#else
+ int rv = gArchiveReader.ExtractFileToStream(mPatchFile, fp);
+#endif
+ fclose(fp);
+ return rv;
+}
+
+int
+PatchFile::Execute()
+{
+ LOG(("EXECUTE PATCH " LOG_S, mFile));
+
+ AutoFile pfile(NS_tfopen(spath, NS_T("rb")));
+ if (pfile == nullptr)
+ return READ_ERROR;
+
+ int rv = MBS_ReadHeader(pfile, &header);
+ if (rv)
+ return rv;
+
+ FILE *origfile = nullptr;
+#ifdef XP_WIN
+ if (NS_tstrcmp(mFile, 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, NS_T("rb"));
+ }
+#else
+ origfile = NS_tfopen(mFile, NS_T("rb"));
+#endif
+
+ if (!origfile) {
+ LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
+ 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, &ss);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
+ errno));
+ return READ_ERROR;
+ }
+
+ rv = backup_create(mFile);
+ if (rv)
+ return rv;
+
+#if defined(HAVE_POSIX_FALLOCATE)
+ AutoFile ofile(ensure_open(mFile, 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,
+ 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, shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
+ ss.st_mode));
+#elif defined(XP_MACOSX)
+ AutoFile ofile(ensure_open(mFile, 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, NS_T("wb+"), ss.st_mode));
+#endif
+
+ if (ofile == nullptr) {
+ LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
+ return WRITE_ERROR_OPEN_PATCH_FILE;
+ }
+
+#ifdef XP_WIN
+ if (!shouldTruncate) {
+ fseek(ofile, 0, SEEK_SET);
+ }
+#endif
+
+ rv = MBS_ApplyPatch(&header, pfile, buf, ofile);
+
+ // Go ahead and do a bit of cleanup now to minimize runtime overhead.
+ // Set pfile to nullptr to make AutoFile close the file so it can be deleted
+ // on Windows.
+ pfile = nullptr;
+ NS_tremove(spath);
+ spath[0] = NS_T('\0');
+ free(buf);
+ buf = nullptr;
+
+ return rv;
+}
+
+void
+PatchFile::Finish(int status)
+{
+ LOG(("FINISH PATCH " LOG_S, mFile));
+
+ backup_finish(mFile, status);
+}
+
+class AddIfFile : public AddFile
+{
+public:
+ AddIfFile() : mTestFile(nullptr) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+protected:
+ const NS_tchar *mTestFile;
+};
+
+int
+AddIfFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<newfile>"
+
+ mTestFile = 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, 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:
+ AddIfNotFile() : mTestFile(NULL) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare();
+ virtual int Execute();
+ virtual void Finish(int status);
+
+protected:
+ const NS_tchar *mTestFile;
+};
+
+int
+AddIfNotFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<newfile>"
+
+ mTestFile = 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, 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:
+ PatchIfFile() : mTestFile(nullptr) { }
+
+ virtual int Parse(NS_tchar *line);
+ virtual int Prepare(); // should check for patch file and for checksum here
+ virtual int Execute();
+ virtual void Finish(int status);
+
+private:
+ const NS_tchar *mTestFile;
+};
+
+int
+PatchIfFile::Parse(NS_tchar *line)
+{
+ // format "<testfile>" "<patchfile>" "<filetopatch>"
+
+ mTestFile = 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, 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"
+#include "pathhash.h"
+#endif
+
+static void
+LaunchCallbackApp(const NS_tchar *workingDir,
+ int argc,
+ NS_tchar **argv,
+ bool usingService)
+{
+ putenv(const_cast<char*>("NO_EM_RESTART="));
+ putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
+
+ // Run from the specified working directory (see bug 312360). This is not
+ // necessary on Windows CE since the application that launches the updater
+ // passes the working directory as an --environ: command line argument.
+ if (NS_tchdir(workingDir) != 0) {
+ LOG(("Warning: chdir failed"));
+ }
+
+#if defined(USE_EXECV)
+ execv(argv[0], argv);
+#elif defined(XP_MACOSX)
+ LaunchChild(argc, 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) {
+ WinLaunchChild(argv[0], argc, argv, nullptr);
+ }
+#else
+# warning "Need implementaton of LaunchCallbackApp"
+#endif
+}
+
+static bool
+WriteStatusFile(const char* aStatus)
+{
+ NS_tchar filename[MAXPATHLEN];
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+
+ // Make sure that the directory for the update status file exists
+ if (ensure_parent_dir(filename))
+ return false;
+
+ AutoFile file(NS_tfopen(filename, NS_T("wb+")));
+ if (file == nullptr)
+ return false;
+
+ if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
+ return false;
+
+ return true;
+}
+
+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);
+}
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+/*
+ * Read the update.status file and sets isPendingService to true if
+ * the status is set to pending-service.
+ *
+ * @param isPendingService Out parameter for specifying if the status
+ * is set to pending-service or not.
+ * @return true if the information was retrieved and it is pending
+ * or pending-service.
+*/
+static bool
+IsUpdateStatusPendingService()
+{
+ NS_tchar filename[MAXPATHLEN];
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+
+ AutoFile file(NS_tfopen(filename, NS_T("rb")));
+ if (file == nullptr)
+ return false;
+
+ char buf[32] = { 0 };
+ fread(buf, sizeof(buf), 1, file);
+
+ const char kPendingService[] = "pending-service";
+ const char kAppliedService[] = "applied-service";
+
+ return (strncmp(buf, kPendingService,
+ sizeof(kPendingService) - 1) == 0) ||
+ (strncmp(buf, kAppliedService,
+ sizeof(kAppliedService) - 1) == 0);
+}
+#endif
+
+#ifdef XP_WIN
+/*
+ * Read the update.status file and sets isSuccess to true if
+ * the status is set to succeeded.
+ *
+ * @param isSucceeded Out parameter for specifying if the status
+ * is set to succeeded or not.
+ * @return true if the information was retrieved and it is succeeded.
+*/
+static bool
+IsUpdateStatusSucceeded(bool &isSucceeded)
+{
+ isSucceeded = false;
+ NS_tchar filename[MAXPATHLEN];
+ NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
+ NS_T("%s/update.status"), gPatchDirPath);
+
+ AutoFile file(NS_tfopen(filename, NS_T("rb")));
+ if (file == nullptr)
+ return false;
+
+ char buf[32] = { 0 };
+ fread(buf, sizeof(buf), 1, file);
+
+ const char kSucceeded[] = "succeeded";
+ isSucceeded = strncmp(buf, kSucceeded,
+ sizeof(kSucceeded) - 1) == 0;
+ return true;
+}
+#endif
+
+/*
+ * Copy the entire contents of the application installation directory to the
+ * destination directory for the update process.
+ *
+ * @return 0 if successful, an error code otherwise.
+ */
+static int
+CopyInstallDirToDestDir()
+{
+ // These files should not be copied over to the updated app
+#ifdef XP_WIN
+#define SKIPLIST_COUNT 3
+#elif XP_MACOSX
+#define SKIPLIST_COUNT 0
+#else
+#define SKIPLIST_COUNT 2
+#endif
+ copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
+#ifndef XP_MACOSX
+ skiplist.append(0, gInstallDirPath, NS_T("updated"));
+ skiplist.append(1, gInstallDirPath, NS_T("updates/0"));
+#ifdef XP_WIN
+ skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock"));
+#endif
+#endif
+
+ return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
+}
+
+/*
+ * Replace the application installation directory with the destination
+ * directory in order to finish a staged update task
+ *
+ * @return 0 if successful, an error code otherwise.
+ */
+static int
+ProcessReplaceRequest()
+{
+ // The replacement algorithm is like this:
+ // 1. Move destDir to tmpDir. In case of failure, abort.
+ // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
+ // 3. Delete tmpDir (or defer it to the next reboot).
+
+#ifdef XP_MACOSX
+ NS_tchar destDir[MAXPATHLEN];
+ NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]),
+ NS_T("%s/Contents"), gInstallDirPath);
+#elif XP_WIN
+ // Windows preserves the case of the file/directory names. We use the
+ // GetLongPathName API in order to get the correct case for the directory
+ // name, so that if the user has used a different case when launching the
+ // application, the installation directory's name does not change.
+ NS_tchar destDir[MAXPATHLEN];
+ if (!GetLongPathNameW(gInstallDirPath, destDir,
+ sizeof(destDir)/sizeof(destDir[0]))) {
+ return NO_INSTALLDIR_ERROR;
+ }
+#else
+ NS_tchar* destDir = gInstallDirPath;
+#endif
+
+ NS_tchar tmpDir[MAXPATHLEN];
+ NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
+ NS_T("%s.bak"), destDir);
+
+ NS_tchar newDir[MAXPATHLEN];
+ NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents"),
+ gWorkingDirPath);
+#else
+ NS_T("%s.bak/updated"),
+ gInstallDirPath);
+#endif
+
+ // First try to remove the possibly existing temp directory, because if this
+ // directory exists, we will fail to rename destDir.
+ // No need to error check here because if this fails, we will fail in the
+ // next step anyways.
+ ensure_remove_recursive(tmpDir);
+
+ LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")",
+ destDir, tmpDir));
+ int rv = rename_file(destDir, tmpDir, true);
+#ifdef XP_WIN
+ // On Windows, if Firefox is launched using the shortcut, it will hold a handle
+ // to its installation directory open, which might not get released in time.
+ // Therefore we wait a little bit here to see if the handle is released.
+ // If it's not released, we just fail to perform the replace request.
+ const int max_retries = 10;
+ int retries = 0;
+ while (rv == WRITE_ERROR && (retries++ < max_retries)) {
+ LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
+ "File: " LOG_S ". Last error: %d, err: %d", retries,
+ destDir, GetLastError(), rv));
+
+ Sleep(100);
+
+ rv = rename_file(destDir, tmpDir, true);
+ }
+#endif
+ if (rv) {
+ // The status file will have 'pending' written to it so there is no value in
+ // returning an error specific for this failure.
+ LOG(("Moving destDir to tmpDir failed, err: %d", rv));
+ return rv;
+ }
+
+ LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
+ newDir, destDir));
+ rv = rename_file(newDir, destDir, true);
+#ifdef XP_MACOSX
+ if (rv) {
+ LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
+ newDir, destDir));
+ copy_recursive_skiplist<0> skiplist;
+ rv = ensure_copy_recursive(newDir, destDir, skiplist);
+ }
+#endif
+ if (rv) {
+ LOG(("Moving newDir to destDir failed, err: %d", rv));
+ LOG(("Now, try to move tmpDir back to destDir"));
+ ensure_remove_recursive(destDir);
+ int rv2 = rename_file(tmpDir, destDir, true);
+ if (rv2) {
+ LOG(("Moving tmpDir back to destDir failed, err: %d", rv2));
+ }
+ // The status file will be have 'pending' written to it so there is no value
+ // in returning an error specific for this failure.
+ return rv;
+ }
+
+ LOG(("Now, remove the tmpDir"));
+ rv = ensure_remove_recursive(tmpDir, true);
+ if (rv) {
+ LOG(("Removing tmpDir failed, err: %d", rv));
+#ifdef XP_WIN
+ NS_tchar deleteDir[MAXPATHLEN];
+ NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]),
+ NS_T("%s\\%s"), destDir, DELETE_DIR);
+ // Attempt to remove the tobedeleted directory and then recreate it if it
+ // was successfully removed.
+ _wrmdir(deleteDir);
+ if (NS_taccess(deleteDir, F_OK)) {
+ NS_tmkdir(deleteDir, 0755);
+ }
+ remove_recursive_on_reboot(tmpDir, deleteDir);
+#endif
+ }
+
+#ifdef XP_MACOSX
+ // On OS X, we we need to remove the staging directory after its Contents
+ // directory has been moved.
+ NS_tchar updatedAppDir[MAXPATHLEN];
+ NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
+ NS_T("%s/Updated.app"), gPatchDirPath);
+ ensure_remove_recursive(updatedAppDir);
+#endif
+
+ gSucceeded = true;
+
+ return 0;
+}
+
+#ifdef XP_WIN
+static void
+WaitForServiceFinishThread(void *param)
+{
+ // We wait at most 10 minutes, we already waited 5 seconds previously
+ // before deciding to show this UI.
+ WaitForServiceStop(SVC_NAME, 595);
+ QuitProgressUI();
+}
+#endif
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+/**
+ * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
+ *
+ * @param path The path to the ini file that is to be read
+ * @param results A pointer to the location to store the read strings
+ * @return OK on success
+ */
+static int
+ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
+{
+ const unsigned int kNumStrings = 1;
+ const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
+ char updater_strings[kNumStrings][MAX_TEXT_LEN];
+
+ int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
+ updater_strings, "Settings");
+
+ strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
+ results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
+
+ return result;
+}
+#endif
+
+static int
+GetUpdateFileName(NS_tchar *fileName, int maxChars)
+{
+#if defined(MOZ_WIDGET_GONK)
+ // If an update.link file exists, then it will contain the name
+ // of the update file (terminated by a newline).
+
+ NS_tchar linkFileName[MAXPATHLEN];
+ NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]),
+ NS_T("%s/update.link"), gPatchDirPath);
+ AutoFile linkFile(NS_tfopen(linkFileName, NS_T("rb")));
+ if (linkFile == nullptr) {
+ NS_tsnprintf(fileName, maxChars,
+ NS_T("%s/update.mar"), gPatchDirPath);
+ return OK;
+ }
+
+ char dataFileName[MAXPATHLEN];
+ size_t bytesRead;
+
+ if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) {
+ *fileName = NS_T('\0');
+ return READ_ERROR;
+ }
+ if (dataFileName[bytesRead-1] == '\n') {
+ // Strip trailing newline (for \n and \r\n)
+ bytesRead--;
+ }
+ if (dataFileName[bytesRead-1] == '\r') {
+ // Strip trailing CR (for \r, \r\n)
+ bytesRead--;
+ }
+ dataFileName[bytesRead] = '\0';
+
+ strncpy(fileName, dataFileName, maxChars-1);
+ fileName[maxChars-1] = '\0';
+#else
+ // We currently only support update.link files under GONK
+ NS_tsnprintf(fileName, maxChars,
+ NS_T("%s/update.mar"), gPatchDirPath);
+#endif
+ return OK;
+}
+
+static void
+UpdateThreadFunc(void *param)
+{
+ // open ZIP archive and process...
+ int rv;
+ if (sReplaceRequest) {
+ rv = ProcessReplaceRequest();
+ } else {
+ NS_tchar dataFile[MAXPATHLEN];
+ rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0]));
+ if (rv == OK) {
+ rv = gArchiveReader.Open(dataFile);
+ }
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+ if (rv == OK) {
+#ifdef XP_WIN
+ HKEY baseKey = nullptr;
+ wchar_t valueName[] = L"Image Path";
+ wchar_t rasenh[] = L"rsaenh.dll";
+ bool reset = false;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
+ 0, KEY_READ | KEY_WRITE,
+ &baseKey) == ERROR_SUCCESS) {
+ wchar_t path[MAX_PATH + 1];
+ DWORD size = sizeof(path);
+ DWORD type;
+ if (RegQueryValueExW(baseKey, valueName, 0, &type,
+ (LPBYTE)path, &size) == ERROR_SUCCESS) {
+ if (type == REG_SZ && wcscmp(path, rasenh) == 0) {
+ wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll";
+ if (RegSetValueExW(baseKey, valueName, 0, REG_SZ,
+ (const BYTE*)rasenhFullPath,
+ sizeof(rasenhFullPath)) == ERROR_SUCCESS) {
+ reset = true;
+ }
+ }
+ }
+ }
+#endif
+ rv = gArchiveReader.VerifySignature();
+#ifdef XP_WIN
+ if (baseKey) {
+ if (reset) {
+ RegSetValueExW(baseKey, valueName, 0, REG_SZ,
+ (const BYTE*)rasenh,
+ sizeof(rasenh));
+ }
+ RegCloseKey(baseKey);
+ }
+#endif
+ }
+
+ if (rv == OK) {
+ if (rv == OK) {
+ NS_tchar updateSettingsPath[MAX_TEXT_LEN];
+ NS_tsnprintf(updateSettingsPath,
+ sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents/Resources/update-settings.ini"),
+#else
+ NS_T("%s/update-settings.ini"),
+#endif
+ gWorkingDirPath);
+ MARChannelStringTable MARStrings;
+ if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
+ // If we can't read from update-settings.ini then we shouldn't impose
+ // a MAR restriction. Some installations won't even include this file.
+ MARStrings.MARChannelID[0] = '\0';
+ }
+
+ rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID,
+ MOZ_APP_VERSION);
+ }
+ }
+#endif
+
+ if (rv == OK && sStagedUpdate && !sIsOSUpdate) {
+#ifdef TEST_UPDATER
+ // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying
+ // the files in dist/bin in the test updater when staging an update since
+ // this can cause tests to timeout.
+ if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) {
+ rv = OK;
+ } else {
+ rv = CopyInstallDirToDestDir();
+ }
+#else
+ rv = CopyInstallDirToDestDir();
+#endif
+ }
+
+ if (rv == OK) {
+ rv = DoUpdate();
+ gArchiveReader.Close();
+ NS_tchar updatingDir[MAXPATHLEN];
+ NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
+ NS_T("%s/updating"), gWorkingDirPath);
+ ensure_remove_recursive(updatingDir);
+ }
+ }
+
+ if (sReplaceRequest && rv) {
+ // When attempting to replace the application, we should fall back
+ // to non-staged updates in case of a failure. We do this by
+ // setting the status to pending, exiting the updater, and
+ // launching the callback application. The callback application's
+ // startup path will see the pending status, and will start the
+ // updater application again in order to apply the update without
+ // staging.
+ ensure_remove_recursive(gWorkingDirPath);
+ WriteStatusFile(sUsingService ? "pending-service" : "pending");
+#ifdef TEST_UPDATER
+ // Some tests need to use --test-process-updates again.
+ putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
+#endif
+ } else {
+ if (rv) {
+ LOG(("failed: %d", rv));
+ } else {
+#ifdef XP_MACOSX
+ // If the update was successful we need to update the timestamp on the
+ // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
+ // picks up any major changes when the bundle is updated.
+ if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) {
+ LOG(("Couldn't set access/modification time on application bundle."));
+ }
+#endif
+
+ LOG(("succeeded"));
+ }
+ WriteStatusFile(rv);
+ }
+
+ LOG(("calling QuitProgressUI"));
+ QuitProgressUI();
+}
+
+int NS_main(int argc, NS_tchar **argv)
+{
+#if defined(MOZ_WIDGET_GONK)
+ if (EnvHasValue("LD_PRELOAD")) {
+ // If the updater is launched with LD_PRELOAD set, then we wind up
+ // preloading libmozglue.so. Under some circumstances, this can cause
+ // the remount of /system to fail when going from rw to ro, so if we
+ // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it.
+ // This will cause the offending preloaded library to be closed.
+ //
+ // For a variety of reasons, this is really hard to do in a safe manner
+ // in the parent process, so we do it here.
+ unsetenv("LD_PRELOAD");
+ execv(argv[0], argv);
+ __android_log_print(ANDROID_LOG_INFO, "updater",
+ "execve failed: errno: %d. Exiting...", errno);
+ _exit(1);
+ }
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
+ // On Windows and Mac we rely on native APIs to do verifications so we don't
+ // need to initialize NSS at all there.
+ // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
+ // databases.
+ if (NSS_NoDB_Init(NULL) != SECSuccess) {
+ PRErrorCode error = PR_GetError();
+ fprintf(stderr, "Could not initialize NSS: %s (%d)",
+ PR_ErrorToName(error), (int) error);
+ _exit(1);
+ }
+#endif
+
+ InitProgressUI(&argc, &argv);
+
+ // To process an update the updater command line must at a minimum have the
+ // directory path containing the updater.mar file to process as the first
+ // argument, the install directory as the second argument, and the directory
+ // to apply the update to as the third argument. When the updater is launched
+ // by another process the PID of the parent process should be provided in the
+ // optional fourth argument and the updater will wait on the parent process to
+ // exit if the value is non-zero and the process is present. This is necessary
+ // due to not being able to update files that are in use on Windows. The
+ // optional fifth argument is the callback's working directory and the
+ // optional sixth argument is the callback path. The callback is the
+ // application to launch after updating and it will be launched when these
+ // arguments are provided whether the update was successful or not. All
+ // remaining arguments are optional and are passed to the callback when it is
+ // launched.
+ if (argc < 4) {
+ fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
+ return 1;
+ }
+
+ // The directory containing the update information.
+ gPatchDirPath = argv[1];
+ // The directory we're going to update to.
+ // We copy this string because we need to remove trailing slashes. The C++
+ // standard says that it's always safe to write to strings pointed to by argv
+ // elements, but I don't necessarily believe it.
+ NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN);
+ gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0');
+ NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH);
+ if (slash && !slash[1]) {
+ *slash = NS_T('\0');
+ }
+
+#ifdef XP_WIN
+ bool useService = false;
+ bool testOnlyFallbackKeyExists = false;
+ bool noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK");
+ putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
+
+ // We never want the service to be used unless we build with
+ // the maintenance service.
+#ifdef MOZ_MAINTENANCE_SERVICE
+ useService = IsUpdateStatusPendingService();
+ // Our tests run with a different apply directory for each test.
+ // We use this registry key on our test slaves to store the
+ // allowed name/issuers.
+ testOnlyFallbackKeyExists = DoesFallbackKeyExist();
+#endif
+
+ // Remove everything except close window from the context menu
+ {
+ HKEY hkApp = nullptr;
+ RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
+ 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
+ &hkApp, nullptr);
+ RegCloseKey(hkApp);
+ if (RegCreateKeyExW(HKEY_CURRENT_USER,
+ L"Software\\Classes\\Applications\\updater.exe",
+ 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
+ &hkApp, nullptr) == ERROR_SUCCESS) {
+ RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
+ RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
+ RegCloseKey(hkApp);
+ }
+ }
+#endif
+
+ // If there is a PID specified and it is not '0' then wait for the process to exit.
+#ifdef XP_WIN
+ __int64 pid = 0;
+#else
+ int pid = 0;
+#endif
+ if (argc > 4) {
+#ifdef XP_WIN
+ pid = _wtoi64(argv[4]);
+#else
+ pid = atoi(argv[4]);
+#endif
+ if (pid == -1) {
+ // This is a signal from the parent process that the updater should stage
+ // the update.
+ sStagedUpdate = true;
+ } else if (NS_tstrstr(argv[4], NS_T("/replace"))) {
+ // We're processing a request to replace the application with a staged
+ // update.
+ sReplaceRequest = true;
+ }
+ }
+
+ // The directory we're going to update to.
+ // We copy this string because we need to remove trailing slashes. The C++
+ // standard says that it's always safe to write to strings pointed to by argv
+ // elements, but I don't necessarily believe it.
+ NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN);
+ gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0');
+ slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH);
+ if (slash && !slash[1]) {
+ *slash = NS_T('\0');
+ }
+
+ if (EnvHasValue("MOZ_OS_UPDATE")) {
+ sIsOSUpdate = true;
+ putenv(const_cast<char*>("MOZ_OS_UPDATE="));
+ }
+
+ if (sReplaceRequest) {
+ // If we're attempting to replace the application, try to append to the
+ // log generated when staging the staged update.
+#ifdef XP_WIN
+ NS_tchar* logDir = gPatchDirPath;
+#else
+#ifdef XP_MACOSX
+ NS_tchar* logDir = gPatchDirPath;
+#else
+ NS_tchar logDir[MAXPATHLEN];
+ NS_tsnprintf(logDir, sizeof(logDir)/sizeof(logDir[0]),
+ NS_T("%s/updated/updates"),
+ gInstallDirPath);
+#endif
+#endif
+
+ LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log"));
+ } else {
+ LogInit(gPatchDirPath, NS_T("update.log"));
+ }
+
+ if (!WriteStatusFile("applying")) {
+ LOG(("failed setting status to 'applying'"));
+ return 1;
+ }
+
+ if (sStagedUpdate) {
+ LOG(("Performing a staged update"));
+ } else if (sReplaceRequest) {
+ LOG(("Performing a replace request"));
+ }
+
+ LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
+ LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
+ LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
+
+#ifdef MOZ_WIDGET_GONK
+ const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
+ if (prioEnv) {
+ int32_t prioVal;
+ int32_t oomScoreAdj;
+ int32_t ioprioClass;
+ int32_t ioprioLevel;
+ if (sscanf(prioEnv, "%d/%d/%d/%d",
+ &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) {
+ LOG(("MOZ_UPDATER_PRIO=%s", prioEnv));
+ if (setpriority(PRIO_PROCESS, 0, prioVal)) {
+ LOG(("setpriority(%d) failed, errno = %d", prioVal, errno));
+ }
+ if (ioprio_set(IOPRIO_WHO_PROCESS, 0,
+ IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) {
+ LOG(("ioprio_set(%d,%d) failed: errno = %d",
+ ioprioClass, ioprioLevel, errno));
+ }
+ FILE *fs = fopen("/proc/self/oom_score_adj", "w");
+ if (fs) {
+ fprintf(fs, "%d", oomScoreAdj);
+ fclose(fs);
+ } else {
+ LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d",
+ errno));
+ }
+ }
+ }
+#endif
+
+#ifdef XP_WIN
+ if (pid > 0) {
+ HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
+ // May return nullptr if the parent process has already gone away.
+ // Otherwise, wait for the parent process to exit before starting the
+ // update.
+ if (parent) {
+ DWORD waitTime = PARENT_WAIT;
+ DWORD result = WaitForSingleObject(parent, waitTime);
+ CloseHandle(parent);
+ if (result != WAIT_OBJECT_0)
+ return 1;
+ }
+ }
+#else
+ if (pid > 0)
+ waitpid(pid, nullptr, 0);
+#endif
+
+ if (sReplaceRequest) {
+#ifdef XP_WIN
+ // On Windows, the current working directory of the process should be changed
+ // so that it's not locked.
+ NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
+ if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
+ NS_tchdir(sysDir);
+ }
+#endif
+ }
+
+ // The callback is the remaining arguments starting at callbackIndex.
+ // The argument specified by callbackIndex is the callback executable and the
+ // argument prior to callbackIndex is the working directory.
+ const int callbackIndex = 6;
+
+#if defined(XP_WIN)
+ sUsingService = EnvHasValue("MOZ_USING_SERVICE");
+ putenv(const_cast<char*>("MOZ_USING_SERVICE="));
+ // lastFallbackError keeps track of the last error for the service not being
+ // used, in case of an error when fallback is not enabled we write the
+ // error to the update.status file.
+ // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
+ // we will instead fallback to not using the service and display a UAC prompt.
+ int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
+
+ // Launch a second instance of the updater with the runas verb on Windows
+ // when write access is denied to the installation directory.
+ HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
+ NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
+ if (!sUsingService &&
+ (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
+ NS_tchar updateLockFilePath[MAXPATHLEN];
+ if (sStagedUpdate) {
+ // When staging an update, the lock file is:
+ // <install_dir>\updated.update_in_progress.lock
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath);
+ } else if (sReplaceRequest) {
+ // When processing a replace request, the lock file is:
+ // <install_dir>\..\moz_update_in_progress.lock
+ NS_tchar installDir[MAXPATHLEN];
+ NS_tstrcpy(installDir, gInstallDirPath);
+ NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
+ *slash = NS_T('\0');
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s\\moz_update_in_progress.lock"), installDir);
+ } else {
+ // In the non-staging update case, the lock file is:
+ // <install_dir>\<app_name>.exe.update_in_progress.lock
+ NS_tsnprintf(updateLockFilePath,
+ sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
+ NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
+ }
+
+ // The update_in_progress.lock file should only exist during an update. In
+ // case it exists attempt to remove it and exit if that fails to prevent
+ // simultaneous updates occurring.
+ if (!_waccess(updateLockFilePath, F_OK) &&
+ NS_tremove(updateLockFilePath) != 0) {
+ // Try to fall back to the old way of doing updates if a staged
+ // update fails.
+ if (sStagedUpdate || sReplaceRequest) {
+ // Note that this could fail, but if it does, there isn't too much we
+ // can do in order to recover anyways.
+ WriteStatusFile("pending");
+ }
+ LOG(("Update already in progress! Exiting"));
+ return 1;
+ }
+
+ updateLockFileHandle = CreateFileW(updateLockFilePath,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ nullptr,
+ OPEN_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE,
+ nullptr);
+
+ NS_tsnprintf(elevatedLockFilePath,
+ sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
+ NS_T("%s/update_elevated.lock"), gPatchDirPath);
+
+ // Even if a file has no sharing access, you can still get its attributes
+ bool startedFromUnelevatedUpdater =
+ GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
+
+ // If we're running from the service, then we were started with the same
+ // token as the service so the permissions are already dropped. If we're
+ // running from an elevated updater that was started from an unelevated
+ // updater, then we drop the permissions here. We do not drop the
+ // permissions on the originally called updater because we use its token
+ // to start the callback application.
+ if (startedFromUnelevatedUpdater) {
+ // Disable every privilege we don't need. Processes started using
+ // CreateProcess will use the same token as this process.
+ UACHelper::DisablePrivileges(nullptr);
+ }
+
+ if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
+ (useService && testOnlyFallbackKeyExists && noServiceFallback)) {
+ if (!_waccess(elevatedLockFilePath, F_OK) &&
+ NS_tremove(elevatedLockFilePath) != 0) {
+ fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
+ return 1;
+ }
+
+ HANDLE elevatedFileHandle;
+ elevatedFileHandle = CreateFileW(elevatedLockFilePath,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ nullptr,
+ OPEN_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE,
+ nullptr);
+
+ if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
+ LOG(("Unable to create elevated lock file! Exiting"));
+ return 1;
+ }
+
+ wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
+ if (!cmdLine) {
+ CloseHandle(elevatedFileHandle);
+ return 1;
+ }
+
+ // Make sure the path to the updater to use for the update is on local.
+ // We do this check to make sure that file locking is available for
+ // race condition security checks.
+ if (useService) {
+ BOOL isLocal = FALSE;
+ useService = IsLocalFile(argv[0], isLocal) && isLocal;
+ }
+
+ // If we have unprompted elevation we should NOT use the service
+ // for the update. Service updates happen with the SYSTEM account
+ // which has more privs than we need to update with.
+ // Windows 8 provides a user interface so users can configure this
+ // behavior and it can be configured in the registry in all Windows
+ // versions that support UAC.
+ if (useService) {
+ BOOL unpromptedElevation;
+ if (IsUnpromptedElevation(unpromptedElevation)) {
+ useService = !unpromptedElevation;
+ }
+ }
+
+ // Make sure the service registry entries for the instsallation path
+ // are available. If not don't use the service.
+ if (useService) {
+ WCHAR maintenanceServiceKey[MAX_PATH + 1];
+ if (CalculateRegistryPathFromFilePath(gInstallDirPath,
+ maintenanceServiceKey)) {
+ HKEY baseKey = nullptr;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ maintenanceServiceKey, 0,
+ KEY_READ | KEY_WOW64_64KEY,
+ &baseKey) == ERROR_SUCCESS) {
+ RegCloseKey(baseKey);
+ } else {
+ useService = testOnlyFallbackKeyExists;
+ if (!useService) {
+ lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
+ }
+ }
+ } else {
+ useService = false;
+ lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
+ }
+ }
+
+ // Originally we used to write "pending" to update.status before
+ // launching the service command. This is no longer needed now
+ // since the service command is launched from updater.exe. If anything
+ // fails in between, we can fall back to using the normal update process
+ // on our own.
+
+ // If we still want to use the service try to launch the service
+ // comamnd for the update.
+ if (useService) {
+ // If the update couldn't be started, then set useService to false so
+ // we do the update the old way.
+ DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
+ useService = (ret == ERROR_SUCCESS);
+ // If the command was launched then wait for the service to be done.
+ if (useService) {
+ bool showProgressUI = false;
+ // Never show the progress UI when staging updates.
+ if (!sStagedUpdate) {
+ // We need to call this separately instead of allowing ShowProgressUI
+ // to initialize the strings because the service will move the
+ // ini file out of the way when running updater.
+ showProgressUI = !InitProgressUIStrings();
+ }
+
+ // Wait for the service to stop for 5 seconds. If the service
+ // has still not stopped then show an indeterminate progress bar.
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
+ if (lastState != SERVICE_STOPPED) {
+ Thread t1;
+ if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 &&
+ showProgressUI) {
+ ShowProgressUI(true, false);
+ }
+ t1.Join();
+ }
+
+ lastState = WaitForServiceStop(SVC_NAME, 1);
+ if (lastState != SERVICE_STOPPED) {
+ // If the service doesn't stop after 10 minutes there is
+ // something seriously wrong.
+ lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
+ useService = false;
+ }
+ } else {
+ lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
+ }
+ }
+
+ // If the service can't be used when staging and update, make sure that
+ // the UAC prompt is not shown! In this case, just set the status to
+ // pending and the update will be applied during the next startup.
+ if (!useService && sStagedUpdate) {
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ WriteStatusPending(gPatchDirPath);
+ return 0;
+ }
+
+ // If we started the service command, and it finished, check the
+ // update.status file to make sure it succeeded, and if it did
+ // we need to manually start the PostUpdate process from the
+ // current user's session of this unelevated updater.exe the
+ // current process is running as.
+ // Note that we don't need to do this if we're just staging the update,
+ // as the PostUpdate step runs when performing the replacing in that case.
+ if (useService && !sStagedUpdate) {
+ bool updateStatusSucceeded = false;
+ if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
+ updateStatusSucceeded) {
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false,
+ nullptr)) {
+ fprintf(stderr, "The post update process which runs as the user"
+ " for service update could not be launched.");
+ }
+ }
+ }
+
+ // If we didn't want to use the service at all, or if an update was
+ // already happening, or launching the service command failed, then
+ // launch the elevated updater.exe as we do without the service.
+ // We don't launch the elevated updater in the case that we did have
+ // write access all along because in that case the only reason we're
+ // using the service is because we are testing.
+ if (!useService && !noServiceFallback &&
+ updateLockFileHandle == INVALID_HANDLE_VALUE) {
+ SHELLEXECUTEINFO sinfo;
+ memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
+ sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
+ sinfo.fMask = SEE_MASK_FLAG_NO_UI |
+ SEE_MASK_FLAG_DDEWAIT |
+ SEE_MASK_NOCLOSEPROCESS;
+ sinfo.hwnd = nullptr;
+ sinfo.lpFile = argv[0];
+ sinfo.lpParameters = cmdLine;
+ sinfo.lpVerb = L"runas";
+ sinfo.nShow = SW_SHOWNORMAL;
+
+ bool result = ShellExecuteEx(&sinfo);
+ free(cmdLine);
+
+ if (result) {
+ WaitForSingleObject(sinfo.hProcess, INFINITE);
+ CloseHandle(sinfo.hProcess);
+ } else {
+ WriteStatusFile(ELEVATION_CANCELED);
+ }
+ }
+
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5], argc - callbackIndex,
+ argv + callbackIndex, sUsingService);
+ }
+
+ CloseHandle(elevatedFileHandle);
+
+ if (!useService && !noServiceFallback &&
+ INVALID_HANDLE_VALUE == updateLockFileHandle) {
+ // We didn't use the service and we did run the elevated updater.exe.
+ // The elevated updater.exe is responsible for writing out the
+ // update.status file.
+ return 0;
+ } else if(useService) {
+ // The service command was launched. The service is responsible for
+ // writing out the update.status file.
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ return 0;
+ } else {
+ // Otherwise the service command was not launched at all.
+ // We are only reaching this code path because we had write access
+ // all along to the directory and a fallback key existed, and we
+ // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
+ // We only currently use this env var from XPCShell tests.
+ CloseHandle(updateLockFileHandle);
+ WriteStatusFile(lastFallbackError);
+ return 0;
+ }
+ }
+ }
+#endif
+
+#if defined(MOZ_WIDGET_GONK)
+ // In gonk, the master b2g process sets its umask to 0027 because
+ // there's no reason for it to ever create world-readable files.
+ // The updater binary, however, needs to do this, and it inherits
+ // the master process's cautious umask. So we drop down a bit here.
+ umask(0022);
+
+ // Remount the /system partition as read-write for gonk. The destructor will
+ // remount /system as read-only. We add an extra level of scope here to avoid
+ // calling LogFinish() before the GonkAutoMounter destructor has a chance
+ // to be called
+ {
+ GonkAutoMounter mounter;
+ if (mounter.GetAccess() != MountAccess::ReadWrite) {
+ WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR);
+ return 1;
+ }
+#endif
+
+ if (sStagedUpdate) {
+ // When staging updates, blow away the old installation directory and create
+ // it from scratch.
+ ensure_remove_recursive(gWorkingDirPath);
+ }
+ if (!sReplaceRequest) {
+ // Change current directory to the directory where we need to apply the update.
+ if (NS_tchdir(gWorkingDirPath) != 0) {
+ // Try to create the destination directory if it doesn't exist
+ int rv = NS_tmkdir(gWorkingDirPath, 0755);
+ if (rv == OK && errno != EEXIST) {
+ // Try changing the current directory again
+ if (NS_tchdir(gWorkingDirPath) != 0) {
+ // OK, time to give up!
+ return 1;
+ }
+ } else {
+ // Failed to create the directory, bail out
+ return 1;
+ }
+ }
+ }
+
+#ifdef XP_WIN
+ // For replace requests, we don't need to do any real updates, so this is not
+ // necessary.
+ if (!sReplaceRequest) {
+ // Allocate enough space for the length of the path an optional additional
+ // trailing slash and null termination.
+ NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar));
+ if (!destpath)
+ return 1;
+
+ NS_tchar *c = destpath;
+ NS_tstrcpy(c, gWorkingDirPath);
+ c += NS_tstrlen(gWorkingDirPath);
+ if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') &&
+ gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) {
+ NS_tstrcat(c, NS_T("/"));
+ c += NS_tstrlen(NS_T("/"));
+ }
+ *c = NS_T('\0');
+ c++;
+
+ gDestPath = destpath;
+ }
+
+ NS_tchar applyDirLongPath[MAXPATHLEN];
+ if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath,
+ sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) {
+ LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
+ LogFinish();
+ WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5], argc - callbackIndex,
+ argv + callbackIndex, sUsingService);
+ }
+ return 1;
+ }
+
+ HANDLE callbackFile = INVALID_HANDLE_VALUE;
+ if (argc > callbackIndex) {
+ // If the callback executable is specified it must exist for a successful
+ // update. It is important we null out the whole buffer here because later
+ // we make the assumption that the callback application is inside the
+ // apply-to dir. If we don't have a fully null'ed out buffer it can lead
+ // to stack corruption which causes crashes and other problems.
+ NS_tchar callbackLongPath[MAXPATHLEN];
+ ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
+ NS_tchar *targetPath = argv[callbackIndex];
+ NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
+ size_t bufferLeft = MAXPATHLEN * 2;
+ if (sReplaceRequest) {
+ // In case of replace requests, we should look for the callback file in
+ // the destination directory.
+ size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
+ gInstallDirPath,
+ nullptr);
+ NS_tchar *p = buffer;
+ NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
+ p += commonPrefixLength;
+ bufferLeft -= commonPrefixLength;
+ NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft);
+
+ size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength);
+ p += len;
+ bufferLeft -= len;
+ *p = NS_T('\\');
+ ++p;
+ bufferLeft--;
+ *p = NS_T('\0');
+ NS_tchar installDir[MAXPATHLEN];
+ NS_tstrcpy(installDir, gInstallDirPath);
+ size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
+ installDir,
+ nullptr);
+ NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength,
+ commonPrefixLength), bufferLeft);
+ targetPath = buffer;
+ }
+ if (!GetLongPathNameW(targetPath, callbackLongPath,
+ sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) {
+ LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
+ LogFinish();
+ WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ }
+ return 1;
+ }
+
+ // Doing this is only necessary when we're actually applying a patch.
+ if (!sReplaceRequest) {
+ int len = NS_tstrlen(applyDirLongPath);
+ NS_tchar *s = callbackLongPath;
+ NS_tchar *d = gCallbackRelPath;
+ // advance to the apply to directory and advance past the trailing backslash
+ // if present.
+ s += len;
+ if (*s == NS_T('\\'))
+ ++s;
+
+ // Copy the string and replace backslashes with forward slashes along the
+ // way.
+ do {
+ if (*s == NS_T('\\'))
+ *d = NS_T('/');
+ else
+ *d = *s;
+ ++s;
+ ++d;
+ } while (*s);
+ *d = NS_T('\0');
+ ++d;
+
+ // Make a copy of the callback executable so it can be read when patching.
+ NS_tsnprintf(gCallbackBackupPath,
+ sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
+ NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
+ NS_tremove(gCallbackBackupPath);
+ CopyFileW(argv[callbackIndex], gCallbackBackupPath, false);
+
+ // Since the process may be signaled as exited by WaitForSingleObject before
+ // the release of the executable image try to lock the main executable file
+ // multiple times before giving up. If we end up giving up, we won't
+ // fail the update.
+ const int max_retries = 10;
+ int retries = 1;
+ DWORD lastWriteError = 0;
+ do {
+ // By opening a file handle wihout FILE_SHARE_READ to the callback
+ // executable, the OS will prevent launching the process while it is
+ // being updated.
+ callbackFile = CreateFileW(targetPath,
+ DELETE | GENERIC_WRITE,
+ // allow delete, rename, and write
+ FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, 0, nullptr);
+ if (callbackFile != INVALID_HANDLE_VALUE)
+ break;
+
+ lastWriteError = GetLastError();
+ LOG(("NS_main: callback app file open attempt %d failed. " \
+ "File: " LOG_S ". Last error: %d", retries,
+ targetPath, lastWriteError));
+
+ Sleep(100);
+ } while (++retries <= max_retries);
+
+ // CreateFileW will fail if the callback executable is already in use.
+ if (callbackFile == INVALID_HANDLE_VALUE) {
+ // Only fail the update if the last error was not a sharing violation.
+ if (lastWriteError != ERROR_SHARING_VIOLATION) {
+ LOG(("NS_main: callback app file in use, failed to exclusively open " \
+ "executable file: " LOG_S, argv[callbackIndex]));
+ LogFinish();
+ if (lastWriteError == ERROR_ACCESS_DENIED) {
+ WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+ } else {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
+ }
+
+ NS_tremove(gCallbackBackupPath);
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ return 1;
+ }
+ LOG(("NS_main: callback app file in use, continuing without " \
+ "exclusive access for executable file: " LOG_S,
+ argv[callbackIndex]));
+ }
+ }
+ }
+
+ // DELETE_DIR is not required when staging an update.
+ if (!sStagedUpdate && !sReplaceRequest) {
+ // The directory to move files that are in use to on Windows. This directory
+ // will be deleted after the update is finished or on OS reboot using
+ // MoveFileEx if it contains files that are in use.
+ if (NS_taccess(DELETE_DIR, F_OK)) {
+ NS_tmkdir(DELETE_DIR, 0755);
+ }
+ }
+#endif /* XP_WIN */
+
+ // Run update process on a background thread. ShowProgressUI may return
+ // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
+ // terminate. Avoid showing the progress UI when staging an update.
+ Thread t;
+ if (t.Run(UpdateThreadFunc, nullptr) == 0) {
+ if (!sStagedUpdate && !sReplaceRequest) {
+ ShowProgressUI();
+ }
+ }
+ t.Join();
+
+#ifdef XP_WIN
+ if (argc > callbackIndex && !sReplaceRequest) {
+ if (callbackFile != INVALID_HANDLE_VALUE) {
+ CloseHandle(callbackFile);
+ }
+ // Remove the copy of the callback executable.
+ NS_tremove(gCallbackBackupPath);
+ }
+
+ if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) {
+ LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
+ DELETE_DIR, errno));
+ // The directory probably couldn't be removed due to it containing files
+ // that are in use and will be removed on OS reboot. The call to remove the
+ // directory on OS reboot is done after the calls to remove the files so the
+ // files are removed first on OS reboot since the directory must be empty
+ // for the directory removal to be successful. The MoveFileEx call to remove
+ // the directory 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 along with its contents
+ // after an update is applied, on reinstall, and on uninstall.
+ if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
+ DELETE_DIR));
+ } else {
+ LOG(("NS_main: failed to schedule OS reboot removal of " \
+ "directory: " LOG_S, DELETE_DIR));
+ }
+ }
+#endif /* XP_WIN */
+
+#if defined(MOZ_WIDGET_GONK)
+ } // end the extra level of scope for the GonkAutoMounter
+#endif
+
+#ifdef XP_MACOSX
+ // When the update is successful remove the precomplete file in the root of
+ // the application bundle and move the distribution directory from
+ // Contents/MacOS to Contents/Resources and if both exist delete the
+ // directory under Contents/MacOS (see Bug 1068439).
+ if (gSucceeded && !sStagedUpdate) {
+ NS_tchar oldPrecomplete[MAXPATHLEN];
+ NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]),
+ NS_T("%s/precomplete"), gInstallDirPath);
+ NS_tremove(oldPrecomplete);
+
+ NS_tchar oldDistDir[MAXPATHLEN];
+ NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
+ NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
+ int rv = NS_taccess(oldDistDir, F_OK);
+ if (!rv) {
+ NS_tchar newDistDir[MAXPATHLEN];
+ NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
+ NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
+ rv = NS_taccess(newDistDir, F_OK);
+ if (!rv) {
+ LOG(("New distribution directory already exists... removing old " \
+ "distribution directory: " LOG_S, oldDistDir));
+ rv = ensure_remove_recursive(oldDistDir);
+ if (rv) {
+ LOG(("Removing old distribution directory failed - err: %d", rv));
+ }
+ } else {
+ LOG(("Moving old distribution directory to new location. src: " LOG_S \
+ ", dst:" LOG_S, oldDistDir, newDistDir));
+ rv = rename_file(oldDistDir, newDistDir, true);
+ if (rv) {
+ LOG(("Moving old distribution directory to new location failed - " \
+ "err: %d", rv));
+ }
+ }
+ }
+ }
+#endif /* XP_MACOSX */
+
+ LogFinish();
+
+ if (argc > callbackIndex) {
+#if defined(XP_WIN)
+ if (gSucceeded) {
+ // The service update will only be executed if it is already installed.
+ // For first time installs of the service, the install will happen from
+ // the PostUpdate process. We do the service update process here
+ // because it's possible we are updating with updater.exe without the
+ // service if the service failed to apply the update. We want to update
+ // the service to a newer version in that case. If we are not running
+ // through the service, then MOZ_USING_SERVICE will not exist.
+ if (!sUsingService) {
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, nullptr)) {
+ LOG(("NS_main: The post update process could not be launched."));
+ }
+
+ StartServiceUpdate(gInstallDirPath);
+ }
+ }
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
+#endif /* XP_WIN */
+#ifdef XP_MACOSX
+ if (gSucceeded) {
+ LaunchMacPostProcess(gInstallDirPath);
+ }
+#endif /* XP_MACOSX */
+ LaunchCallbackApp(argv[5],
+ argc - callbackIndex,
+ argv + callbackIndex,
+ sUsingService);
+ }
+
+ return gSucceeded ? 0 : 1;
+}
+
+class ActionList
+{
+public:
+ ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
+ ~ActionList();
+
+ void Append(Action* action);
+ int Prepare();
+ int Execute();
+ void Finish(int status);
+
+private:
+ Action *mFirst;
+ Action *mLast;
+ int mCount;
+};
+
+ActionList::~ActionList()
+{
+ Action* a = mFirst;
+ while (a) {
+ Action *b = a;
+ a = a->mNext;
+ delete b;
+ }
+}
+
+void
+ActionList::Append(Action *action)
+{
+ if (mLast)
+ mLast->mNext = action;
+ else
+ mFirst = action;
+
+ mLast = action;
+ mCount++;
+}
+
+int
+ActionList::Prepare()
+{
+ // If the action list is empty then we should fail in order to signal that
+ // something has gone wrong. Otherwise we report success when nothing is
+ // actually done. See bug 327140.
+ if (mCount == 0) {
+ LOG(("empty action list"));
+ return MAR_ERROR_EMPTY_ACTION_LIST;
+ }
+
+ Action *a = mFirst;
+ int i = 0;
+ while (a) {
+ int rv = a->Prepare();
+ if (rv)
+ return rv;
+
+ float percent = float(++i) / float(mCount);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ return OK;
+}
+
+int
+ActionList::Execute()
+{
+ int currentProgress = 0, maxProgress = 0;
+ Action *a = mFirst;
+ while (a) {
+ maxProgress += a->mProgressCost;
+ a = a->mNext;
+ }
+
+ a = mFirst;
+ while (a) {
+ int rv = a->Execute();
+ if (rv) {
+ LOG(("### execution failed"));
+ return rv;
+ }
+
+ currentProgress += a->mProgressCost;
+ float percent = float(currentProgress) / float(maxProgress);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE +
+ PROGRESS_EXECUTE_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ return OK;
+}
+
+void
+ActionList::Finish(int status)
+{
+ Action *a = mFirst;
+ int i = 0;
+ while (a) {
+ a->Finish(status);
+
+ float percent = float(++i) / float(mCount);
+ UpdateProgressUI(PROGRESS_PREPARE_SIZE +
+ PROGRESS_EXECUTE_SIZE +
+ PROGRESS_FINISH_SIZE * percent);
+
+ a = a->mNext;
+ }
+
+ if (status == OK)
+ gSucceeded = true;
+}
+
+
+#ifdef XP_WIN
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ WIN32_FIND_DATAW finddata;
+ HANDLE hFindFile;
+ NS_tchar searchspec[MAXPATHLEN];
+ NS_tchar foundpath[MAXPATHLEN];
+
+ NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
+ NS_T("%s*"), dirpath);
+ const NS_tchar *pszSpec = get_full_path(searchspec);
+
+ hFindFile = FindFirstFileW(pszSpec, &finddata);
+ if (hFindFile != INVALID_HANDLE_VALUE) {
+ do {
+ // Don't process the current or parent directory.
+ if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
+ NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
+ continue;
+
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s%s"), dirpath, finddata.cFileName);
+ if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), foundpath);
+ // Recurse into the directory.
+ rv = add_dir_entries(foundpath, list);
+ if (rv) {
+ LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
+ return rv;
+ }
+ } else {
+ // Add the file to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ return rv;
+ }
+
+ list->Append(action);
+ }
+ } while (FindNextFileW(hFindFile, &finddata) != 0);
+
+ FindClose(hFindFile);
+ {
+ // Add the directory to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(dirpath);
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ if (rv)
+ LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
+ quotedpath, rv));
+ else
+ list->Append(action);
+ }
+ }
+
+ return rv;
+}
+
+#elif defined(SOLARIS)
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ NS_tchar searchpath[MAXPATHLEN];
+ NS_tchar foundpath[MAXPATHLEN];
+ struct {
+ dirent dent_buffer;
+ char chars[MAXNAMLEN];
+ } ent_buf;
+ struct dirent* ent;
+
+
+ NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
+ dirpath);
+ // Remove the trailing slash so the paths don't contain double slashes. The
+ // existence of the slash has already been checked in DoUpdate.
+ searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
+
+ DIR* dir = opendir(searchpath);
+ if (!dir) {
+ LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath,
+ errno));
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+ }
+
+ while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) {
+ if ((strcmp(ent->d_name, ".") == 0) ||
+ (strcmp(ent->d_name, "..") == 0))
+ continue;
+
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s%s"), dirpath, ent->d_name);
+ struct stat64 st_buf;
+ int test = stat64(foundpath, &st_buf);
+ if (test) {
+ closedir(dir);
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+ }
+ if (S_ISDIR(st_buf.st_mode)) {
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), foundpath);
+ // Recurse into the directory.
+ rv = add_dir_entries(foundpath, list);
+ if (rv) {
+ LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
+ closedir(dir);
+ return rv;
+ }
+ } else {
+ // Add the file to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath) {
+ closedir(dir);
+ return PARSE_ERROR;
+ }
+
+ Action *action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ closedir(dir);
+ return rv;
+ }
+
+ list->Append(action);
+ }
+ }
+ closedir(dir);
+
+ // Add the directory to be removed to the ActionList.
+ NS_tchar *quotedpath = get_quoted_path(dirpath);
+ if (!quotedpath)
+ return PARSE_ERROR;
+
+ Action *action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
+ quotedpath, rv));
+ }
+ else {
+ list->Append(action);
+ }
+
+ return rv;
+}
+
+#else
+
+int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
+{
+ int rv = OK;
+ FTS *ftsdir;
+ FTSENT *ftsdirEntry;
+ NS_tchar searchpath[MAXPATHLEN];
+
+ NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
+ dirpath);
+ // Remove the trailing slash so the paths don't contain double slashes. The
+ // existence of the slash has already been checked in DoUpdate.
+ searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
+ char* const pathargv[] = {searchpath, nullptr};
+
+ // FTS_NOCHDIR is used so relative paths from the destination directory are
+ // returned.
+ if (!(ftsdir = fts_open(pathargv,
+ FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
+ nullptr)))
+ return UNEXPECTED_FILE_OPERATION_ERROR;
+
+ while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
+ NS_tchar foundpath[MAXPATHLEN];
+ NS_tchar *quotedpath;
+ Action *action = nullptr;
+
+ switch (ftsdirEntry->fts_info) {
+ // Filesystem objects that shouldn't be in the application's directories
+ case FTS_SL:
+ case FTS_SLNONE:
+ case FTS_DEFAULT:
+ LOG(("add_dir_entries: found a non-standard file: " LOG_S,
+ ftsdirEntry->fts_path));
+ // Fall through and try to remove as a file
+
+ // Files
+ case FTS_F:
+ case FTS_NSOK:
+ // Add the file to be removed to the ActionList.
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s"), ftsdirEntry->fts_accpath);
+ quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+ action = new RemoveFile();
+ rv = action->Parse(quotedpath);
+ if (!rv)
+ list->Append(action);
+ break;
+
+ // Directories
+ case FTS_DP:
+ rv = OK;
+ // Add the directory to be removed to the ActionList.
+ NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
+ NS_T("%s/"), ftsdirEntry->fts_accpath);
+ quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+
+ action = new RemoveDir();
+ rv = action->Parse(quotedpath);
+ if (!rv)
+ list->Append(action);
+ break;
+
+ // Errors
+ case FTS_DNR:
+ case FTS_NS:
+ // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
+ // we're racing with ourselves. Though strange, the entry will be
+ // removed anyway.
+ if (ENOENT == ftsdirEntry->fts_errno) {
+ rv = OK;
+ break;
+ }
+ // Fall through
+
+ case FTS_ERR:
+ rv = UNEXPECTED_FILE_OPERATION_ERROR;
+ LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
+ ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
+ break;
+
+ case FTS_DC:
+ rv = UNEXPECTED_FILE_OPERATION_ERROR;
+ LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
+ ftsdirEntry->fts_path));
+ break;
+
+ default:
+ // FTS_D is ignored and FTS_DP is used instead (post-order).
+ rv = OK;
+ break;
+ }
+
+ if (rv != OK)
+ break;
+ }
+
+ fts_close(ftsdir);
+
+ return rv;
+}
+#endif
+
+static NS_tchar*
+GetManifestContents(const NS_tchar *manifest)
+{
+ AutoFile mfile(NS_tfopen(manifest, NS_T("rb")));
+ if (mfile == nullptr) {
+ LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
+ return nullptr;
+ }
+
+ struct stat ms;
+ int rv = fstat(fileno((FILE *)mfile), &ms);
+ if (rv) {
+ LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
+ return nullptr;
+ }
+
+ char *mbuf = (char *) malloc(ms.st_size + 1);
+ if (!mbuf)
+ return nullptr;
+
+ size_t r = ms.st_size;
+ char *rb = mbuf;
+ while (r) {
+ const size_t count = mmin(SSIZE_MAX, r);
+ size_t c = fread(rb, 1, count, mfile);
+ if (c != count) {
+ LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
+ return nullptr;
+ }
+
+ r -= c;
+ rb += c;
+ }
+ mbuf[ms.st_size] = '\0';
+ rb = mbuf;
+
+#ifndef XP_WIN
+ return rb;
+#else
+ NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
+ if (!wrb)
+ return nullptr;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
+ ms.st_size + 1)) {
+ LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
+ free(mbuf);
+ free(wrb);
+ return nullptr;
+ }
+ free(mbuf);
+
+ return wrb;
+#endif
+}
+
+int AddPreCompleteActions(ActionList *list)
+{
+ if (sIsOSUpdate) {
+ return OK;
+ }
+
+#ifdef XP_MACOSX
+ NS_tchar *rb = GetManifestContents(NS_T("Contents/Resources/precomplete"));
+#else
+ NS_tchar *rb = GetManifestContents(NS_T("precomplete"));
+#endif
+ if (rb == nullptr) {
+ LOG(("AddPreCompleteActions: error getting contents of precomplete " \
+ "manifest"));
+ // Applications aren't required to have a precomplete manifest. The mar
+ // generation scripts enforce the presence of a precomplete manifest.
+ return OK;
+ }
+
+ int rv;
+ NS_tchar *line;
+ while((line = mstrtok(kNL, &rb)) != 0) {
+ // skip comments
+ if (*line == NS_T('#'))
+ continue;
+
+ NS_tchar *token = mstrtok(kWhitespace, &line);
+ if (!token) {
+ LOG(("AddPreCompleteActions: token not found in manifest"));
+ return PARSE_ERROR;
+ }
+
+ Action *action = nullptr;
+ if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
+ action = new RemoveFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported
+ continue;
+ }
+ else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
+ action = new RemoveDir();
+ }
+ else {
+ LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
+ return PARSE_ERROR;
+ }
+
+ if (!action)
+ return BAD_ACTION_ERROR;
+
+ rv = action->Parse(line);
+ if (rv)
+ return rv;
+
+ list->Append(action);
+ }
+
+ return OK;
+}
+
+int DoUpdate()
+{
+ NS_tchar manifest[MAXPATHLEN];
+ NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
+ NS_T("%s/updating/update.manifest"), gWorkingDirPath);
+ ensure_parent_dir(manifest);
+
+ // extract the manifest
+ int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest);
+ if (rv) {
+ rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest);
+ if (rv) {
+ LOG(("DoUpdate: error extracting manifest file"));
+ return rv;
+ }
+ }
+
+ NS_tchar *rb = GetManifestContents(manifest);
+ NS_tremove(manifest);
+ if (rb == nullptr) {
+ LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
+ return READ_ERROR;
+ }
+
+
+ ActionList list;
+ NS_tchar *line;
+ bool isFirstAction = true;
+
+ while((line = mstrtok(kNL, &rb)) != 0) {
+ // skip comments
+ if (*line == NS_T('#'))
+ continue;
+
+ NS_tchar *token = mstrtok(kWhitespace, &line);
+ if (!token) {
+ LOG(("DoUpdate: token not found in manifest"));
+ return PARSE_ERROR;
+ }
+
+ if (isFirstAction) {
+ isFirstAction = false;
+ // The update manifest isn't required to have a type declaration. The mar
+ // generation scripts enforce the presence of the type declaration.
+ if (NS_tstrcmp(token, NS_T("type")) == 0) {
+ const NS_tchar *type = mstrtok(kQuote, &line);
+ LOG(("UPDATE TYPE " LOG_S, type));
+ if (NS_tstrcmp(type, NS_T("complete")) == 0) {
+ rv = AddPreCompleteActions(&list);
+ if (rv)
+ return rv;
+ }
+ continue;
+ }
+ }
+
+ Action *action = nullptr;
+ if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
+ action = new RemoveFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
+ action = new RemoveDir();
+ }
+ else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive
+ const NS_tchar *reldirpath = mstrtok(kQuote, &line);
+ if (!reldirpath)
+ return PARSE_ERROR;
+
+ if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
+ return PARSE_ERROR;
+
+ rv = add_dir_entries(reldirpath, &list);
+ if (rv)
+ return rv;
+
+ continue;
+ }
+ else if (NS_tstrcmp(token, NS_T("add")) == 0) {
+ action = new AddFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("patch")) == 0) {
+ action = new PatchFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists
+ action = new AddIfFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists
+ action = new AddIfNotFile();
+ }
+ else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists
+ action = new PatchIfFile();
+ }
+ else {
+ LOG(("DoUpdate: unknown token: " LOG_S, token));
+ return PARSE_ERROR;
+ }
+
+ if (!action)
+ return BAD_ACTION_ERROR;
+
+ rv = action->Parse(line);
+ if (rv)
+ return rv;
+
+ list.Append(action);
+ }
+
+ rv = list.Prepare();
+ if (rv)
+ return rv;
+
+ rv = list.Execute();
+
+ list.Finish(rv);
+ return rv;
+}
diff --git a/onlineupdate/source/update/updater/updater.exe.comctl32.manifest b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest
new file mode 100644
index 000000000000..9a6cdb565fe1
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Updater"
+ type="win32"
+/>
+<description>Updater</description>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/onlineupdate/source/update/updater/updater.exe.manifest b/onlineupdate/source/update/updater/updater.exe.manifest
new file mode 100644
index 000000000000..cd229c954109
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.exe.manifest
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Updater"
+ type="win32"
+/>
+<description>Updater</description>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/onlineupdate/source/update/updater/updater.ico b/onlineupdate/source/update/updater/updater.ico
new file mode 100644
index 000000000000..48457029d6aa
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.ico
Binary files differ
diff --git a/onlineupdate/source/update/updater/updater.png b/onlineupdate/source/update/updater/updater.png
new file mode 100644
index 000000000000..7b5e78907785
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.png
Binary files differ
diff --git a/onlineupdate/source/update/updater/updater.rc b/onlineupdate/source/update/updater/updater.rc
new file mode 100644
index 000000000000..bfc80142cea4
--- /dev/null
+++ b/onlineupdate/source/update/updater/updater.rc
@@ -0,0 +1,137 @@
+/* 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/. */
+
+// Microsoft Visual C++ generated resource script.
+//
+#ifdef TEST_UPDATER
+#include "../resource.h"
+#define MANIFEST_PATH "../updater.exe.manifest"
+#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest"
+#define ICON_PATH "../updater.ico"
+#else
+#include "resource.h"
+#define MANIFEST_PATH "updater.exe.manifest"
+#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest"
+#define ICON_PATH "updater.ico"
+#endif
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winresrc.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1 RT_MANIFEST MANIFEST_PATH
+IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+IDI_DIALOG ICON ICON_PATH
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Embedded an identifier to uniquely identiy this as a Mozilla updater.
+//
+
+STRINGTABLE
+{
+ IDS_UPDATER_IDENTITY, "moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49"
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_DIALOG DIALOGEX 0, 0, 253, 41
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10
+ LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_DIALOG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 246
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 39
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winresrc.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/onlineupdate/source/update/updater/win_dirent.cpp b/onlineupdate/source/update/updater/win_dirent.cpp
new file mode 100644
index 000000000000..b0807ba5e193
--- /dev/null
+++ b/onlineupdate/source/update/updater/win_dirent.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "win_dirent.h"
+#include <errno.h>
+#include <string.h>
+
+// This file implements the minimum set of dirent APIs used by updater.cpp on
+// Windows. If updater.cpp is modified to use more of this API, we need to
+// implement those parts here too.
+
+static dirent gDirEnt;
+
+DIR::DIR(const WCHAR* path)
+ : findHandle(INVALID_HANDLE_VALUE)
+{
+ memset(name, 0, sizeof(name));
+ wcsncpy(name, path, sizeof(name)/sizeof(name[0]));
+ wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1);
+}
+
+DIR::~DIR()
+{
+ if (findHandle != INVALID_HANDLE_VALUE) {
+ FindClose(findHandle);
+ }
+}
+
+dirent::dirent()
+{
+ d_name[0] = L'\0';
+}
+
+DIR*
+opendir(const WCHAR* path)
+{
+ return new DIR(path);
+}
+
+int
+closedir(DIR* dir)
+{
+ delete dir;
+ return 0;
+}
+
+dirent* readdir(DIR* dir)
+{
+ WIN32_FIND_DATAW data;
+ if (dir->findHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = FindNextFileW(dir->findHandle, &data);
+ if (!result) {
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ }
+ return 0;
+ }
+ } else {
+ // Reading the first directory entry
+ dir->findHandle = FindFirstFileW(dir->name, &data);
+ if (dir->findHandle == INVALID_HANDLE_VALUE) {
+ if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+ errno = ENOENT;
+ } else {
+ errno = EBADF;
+ }
+ return 0;
+ }
+ }
+ memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name));
+ wcsncpy(gDirEnt.d_name, data.cFileName,
+ sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0]));
+ return &gDirEnt;
+}
+
diff --git a/onlineupdate/source/update/updater/xpcshellCertificate.der b/onlineupdate/source/update/updater/xpcshellCertificate.der
new file mode 100644
index 000000000000..185b2dff4a69
--- /dev/null
+++ b/onlineupdate/source/update/updater/xpcshellCertificate.der
Binary files differ