summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/updater
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/update/updater')
-rw-r--r--toolkit/mozapps/update/updater/Launchd.plist10
-rw-r--r--toolkit/mozapps/update/updater/Makefile.in28
-rw-r--r--toolkit/mozapps/update/updater/TsanOptions.cpp23
-rw-r--r--toolkit/mozapps/update/updater/archivereader.cpp348
-rw-r--r--toolkit/mozapps/update/updater/archivereader.h46
-rw-r--r--toolkit/mozapps/update/updater/autograph_stage.pem14
-rw-r--r--toolkit/mozapps/update/updater/bspatch/LICENSE23
-rw-r--r--toolkit/mozapps/update/updater/bspatch/bspatch.cpp216
-rw-r--r--toolkit/mozapps/update/updater/bspatch/bspatch.h93
-rw-r--r--toolkit/mozapps/update/updater/bspatch/moz.build22
-rw-r--r--toolkit/mozapps/update/updater/bspatch/moz.yaml30
-rw-r--r--toolkit/mozapps/update/updater/crctable.h71
-rw-r--r--toolkit/mozapps/update/updater/dep1.derbin0 -> 1215 bytes
-rw-r--r--toolkit/mozapps/update/updater/dep2.derbin0 -> 1215 bytes
-rw-r--r--toolkit/mozapps/update/updater/gen_cert_header.py27
-rw-r--r--toolkit/mozapps/update/updater/launchchild_osx.mm519
-rw-r--r--toolkit/mozapps/update/updater/loaddlls.cpp84
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in40
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in8
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib19
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib22
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icnsbin0 -> 55969 bytes
-rw-r--r--toolkit/mozapps/update/updater/module.ver1
-rw-r--r--toolkit/mozapps/update/updater/moz.build78
-rw-r--r--toolkit/mozapps/update/updater/nightly_aurora_level3_primary.derbin0 -> 1225 bytes
-rw-r--r--toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.derbin0 -> 1225 bytes
-rw-r--r--toolkit/mozapps/update/updater/progressui.h40
-rw-r--r--toolkit/mozapps/update/updater/progressui_gtk.cpp121
-rw-r--r--toolkit/mozapps/update/updater/progressui_null.cpp15
-rw-r--r--toolkit/mozapps/update/updater/progressui_osx.mm137
-rw-r--r--toolkit/mozapps/update/updater/progressui_win.cpp302
-rw-r--r--toolkit/mozapps/update/updater/release_primary.derbin0 -> 1225 bytes
-rw-r--r--toolkit/mozapps/update/updater/release_secondary.derbin0 -> 1225 bytes
-rw-r--r--toolkit/mozapps/update/updater/resource.h29
-rw-r--r--toolkit/mozapps/update/updater/updater-common.build142
-rw-r--r--toolkit/mozapps/update/updater/updater-dep/moz.build13
-rw-r--r--toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in48
-rw-r--r--toolkit/mozapps/update/updater/updater-xpcshell/moz.build13
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp4909
-rw-r--r--toolkit/mozapps/update/updater/updater.exe.comctl32.manifest43
-rw-r--r--toolkit/mozapps/update/updater/updater.exe.manifest31
-rw-r--r--toolkit/mozapps/update/updater/updater.icobin0 -> 92854 bytes
-rw-r--r--toolkit/mozapps/update/updater/updater.pngbin0 -> 2153 bytes
-rw-r--r--toolkit/mozapps/update/updater/updater.rc137
-rw-r--r--toolkit/mozapps/update/updater/xpcshellCertificate.derbin0 -> 1189 bytes
45 files changed, 7702 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/updater/Launchd.plist b/toolkit/mozapps/update/updater/Launchd.plist
new file mode 100644
index 0000000000..f0b5cef085
--- /dev/null
+++ b/toolkit/mozapps/update/updater/Launchd.plist
@@ -0,0 +1,10 @@
+<?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>Label</key>
+ <string>org.mozilla.updater</string>
+ <key>RunAtLoad</key>
+ <true/>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in
new file mode 100644
index 0000000000..70cf32378a
--- /dev/null
+++ b/toolkit/mozapps/update/updater/Makefile.in
@@ -0,0 +1,28 @@
+# 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
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+export::
+ $(call py_action,preprocessor dist/bin/Info.plist,-Fsubstitution -DMOZ_MACBUNDLE_ID='$(MOZ_MACBUNDLE_ID)' $(srcdir)/macbuild/Contents/Info.plist.in -o $(DIST)/bin/Info.plist)
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/updater.app
+ rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app
+ rsync -a -C $(DIST)/bin/Info.plist $(DIST)/bin/updater.app/Contents
+ $(call py_action,preprocessor updater.app/Contents/Resources/English.lproj/InfoPlist.strings,-Fsubstitution --output-encoding utf-16 -DAPP_NAME='$(MOZ_APP_DISPLAYNAME)' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in -o $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings)
+ $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS
+ $(NSINSTALL) $(DIST)/bin/org.mozilla.updater $(DIST)/bin/updater.app/Contents/MacOS
+endif
diff --git a/toolkit/mozapps/update/updater/TsanOptions.cpp b/toolkit/mozapps/update/updater/TsanOptions.cpp
new file mode 100644
index 0000000000..44a0b53afc
--- /dev/null
+++ b/toolkit/mozapps/update/updater/TsanOptions.cpp
@@ -0,0 +1,23 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "mozilla/TsanOptions.h"
+
+#ifndef _MSC_VER // Not supported by clang-cl yet
+
+// See also mozglue/build/TsanOptions.cpp before modifying this
+extern "C" const char* __tsan_default_suppressions() {
+ // clang-format off
+ return "# Add your suppressions below\n"
+
+ // External uninstrumented libraries
+ MOZ_TSAN_DEFAULT_EXTLIB_SUPPRESSIONS
+
+ // End of suppressions.
+ ; // Please keep this semicolon.
+ // clang-format on
+}
+#endif // _MSC_VER
diff --git a/toolkit/mozapps/update/updater/archivereader.cpp b/toolkit/mozapps/update/updater/archivereader.cpp
new file mode 100644
index 0000000000..d3d1242db8
--- /dev/null
+++ b/toolkit/mozapps/update/updater/archivereader.cpp
@@ -0,0 +1,348 @@
+/* -*- 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>
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+#include "archivereader.h"
+#include "updatererrors.h"
+#ifdef XP_WIN
+# include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp
+# include "updatehelper.h"
+#endif
+#define XZ_USE_CRC64
+#include "xz.h"
+
+// 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"
+# elif DEP_UPDATER
+# include "../dep1Cert.h"
+# include "../dep2Cert.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_WIN)
+# include <io.h>
+#endif
+
+/**
+ * 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);
+# elif DEP_UPDATER
+ int rv = VerifyLoadedCert(mArchive, dep1CertData);
+ if (rv != OK) {
+ rv = VerifyLoadedCert(mArchive, dep2CertData);
+ }
+# 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 (!mInBuf) {
+ mInBuf = (uint8_t*)malloc(mInBufSize);
+ if (!mInBuf) {
+ // Try again with a smaller buffer.
+ mInBufSize = 1024;
+ mInBuf = (uint8_t*)malloc(mInBufSize);
+ if (!mInBuf) {
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+ }
+
+ if (!mOutBuf) {
+ mOutBuf = (uint8_t*)malloc(mOutBufSize);
+ if (!mOutBuf) {
+ // Try again with a smaller buffer.
+ mOutBufSize = 1024;
+ mOutBuf = (uint8_t*)malloc(mOutBufSize);
+ if (!mOutBuf) {
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ }
+ }
+
+ MarReadResult result =
+#ifdef XP_WIN
+ mar_wopen(path, &mArchive);
+#else
+ mar_open(path, &mArchive);
+#endif
+ if (result == MAR_MEM_ERROR) {
+ return ARCHIVE_READER_MEM_ERROR;
+ }
+ if (result != MAR_READ_SUCCESS) {
+ return READ_ERROR;
+ }
+
+ xz_crc32_init();
+ xz_crc64_init();
+
+ return OK;
+}
+
+void ArchiveReader::Close() {
+ if (mArchive) {
+ mar_close(mArchive);
+ mArchive = nullptr;
+ }
+
+ if (mInBuf) {
+ free(mInBuf);
+ mInBuf = nullptr;
+ }
+
+ if (mOutBuf) {
+ free(mOutBuf);
+ mOutBuf = 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 */
+
+ int offset, inlen, ret = OK;
+ struct xz_buf strm = {0};
+ enum xz_ret xz_rv = XZ_OK;
+
+ struct xz_dec* dec = xz_dec_init(XZ_DYNALLOC, 64 * 1024 * 1024);
+ if (!dec) {
+ return UNEXPECTED_XZ_ERROR;
+ }
+
+ strm.in = mInBuf;
+ strm.in_pos = 0;
+ strm.in_size = 0;
+ strm.out = mOutBuf;
+ strm.out_pos = 0;
+ strm.out_size = mOutBufSize;
+
+ offset = 0;
+ for (;;) {
+ if (!item->length) {
+ ret = UNEXPECTED_MAR_ERROR;
+ break;
+ }
+
+ if (offset < (int)item->length && strm.in_pos == strm.in_size) {
+ inlen = mar_read(mArchive, item, offset, mInBuf, mInBufSize);
+ if (inlen <= 0) {
+ ret = READ_ERROR;
+ break;
+ }
+ offset += inlen;
+ strm.in_size = inlen;
+ strm.in_pos = 0;
+ }
+
+ xz_rv = xz_dec_run(dec, &strm);
+
+ if (strm.out_pos == mOutBufSize) {
+ if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) {
+ ret = WRITE_ERROR_EXTRACT;
+ break;
+ }
+
+ strm.out_pos = 0;
+ }
+
+ if (xz_rv == XZ_OK) {
+ // There is still more data to decompress.
+ continue;
+ }
+
+ // The return value of xz_dec_run is not XZ_OK and if it isn't XZ_STREAM_END
+ // an error has occured.
+ if (xz_rv != XZ_STREAM_END) {
+ ret = UNEXPECTED_XZ_ERROR;
+ break;
+ }
+
+ // Write out the remainder of the decompressed data. In the case of
+ // strm.out_pos == 0 this is needed to create empty files included in the
+ // mar file.
+ if (fwrite(mOutBuf, 1, strm.out_pos, fp) != strm.out_pos) {
+ ret = WRITE_ERROR_EXTRACT;
+ }
+ break;
+ }
+
+ xz_dec_end(dec);
+ return ret;
+}
diff --git a/toolkit/mozapps/update/updater/archivereader.h b/toolkit/mozapps/update/updater/archivereader.h
new file mode 100644
index 0000000000..1f18327f1d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/archivereader.h
@@ -0,0 +1,46 @@
+/* -*- 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
+# include <windows.h>
+
+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() = default;
+ ~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 = nullptr;
+ uint8_t* mInBuf = nullptr;
+ uint8_t* mOutBuf = nullptr;
+ size_t mInBufSize = 262144;
+ size_t mOutBufSize = 262144;
+};
+
+#endif // ArchiveReader_h__
diff --git a/toolkit/mozapps/update/updater/autograph_stage.pem b/toolkit/mozapps/update/updater/autograph_stage.pem
new file mode 100644
index 0000000000..bd8513a04d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/autograph_stage.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvN249lqptaK9L7VltsDt
+6X/Hik/iqHSdJMwAWOoB8exyUvura0VMZhYNGCl046zKnE9aX5aMk4s4MJX0Kw9Q
+KofWUZ+hni18gyXFjecg6AyuEiMAJpSDknWnkZ1hucXTLNpwXwRHPW5YHIinKidz
+kTCREsZl0IU+gieEYXziQ4eBvc9eSNnprKhN/00AxHlmwCtY+3HLso9PYptcOspf
+yuQC/PKLwBb6hqcwEoHsT0w1roRRSACZCHfJYtzXteW7uY3NcUOrSlWFMtZguXuO
+K0U/OJaVnfcJ6REB9HTAzgmL54QlXlGTge8Vj+XMx4GqZD1fuM7rctIFclSL/wWi
+tq8MOedINL2lj2YKB8ArU2kWmi+v7HLcS94WHHcGsBh7SrNRZQEfiMBKrHHW+mqO
+xRRbyR3zAn6M78UOFqMboEQWzWHKFNhw8VI1CA8maylNuArAZhJzdLvUUo2IuQQo
+floKjdeooezDYBeeeJXOcGUv3VrulIuL3MA5k1l+c6uBX7NFWX8ukBTG09b3sNP+
+iH4P2AIcKoccxFpjswlUZCnSKF0jRu1Ue+IulHDNzora8WDOqK0IsfNfZMNyykGf
+8WsELSO3m4CxXuCbY8hmm67dTQ5DKYf874GUm7yOCe2u4piRSJ20eA4WguwxmEIj
+96Kk7NgCLtRU3G754oOTksUCAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/toolkit/mozapps/update/updater/bspatch/LICENSE b/toolkit/mozapps/update/updater/bspatch/LICENSE
new file mode 100644
index 0000000000..f2521d71cd
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch/LICENSE
@@ -0,0 +1,23 @@
+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.
diff --git a/toolkit/mozapps/update/updater/bspatch/bspatch.cpp b/toolkit/mozapps/update/updater/bspatch/bspatch.cpp
new file mode 100644
index 0000000000..8eabd0e427
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch/bspatch.cpp
@@ -0,0 +1,216 @@
+/*-
+ * 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 "updatererrors.h"
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#if defined(XP_WIN)
+# include <io.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 != 0) {
+ return READ_ERROR;
+ }
+
+ if (memcmp(header->tag, "MBDIFF10", 8) != 0) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+
+ if (hs.st_size > INT_MAX) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+
+ size_t size = static_cast<size_t>(hs.st_size);
+ if (size < sizeof(MBSPatchHeader)) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+ size -= sizeof(MBSPatchHeader);
+
+ if (size < header->cblen) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+ size -= header->cblen;
+
+ if (size < header->difflen) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+ size -= header->difflen;
+
+ if (size < header->extralen) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+ size -= header->extralen;
+
+ if (size != 0) {
+ return UNEXPECTED_BSPATCH_ERROR;
+ }
+
+ return OK;
+}
+
+int MBS_ApplyPatch(const MBSPatchHeader* header, FILE* patchFile,
+ unsigned char* fbuffer, FILE* file) {
+ unsigned char* fbufstart = fbuffer;
+ 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;
+
+ if (c == 0 && r) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ }
+
+ {
+ MBSPatchTriple* ctrlsrc = (MBSPatchTriple*)buf;
+ if (header->cblen % sizeof(MBSPatchTriple) != 0) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+
+ 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;
+
+ while (ctrlsrc < ctrlend) {
+ 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 (ctrlsrc->x > static_cast<size_t>(fbufend - fbuffer) ||
+ ctrlsrc->x > static_cast<size_t>(diffend - diffsrc)) {
+ 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 (ctrlsrc->y > static_cast<size_t>(extraend - extrasrc)) {
+ 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 (ctrlsrc->z < fbufstart - fbuffer || ctrlsrc->z > fbufend - fbuffer) {
+ rv = UNEXPECTED_BSPATCH_ERROR;
+ goto end;
+ }
+ fbuffer += ctrlsrc->z;
+
+ /* and on to the next control block */
+
+ ++ctrlsrc;
+ }
+ }
+
+end:
+ free(buf);
+ return rv;
+}
diff --git a/toolkit/mozapps/update/updater/bspatch/bspatch.h b/toolkit/mozapps/update/updater/bspatch/bspatch.h
new file mode 100644
index 0000000000..189e618557
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch/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/toolkit/mozapps/update/updater/bspatch/moz.build b/toolkit/mozapps/update/updater/bspatch/moz.build
new file mode 100644
index 0000000000..46c6d11fad
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; 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/.
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ USE_STATIC_LIBS = True
+
+EXPORTS += [
+ "bspatch.h",
+]
+
+SOURCES += [
+ "bspatch.cpp",
+]
+
+USE_LIBS += [
+ "updatecommon",
+]
+
+Library("bspatch")
diff --git a/toolkit/mozapps/update/updater/bspatch/moz.yaml b/toolkit/mozapps/update/updater/bspatch/moz.yaml
new file mode 100644
index 0000000000..ce611f97a1
--- /dev/null
+++ b/toolkit/mozapps/update/updater/bspatch/moz.yaml
@@ -0,0 +1,30 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: "Toolkit"
+ component: "Application Update"
+
+# The source from this directory was adapted from Colin Percival's bspatch
+# tool in mid 2005 and was obtained from bsdiff version 4.2. Edits were
+# later added by the Chromium dev team and were copied to here as well
+
+# Document the source of externally hosted code
+origin:
+ name: "bsdiff/bspatch"
+ description: "Builds and applies patches to binary files"
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: "https://www.daemonology.net/bsdiff/bsdiff-4.2.tar.gz"
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: "version 4.2"
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: "BSD-2-Clause"
diff --git a/toolkit/mozapps/update/updater/crctable.h b/toolkit/mozapps/update/updater/crctable.h
new file mode 100644
index 0000000000..dadabcc9a1
--- /dev/null
+++ b/toolkit/mozapps/update/updater/crctable.h
@@ -0,0 +1,71 @@
+/**
+ * This file was part of bzip2/libbzip2.
+ * We extracted only this table for bzip2 crc comptability
+ */
+
+/**
+ I think this is an implementation of the AUTODIN-II,
+ Ethernet & FDDI 32-bit CRC standard. Vaguely derived
+ from code by Rob Warnock, in Section 51 of the
+ comp.compression FAQ.
+*/
+unsigned int BZ2_crc32Table[256] = {
+
+ /*-- Ugly, innit? --*/
+
+ 0x00000000L, 0x04c11db7L, 0x09823b6eL, 0x0d4326d9L, 0x130476dcL,
+ 0x17c56b6bL, 0x1a864db2L, 0x1e475005L, 0x2608edb8L, 0x22c9f00fL,
+ 0x2f8ad6d6L, 0x2b4bcb61L, 0x350c9b64L, 0x31cd86d3L, 0x3c8ea00aL,
+ 0x384fbdbdL, 0x4c11db70L, 0x48d0c6c7L, 0x4593e01eL, 0x4152fda9L,
+ 0x5f15adacL, 0x5bd4b01bL, 0x569796c2L, 0x52568b75L, 0x6a1936c8L,
+ 0x6ed82b7fL, 0x639b0da6L, 0x675a1011L, 0x791d4014L, 0x7ddc5da3L,
+ 0x709f7b7aL, 0x745e66cdL, 0x9823b6e0L, 0x9ce2ab57L, 0x91a18d8eL,
+ 0x95609039L, 0x8b27c03cL, 0x8fe6dd8bL, 0x82a5fb52L, 0x8664e6e5L,
+ 0xbe2b5b58L, 0xbaea46efL, 0xb7a96036L, 0xb3687d81L, 0xad2f2d84L,
+ 0xa9ee3033L, 0xa4ad16eaL, 0xa06c0b5dL, 0xd4326d90L, 0xd0f37027L,
+ 0xddb056feL, 0xd9714b49L, 0xc7361b4cL, 0xc3f706fbL, 0xceb42022L,
+ 0xca753d95L, 0xf23a8028L, 0xf6fb9d9fL, 0xfbb8bb46L, 0xff79a6f1L,
+ 0xe13ef6f4L, 0xe5ffeb43L, 0xe8bccd9aL, 0xec7dd02dL, 0x34867077L,
+ 0x30476dc0L, 0x3d044b19L, 0x39c556aeL, 0x278206abL, 0x23431b1cL,
+ 0x2e003dc5L, 0x2ac12072L, 0x128e9dcfL, 0x164f8078L, 0x1b0ca6a1L,
+ 0x1fcdbb16L, 0x018aeb13L, 0x054bf6a4L, 0x0808d07dL, 0x0cc9cdcaL,
+ 0x7897ab07L, 0x7c56b6b0L, 0x71159069L, 0x75d48ddeL, 0x6b93dddbL,
+ 0x6f52c06cL, 0x6211e6b5L, 0x66d0fb02L, 0x5e9f46bfL, 0x5a5e5b08L,
+ 0x571d7dd1L, 0x53dc6066L, 0x4d9b3063L, 0x495a2dd4L, 0x44190b0dL,
+ 0x40d816baL, 0xaca5c697L, 0xa864db20L, 0xa527fdf9L, 0xa1e6e04eL,
+ 0xbfa1b04bL, 0xbb60adfcL, 0xb6238b25L, 0xb2e29692L, 0x8aad2b2fL,
+ 0x8e6c3698L, 0x832f1041L, 0x87ee0df6L, 0x99a95df3L, 0x9d684044L,
+ 0x902b669dL, 0x94ea7b2aL, 0xe0b41de7L, 0xe4750050L, 0xe9362689L,
+ 0xedf73b3eL, 0xf3b06b3bL, 0xf771768cL, 0xfa325055L, 0xfef34de2L,
+ 0xc6bcf05fL, 0xc27dede8L, 0xcf3ecb31L, 0xcbffd686L, 0xd5b88683L,
+ 0xd1799b34L, 0xdc3abdedL, 0xd8fba05aL, 0x690ce0eeL, 0x6dcdfd59L,
+ 0x608edb80L, 0x644fc637L, 0x7a089632L, 0x7ec98b85L, 0x738aad5cL,
+ 0x774bb0ebL, 0x4f040d56L, 0x4bc510e1L, 0x46863638L, 0x42472b8fL,
+ 0x5c007b8aL, 0x58c1663dL, 0x558240e4L, 0x51435d53L, 0x251d3b9eL,
+ 0x21dc2629L, 0x2c9f00f0L, 0x285e1d47L, 0x36194d42L, 0x32d850f5L,
+ 0x3f9b762cL, 0x3b5a6b9bL, 0x0315d626L, 0x07d4cb91L, 0x0a97ed48L,
+ 0x0e56f0ffL, 0x1011a0faL, 0x14d0bd4dL, 0x19939b94L, 0x1d528623L,
+ 0xf12f560eL, 0xf5ee4bb9L, 0xf8ad6d60L, 0xfc6c70d7L, 0xe22b20d2L,
+ 0xe6ea3d65L, 0xeba91bbcL, 0xef68060bL, 0xd727bbb6L, 0xd3e6a601L,
+ 0xdea580d8L, 0xda649d6fL, 0xc423cd6aL, 0xc0e2d0ddL, 0xcda1f604L,
+ 0xc960ebb3L, 0xbd3e8d7eL, 0xb9ff90c9L, 0xb4bcb610L, 0xb07daba7L,
+ 0xae3afba2L, 0xaafbe615L, 0xa7b8c0ccL, 0xa379dd7bL, 0x9b3660c6L,
+ 0x9ff77d71L, 0x92b45ba8L, 0x9675461fL, 0x8832161aL, 0x8cf30badL,
+ 0x81b02d74L, 0x857130c3L, 0x5d8a9099L, 0x594b8d2eL, 0x5408abf7L,
+ 0x50c9b640L, 0x4e8ee645L, 0x4a4ffbf2L, 0x470cdd2bL, 0x43cdc09cL,
+ 0x7b827d21L, 0x7f436096L, 0x7200464fL, 0x76c15bf8L, 0x68860bfdL,
+ 0x6c47164aL, 0x61043093L, 0x65c52d24L, 0x119b4be9L, 0x155a565eL,
+ 0x18197087L, 0x1cd86d30L, 0x029f3d35L, 0x065e2082L, 0x0b1d065bL,
+ 0x0fdc1becL, 0x3793a651L, 0x3352bbe6L, 0x3e119d3fL, 0x3ad08088L,
+ 0x2497d08dL, 0x2056cd3aL, 0x2d15ebe3L, 0x29d4f654L, 0xc5a92679L,
+ 0xc1683bceL, 0xcc2b1d17L, 0xc8ea00a0L, 0xd6ad50a5L, 0xd26c4d12L,
+ 0xdf2f6bcbL, 0xdbee767cL, 0xe3a1cbc1L, 0xe760d676L, 0xea23f0afL,
+ 0xeee2ed18L, 0xf0a5bd1dL, 0xf464a0aaL, 0xf9278673L, 0xfde69bc4L,
+ 0x89b8fd09L, 0x8d79e0beL, 0x803ac667L, 0x84fbdbd0L, 0x9abc8bd5L,
+ 0x9e7d9662L, 0x933eb0bbL, 0x97ffad0cL, 0xafb010b1L, 0xab710d06L,
+ 0xa6322bdfL, 0xa2f33668L, 0xbcb4666dL, 0xb8757bdaL, 0xb5365d03L,
+ 0xb1f740b4L};
+
+/*-------------------------------------------------------------*/
+/*--- end crctable.h ---*/
+/*-------------------------------------------------------------*/
diff --git a/toolkit/mozapps/update/updater/dep1.der b/toolkit/mozapps/update/updater/dep1.der
new file mode 100644
index 0000000000..655c2d10d4
--- /dev/null
+++ b/toolkit/mozapps/update/updater/dep1.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/dep2.der b/toolkit/mozapps/update/updater/dep2.der
new file mode 100644
index 0000000000..c59ac7f790
--- /dev/null
+++ b/toolkit/mozapps/update/updater/dep2.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/gen_cert_header.py b/toolkit/mozapps/update/updater/gen_cert_header.py
new file mode 100644
index 0000000000..da78cad674
--- /dev/null
+++ b/toolkit/mozapps/update/updater/gen_cert_header.py
@@ -0,0 +1,27 @@
+# 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 os
+
+
+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(out_fh, in_filename):
+ assert out_fh.name.endswith(".h")
+ array_name = os.path.basename(out_fh.name)[:-2] + "Data"
+ hexified = ["0x%02x" % byte for byte in file_byte_generator(in_filename)]
+
+ print("const uint8_t " + array_name + "[] = {", file=out_fh)
+ print(", ".join(hexified), file=out_fh)
+ print("};", file=out_fh)
+ return 0
diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm
new file mode 100644
index 0000000000..917f282d9f
--- /dev/null
+++ b/toolkit/mozapps/update/updater/launchchild_osx.mm
@@ -0,0 +1,519 @@
+/* -*- 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 <SystemConfiguration/SystemConfiguration.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include "readstrings.h"
+
+#define ARCH_PATH "/usr/bin/arch"
+#if defined(__x86_64__)
+// Work around the fact that this constant is not available in the macOS SDK
+# define kCFBundleExecutableArchitectureARM64 0x0100000c
+#endif
+
+class MacAutoreleasePool {
+ public:
+ MacAutoreleasePool() { mPool = [[NSAutoreleasePool alloc] init]; }
+ ~MacAutoreleasePool() { [mPool release]; }
+
+ private:
+ NSAutoreleasePool* mPool;
+};
+
+#if defined(__x86_64__)
+/*
+ * Returns true if the process is running under Rosetta translation. Returns
+ * false if running natively or if an error was encountered. We use the
+ * `sysctl.proc_translated` sysctl which is documented by Apple to be used
+ * for this purpose.
+ */
+bool IsProcessRosettaTranslated() {
+ int ret = 0;
+ size_t size = sizeof(ret);
+ if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
+ if (errno != ENOENT) {
+ fprintf(stderr, "Failed to check for translation environment\n");
+ }
+ return false;
+ }
+ return (ret == 1);
+}
+
+// Returns true if the binary at |executablePath| can be executed natively
+// on an arm64 Mac. Returns false otherwise or if an error occurred.
+bool IsBinaryArmExecutable(const char* executablePath) {
+ bool isArmExecutable = false;
+
+ CFURLRef url = ::CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault, (const UInt8*)executablePath, strlen(executablePath),
+ false);
+ if (!url) {
+ return false;
+ }
+
+ CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url);
+ if (!archs) {
+ CFRelease(url);
+ return false;
+ }
+
+ CFIndex archCount = ::CFArrayGetCount(archs);
+ for (CFIndex i = 0; i < archCount; i++) {
+ CFNumberRef currentArch =
+ static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archs, i));
+ int currentArchInt = 0;
+ if (!::CFNumberGetValue(currentArch, kCFNumberIntType, &currentArchInt)) {
+ continue;
+ }
+ if (currentArchInt == kCFBundleExecutableArchitectureARM64) {
+ isArmExecutable = true;
+ break;
+ }
+ }
+
+ CFRelease(url);
+ CFRelease(archs);
+
+ return isArmExecutable;
+}
+
+// Returns true if the executable provided in |executablePath| should be
+// launched with a preference for arm64. After updating from an x64 version
+// running under Rosetta, if the update is to a universal binary with arm64
+// support we want to switch to arm64 execution mode. Returns true if those
+// conditions are met and the arch(1) utility at |archPath| is executable.
+// It should be safe to always launch with arch and fallback to x64, but we
+// limit its use to the only scenario it is necessary to minimize risk.
+bool ShouldPreferArmLaunch(const char* archPath, const char* executablePath) {
+ // If not running under Rosetta, we are not on arm64 hardware.
+ if (!IsProcessRosettaTranslated()) {
+ return false;
+ }
+
+ // Ensure the arch(1) utility is present and executable.
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+ NSString* archPathString = [NSString stringWithUTF8String:archPath];
+ if (![fileMgr isExecutableFileAtPath:archPathString]) {
+ return false;
+ }
+
+ // Ensure the binary can be run natively on arm64.
+ return IsBinaryArmExecutable(executablePath);
+}
+#endif // __x86_64__
+
+void LaunchChild(int argc, const char** argv) {
+ MacAutoreleasePool pool;
+
+ @try {
+ bool preferArmLaunch = false;
+
+#if defined(__x86_64__)
+ // When running under Rosetta, child processes inherit the architecture
+ // preference of their parent and therefore universal binaries launched
+ // by an emulated x64 process will launch in x64 mode. If we are running
+ // under Rosetta, launch the child process with a preference for arm64 so
+ // that we will switch to arm64 execution if we have just updated from
+ // x64 to a universal build. This includes if we were already a universal
+ // build and the user is intentionally running under Rosetta.
+ preferArmLaunch = ShouldPreferArmLaunch(ARCH_PATH, argv[0]);
+#endif // __x86_64__
+
+ NSString* launchPath;
+ NSMutableArray* arguments;
+
+ if (preferArmLaunch) {
+ launchPath = [NSString stringWithUTF8String:ARCH_PATH];
+
+ // Size the arguments array to include all the arguments
+ // in |argv| plus two arguments to pass to the arch(1) utility.
+ arguments = [NSMutableArray arrayWithCapacity:argc + 2];
+ [arguments addObject:[NSString stringWithUTF8String:"-arm64"]];
+ [arguments addObject:[NSString stringWithUTF8String:"-x86_64"]];
+
+ // Add the first argument from |argv|. The rest are added below.
+ [arguments addObject:[NSString stringWithUTF8String:argv[0]]];
+ } else {
+ launchPath = [NSString stringWithUTF8String:argv[0]];
+ arguments = [NSMutableArray arrayWithCapacity:argc - 1];
+ }
+
+ for (int i = 1; i < argc; i++) {
+ [arguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+ [NSTask launchedTaskWithLaunchPath:launchPath arguments:arguments];
+ } @catch (NSException* e) {
+ NSLog(@"%@: %@", e.name, e.reason);
+ }
+}
+
+void LaunchMacPostProcess(const char* aAppBundle) {
+ MacAutoreleasePool pool;
+
+ // Launch helper to perform post processing for the update; this is the Mac
+ // analogue of LaunchWinPostProcess (PostUpdateWin).
+ 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
+ return;
+ }
+
+ int readResult;
+ mozilla::UniquePtr<char[]> values[2];
+ readResult = ReadStrings([iniPath UTF8String], "ExeRelPath\0ExeArg\0", 2,
+ values, "PostUpdateMac");
+ if (readResult) {
+ return;
+ }
+
+ NSString* exeRelPath = [NSString stringWithUTF8String:values[0].get()];
+ NSString* exeArg = [NSString stringWithUTF8String:values[1].get()];
+ if (!exeArg || !exeRelPath) {
+ return;
+ }
+
+ // The path must not traverse directories and it must be a relative path.
+ if ([exeRelPath isEqualToString:@".."] || [exeRelPath hasPrefix:@"/"] ||
+ [exeRelPath hasPrefix:@"../"] || [exeRelPath hasSuffix:@"/.."] ||
+ [exeRelPath containsString:@"/../"]) {
+ return;
+ }
+
+ NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle];
+ exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath];
+
+ mozilla::UniquePtr<char[]> optVal;
+ readResult = ReadStrings([iniPath UTF8String], "ExeAsync\0", 1, &optVal,
+ "PostUpdateMac");
+
+ NSTask* task = [[NSTask alloc] init];
+ [task setLaunchPath:exeFullPath];
+ [task setArguments:[NSArray arrayWithObject:exeArg]];
+ [task launch];
+ if (!readResult) {
+ NSString* exeAsync = [NSString stringWithUTF8String:optVal.get()];
+ if ([exeAsync isEqualToString:@"false"]) {
+ [task waitUntilExit];
+ }
+ }
+ // ignore the return value of the task, there's nothing we can do with it
+ [task release];
+}
+
+id ConnectToUpdateServer() {
+ MacAutoreleasePool pool;
+
+ id updateServer = nil;
+ BOOL isConnected = NO;
+ int currTry = 0;
+ const int numRetries = 10; // Number of IPC connection retries before
+ // giving up.
+ while (!isConnected && currTry < numRetries) {
+ @try {
+ updateServer = (id)[NSConnection
+ rootProxyForConnectionWithRegisteredName:@"org.mozilla.updater.server"
+ host:nil
+ usingNameServer:[NSSocketPortNameServer
+ sharedInstance]];
+ if (!updateServer ||
+ ![updateServer respondsToSelector:@selector(abort)] ||
+ ![updateServer respondsToSelector:@selector(getArguments)] ||
+ ![updateServer respondsToSelector:@selector(shutdown)]) {
+ NSLog(@"Server doesn't exist or doesn't provide correct selectors.");
+ sleep(1); // Wait 1 second.
+ currTry++;
+ } else {
+ isConnected = YES;
+ }
+ } @catch (NSException* e) {
+ NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason);
+ sleep(1); // Wait 1 second.
+ currTry++;
+ }
+ }
+ if (!isConnected) {
+ NSLog(@"Failed to connect to update server after several retries.");
+ return nil;
+ }
+ return updateServer;
+}
+
+void CleanupElevatedMacUpdate(bool aFailureOccurred) {
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (updateServer) {
+ @try {
+ if (aFailureOccurred) {
+ [updateServer performSelector:@selector(abort)];
+ } else {
+ [updateServer performSelector:@selector(shutdown)];
+ }
+ } @catch (NSException* e) {
+ }
+ }
+
+ NSFileManager* manager = [NSFileManager defaultManager];
+ [manager
+ removeItemAtPath:@"/Library/PrivilegedHelperTools/org.mozilla.updater"
+ error:nil];
+ [manager removeItemAtPath:@"/Library/LaunchDaemons/org.mozilla.updater.plist"
+ error:nil];
+ const char* launchctlArgs[] = {"/bin/launchctl", "remove",
+ "org.mozilla.updater"};
+ // The following call will terminate the current process due to the "remove"
+ // argument in launchctlArgs.
+ LaunchChild(3, launchctlArgs);
+}
+
+// Note: Caller is responsible for freeing argv.
+bool ObtainUpdaterArguments(int* argc, char*** argv) {
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (!updateServer) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+
+ @try {
+ NSArray* updaterArguments =
+ [updateServer performSelector:@selector(getArguments)];
+ *argc = [updaterArguments count];
+ char** tempArgv = (char**)malloc(sizeof(char*) * (*argc));
+ for (int i = 0; i < *argc; i++) {
+ int argLen = [[updaterArguments objectAtIndex:i] length] + 1;
+ tempArgv[i] = (char*)malloc(argLen);
+ strncpy(tempArgv[i], [[updaterArguments objectAtIndex:i] UTF8String],
+ argLen);
+ }
+ *argv = tempArgv;
+ } @catch (NSException* e) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+ return true;
+}
+
+/**
+ * The ElevatedUpdateServer is launched from a non-elevated updater process.
+ * It allows an elevated updater process (usually a privileged helper tool) to
+ * connect to it and receive all the necessary arguments to complete a
+ * successful update.
+ */
+@interface ElevatedUpdateServer : NSObject {
+ NSArray* mUpdaterArguments;
+ BOOL mShouldKeepRunning;
+ BOOL mAborted;
+}
+- (id)initWithArgs:(NSArray*)args;
+- (BOOL)runServer;
+- (NSArray*)getArguments;
+- (void)abort;
+- (BOOL)wasAborted;
+- (void)shutdown;
+- (BOOL)shouldKeepRunning;
+@end
+
+@implementation ElevatedUpdateServer
+
+- (id)initWithArgs:(NSArray*)args {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+ mUpdaterArguments = args;
+ mShouldKeepRunning = YES;
+ mAborted = NO;
+ return self;
+}
+
+- (BOOL)runServer {
+ NSPort* serverPort = [NSSocketPort port];
+ NSConnection* server = [NSConnection connectionWithReceivePort:serverPort
+ sendPort:serverPort];
+ [server setRootObject:self];
+ if ([server registerName:@"org.mozilla.updater.server"
+ withNameServer:[NSSocketPortNameServer sharedInstance]] == NO) {
+ NSLog(@"Unable to register as DirectoryServer.");
+ NSLog(@"Is another copy running?");
+ return NO;
+ }
+
+ while ([self shouldKeepRunning] &&
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]])
+ ;
+ return ![self wasAborted];
+}
+
+- (NSArray*)getArguments {
+ return mUpdaterArguments;
+}
+
+- (void)abort {
+ mAborted = YES;
+ [self shutdown];
+}
+
+- (BOOL)wasAborted {
+ return mAborted;
+}
+
+- (void)shutdown {
+ mShouldKeepRunning = NO;
+}
+
+- (BOOL)shouldKeepRunning {
+ return mShouldKeepRunning;
+}
+
+@end
+
+bool ServeElevatedUpdate(int argc, const char** argv) {
+ MacAutoreleasePool pool;
+
+ NSMutableArray* updaterArguments = [NSMutableArray arrayWithCapacity:argc];
+ for (int i = 0; i < argc; i++) {
+ [updaterArguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+
+ ElevatedUpdateServer* updater =
+ [[ElevatedUpdateServer alloc] initWithArgs:[updaterArguments copy]];
+ bool didSucceed = [updater runServer];
+
+ [updater release];
+ return didSucceed;
+}
+
+bool IsOwnedByGroupAdmin(const char* aAppBundle) {
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ NSDictionary* attributes = [fileManager attributesOfItemAtPath:appDir
+ error:nil];
+ bool isOwnedByAdmin = false;
+ if (attributes &&
+ [[attributes valueForKey:NSFileGroupOwnerAccountID] intValue] == 80) {
+ isOwnedByAdmin = true;
+ }
+ return isOwnedByAdmin;
+}
+
+void SetGroupOwnershipAndPermissions(const char* aAppBundle) {
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ NSError* error = nil;
+ NSArray* paths = [fileManager subpathsOfDirectoryAtPath:appDir error:&error];
+ if (error) {
+ return;
+ }
+
+ // Set group ownership of Firefox.app to 80 ("admin") and permissions to
+ // 0775.
+ if (![fileManager setAttributes:@{
+ NSFileGroupOwnerAccountID : @(80),
+ NSFilePosixPermissions : @(0775)
+ }
+ ofItemAtPath:appDir
+ error:&error] ||
+ error) {
+ return;
+ }
+
+ NSArray* permKeys = [NSArray
+ arrayWithObjects:NSFileGroupOwnerAccountID, NSFilePosixPermissions, nil];
+ // For all descendants of Firefox.app, set group ownership to 80 ("admin") and
+ // ensure write permission for the group.
+ for (NSString* currPath in paths) {
+ NSString* child = [appDir stringByAppendingPathComponent:currPath];
+ NSDictionary* oldAttributes = [fileManager attributesOfItemAtPath:child
+ error:&error];
+ if (error) {
+ return;
+ }
+ // Skip symlinks, since they could be pointing to files outside of the .app
+ // bundle.
+ if ([oldAttributes fileType] == NSFileTypeSymbolicLink) {
+ continue;
+ }
+ NSNumber* oldPerms =
+ (NSNumber*)[oldAttributes valueForKey:NSFilePosixPermissions];
+ NSArray* permObjects = [NSArray
+ arrayWithObjects:[NSNumber numberWithUnsignedLong:80],
+ [NSNumber
+ numberWithUnsignedLong:[oldPerms shortValue] |
+ 020],
+ nil];
+ NSDictionary* attributes = [NSDictionary dictionaryWithObjects:permObjects
+ forKeys:permKeys];
+ if (![fileManager setAttributes:attributes
+ ofItemAtPath:child
+ error:&error] ||
+ error) {
+ return;
+ }
+ }
+}
+
+/**
+ * Helper to launch macOS tasks via NSTask.
+ */
+static void LaunchTask(NSString* aPath, NSArray* aArguments) {
+ NSTask* task = [[NSTask alloc] init];
+ [task setExecutableURL:[NSURL fileURLWithPath:aPath]];
+ if (aArguments) {
+ [task setArguments:aArguments];
+ }
+ [task launchAndReturnError:nil];
+ [task release];
+}
+
+static void RegisterAppWithLaunchServices(NSString* aBundlePath) {
+ NSArray* arguments = @[ @"-f", aBundlePath ];
+ LaunchTask(@"/System/Library/Frameworks/CoreServices.framework/Frameworks/"
+ @"LaunchServices.framework/Support/lsregister",
+ arguments);
+}
+
+static void StripQuarantineBit(NSString* aBundlePath) {
+ NSArray* arguments = @[ @"-d", @"com.apple.quarantine", aBundlePath ];
+ LaunchTask(@"/usr/bin/xattr", arguments);
+}
+
+bool PerformInstallationFromDMG(int argc, char** argv) {
+ MacAutoreleasePool pool;
+ if (argc < 4) {
+ return false;
+ }
+ NSString* bundlePath = [NSString stringWithUTF8String:argv[2]];
+ NSString* destPath = [NSString stringWithUTF8String:argv[3]];
+ if ([[NSFileManager defaultManager] copyItemAtPath:bundlePath
+ toPath:destPath
+ error:nil]) {
+ RegisterAppWithLaunchServices(destPath);
+ StripQuarantineBit(destPath);
+ return true;
+ }
+ return false;
+}
diff --git a/toolkit/mozapps/update/updater/loaddlls.cpp b/toolkit/mozapps/update/updater/loaddlls.cpp
new file mode 100644
index 0000000000..462bd0bc18
--- /dev/null
+++ b/toolkit/mozapps/update/updater/loaddlls.cpp
@@ -0,0 +1,84 @@
+/* -*- 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/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
new file mode 100644
index 0000000000..397818ec0c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
@@ -0,0 +1,40 @@
+<?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>CFBundleDisplayName</key>
+ <string>updater</string>
+ <key>CFBundleExecutable</key>
+ <string>org.mozilla.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>CFBundleName</key>
+ <string>updater</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSHasLocalizedDisplayName</key>
+ <true/>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSRequiresAquaSystemAppearance</key>
+ <false/>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSUIElement</key>
+ <true/>
+ <key>SMAuthorizedClients</key>
+ <array>
+ <string>identifier "@MOZ_MACBUNDLE_ID@" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96"</string>
+ </array>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 0000000000..e8036ec8cc
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,8 @@
+/* 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 = "Software Update";
+CFBundleDisplayName = "@APP_NAME@ Software Update";
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..6cfb50406b
--- /dev/null
+++ b/toolkit/mozapps/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/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..1509178370
--- /dev/null
+++ b/toolkit/mozapps/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/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
new file mode 100644
index 0000000000..d7499c6692
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
Binary files differ
diff --git a/toolkit/mozapps/update/updater/module.ver b/toolkit/mozapps/update/updater/module.ver
new file mode 100644
index 0000000000..771416bb11
--- /dev/null
+++ b/toolkit/mozapps/update/updater/module.ver
@@ -0,0 +1 @@
+WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ Software Updater
diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build
new file mode 100644
index 0000000000..eede9cd723
--- /dev/null
+++ b/toolkit/mozapps/update/updater/moz.build
@@ -0,0 +1,78 @@
+# -*- Mode: python; 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/.
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ Program("org.mozilla.updater")
+else:
+ Program("updater")
+
+updater_rel_path = ""
+include("updater-common.build")
+DIRS += ["updater-dep"]
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += ["updater-xpcshell"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LDFLAGS += [
+ "-sectcreate",
+ "__TEXT",
+ "__info_plist",
+ TOPOBJDIR + "/dist/bin/Info.plist",
+ "-sectcreate",
+ "__TEXT",
+ "__launchd_plist",
+ SRCDIR + "/Launchd.plist",
+ ]
+
+GENERATED_FILES = [
+ "dep1Cert.h",
+ "dep2Cert.h",
+ "primaryCert.h",
+ "secondaryCert.h",
+ "xpcshellCert.h",
+]
+
+primary_cert = GENERATED_FILES["primaryCert.h"]
+secondary_cert = GENERATED_FILES["secondaryCert.h"]
+
+# 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
+xpcshell_cert = GENERATED_FILES["xpcshellCert.h"]
+dep1_cert = GENERATED_FILES["dep1Cert.h"]
+dep2_cert = GENERATED_FILES["dep2Cert.h"]
+
+primary_cert.script = "gen_cert_header.py:create_header"
+secondary_cert.script = "gen_cert_header.py:create_header"
+xpcshell_cert.script = "gen_cert_header.py:create_header"
+dep1_cert.script = "gen_cert_header.py:create_header"
+dep2_cert.script = "gen_cert_header.py:create_header"
+
+if CONFIG["MOZ_UPDATE_CHANNEL"] in ("beta", "release", "esr"):
+ primary_cert.inputs += ["release_primary.der"]
+ secondary_cert.inputs += ["release_secondary.der"]
+elif CONFIG["MOZ_UPDATE_CHANNEL"] in (
+ "nightly",
+ "aurora",
+ "nightly-elm",
+ "nightly-pine",
+ "nightly-profiling",
+ "nightly-oak",
+ "nightly-ux",
+ "nightly-larch",
+):
+ primary_cert.inputs += ["nightly_aurora_level3_primary.der"]
+ secondary_cert.inputs += ["nightly_aurora_level3_secondary.der"]
+else:
+ primary_cert.inputs += ["dep1.der"]
+ secondary_cert.inputs += ["dep2.der"]
+
+dep1_cert.inputs += ["dep1.der"]
+dep2_cert.inputs += ["dep2.der"]
+xpcshell_cert.inputs += ["xpcshellCertificate.der"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ FINAL_TARGET_FILES.icons += ["updater.png"]
diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
new file mode 100644
index 0000000000..44fd95dcff
--- /dev/null
+++ b/toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
new file mode 100644
index 0000000000..90f8e6e82c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/progressui.h b/toolkit/mozapps/update/updater/progressui.h
new file mode 100644
index 0000000000..e283c4d1cd
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui.h
@@ -0,0 +1,40 @@
+/* -*- 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();
+#elif defined(XP_MACOSX)
+// Called on the main thread at startup
+int ShowProgressUI(bool indeterminate = false);
+#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/toolkit/mozapps/update/updater/progressui_gtk.cpp b/toolkit/mozapps/update/updater/progressui_gtk.cpp
new file mode 100644
index 0000000000..cfdcd5587c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_gtk.cpp
@@ -0,0 +1,121 @@
+/* -*- 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 <gtk/gtk.h>
+#include <unistd.h>
+#include "mozilla/Sprintf.h"
+#include "mozilla/Atomics.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "updatererrors.h"
+
+#define TIMER_INTERVAL 100
+
+static float sProgressVal; // between 0 and 100
+static mozilla::Atomic<gboolean> sQuit(FALSE);
+static gboolean sEnableUI;
+static guint sTimerID;
+
+static GtkWidget* sWin;
+static GtkWidget* sLabel;
+static GtkWidget* sProgressBar;
+static GdkPixbuf* sPixbuf;
+
+StringTable sStrings;
+
+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) {
+ sEnableUI = gtk_init_check(pargc, pargv);
+ if (sEnableUI) {
+ // Prepare to show the UI here in case the files are modified by the update.
+ char ini_path[PATH_MAX];
+ SprintfLiteral(ini_path, "%s.ini", (*pargv)[0]);
+ if (ReadStrings(ini_path, &sStrings) != OK) {
+ sEnableUI = false;
+ return -1;
+ }
+
+ char icon_path[PATH_MAX];
+ SprintfLiteral(icon_path, "%s/icons/updater.png", (*pargv)[2]);
+ sPixbuf = gdk_pixbuf_new_from_file(icon_path, nullptr);
+ }
+ 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;
+ }
+
+ sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if (!sWin) {
+ return -1;
+ }
+
+ g_signal_connect(G_OBJECT(sWin), "delete_event", G_CALLBACK(OnDeleteEvent),
+ nullptr);
+
+ gtk_window_set_title(GTK_WINDOW(sWin), sStrings.title.get());
+ 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);
+ gtk_window_set_icon(GTK_WINDOW(sWin), sPixbuf);
+ g_object_unref(sPixbuf);
+
+ GtkWidget* vbox = gtk_vbox_new(TRUE, 6);
+ sLabel = gtk_label_new(sStrings.info.get());
+ 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/toolkit/mozapps/update/updater/progressui_null.cpp b/toolkit/mozapps/update/updater/progressui_null.cpp
new file mode 100644
index 0000000000..49877b2faf
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_null.cpp
@@ -0,0 +1,15 @@
+/* -*- 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/toolkit/mozapps/update/updater/progressui_osx.mm b/toolkit/mozapps/update/updater/progressui_osx.mm
new file mode 100644
index 0000000000..4a9f8ef3b1
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_osx.mm
@@ -0,0 +1,137 @@
+/* -*- 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 "mozilla/Sprintf.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "updatererrors.h"
+
+#define TIMER_INTERVAL 0.2
+
+static float sProgressVal; // between 0 and 100
+static BOOL sQuit = NO;
+static BOOL sIndeterminate = NO;
+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.get()]];
+ [progressTextField
+ setStringValue:[NSString stringWithUTF8String:sLabels.info.get()]];
+
+ 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:sIndeterminate];
+ [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(bool indeterminate) {
+ if (!sUpdatePath) {
+ // InitProgressUI was never called.
+ 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 path[PATH_MAX];
+ SprintfLiteral(path, "%s/updater.ini", sUpdatePath);
+ if (ReadStrings(path, &sLabels) != OK) {
+ return -1;
+ }
+
+ sIndeterminate = indeterminate;
+ [NSApplication sharedApplication];
+ [[NSBundle mainBundle] loadNibNamed:@"MainMenu"
+ owner:NSApp
+ topLevelObjects:nil];
+ [NSApp run];
+
+ return 0;
+}
+
+// Called on a background thread
+void QuitProgressUI() { sQuit = YES; }
+
+// Called on a background thread
+void UpdateProgressUI(float progress) {
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/toolkit/mozapps/update/updater/progressui_win.cpp b/toolkit/mozapps/update/updater/progressui_win.cpp
new file mode 100644
index 0000000000..51bd2d8cce
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_win.cpp
@@ -0,0 +1,302 @@
+/* -*- 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 "updatererrors.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) {
+ mozilla::UniquePtr<WCHAR[]> szwTitle;
+ mozilla::UniquePtr<WCHAR[]> szwInfo;
+
+ int bufferSize =
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title.get(), -1, nullptr, 0);
+ szwTitle = mozilla::MakeUnique<WCHAR[]>(bufferSize);
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title.get(), -1, szwTitle.get(),
+ bufferSize);
+ bufferSize =
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info.get(), -1, nullptr, 0);
+ szwInfo = mozilla::MakeUnique<WCHAR[]>(bufferSize);
+ MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info.get(), -1, szwInfo.get(),
+ bufferSize);
+
+ SetWindowTextW(hDlg, szwTitle.get());
+ SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo.get());
+
+ // 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 = NULL;
+ 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.get(), -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/toolkit/mozapps/update/updater/release_primary.der b/toolkit/mozapps/update/updater/release_primary.der
new file mode 100644
index 0000000000..1d94f88ad7
--- /dev/null
+++ b/toolkit/mozapps/update/updater/release_primary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/release_secondary.der b/toolkit/mozapps/update/updater/release_secondary.der
new file mode 100644
index 0000000000..474706c4b7
--- /dev/null
+++ b/toolkit/mozapps/update/updater/release_secondary.der
Binary files differ
diff --git a/toolkit/mozapps/update/updater/resource.h b/toolkit/mozapps/update/updater/resource.h
new file mode 100644
index 0000000000..1dcb47fca1
--- /dev/null
+++ b/toolkit/mozapps/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/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build
new file mode 100644
index 0000000000..fe0b4a85fa
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-common.build
@@ -0,0 +1,142 @@
+# -*- Mode: python; 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/.
+
+link_with_nss = CONFIG["MOZ_USE_NSS_FOR_MAR"] or (
+ CONFIG["OS_ARCH"] == "Linux" and CONFIG["MOZ_VERIFY_MAR_SIGNATURE"]
+)
+if link_with_nss:
+ DEFINES["MAR_NSS"] = True
+
+srcs = [
+ "archivereader.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",
+ ]
+ RCINCLUDE = "%supdater.rc" % updater_rel_path
+ DEFINES["UNICODE"] = True
+ DEFINES["_UNICODE"] = True
+ USE_STATIC_LIBS = True
+
+ # Pick up nsWindowsRestart.cpp
+ LOCAL_INCLUDES += [
+ "/toolkit/xre",
+ ]
+ OS_LIBS += [
+ "comctl32",
+ "ws2_32",
+ "shell32",
+ "shlwapi",
+ "gdi32",
+ "user32",
+ "userenv",
+ "uuid",
+ ]
+
+ if not link_with_nss:
+ OS_LIBS += [
+ "crypt32",
+ "advapi32",
+ ]
+
+USE_LIBS += [
+ "bspatch",
+ "mar",
+ "updatecommon",
+ "xz-embedded",
+]
+
+if link_with_nss:
+ USE_LIBS += [
+ "nspr",
+ "nss",
+ "signmar",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "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 SystemConfiguration",
+ ]
+ if link_with_nss:
+ LDFLAGS += ["-Wl,-rpath,@executable_path/../../../"]
+ else:
+ OS_LIBS += [
+ "-framework Security",
+ ]
+ UNIFIED_SOURCES += [
+ "/toolkit/xre/updaterfileutils_osx.mm",
+ ]
+ LOCAL_INCLUDES += [
+ "/toolkit/xre",
+ ]
+
+if have_progressui == 0:
+ srcs += [
+ "progressui_null.cpp",
+ ]
+
+SOURCES += sorted(srcs)
+
+if CONFIG["MOZ_TSAN"]:
+ # Since mozglue is not linked to the updater,
+ # we need to include our own TSan suppression list.
+ SOURCES += [
+ "TsanOptions.cpp",
+ ]
+
+DEFINES["SPRINTF_H_USES_VSNPRINTF"] = True
+DEFINES["NS_NO_XPCOM"] = True
+DisableStlWrapping()
+for var in ("MAR_CHANNEL_ID", "MOZ_APP_VERSION"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+LOCAL_INCLUDES += [
+ "/toolkit/mozapps/update/common",
+ "/xpcom/base", # for nsVersionComparator.cpp
+]
+
+DELAYLOAD_DLLS += [
+ "crypt32.dll",
+ "comctl32.dll",
+ "userenv.dll",
+ "wsock32.dll",
+]
+
+if CONFIG["CC_TYPE"] == "clang-cl":
+ WIN32_EXE_LDFLAGS += ["-ENTRY:wmainCRTStartup"]
+elif CONFIG["OS_ARCH"] == "WINNT":
+ WIN32_EXE_LDFLAGS += ["-municode"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+ OS_LIBS += CONFIG["MOZ_GTK3_LIBS"]
+
+if CONFIG["CC_TYPE"] == "gcc":
+ CXXFLAGS += ["-Wno-format-truncation"]
diff --git a/toolkit/mozapps/update/updater/updater-dep/moz.build b/toolkit/mozapps/update/updater/updater-dep/moz.build
new file mode 100644
index 0000000000..89c148987c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-dep/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; 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/.
+
+FINAL_TARGET = "_tests/updater-dep"
+
+Program("updater-dep")
+
+updater_rel_path = "../"
+DEFINES["DEP_UPDATER"] = True
+include("../updater-common.build")
diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
new file mode 100644
index 0000000000..533533c4d9
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/Makefile.in
@@ -0,0 +1,48 @@
+# 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
+
+XPCSHELLTESTDIR = $(topobjdir)/_tests/xpcshell/toolkit/mozapps/update/tests
+
+ifeq (,$(MOZ_SUITE)$(MOZ_THUNDERBIRD))
+MOCHITESTBROWSERDIR = $(topobjdir)/_tests/testing/mochitest/browser/toolkit/mozapps/update/tests/browser
+endif
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+ifneq (,$(COMPILE_ENVIRONMENT)$(MOZ_ARTIFACT_BUILDS))
+tools::
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ # Copy for xpcshell tests
+ $(NSINSTALL) -D $(XPCSHELLTESTDIR)/data/updater-xpcshell.app
+ rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTDIR)/data/updater-xpcshell.app
+ $(call py_action,preprocessor updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings,-Fsubstitution --output-encoding utf-16 -DAPP_NAME='$(MOZ_APP_DISPLAYNAME)' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in -o $(XPCSHELLTESTDIR)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings)
+ $(NSINSTALL) -D $(XPCSHELLTESTDIR)/data/updater-xpcshell.app/Contents/MacOS
+ $(NSINSTALL) $(FINAL_TARGET)/updater-xpcshell $(XPCSHELLTESTDIR)/data/updater-xpcshell.app/Contents/MacOS
+ rm -Rf $(XPCSHELLTESTDIR)/data/updater.app
+ mv $(XPCSHELLTESTDIR)/data/updater-xpcshell.app $(XPCSHELLTESTDIR)/data/updater.app
+ mv $(XPCSHELLTESTDIR)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTDIR)/data/updater.app/Contents/MacOS/org.mozilla.updater
+
+ifdef MOCHITESTBROWSERDIR
+ rsync -a -C $(XPCSHELLTESTDIR)/data/updater.app $(MOCHITESTBROWSERDIR)/
+endif
+else
+ $(MKDIR) -p $(XPCSHELLTESTDIR)/data
+ cp $(FINAL_TARGET)/updater-xpcshell$(BIN_SUFFIX) $(XPCSHELLTESTDIR)/data/updater$(BIN_SUFFIX)
+ifdef MOCHITESTBROWSERDIR
+ $(MKDIR) -p $(MOCHITESTBROWSERDIR)
+ cp $(FINAL_TARGET)/updater-xpcshell$(BIN_SUFFIX) $(MOCHITESTBROWSERDIR)/updater$(BIN_SUFFIX)
+endif
+endif
+endif
diff --git a/toolkit/mozapps/update/updater/updater-xpcshell/moz.build b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
new file mode 100644
index 0000000000..33558a2c59
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater-xpcshell/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; 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/.
+
+FINAL_TARGET = "_tests/xpcshell/toolkit/mozapps/update/tests"
+
+Program("updater-xpcshell")
+
+updater_rel_path = "../"
+DEFINES["TEST_UPDATER"] = True
+include("../updater-common.build")
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
new file mode 100644
index 0000000000..947e84aac3
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -0,0 +1,4909 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Manifest Format
+ * ---------------
+ *
+ * contents = 1*( line )
+ * line = method LWS *( param LWS ) CRLF
+ * CRLF = "\r\n"
+ * LWS = 1*( " " | "\t" )
+ *
+ * Available methods for the manifest file:
+ *
+ * updatev3.manifest
+ * -----------------
+ * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
+ * "remove" | "rmdir" | "rmrfdir" | type
+ *
+ * 'add-if-not' adds a file if it doesn't exist.
+ *
+ * 'type' is the update type (e.g. complete or partial) and when present MUST
+ * be the first entry in the update manifest. The type is used to support
+ * removing files that no longer exist when when applying a complete update by
+ * causing the actions defined in the precomplete file to be performed.
+ *
+ * precomplete
+ * -----------
+ * method = "remove" | "rmdir"
+ */
+#include "bspatch.h"
+#include "progressui.h"
+#include "archivereader.h"
+#include "readstrings.h"
+#include "updatererrors.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "updatecommon.h"
+#ifdef XP_MACOSX
+# include "updaterfileutils_osx.h"
+#endif // XP_MACOSX
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/UniquePtr.h"
+#ifdef XP_WIN
+# include "mozilla/Maybe.h"
+# include "mozilla/WinHeaderOnlyUtils.h"
+# include <climits>
+#endif // XP_WIN
+
+// Amount of the progress bar to use in each of the 3 update stages,
+// should total 100.0.
+#define PROGRESS_PREPARE_SIZE 20.0f
+#define PROGRESS_EXECUTE_SIZE 75.0f
+#define PROGRESS_FINISH_SIZE 5.0f
+
+// Maximum amount of time in ms to wait for the parent process to close. The 30
+// seconds is rather long but there have been bug reports where the parent
+// process has exited after 10 seconds and it is better to give it a chance.
+#define PARENT_WAIT 30000
+
+#if defined(XP_MACOSX)
+// These functions are defined in launchchild_osx.mm
+void CleanupElevatedMacUpdate(bool aFailureOccurred);
+bool IsOwnedByGroupAdmin(const char* aAppBundle);
+bool IsRecursivelyWritable(const char* aPath);
+void LaunchChild(int argc, const char** argv);
+void LaunchMacPostProcess(const char* aAppBundle);
+bool ObtainUpdaterArguments(int* argc, char*** argv);
+bool ServeElevatedUpdate(int argc, const char** argv);
+void SetGroupOwnershipAndPermissions(const char* aAppBundle);
+bool PerformInstallationFromDMG(int argc, char** argv);
+struct UpdateServerThreadArgs {
+ int argc;
+ const NS_tchar** argv;
+};
+#endif
+
+#ifndef _O_BINARY
+# define _O_BINARY 0
+#endif
+
+#ifndef NULL
+# define NULL (0)
+#endif
+
+#ifndef SSIZE_MAX
+# define SSIZE_MAX LONG_MAX
+#endif
+
+// We want to use execv to invoke the callback executable on platforms where
+// we were launched using execv. See nsUpdateDriver.cpp.
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
+# define USE_EXECV
+#endif
+
+#if defined(XP_OPENBSD)
+# define stat64 stat
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && defined(MAR_NSS)
+# include "nss.h"
+# include "prerror.h"
+#endif
+
+#include "crctable.h"
+
+#ifdef XP_WIN
+# ifdef MOZ_MAINTENANCE_SERVICE
+# include "registrycertificates.h"
+# endif
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+# include "updatehelper.h"
+
+// Closes the handle if valid and if the updater is elevated returns with the
+// return code specified. This prevents multiple launches of the callback
+// application by preventing the elevated process from launching the callback.
+# define EXIT_WHEN_ELEVATED(path, handle, retCode) \
+ { \
+ if (handle != INVALID_HANDLE_VALUE) { \
+ CloseHandle(handle); \
+ } \
+ if (NS_tremove(path) && errno != ENOENT) { \
+ return retCode; \
+ } \
+ }
+#endif
+
+//-----------------------------------------------------------------------------
+
+// This BZ2_crc32Table variable lives in libbz2. We just took the
+// data structure from bz2 and created crctables.h
+
+static unsigned int crc32(const unsigned char* buf, unsigned int len) {
+ unsigned int crc = 0xffffffffL;
+
+ const unsigned char* end = buf + len;
+ for (; buf != end; ++buf)
+ crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
+
+ crc = ~crc;
+ return crc;
+}
+
+//-----------------------------------------------------------------------------
+
+// A simple stack based container for a FILE struct that closes the
+// file descriptor from its destructor.
+class AutoFile {
+ public:
+ explicit AutoFile(FILE* file = nullptr) : mFile(file) {}
+
+ ~AutoFile() { close(); }
+
+ AutoFile& operator=(FILE* file) {
+ close();
+ mFile = file;
+ return *this;
+ }
+
+ operator FILE*() { return mFile; }
+
+ FILE* get() { return mFile; }
+
+ private:
+ FILE* mFile;
+
+ void close() {
+ if (mFile != nullptr) {
+ int rv = fclose(mFile);
+ if (rv != 0) {
+ LOG(("File close did not execute successfully"));
+ }
+ mFile = nullptr;
+ }
+ }
+};
+
+struct MARChannelStringTable {
+ MARChannelStringTable() {
+ MARChannelID = mozilla::MakeUnique<char[]>(1);
+ MARChannelID[0] = '\0';
+ }
+
+ mozilla::UniquePtr<char[]> MARChannelID;
+};
+
+//-----------------------------------------------------------------------------
+
+#ifdef XP_MACOSX
+
+// Just a simple class that sets a umask value in its constructor and resets
+// it in its destructor.
+class UmaskContext {
+ public:
+ explicit UmaskContext(mode_t umaskToSet);
+ ~UmaskContext();
+
+ private:
+ mode_t mPreviousUmask;
+};
+
+UmaskContext::UmaskContext(mode_t umaskToSet) {
+ mPreviousUmask = umask(umaskToSet);
+}
+
+UmaskContext::~UmaskContext() { umask(mPreviousUmask); }
+
+#endif
+
+//-----------------------------------------------------------------------------
+
+typedef void (*ThreadFunc)(void* param);
+
+#ifdef XP_WIN
+# include <process.h>
+
+class Thread {
+ public:
+ int Run(ThreadFunc func, void* param) {
+ mThreadFunc = func;
+ mThreadParam = param;
+
+ unsigned int threadID;
+
+ mThread =
+ (HANDLE)_beginthreadex(nullptr, 0, ThreadMain, this, 0, &threadID);
+
+ return mThread ? 0 : -1;
+ }
+ int Join() {
+ WaitForSingleObject(mThread, INFINITE);
+ CloseHandle(mThread);
+ return 0;
+ }
+
+ private:
+ static unsigned __stdcall ThreadMain(void* p) {
+ Thread* self = (Thread*)p;
+ self->mThreadFunc(self->mThreadParam);
+ return 0;
+ }
+ HANDLE mThread;
+ ThreadFunc mThreadFunc;
+ void* mThreadParam;
+};
+
+#elif defined(XP_UNIX)
+# include <pthread.h>
+
+class Thread {
+ public:
+ int Run(ThreadFunc func, void* param) {
+ return pthread_create(&thr, nullptr, (void* (*)(void*))func, param);
+ }
+ int Join() {
+ void* result;
+ return pthread_join(thr, &result);
+ }
+
+ private:
+ pthread_t thr;
+};
+
+#else
+# error "Unsupported platform"
+#endif
+
+//-----------------------------------------------------------------------------
+
+static NS_tchar gPatchDirPath[MAXPATHLEN];
+static NS_tchar gInstallDirPath[MAXPATHLEN];
+static NS_tchar gWorkingDirPath[MAXPATHLEN];
+static ArchiveReader gArchiveReader;
+static bool gSucceeded = false;
+static bool sStagedUpdate = false;
+static bool sReplaceRequest = false;
+static bool sUsingService = false;
+// The updater binary can potentially run twice. It will always initially run
+// with `gIsElevated == false`. If it is run an additional time with elevation,
+// that iteration will run with `gIsElevated == true`.
+static bool gIsElevated = false;
+
+// Normally, we run updates as a result of user action (the user started Firefox
+// or clicked a "Restart to Update" button). But there are some cases when
+// we are not:
+// a) The callback app is a background task. If true then the updater is
+// likely being run as part of a background task.
+// The updater could be run with no callback, but this only happens
+// when performing a staged update (see calls to ProcessUpdates), and there
+// are already checks for sStagedUpdate when showing UI or elevating.
+// b) The environment variable MOZ_APP_SILENT_START is set and not empty. This
+// is set, for instance, on macOS when Firefox had no windows open for a
+// while and restarted to apply updates.
+//
+// In these cases, the update should be installed silently, so we shouldn't:
+// a) show progress UI
+// b) prompt for elevation
+static bool sUpdateSilently = false;
+
+#ifdef XP_WIN
+static NS_tchar gCallbackRelPath[MAXPATHLEN];
+static NS_tchar gCallbackBackupPath[MAXPATHLEN];
+static NS_tchar gDeleteDirPath[MAXPATHLEN];
+
+// Whether to copy the update-elevated.log and update.status file to the update
+// patch directory from a secure directory.
+static bool gCopyOutputFiles = false;
+// Whether to write the update-elevated.log and update.status file to a secure
+// directory.
+static bool gUseSecureOutputPath = false;
+#endif
+
+static const NS_tchar kWhitespace[] = NS_T(" \t");
+static const NS_tchar kNL[] = NS_T("\r\n");
+static const NS_tchar kQuote[] = NS_T("\"");
+
+static inline size_t mmin(size_t a, size_t b) { return (a > b) ? b : a; }
+
+static NS_tchar* mstrtok(const NS_tchar* delims, NS_tchar** str) {
+ if (!*str || !**str) {
+ *str = nullptr;
+ return nullptr;
+ }
+
+ // skip leading "whitespace"
+ NS_tchar* ret = *str;
+ const NS_tchar* d;
+ do {
+ for (d = delims; *d != NS_T('\0'); ++d) {
+ if (*ret == *d) {
+ ++ret;
+ break;
+ }
+ }
+ } while (*d);
+
+ if (!*ret) {
+ *str = ret;
+ return nullptr;
+ }
+
+ NS_tchar* i = ret;
+ do {
+ for (d = delims; *d != NS_T('\0'); ++d) {
+ if (*i == *d) {
+ *i = NS_T('\0');
+ *str = ++i;
+ return ret;
+ }
+ }
+ ++i;
+ } while (*i);
+
+ *str = nullptr;
+ return ret;
+}
+
+#if defined(TEST_UPDATER) || defined(XP_WIN) || defined(XP_MACOSX)
+static bool EnvHasValue(const char* name) {
+ const char* val = getenv(name);
+ return (val && *val);
+}
+#endif
+
+static const NS_tchar* UpdateLogFilename() {
+ if (gIsElevated) {
+ return NS_T("update-elevated.log");
+ }
+ return NS_T("update.log");
+}
+
+#ifdef XP_WIN
+/**
+ * Obtains the update ID from the secure id file located in secure output
+ * directory.
+ *
+ * @param outBuf
+ * A buffer of size UUID_LEN (e.g. 37) to store the result. The uuid is
+ * 36 characters in length and 1 more for null termination.
+ * @return true if successful
+ */
+bool GetSecureID(char* outBuf) {
+ NS_tchar idFilePath[MAX_PATH + 1] = {L'\0'};
+ if (!GetSecureOutputFilePath(gPatchDirPath, L".id", idFilePath)) {
+ return false;
+ }
+
+ AutoFile idFile(NS_tfopen(idFilePath, NS_T("rb")));
+ if (idFile == nullptr) {
+ return false;
+ }
+
+ size_t read = fread(outBuf, UUID_LEN - 1, 1, idFile);
+ if (read != 1) {
+ return false;
+ }
+
+ outBuf[UUID_LEN - 1] = '\0';
+ return true;
+}
+#endif
+
+/**
+ * Calls LogFinish for the update log. On Windows, the unelevated updater copies
+ * the update status file and the update log file that were written by the
+ * elevated updater from the secure directory to the update patch directory.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ * because this function copies the update status file for the elevated
+ * updater and writing the status file after calling output_finish will
+ * overwrite it.
+ */
+static void output_finish() {
+ LogFinish();
+#ifdef XP_WIN
+ if (gCopyOutputFiles) {
+ NS_tchar srcStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
+ if (GetSecureOutputFilePath(gPatchDirPath, L".status", srcStatusPath)) {
+ NS_tchar dstStatusPath[MAXPATHLEN + 1] = {NS_T('\0')};
+ NS_tsnprintf(dstStatusPath,
+ sizeof(dstStatusPath) / sizeof(dstStatusPath[0]),
+ NS_T("%s\\update.status"), gPatchDirPath);
+ CopyFileW(srcStatusPath, dstStatusPath, false);
+ }
+
+ NS_tchar srcLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
+ if (GetSecureOutputFilePath(gPatchDirPath, L".log", srcLogPath)) {
+ NS_tchar dstLogPath[MAXPATHLEN + 1] = {NS_T('\0')};
+ // Unconditionally use "update-elevated.log" here rather than
+ // `UpdateLogFilename` since (a) secure output files are only created by
+ // elevated instances and (b) the copying of the secure output file is
+ // done by the unelevated instance, so `UpdateLogFilename` will return
+ // the wrong thing for this.
+ NS_tsnprintf(dstLogPath, sizeof(dstLogPath) / sizeof(dstLogPath[0]),
+ NS_T("%s\\update-elevated.log"), gPatchDirPath);
+ CopyFileW(srcLogPath, dstLogPath, false);
+ }
+ }
+#endif
+}
+
+/**
+ * Coverts a relative update path to a full path.
+ *
+ * @param relpath
+ * The relative path to convert to a full path.
+ * @return valid filesystem full path or nullptr if memory allocation fails.
+ */
+static NS_tchar* get_full_path(const NS_tchar* relpath) {
+ NS_tchar* destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+ size_t lendestpath = NS_tstrlen(destpath);
+ size_t lenrelpath = NS_tstrlen(relpath);
+ NS_tchar* s = new NS_tchar[lendestpath + lenrelpath + 2];
+
+ NS_tchar* c = s;
+
+ NS_tstrcpy(c, destpath);
+ c += lendestpath;
+ NS_tstrcat(c, NS_T("/"));
+ c++;
+
+ NS_tstrcat(c, relpath);
+ c += lenrelpath;
+ *c = NS_T('\0');
+ return s;
+}
+
+/**
+ * Converts a full update path into a relative path; reverses get_full_path.
+ *
+ * @param fullpath
+ * The absolute path to convert into a relative path.
+ * return pointer to the location within fullpath where the relative path starts
+ * or fullpath itself if it already looks relative.
+ */
+#ifndef XP_WIN
+static const NS_tchar* get_relative_path(const NS_tchar* fullpath) {
+ if (fullpath[0] != '/') {
+ return fullpath;
+ }
+
+ NS_tchar* prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
+
+ // If the path isn't long enough to be absolute, return it as-is.
+ if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix)) {
+ return fullpath;
+ }
+
+ return fullpath + NS_tstrlen(prefix) + 1;
+}
+#endif
+
+/**
+ * Gets the platform specific path and performs simple checks to the path. If
+ * the path checks don't pass nullptr will be returned.
+ *
+ * @param line
+ * The line from the manifest that contains the path.
+ * @param isdir
+ * Whether the path is a directory path. Defaults to false.
+ * @return valid filesystem path or nullptr if the path checks fail.
+ */
+static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
+ NS_tchar* path = mstrtok(kQuote, line);
+ if (!path) {
+ LOG(("get_valid_path: unable to determine path: " LOG_S, *line));
+ return nullptr;
+ }
+
+ // All paths must be relative from the current working directory
+ if (path[0] == NS_T('/')) {
+ LOG(("get_valid_path: path must be relative: " LOG_S, path));
+ return nullptr;
+ }
+
+#ifdef XP_WIN
+ // All paths must be relative from the current working directory
+ if (path[0] == NS_T('\\') || path[1] == NS_T(':')) {
+ LOG(("get_valid_path: path must be relative: " LOG_S, path));
+ return nullptr;
+ }
+#endif
+
+ if (isdir) {
+ // Directory paths must have a trailing forward slash.
+ if (path[NS_tstrlen(path) - 1] != NS_T('/')) {
+ LOG(
+ ("get_valid_path: directory paths must have a trailing forward "
+ "slash: " LOG_S,
+ path));
+ return nullptr;
+ }
+
+ // Remove the trailing forward slash because stat on Windows will return
+ // ENOENT if the path has a trailing slash.
+ path[NS_tstrlen(path) - 1] = NS_T('\0');
+ }
+
+ // Don't allow relative paths that resolve to a parent directory.
+ if (NS_tstrstr(path, NS_T("..")) != nullptr) {
+ LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
+ return nullptr;
+ }
+
+ return path;
+}
+
+/*
+ * Gets a quoted path. The return value is malloc'd and it is the responsibility
+ * of the caller to free it.
+ *
+ * @param path
+ * The path to quote.
+ * @return On success the quoted path and nullptr otherwise.
+ */
+static NS_tchar* get_quoted_path(const NS_tchar* path) {
+ size_t lenQuote = NS_tstrlen(kQuote);
+ size_t lenPath = NS_tstrlen(path);
+ size_t len = lenQuote + lenPath + lenQuote + 1;
+
+ NS_tchar* s = (NS_tchar*)malloc(len * sizeof(NS_tchar));
+ if (!s) {
+ return nullptr;
+ }
+
+ NS_tchar* c = s;
+ NS_tstrcpy(c, kQuote);
+ c += lenQuote;
+ NS_tstrcat(c, path);
+ c += lenPath;
+ NS_tstrcat(c, kQuote);
+ c += lenQuote;
+ *c = NS_T('\0');
+ return s;
+}
+
+static void ensure_write_permissions(const NS_tchar* path) {
+#ifdef XP_WIN
+ (void)_wchmod(path, _S_IREAD | _S_IWRITE);
+#else
+ struct stat fs;
+ if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) {
+ (void)chmod(path, fs.st_mode | S_IWUSR);
+ }
+#endif
+}
+
+static int ensure_remove(const NS_tchar* path) {
+ ensure_write_permissions(path);
+ int rv = NS_tremove(path);
+ if (rv) {
+ LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
+ path, rv, errno));
+ }
+ return rv;
+}
+
+// Remove the directory pointed to by path and all of its files and
+// sub-directories.
+static int ensure_remove_recursive(const NS_tchar* path,
+ bool continueEnumOnFailure = false) {
+ // We use lstat rather than stat here so that we can successfully remove
+ // symlinks.
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ // This error is benign
+ return rv;
+ }
+ if (!S_ISDIR(sInfo.st_mode)) {
+ return ensure_remove(path);
+ }
+
+ NS_tDIR* dir;
+ NS_tdirent* entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return rv;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ rv = ensure_remove_recursive(childPath);
+ if (rv && !continueEnumOnFailure) {
+ break;
+ }
+ }
+ }
+
+ NS_tclosedir(dir);
+
+ if (rv == OK) {
+ ensure_write_permissions(path);
+ rv = NS_trmdir(path);
+ if (rv) {
+ LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ }
+ }
+ return rv;
+}
+
+static bool is_read_only(const NS_tchar* flags) {
+ size_t length = NS_tstrlen(flags);
+ if (length == 0) {
+ return false;
+ }
+
+ // Make sure the string begins with "r"
+ if (flags[0] != NS_T('r')) {
+ return false;
+ }
+
+ // Look for "r+" or "r+b"
+ if (length > 1 && flags[1] == NS_T('+')) {
+ return false;
+ }
+
+ // Look for "rb+"
+ if (NS_tstrcmp(flags, NS_T("rb+")) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static FILE* ensure_open(const NS_tchar* path, const NS_tchar* flags,
+ unsigned int options) {
+ ensure_write_permissions(path);
+ FILE* f = NS_tfopen(path, flags);
+ if (is_read_only(flags)) {
+ // Don't attempt to modify the file permissions if the file is being opened
+ // in read-only mode.
+ return f;
+ }
+ if (NS_tchmod(path, options) != 0) {
+ if (f != nullptr) {
+ fclose(f);
+ }
+ return nullptr;
+ }
+ struct NS_tstat_t ss;
+ if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) {
+ if (f != nullptr) {
+ fclose(f);
+ }
+ return nullptr;
+ }
+ return f;
+}
+
+// Ensure that the directory containing this file exists.
+static int ensure_parent_dir(const NS_tchar* path) {
+ int rv = OK;
+
+ NS_tchar* slash = (NS_tchar*)NS_tstrrchr(path, NS_T('/'));
+ if (slash) {
+ *slash = NS_T('\0');
+ rv = ensure_parent_dir(path);
+ // Only attempt to create the directory if we're not at the root
+ if (rv == OK && *path) {
+ rv = NS_tmkdir(path, 0755);
+ // If the directory already exists, then ignore the error.
+ if (rv < 0 && errno != EEXIST) {
+ LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", "
+ "err: %d",
+ path, errno));
+ rv = WRITE_ERROR;
+ } else {
+ rv = OK;
+ }
+ }
+ *slash = NS_T('/');
+ }
+ return rv;
+}
+
+#ifdef XP_UNIX
+static int ensure_copy_symlink(const NS_tchar* path, const NS_tchar* dest) {
+ // Copy symlinks by creating a new symlink to the same target
+ NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
+ int rv = readlink(path, target, MAXPATHLEN);
+ if (rv == -1) {
+ LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+ rv = symlink(target, dest);
+ if (rv == -1) {
+ LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S
+ ", target: " LOG_S " err: %d",
+ dest, target, errno));
+ return READ_ERROR;
+ }
+ return 0;
+}
+#endif
+
+// Copy the file named path onto a new file named dest.
+static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) {
+#ifdef XP_WIN
+ // Fast path for Windows
+ bool result = CopyFileW(path, dest, false);
+ if (!result) {
+ LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S
+ ", lasterr: %lx",
+ path, dest, GetLastError()));
+ return WRITE_ERROR_FILE_COPY;
+ }
+ return OK;
+#else
+ struct NS_tstat_t ss;
+ int rv = NS_tlstat(path, &ss);
+ if (rv) {
+ LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+
+# ifdef XP_UNIX
+ if (S_ISLNK(ss.st_mode)) {
+ return ensure_copy_symlink(path, dest);
+ }
+# endif
+
+ AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
+ if (!infile) {
+ LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
+ path, errno));
+ return READ_ERROR;
+ }
+ AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode));
+ if (!outfile) {
+ LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
+ dest, errno));
+ return WRITE_ERROR;
+ }
+
+ // This block size was chosen pretty arbitrarily but seems like a reasonable
+ // compromise. For example, the optimal block size on a modern OS X machine
+ // is 100k */
+ const int blockSize = 32 * 1024;
+ void* buffer = malloc(blockSize);
+ if (!buffer) {
+ return UPDATER_MEM_ERROR;
+ }
+
+ while (!feof(infile.get())) {
+ size_t read = fread(buffer, 1, blockSize, infile);
+ if (ferror(infile.get())) {
+ LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", path,
+ errno));
+ free(buffer);
+ return READ_ERROR;
+ }
+
+ size_t written = 0;
+
+ while (written < read) {
+ size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
+ if (chunkWritten <= 0) {
+ LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", dest,
+ errno));
+ free(buffer);
+ return WRITE_ERROR_FILE_COPY;
+ }
+
+ written += chunkWritten;
+ }
+ }
+
+ rv = NS_tchmod(dest, ss.st_mode);
+
+ free(buffer);
+ return rv;
+#endif
+}
+
+template <unsigned N>
+struct copy_recursive_skiplist {
+ NS_tchar paths[N][MAXPATHLEN];
+
+ void append(unsigned index, const NS_tchar* path, const NS_tchar* suffix) {
+ NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
+ }
+
+ bool find(const NS_tchar* path) {
+ for (int i = 0; i < static_cast<int>(N); ++i) {
+ if (!NS_tstricmp(paths[i], path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+// Copy all of the files and subdirectories under path to a new directory named
+// dest. The path names in the skiplist will be skipped and will not be copied.
+template <unsigned N>
+static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest,
+ copy_recursive_skiplist<N>& skiplist) {
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return READ_ERROR;
+ }
+
+#ifdef XP_UNIX
+ if (S_ISLNK(sInfo.st_mode)) {
+ return ensure_copy_symlink(path, dest);
+ }
+#endif
+
+ if (!S_ISDIR(sInfo.st_mode)) {
+ return ensure_copy(path, dest);
+ }
+
+ rv = NS_tmkdir(dest, sInfo.st_mode);
+ if (rv < 0 && errno != EEXIST) {
+ LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return WRITE_ERROR;
+ }
+
+ NS_tDIR* dir;
+ NS_tdirent* entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("ensure_copy_recursive: path is not a directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return READ_ERROR;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ if (skiplist.find(childPath)) {
+ continue;
+ }
+ NS_tchar childPathDest[MAXPATHLEN];
+ NS_tsnprintf(childPathDest,
+ sizeof(childPathDest) / sizeof(childPathDest[0]),
+ NS_T("%s/%s"), dest, entry->d_name);
+ rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
+ if (rv) {
+ break;
+ }
+ }
+ }
+ NS_tclosedir(dir);
+ return rv;
+}
+
+// Renames the specified file to the new file specified. If the destination file
+// exists it is removed.
+static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
+ bool allowDirs = false) {
+ int rv = ensure_parent_dir(dpath);
+ if (rv) {
+ return rv;
+ }
+
+ struct NS_tstat_t spathInfo;
+ rv = NS_tstat(spath, &spathInfo);
+ if (rv) {
+ LOG(("rename_file: failed to read file status info: " LOG_S ", "
+ "err: %d",
+ spath, errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISREG(spathInfo.st_mode)) {
+ if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
+ LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
+ spath, errno));
+ return RENAME_ERROR_EXPECTED_FILE;
+ }
+ LOG(("rename_file: proceeding to rename the directory"));
+ }
+
+ if (!NS_taccess(dpath, F_OK)) {
+ if (ensure_remove(dpath)) {
+ LOG(
+ ("rename_file: destination file exists and could not be "
+ "removed: " LOG_S,
+ dpath));
+ return WRITE_ERROR_DELETE_FILE;
+ }
+ }
+
+ if (NS_trename(spath, dpath) != 0) {
+ LOG(("rename_file: failed to rename file - src: " LOG_S ", "
+ "dst:" LOG_S ", err: %d",
+ spath, dpath, errno));
+ return WRITE_ERROR;
+ }
+
+ return OK;
+}
+
+#ifdef XP_WIN
+// Remove the directory pointed to by path and all of its files and
+// sub-directories. If a file is in use move it to the tobedeleted directory
+// and attempt to schedule removal of the file on reboot
+static int remove_recursive_on_reboot(const NS_tchar* path,
+ const NS_tchar* deleteDir) {
+ struct NS_tstat_t sInfo;
+ int rv = NS_tlstat(path, &sInfo);
+ if (rv) {
+ // This error is benign
+ return rv;
+ }
+
+ if (!S_ISDIR(sInfo.st_mode)) {
+ NS_tchar tmpDeleteFile[MAXPATHLEN + 1];
+ GetUUIDTempFilePath(deleteDir, L"rep", tmpDeleteFile);
+ if (NS_tremove(tmpDeleteFile) && errno != ENOENT) {
+ LOG(("remove_recursive_on_reboot: failed to remove temporary file: " LOG_S
+ ", err: %d",
+ tmpDeleteFile, errno));
+ }
+ rv = rename_file(path, tmpDeleteFile, false);
+ if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr,
+ MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(
+ ("remove_recursive_on_reboot: file will be removed on OS "
+ "reboot: " LOG_S,
+ rv ? path : tmpDeleteFile));
+ } else {
+ LOG((
+ "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
+ "file: " LOG_S,
+ rv ? path : tmpDeleteFile));
+ }
+ return rv;
+ }
+
+ NS_tDIR* dir;
+ NS_tdirent* entry;
+
+ dir = NS_topendir(path);
+ if (!dir) {
+ LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ return rv;
+ }
+
+ while ((entry = NS_treaddir(dir)) != 0) {
+ if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
+ NS_tstrcmp(entry->d_name, NS_T(".."))) {
+ NS_tchar childPath[MAXPATHLEN];
+ NS_tsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
+ NS_T("%s/%s"), path, entry->d_name);
+ // There is no need to check the return value of this call since this
+ // function is only called after an update is successful and there is not
+ // much that can be done to recover if it isn't successful. There is also
+ // no need to log the value since it will have already been logged.
+ remove_recursive_on_reboot(childPath, deleteDir);
+ }
+ }
+
+ NS_tclosedir(dir);
+
+ if (rv == OK) {
+ ensure_write_permissions(path);
+ rv = NS_trmdir(path);
+ if (rv) {
+ LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
+ ", rv: %d, err: %d",
+ path, rv, errno));
+ }
+ }
+ return rv;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+// Create a backup of the specified file by renaming it.
+static int backup_create(const NS_tchar* path) {
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ return rename_file(path, backup);
+}
+
+// Rename the backup of the specified file that was created by renaming it back
+// to the original file.
+static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) {
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
+ if (NS_taccess(backup, F_OK)) {
+ LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
+ return OK;
+ }
+
+ return rename_file(backup, path);
+}
+
+// Discard the backup of the specified file that was created by renaming it.
+static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
+ NS_tchar backup[MAXPATHLEN];
+ NS_tsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
+ NS_T("%s") BACKUP_EXT, path);
+
+ NS_tchar relBackup[MAXPATHLEN];
+ NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
+ NS_T("%s") BACKUP_EXT, relPath);
+
+ // Nothing to discard
+ if (NS_taccess(backup, F_OK)) {
+ return OK;
+ }
+
+ int rv = ensure_remove(backup);
+#if defined(XP_WIN)
+ if (rv && !sStagedUpdate && !sReplaceRequest) {
+ LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
+ NS_tchar path[MAXPATHLEN + 1];
+ GetUUIDTempFilePath(gDeleteDirPath, L"moz", path);
+ if (rename_file(backup, path)) {
+ LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
+ relBackup, relPath));
+ return WRITE_ERROR_DELETE_BACKUP;
+ }
+ // The MoveFileEx call to remove the file on OS reboot will fail if the
+ // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
+ // but this is ok since the installer / uninstaller will delete the
+ // directory containing the file along with its contents after an update is
+ // applied, on reinstall, and on uninstall.
+ if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(
+ ("backup_discard: file renamed and will be removed on OS "
+ "reboot: " LOG_S,
+ relPath));
+ } else {
+ LOG(
+ ("backup_discard: failed to schedule OS reboot removal of "
+ "file: " LOG_S,
+ relPath));
+ }
+ }
+#else
+ if (rv) {
+ return WRITE_ERROR_DELETE_BACKUP;
+ }
+#endif
+
+ return OK;
+}
+
+// Helper function for post-processing a temporary backup.
+static void backup_finish(const NS_tchar* path, const NS_tchar* relPath,
+ int status) {
+ if (status == OK) {
+ backup_discard(path, relPath);
+ } else {
+ backup_restore(path, relPath);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+static int DoUpdate();
+
+class Action {
+ public:
+ Action() : mProgressCost(1), mNext(nullptr) {}
+ virtual ~Action() = default;
+
+ virtual int Parse(NS_tchar* line) = 0;
+
+ // Do any preprocessing to ensure that the action can be performed. Execute
+ // will be called if this Action and all others return OK from this method.
+ virtual int Prepare() = 0;
+
+ // Perform the operation. Return OK to indicate success. After all actions
+ // have been executed, Finish will be called. A requirement of Execute is
+ // that its operation be reversable from Finish.
+ virtual int Execute() = 0;
+
+ // Finish is called after execution of all actions. If status is OK, then
+ // all actions were successfully executed. Otherwise, some action failed.
+ virtual void Finish(int status) = 0;
+
+ int mProgressCost;
+
+ private:
+ Action* mNext;
+
+ friend class ActionList;
+};
+
+class RemoveFile : public Action {
+ public:
+ RemoveFile() : mSkip(0) {}
+
+ int Parse(NS_tchar* line) override;
+ int Prepare() override;
+ int Execute() override;
+ void Finish(int status) override;
+
+ private:
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ int mSkip;
+};
+
+int RemoveFile::Parse(NS_tchar* line) {
+ // format "<deadfile>"
+
+ NS_tchar* validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int RemoveFile::Prepare() {
+ // Skip the file if it already doesn't exist.
+ int rv = NS_taccess(mFile.get(), F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // Make sure that we're actually a file...
+ struct NS_tstat_t fileInfo;
+ rv = NS_tstat(mFile.get(), &fileInfo);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
+ errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISREG(fileInfo.st_mode)) {
+ LOG(("path present, but not a file: " LOG_S, mFile.get()));
+ return DELETE_ERROR_EXPECTED_FILE;
+ }
+
+ NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/'));
+ if (slash) {
+ *slash = NS_T('\0');
+ rv = NS_taccess(mFile.get(), W_OK);
+ *slash = NS_T('/');
+ } else {
+ rv = NS_taccess(NS_T("."), W_OK);
+ }
+
+ if (rv) {
+ LOG(("access failed: %d", errno));
+ return WRITE_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ return OK;
+}
+
+int RemoveFile::Execute() {
+ if (mSkip) {
+ return OK;
+ }
+
+ LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // The file is checked for existence here and in Prepare since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mFile.get(), F_OK);
+ if (rv) {
+ LOG(("file cannot be removed because it does not exist; skipping"));
+ mSkip = 1;
+ return OK;
+ }
+
+ if (sStagedUpdate) {
+ // Staged updates don't need backup files so just remove it.
+ rv = ensure_remove(mFile.get());
+ if (rv) {
+ return rv;
+ }
+ } else {
+ // Rename the old file. It will be removed in Finish.
+ rv = backup_create(mFile.get());
+ if (rv) {
+ LOG(("backup_create failed: %d", rv));
+ return rv;
+ }
+ }
+
+ return OK;
+}
+
+void RemoveFile::Finish(int status) {
+ if (mSkip) {
+ return;
+ }
+
+ LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
+
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ backup_finish(mFile.get(), mRelPath.get(), status);
+ }
+}
+
+class RemoveDir : public Action {
+ public:
+ RemoveDir() : mSkip(0) {}
+
+ int Parse(NS_tchar* line) override;
+ int Prepare() override; // check that the source dir exists
+ int Execute() override;
+ void Finish(int status) override;
+
+ private:
+ mozilla::UniquePtr<NS_tchar[]> mDir;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ int mSkip;
+};
+
+int RemoveDir::Parse(NS_tchar* line) {
+ // format "<deaddir>/"
+
+ NS_tchar* validPath = get_valid_path(&line, true);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mDir.reset(get_full_path(validPath));
+ if (!mDir) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int RemoveDir::Prepare() {
+ // We expect the directory to exist if we are to remove it.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ mSkip = 1;
+ mProgressCost = 0;
+ return OK;
+ }
+
+ LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // Make sure that we're actually a dir.
+ struct NS_tstat_t dirInfo;
+ rv = NS_tstat(mDir.get(), &dirInfo);
+ if (rv) {
+ LOG(("failed to read directory status info: " LOG_S ", err: %d",
+ mRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ if (!S_ISDIR(dirInfo.st_mode)) {
+ LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
+ return DELETE_ERROR_EXPECTED_DIR;
+ }
+
+ rv = NS_taccess(mDir.get(), W_OK);
+ if (rv) {
+ LOG(("access failed: %d, %d", rv, errno));
+ return WRITE_ERROR_DIR_ACCESS_DENIED;
+ }
+
+ return OK;
+}
+
+int RemoveDir::Execute() {
+ if (mSkip) {
+ return OK;
+ }
+
+ LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // The directory is checked for existence at every step since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ LOG(("directory no longer exists; skipping"));
+ mSkip = 1;
+ }
+
+ return OK;
+}
+
+void RemoveDir::Finish(int status) {
+ if (mSkip || status != OK) {
+ return;
+ }
+
+ LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
+
+ // The directory is checked for existence at every step since it might have
+ // been removed by a separate instruction: bug 311099.
+ int rv = NS_taccess(mDir.get(), F_OK);
+ if (rv) {
+ LOG(("directory no longer exists; skipping"));
+ return;
+ }
+
+ if (status == OK) {
+ if (NS_trmdir(mDir.get())) {
+ LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
+ mRelPath.get(), rv, errno));
+ }
+ }
+}
+
+class AddFile : public Action {
+ public:
+ AddFile() : mAdded(false) {}
+
+ int Parse(NS_tchar* line) override;
+ int Prepare() override;
+ int Execute() override;
+ void Finish(int status) override;
+
+ private:
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mRelPath;
+ bool mAdded;
+};
+
+int AddFile::Parse(NS_tchar* line) {
+ // format "<newfile>"
+
+ NS_tchar* validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int AddFile::Prepare() {
+ LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
+
+ return OK;
+}
+
+int AddFile::Execute() {
+ LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
+
+ int rv;
+
+ // First make sure that we can actually get rid of any existing file.
+ rv = NS_taccess(mFile.get(), F_OK);
+ if (rv == 0) {
+ if (sStagedUpdate) {
+ // Staged updates don't need backup files so just remove it.
+ rv = ensure_remove(mFile.get());
+ } else {
+ rv = backup_create(mFile.get());
+ }
+ if (rv) {
+ return rv;
+ }
+ } else {
+ rv = ensure_parent_dir(mFile.get());
+ if (rv) {
+ return rv;
+ }
+ }
+
+#ifdef XP_WIN
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
+ MAXPATHLEN, nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %lu", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
+#else
+ rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
+#endif
+ if (!rv) {
+ mAdded = true;
+ }
+ return rv;
+}
+
+void AddFile::Finish(int status) {
+ LOG(("FINISH ADD " LOG_S, mRelPath.get()));
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ // When there is an update failure and a file has been added it is removed
+ // here since there might not be a backup to replace it.
+ if (status && mAdded) {
+ if (NS_tremove(mFile.get()) && errno != ENOENT) {
+ LOG(("non-fatal error after update failure removing added file: " LOG_S
+ ", err: %d",
+ mFile.get(), errno));
+ }
+ }
+ backup_finish(mFile.get(), mRelPath.get(), status);
+ }
+}
+
+class PatchFile : public Action {
+ public:
+ PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) {}
+
+ ~PatchFile() override;
+
+ int Parse(NS_tchar* line) override;
+ int Prepare() override; // should check for patch file and for checksum here
+ int Execute() override;
+ void Finish(int status) override;
+
+ private:
+ int LoadSourceFile(FILE* ofile);
+
+ static int sPatchIndex;
+
+ const NS_tchar* mPatchFile;
+ mozilla::UniquePtr<NS_tchar[]> mFile;
+ mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
+ int mPatchIndex;
+ MBSPatchHeader header;
+ unsigned char* buf;
+ NS_tchar spath[MAXPATHLEN];
+ AutoFile mPatchStream;
+};
+
+int PatchFile::sPatchIndex = 0;
+
+PatchFile::~PatchFile() {
+ // Make sure mPatchStream gets unlocked on Windows; the system will do that,
+ // but not until some indeterminate future time, and we want determinism.
+ // Normally this happens at the end of Execute, when we close the stream;
+ // this call is here in case Execute errors out.
+#ifdef XP_WIN
+ if (mPatchStream) {
+ UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
+ }
+#endif
+ // Patch files are written to the <working_dir>/updating directory which is
+ // removed after the update has finished so don't delete patch files here.
+
+ if (buf) {
+ free(buf);
+ }
+}
+
+int PatchFile::LoadSourceFile(FILE* ofile) {
+ struct stat os;
+ int rv = fstat(fileno((FILE*)ofile), &os);
+ if (rv) {
+ LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", "
+ "err: %d",
+ mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ if (uint32_t(os.st_size) != header.slen) {
+ LOG(
+ ("LoadSourceFile: destination file size %d does not match expected "
+ "size %d",
+ uint32_t(os.st_size), header.slen));
+ return LOADSOURCE_ERROR_WRONG_SIZE;
+ }
+
+ buf = (unsigned char*)malloc(header.slen);
+ if (!buf) {
+ return UPDATER_MEM_ERROR;
+ }
+
+ size_t r = header.slen;
+ unsigned char* rb = buf;
+ while (r) {
+ const size_t count = mmin(SSIZE_MAX, r);
+ size_t c = fread(rb, 1, count, ofile);
+ if (c != count) {
+ LOG(("LoadSourceFile: error reading destination file: " LOG_S,
+ mFileRelPath.get()));
+ return READ_ERROR;
+ }
+
+ r -= c;
+ rb += c;
+ }
+
+ // Verify that the contents of the source file correspond to what we expect.
+
+ unsigned int crc = crc32(buf, header.slen);
+
+ if (crc != header.scrc32) {
+ LOG(
+ ("LoadSourceFile: destination file crc %d does not match expected "
+ "crc %d",
+ crc, header.scrc32));
+ return CRC_ERROR;
+ }
+
+ return OK;
+}
+
+int PatchFile::Parse(NS_tchar* line) {
+ // format "<patchfile>" "<filetopatch>"
+
+ // Get the path to the patch file inside of the mar
+ mPatchFile = mstrtok(kQuote, &line);
+ if (!mPatchFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar* q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ NS_tchar* validPath = get_valid_path(&line);
+ if (!validPath) {
+ return PARSE_ERROR;
+ }
+
+ mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN);
+ NS_tstrcpy(mFileRelPath.get(), validPath);
+
+ mFile.reset(get_full_path(validPath));
+ if (!mFile) {
+ return PARSE_ERROR;
+ }
+
+ return OK;
+}
+
+int PatchFile::Prepare() {
+ LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
+
+ // extract the patch to a temporary file
+ mPatchIndex = sPatchIndex++;
+
+ NS_tsnprintf(spath, sizeof(spath) / sizeof(spath[0]),
+ NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
+
+ // The removal of pre-existing patch files here is in case a previous update
+ // crashed and left these files behind.
+ if (NS_tremove(spath) && errno != ENOENT) {
+ LOG(("failure removing pre-existing patch file: " LOG_S ", err: %d", spath,
+ errno));
+ return WRITE_ERROR;
+ }
+
+ mPatchStream = NS_tfopen(spath, NS_T("wb+"));
+ if (!mPatchStream) {
+ return WRITE_ERROR;
+ }
+
+#ifdef XP_WIN
+ // Lock the patch file, so it can't be messed with between
+ // when we're done creating it and when we go to apply it.
+ if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1)) {
+ LOG(("Couldn't lock patch file: %lu", GetLastError()));
+ return LOCK_ERROR_PATCH_FILE;
+ }
+
+ char sourcefile[MAXPATHLEN];
+ if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
+ nullptr, nullptr)) {
+ LOG(("error converting wchar to utf8: %lu", GetLastError()));
+ return STRING_CONVERSION_ERROR;
+ }
+
+ int rv = gArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
+#else
+ int rv = gArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
+#endif
+
+ return rv;
+}
+
+int PatchFile::Execute() {
+ LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
+
+ fseek(mPatchStream, 0, SEEK_SET);
+
+ int rv = MBS_ReadHeader(mPatchStream, &header);
+ if (rv) {
+ return rv;
+ }
+
+ FILE* origfile = nullptr;
+#ifdef XP_WIN
+ if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
+ // Read from the copy of the callback when patching since the callback can't
+ // be opened for reading to prevent the application from being launched.
+ origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
+ } else {
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
+ }
+#else
+ origfile = NS_tfopen(mFile.get(), NS_T("rb"));
+#endif
+
+ if (!origfile) {
+ LOG(("unable to open destination file: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ rv = LoadSourceFile(origfile);
+ fclose(origfile);
+ if (rv) {
+ LOG(("LoadSourceFile failed"));
+ return rv;
+ }
+
+ // Rename the destination file if it exists before proceeding so it can be
+ // used to restore the file to its original state if there is an error.
+ struct NS_tstat_t ss;
+ rv = NS_tstat(mFile.get(), &ss);
+ if (rv) {
+ LOG(("failed to read file status info: " LOG_S ", err: %d",
+ mFileRelPath.get(), errno));
+ return READ_ERROR;
+ }
+
+ // Staged updates don't need backup files.
+ if (!sStagedUpdate) {
+ rv = backup_create(mFile.get());
+ if (rv) {
+ return rv;
+ }
+ }
+
+#if defined(HAVE_POSIX_FALLOCATE)
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+ posix_fallocate(fileno((FILE*)ofile), 0, header.dlen);
+#elif defined(XP_WIN)
+ bool shouldTruncate = true;
+ // Creating the file, setting the size, and then closing the file handle
+ // lessens fragmentation more than any other method tested. Other methods that
+ // have been tested are:
+ // 1. _chsize / _chsize_s reduced fragmentation though not completely.
+ // 2. _get_osfhandle and then setting the size reduced fragmentation though
+ // not completely. There are also reports of _get_osfhandle failing on
+ // mingw.
+ HANDLE hfile = CreateFileW(mFile.get(), GENERIC_WRITE, 0, nullptr,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ if (hfile != INVALID_HANDLE_VALUE) {
+ if (SetFilePointer(hfile, header.dlen, nullptr, FILE_BEGIN) !=
+ INVALID_SET_FILE_POINTER &&
+ SetEndOfFile(hfile) != 0) {
+ shouldTruncate = false;
+ }
+ CloseHandle(hfile);
+ }
+
+ AutoFile ofile(ensure_open(
+ mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"), ss.st_mode));
+#elif defined(XP_MACOSX)
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+ // Modified code from FileUtils.cpp
+ fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
+ // Try to get a continous chunk of disk space
+ rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
+ if (rv == -1) {
+ // OK, perhaps we are too fragmented, allocate non-continuous
+ store.fst_flags = F_ALLOCATEALL;
+ rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
+ }
+
+ if (rv != -1) {
+ ftruncate(fileno((FILE*)ofile), header.dlen);
+ }
+#else
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+#endif
+
+ if (ofile == nullptr) {
+ LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
+ errno));
+ return WRITE_ERROR_OPEN_PATCH_FILE;
+ }
+
+#ifdef XP_WIN
+ if (!shouldTruncate) {
+ fseek(ofile, 0, SEEK_SET);
+ }
+#endif
+
+ rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);
+
+ // Go ahead and do a bit of cleanup now to minimize runtime overhead.
+ // Make sure mPatchStream gets unlocked on Windows; the system will do that,
+ // but not until some indeterminate future time, and we want determinism.
+#ifdef XP_WIN
+ UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
+#endif
+ // Set mPatchStream to nullptr to make AutoFile close the file,
+ // so it can be deleted on Windows.
+ mPatchStream = nullptr;
+ // Patch files are written to the <working_dir>/updating directory which is
+ // removed after the update has finished so don't delete patch files here.
+ spath[0] = NS_T('\0');
+ free(buf);
+ buf = nullptr;
+
+ return rv;
+}
+
+void PatchFile::Finish(int status) {
+ LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
+
+ // Staged updates don't create backup files.
+ if (!sStagedUpdate) {
+ backup_finish(mFile.get(), mFileRelPath.get(), status);
+ }
+}
+
+class AddIfFile : public AddFile {
+ public:
+ int Parse(NS_tchar* line) override;
+ int Prepare() override;
+ int Execute() override;
+ void Finish(int status) override;
+
+ protected:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int AddIfFile::Parse(NS_tchar* line) {
+ // format "<testfile>" "<newfile>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar* q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return AddFile::Parse(line);
+}
+
+int AddIfFile::Prepare() {
+ // If the test file does not exist, then skip this action.
+ if (NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = nullptr;
+ return OK;
+ }
+
+ return AddFile::Prepare();
+}
+
+int AddIfFile::Execute() {
+ if (!mTestFile) {
+ return OK;
+ }
+
+ return AddFile::Execute();
+}
+
+void AddIfFile::Finish(int status) {
+ if (!mTestFile) {
+ return;
+ }
+
+ AddFile::Finish(status);
+}
+
+class AddIfNotFile : public AddFile {
+ public:
+ int Parse(NS_tchar* line) override;
+ int Prepare() override;
+ int Execute() override;
+ void Finish(int status) override;
+
+ protected:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int AddIfNotFile::Parse(NS_tchar* line) {
+ // format "<testfile>" "<newfile>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar* q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return AddFile::Parse(line);
+}
+
+int AddIfNotFile::Prepare() {
+ // If the test file exists, then skip this action.
+ if (!NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = NULL;
+ return OK;
+ }
+
+ return AddFile::Prepare();
+}
+
+int AddIfNotFile::Execute() {
+ if (!mTestFile) {
+ return OK;
+ }
+
+ return AddFile::Execute();
+}
+
+void AddIfNotFile::Finish(int status) {
+ if (!mTestFile) {
+ return;
+ }
+
+ AddFile::Finish(status);
+}
+
+class PatchIfFile : public PatchFile {
+ public:
+ int Parse(NS_tchar* line) override;
+ int Prepare() override; // should check for patch file and for checksum here
+ int Execute() override;
+ void Finish(int status) override;
+
+ private:
+ mozilla::UniquePtr<NS_tchar[]> mTestFile;
+};
+
+int PatchIfFile::Parse(NS_tchar* line) {
+ // format "<testfile>" "<patchfile>" "<filetopatch>"
+
+ mTestFile.reset(get_full_path(get_valid_path(&line)));
+ if (!mTestFile) {
+ return PARSE_ERROR;
+ }
+
+ // consume whitespace between args
+ NS_tchar* q = mstrtok(kQuote, &line);
+ if (!q) {
+ return PARSE_ERROR;
+ }
+
+ return PatchFile::Parse(line);
+}
+
+int PatchIfFile::Prepare() {
+ // If the test file does not exist, then skip this action.
+ if (NS_taccess(mTestFile.get(), F_OK)) {
+ mTestFile = nullptr;
+ return OK;
+ }
+
+ return PatchFile::Prepare();
+}
+
+int PatchIfFile::Execute() {
+ if (!mTestFile) {
+ return OK;
+ }
+
+ return PatchFile::Execute();
+}
+
+void PatchIfFile::Finish(int status) {
+ if (!mTestFile) {
+ return;
+ }
+
+ PatchFile::Finish(status);
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef XP_WIN
+# include "nsWindowsRestart.cpp"
+# include "nsWindowsHelpers.h"
+# include "uachelper.h"
+# ifdef MOZ_MAINTENANCE_SERVICE
+# include "pathhash.h"
+# endif
+
+/**
+ * Launch the post update application (helper.exe). It takes in the path of the
+ * callback application to calculate the path of helper.exe. For service updates
+ * this is called from both the system account and the current user account.
+ *
+ * @param installationDir The path to the callback application binary.
+ * @param updateInfoDir The directory where update info is stored.
+ * @return true if there was no error starting the process.
+ */
+bool LaunchWinPostProcess(const WCHAR* installationDir,
+ const WCHAR* updateInfoDir) {
+ WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'};
+ wcsncpy(workingDirectory, installationDir, MAX_PATH);
+
+ // Launch helper.exe to perform post processing (e.g. registry and log file
+ // modifications) for the update.
+ WCHAR inifile[MAX_PATH + 1] = {L'\0'};
+ wcsncpy(inifile, installationDir, MAX_PATH);
+ if (!PathAppendSafe(inifile, L"updater.ini")) {
+ LOG(
+ ("LaunchWinPostProcess failed because PathAppendSafe failed when "
+ "getting INI path"));
+ return false;
+ }
+
+ WCHAR exefile[MAX_PATH + 1];
+ WCHAR exearg[MAX_PATH + 1];
+ if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
+ exefile, MAX_PATH + 1, inifile)) {
+ LOG(("LaunchWinPostProcess failed due to failure to retrieve ExeRelPath"));
+ return false;
+ }
+
+ if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
+ MAX_PATH + 1, inifile)) {
+ LOG(("LaunchWinPostProcess failed due to failure to retrieve ExeArg"));
+ return false;
+ }
+
+ // The relative path must not contain directory traversals, current directory,
+ // or colons.
+ if (wcsstr(exefile, L"..") != nullptr || wcsstr(exefile, L"./") != nullptr ||
+ wcsstr(exefile, L".\\") != nullptr || wcsstr(exefile, L":") != nullptr) {
+ LOG(
+ ("LaunchWinPostProcess failed because executable path contains "
+ "disallowed characters"));
+ return false;
+ }
+
+ // The relative path must not start with a decimal point, backslash, or
+ // forward slash.
+ if (exefile[0] == L'.' || exefile[0] == L'\\' || exefile[0] == L'/') {
+ LOG(("LaunchWinPostProcess failed because first character is invalid"));
+ return false;
+ }
+
+ WCHAR exefullpath[MAX_PATH + 1] = {L'\0'};
+ wcsncpy(exefullpath, installationDir, MAX_PATH);
+ if (!PathAppendSafe(exefullpath, exefile)) {
+ LOG(
+ ("LaunchWinPostProcess failed because PathAppendSafe failed when "
+ "getting full executable path"));
+ return false;
+ }
+
+ if (!IsValidFullPath(exefullpath)) {
+ LOG(
+ ("LaunchWinPostProcess failed because executable path is not a valid, "
+ "full path"));
+ return false;
+ }
+
+# if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE)
+ if (sUsingService &&
+ !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
+ LOG(
+ ("LaunchWinPostProcess failed because the binary doesn't match the "
+ "allowed certificates"));
+ return false;
+ }
+# endif
+
+ WCHAR dlogFile[MAX_PATH + 1];
+ if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
+ LOG(("LaunchWinPostProcess failed because dlogFile path is unavailable"));
+ return false;
+ }
+
+ WCHAR slogFile[MAX_PATH + 1] = {L'\0'};
+ if (gCopyOutputFiles) {
+ if (!GetSecureOutputFilePath(gPatchDirPath, L".log", slogFile)) {
+ LOG(
+ ("LaunchWinPostProcess failed because a secure slogFile path is "
+ "unavailable"));
+ return false;
+ }
+ } else {
+ wcsncpy(slogFile, updateInfoDir, MAX_PATH);
+ if (!PathAppendSafe(slogFile, UpdateLogFilename())) {
+ LOG(("LaunchWinPostProcess failed because slogFile path is unavailable"));
+ return false;
+ }
+ }
+
+ WCHAR dummyArg[14] = {L'\0'};
+ wcsncpy(dummyArg, L"argv0ignored ",
+ sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
+
+ size_t len = wcslen(exearg) + wcslen(dummyArg);
+ WCHAR* cmdline = (WCHAR*)malloc((len + 1) * sizeof(WCHAR));
+ if (!cmdline) {
+ LOG(
+ ("LaunchWinPostProcess failed due to failure to allocate %zu wchars "
+ "for cmdline",
+ len + 1));
+ return false;
+ }
+
+ wcsncpy(cmdline, dummyArg, len);
+ wcscat(cmdline, exearg);
+
+ // We want to launch the post update helper app to update the Windows
+ // registry even if there is a failure with removing the uninstall.update
+ // file or copying the update.log file.
+ CopyFileW(slogFile, dlogFile, false);
+
+ STARTUPINFOW si = {sizeof(si), 0};
+ si.lpDesktop = const_cast<LPWSTR>(L""); // -Wwritable-strings
+ PROCESS_INFORMATION pi = {0};
+
+ bool ok = CreateProcessW(exefullpath, cmdline,
+ nullptr, // no special security attributes
+ nullptr, // no special thread attributes
+ false, // don't inherit filehandles
+ 0, // No special process creation flags
+ nullptr, // inherit my environment
+ workingDirectory, &si, &pi);
+ free(cmdline);
+ if (ok) {
+ LOG(("LaunchWinPostProcess - Waiting for process to complete"));
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ LOG(("LaunchWinPostProcess - Process completed"));
+ } else {
+ LOG(("LaunchWinPostProcess - CreateProcessW failed: %lu", GetLastError()));
+ }
+ return ok;
+}
+
+#endif
+
+static void LaunchCallbackApp(const NS_tchar* workingDir, int argc,
+ NS_tchar** argv, bool usingService) {
+ putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
+
+ // Run from the specified working directory (see bug 312360).
+ if (NS_tchdir(workingDir) != 0) {
+ LOG(("Warning: chdir failed"));
+ }
+
+#if defined(USE_EXECV)
+ execv(argv[0], argv);
+#elif defined(XP_MACOSX)
+ LaunchChild(argc, (const char**)argv);
+#elif defined(XP_WIN)
+ // Do not allow the callback to run when running an update through the
+ // service as session 0. The unelevated updater.exe will do the launching.
+ if (!usingService) {
+ HANDLE hProcess;
+ if (WinLaunchChild(argv[0], argc, argv, nullptr, &hProcess)) {
+ // Keep the current process around until the callback process has created
+ // its message queue, to avoid the launched process's windows being forced
+ // into the background.
+ mozilla::WaitForInputIdle(hProcess);
+ CloseHandle(hProcess);
+ }
+ }
+#else
+# warning "Need implementaton of LaunchCallbackApp"
+#endif
+}
+
+static bool WriteToFile(const NS_tchar* aFilename, const char* aStatus) {
+ LOG(("Writing status to file: %s", aStatus));
+
+ NS_tchar statusFilePath[MAXPATHLEN + 1] = {NS_T('\0')};
+#if defined(XP_WIN)
+ if (gUseSecureOutputPath) {
+ if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
+ LOG(("WriteToFile failed to get secure output path"));
+ return false;
+ }
+ } else {
+ NS_tsnprintf(statusFilePath,
+ sizeof(statusFilePath) / sizeof(statusFilePath[0]),
+ NS_T("%s\\%s"), gPatchDirPath, aFilename);
+ }
+#else
+ NS_tsnprintf(statusFilePath,
+ sizeof(statusFilePath) / sizeof(statusFilePath[0]),
+ NS_T("%s/%s"), gPatchDirPath, aFilename);
+ // Make sure that the directory for the update status file exists
+ if (ensure_parent_dir(statusFilePath)) {
+ LOG(("WriteToFile failed to ensure parent directory's existence"));
+ return false;
+ }
+#endif
+
+ AutoFile statusFile(NS_tfopen(statusFilePath, NS_T("wb+")));
+ if (statusFile == nullptr) {
+ LOG(("WriteToFile failed to open status file: %d", errno));
+ return false;
+ }
+
+ if (fwrite(aStatus, strlen(aStatus), 1, statusFile) != 1) {
+ LOG(("WriteToFile failed to write to status file: %d", errno));
+ return false;
+ }
+
+#if defined(XP_WIN)
+ if (gUseSecureOutputPath) {
+ // This is done after the update status file has been written so if the
+ // write to the update status file fails an existing update status file
+ // won't be used.
+ if (!WriteSecureIDFile(gPatchDirPath)) {
+ LOG(("WriteToFile failed to write secure ID file"));
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+/**
+ * Writes a string to the update.status file.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ * because the output_finish function copies the update status file for
+ * the elevated updater and writing the status file after calling
+ * output_finish will overwrite it.
+ *
+ * @param aStatus
+ * The string to write to the update.status file.
+ * @return true on success.
+ */
+static bool WriteStatusFile(const char* aStatus) {
+ return WriteToFile(NS_T("update.status"), aStatus);
+}
+
+/**
+ * Writes a string to the update.status file based on the status param.
+ *
+ * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
+ * because the output_finish function copies the update status file for
+ * the elevated updater and writing the status file after calling
+ * output_finish will overwrite it.
+ *
+ * @param status
+ * A status code used to determine what string to write to the
+ * update.status file (see code).
+ */
+static void WriteStatusFile(int status) {
+ const char* text;
+
+ char buf[32];
+ if (status == OK) {
+ if (sStagedUpdate) {
+ text = "applied\n";
+ } else {
+ text = "succeeded\n";
+ }
+ } else {
+ snprintf(buf, sizeof(buf) / sizeof(buf[0]), "failed: %d\n", status);
+ text = buf;
+ }
+
+ WriteStatusFile(text);
+}
+
+#if defined(XP_WIN)
+/*
+ * Parses the passed contents of an update status file and checks if the
+ * contained status matches the expected status.
+ *
+ * @param statusString The status file contents.
+ * @param expectedStatus The status to compare the update status file's
+ * contents against.
+ * @param errorCode Optional out parameter. If a pointer is passed and the
+ * update status file contains an error code, the code
+ * will be returned via the out parameter. If a pointer is
+ * passed and the update status file does not contain an error
+ * code, or any error code after the status could not be
+ * parsed, mozilla::Nothing will be returned via this
+ * parameter.
+ * @return true if the status is set to the value indicated by expectedStatus.
+ */
+static bool UpdateStatusIs(const char* statusString, const char* expectedStatus,
+ mozilla::Maybe<int>* errorCode = nullptr) {
+ if (errorCode) {
+ *errorCode = mozilla::Nothing();
+ }
+
+ // Parse the update status file. Expected format is:
+ // Update status string
+ // Optionally followed by:
+ // Colon character (':')
+ // Space character (' ')
+ // Integer error code
+ // Newline character
+ const char* statusEnd = strchr(statusString, ':');
+ if (statusEnd == nullptr) {
+ statusEnd = strchr(statusString, '\n');
+ }
+ if (statusEnd == nullptr) {
+ statusEnd = strchr(statusString, '\0');
+ }
+ size_t statusLen = statusEnd - statusString;
+ size_t expectedStatusLen = strlen(expectedStatus);
+
+ bool statusMatch =
+ statusLen == expectedStatusLen &&
+ strncmp(statusString, expectedStatus, expectedStatusLen) == 0;
+
+ // We only need to continue parsing if (a) there is a place to store the error
+ // code if we parse it, and (b) there is a status code to parse. If the status
+ // string didn't end with a ':', there won't be an error code after it.
+ if (!errorCode || *statusEnd != ':') {
+ return statusMatch;
+ }
+
+ const char* errorCodeStart = statusEnd + 1;
+ char* errorCodeEnd = nullptr;
+ // strtol skips an arbitrary number of leading whitespace characters. This
+ // technically allows us to successfully consume slightly misformatted status
+ // files, since the expected format is for there to be a single space only.
+ long longErrorCode = strtol(errorCodeStart, &errorCodeEnd, 10);
+ if (errorCodeEnd != errorCodeStart && longErrorCode < INT_MAX &&
+ longErrorCode > INT_MIN) {
+ // We don't allow equality with INT_MAX/INT_MIN for two reasons. It could
+ // be that, on this platform, INT_MAX/INT_MIN equal LONG_MAX/LONG_MIN, which
+ // is what strtol gives us if the parsed value was out of bounds. And those
+ // values are already way, way outside the set of valid update error codes
+ // anyways.
+ errorCode->emplace(static_cast<int>(longErrorCode));
+ }
+ return statusMatch;
+}
+
+/*
+ * Reads the secure update status file and sets statusMatch to true if the
+ * status matches the expected status that was passed.
+ *
+ * @param expectedStatus The status to compare the update status file's
+ * contents against.
+ * @param statusMatch Out parameter for specifying if the status is set to
+ * the value indicated by expectedStatus
+ * @param errorCode Optional out parameter. If a pointer is passed and the
+ * update status file contains an error code, the code
+ * will be returned via the out parameter. If a pointer is
+ * passed and the update status file does not contain an error
+ * code, or any error code after the status could not be
+ * parsed, mozilla::Nothing will be returned via this
+ * parameter.
+ * @return true if the information was retrieved successfully.
+ */
+static bool CompareSecureUpdateStatus(
+ const char* expectedStatus, bool& statusMatch,
+ mozilla::Maybe<int>* errorCode = nullptr) {
+ NS_tchar statusFilePath[MAX_PATH + 1] = {L'\0'};
+ if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
+ LOG(
+ ("CompareSecureUpdateStatus failed due to GetSecureOutputFilePath "
+ "failure"));
+ return false;
+ }
+
+ AutoFile file(NS_tfopen(statusFilePath, NS_T("rb")));
+ if (file == nullptr) {
+ LOG(("CompareSecureUpdateStatus failed to open the secure status file: %d",
+ errno));
+ return false;
+ }
+
+ const size_t bufferLength = 32;
+ char buf[bufferLength] = {0};
+ size_t charsRead = fread(buf, sizeof(buf[0]), bufferLength - 1, file);
+ if (ferror(file)) {
+ LOG(("CompareSecureUpdateStatus failed to read status file"));
+ return false;
+ }
+ buf[charsRead] = '\0';
+
+ statusMatch = UpdateStatusIs(buf, expectedStatus, errorCode);
+ LOG(("CompareSecureUpdateStatus %s %s %s", buf,
+ statusMatch ? "matches" : "does not match", expectedStatus));
+ return true;
+}
+
+/*
+ * Reads the secure update status file and sets isSucceeded 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 successfully.
+ */
+static bool IsSecureUpdateStatusSucceeded(bool& isSucceeded) {
+ return CompareSecureUpdateStatus("succeeded", isSucceeded);
+}
+#endif
+
+#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;
+ }
+
+ const size_t bufferLength = 32;
+ char buf[bufferLength] = {0};
+ size_t charsRead = fread(buf, sizeof(buf[0]), bufferLength - 1, file);
+ if (ferror(file)) {
+ return false;
+ }
+ buf[charsRead] = '\0';
+
+ return UpdateStatusIs(buf, "pending-service") ||
+ UpdateStatusIs(buf, "applied-service");
+}
+
+/*
+ * Reads the secure update status file and sets isFailed to true if the
+ * status is set to failed.
+ *
+ * @param isFailed Out parameter for specifying if the status
+ * is set to failed or not.
+ * @param errorCode Optional out parameter. If a pointer is passed and the
+ * update status file contains an error code, the code
+ * will be returned via the out parameter. If a pointer is
+ * passed and the update status file does not contain an error
+ * code, or any error code after the status could not be
+ * parsed, mozilla::Nothing will be returned via this
+ * parameter.
+ * @return true if the information was retrieved successfully.
+ */
+static bool IsSecureUpdateStatusFailed(
+ bool& isFailed, mozilla::Maybe<int>* errorCode = nullptr) {
+ return CompareSecureUpdateStatus("failed", isFailed, errorCode);
+}
+
+/**
+ * This function determines whether the error represented by the passed error
+ * code could potentially be recovered from or bypassed by updating without
+ * using the Maintenance Service (i.e. by showing a UAC prompt).
+ * We don't really want to show a UAC prompt, but it's preferable over the
+ * manual update doorhanger
+ *
+ * @param errorCode An integer error code from the update.status file. Should
+ * be one of the codes enumerated in updatererrors.h.
+ * @returns true if the code represents a Maintenance Service specific error.
+ * Otherwise, false.
+ */
+static bool IsServiceSpecificErrorCode(int errorCode) {
+ return ((errorCode >= 24 && errorCode <= 33) ||
+ (errorCode >= 49 && errorCode <= 58));
+}
+#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: %lu, 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;
+ }
+
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
+ // Platforms that have their updates directory in the installation directory
+ // need to have the last-update.log and backup-update.log files moved from the
+ // old installation directory to the new installation directory.
+ NS_tchar tmpLog[MAXPATHLEN];
+ NS_tsnprintf(tmpLog, sizeof(tmpLog) / sizeof(tmpLog[0]),
+ NS_T("%s/updates/last-update.log"), tmpDir);
+ if (!NS_taccess(tmpLog, F_OK)) {
+ NS_tchar destLog[MAXPATHLEN];
+ NS_tsnprintf(destLog, sizeof(destLog) / sizeof(destLog[0]),
+ NS_T("%s/updates/last-update.log"), destDir);
+ if (NS_tremove(destLog) && errno != ENOENT) {
+ LOG(("non-fatal error removing log file: " LOG_S ", err: %d", destLog,
+ errno));
+ }
+ NS_trename(tmpLog, destLog);
+ }
+#endif
+
+ 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;
+}
+
+#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
+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";
+ int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
+ &results->MARChannelID, "Settings");
+
+ return result;
+}
+#endif
+
+static int GetUpdateFileName(NS_tchar* fileName, int maxChars) {
+ NS_tsnprintf(fileName, maxChars, NS_T("%s/update.mar"), gPatchDirPath);
+ 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) {
+ rv = gArchiveReader.VerifySignature();
+ }
+
+ if (rv == OK) {
+ NS_tchar updateSettingsPath[MAXPATHLEN];
+ 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
+ gInstallDirPath);
+ MARChannelStringTable MARStrings;
+ if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
+ rv = UPDATE_SETTINGS_FILE_CHANNEL;
+ } else {
+ rv = gArchiveReader.VerifyProductInformation(
+ MARStrings.MARChannelID.get(), MOZ_APP_VERSION);
+ }
+ }
+#endif
+
+ if (rv == OK && sStagedUpdate) {
+#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 if (EnvHasValue("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE")) {
+ // The following is to simulate staging so the UI tests have time to
+ // show that the update is being staged.
+ NS_tchar continueFilePath[MAXPATHLEN] = {NS_T('\0')};
+ NS_tsnprintf(continueFilePath,
+ sizeof(continueFilePath) / sizeof(continueFilePath[0]),
+ NS_T("%s/continueStaging"), gInstallDirPath);
+ // Use 300 retries for staging requests to lessen the likelihood of
+ // tests intermittently failing on verify tasks due to launching the
+ // updater. The total time to wait with the default interval of 100 ms
+ // is approximately 30 seconds. The total time for tests is longer to
+ // account for the extra time it takes for the updater to launch.
+ const int max_retries = 300;
+ int retries = 0;
+ while (retries++ < max_retries) {
+# ifdef XP_WIN
+ Sleep(100);
+# else
+ usleep(100000);
+# endif
+ // Continue after the continue file exists and is removed.
+ if (!NS_tremove(continueFilePath)) {
+ break;
+ }
+ }
+ 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 (rv && (sReplaceRequest || sStagedUpdate)) {
+ ensure_remove_recursive(gWorkingDirPath);
+ // 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.
+ if (sReplaceRequest) {
+ WriteStatusFile(sUsingService ? "pending-service" : "pending");
+ } else {
+ WriteStatusFile(rv);
+ }
+ LOG(("failed: %d", rv));
+#ifdef TEST_UPDATER
+ // Some tests need to use --test-process-updates again.
+ putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
+#endif
+ } else {
+#ifdef TEST_UPDATER
+ const char* forceErrorCodeString = getenv("MOZ_FORCE_ERROR_CODE");
+ if (forceErrorCodeString && *forceErrorCodeString) {
+ rv = atoi(forceErrorCodeString);
+ }
+#endif
+ 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();
+}
+
+#ifdef XP_MACOSX
+static void ServeElevatedUpdateThreadFunc(void* param) {
+ UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
+ gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
+ if (!gSucceeded) {
+ WriteStatusFile(ELEVATION_CANCELED);
+ }
+ QuitProgressUI();
+}
+
+void freeArguments(int argc, char** argv) {
+ for (int i = 0; i < argc; i++) {
+ free(argv[i]);
+ }
+ free(argv);
+}
+#endif
+
+int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
+ int callbackIndex
+#ifdef XP_WIN
+ ,
+ const WCHAR* elevatedLockFilePath,
+ HANDLE updateLockFileHandle
+#elif XP_MACOSX
+ ,
+ mozilla::UniquePtr<UmaskContext>
+ umaskContext
+#endif
+) {
+ // We want to make sure to call `output_finish` before we leave this function
+ // and, if we end up launching the callback app, we want to call it before
+ // we do that (so that the callback app can operate on the output).
+ // But we want to do this as late as possible to make the log as detailed as
+ // possible.
+ class RaiiOutputFinish {
+ public:
+ RaiiOutputFinish() : mCalled(false) {}
+ ~RaiiOutputFinish() { call(); }
+ void call() {
+ if (!mCalled) {
+ mCalled = true;
+ output_finish();
+ }
+ }
+
+ private:
+ bool mCalled;
+ } raii_output_finish;
+
+#ifdef XP_MACOSX
+ umaskContext.reset();
+#endif
+
+ if (argc > callbackIndex) {
+#if defined(XP_WIN)
+ if (gSucceeded) {
+ LOG(("Launching Windows post update process"));
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
+ LOG(("The post update process was not launched successfully"));
+ }
+
+# ifdef MOZ_MAINTENANCE_SERVICE
+ // 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) {
+ LOG(("Starting Service Update before launching callback app"));
+ StartServiceUpdate(gInstallDirPath);
+ }
+# endif
+ } else {
+ LOG(("Not launching Windows post update process because !gSucceeded"));
+ }
+
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
+#elif XP_MACOSX
+ if (!gIsElevated) {
+ if (gSucceeded) {
+ LOG(("Launching macOS post update process"));
+ LaunchMacPostProcess(gInstallDirPath);
+ } else {
+ LOG(("Not launching macOS post update process because !gSucceeded"));
+ }
+#endif
+
+ raii_output_finish.call();
+ LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
+ sUsingService);
+#ifdef XP_MACOSX
+ } else { // isElevated
+ LOG(
+ ("Not elevated. Skipping LaunchMacPostProcess and "
+ "LaunchCallbackApp"));
+ }
+#endif /* XP_MACOSX */
+}
+else {
+ LOG(
+ ("No callback arg. Skipping LaunchWinPostProcess and "
+ "LaunchCallbackApp"));
+}
+return 0;
+}
+
+bool ShouldRunSilently(int argc, NS_tchar** argv) {
+#ifdef MOZ_BACKGROUNDTASKS
+ // If the callback has a --backgroundtask switch, consider it a background
+ // task. The CheckArg semantics aren't reproduced in full here,
+ // there's e.g. no check for a parameter and no case-insensitive comparison.
+ for (int i = 1; i < argc; ++i) {
+ if (const auto option = mozilla::internal::ReadAsOption(argv[i])) {
+ const NS_tchar* arg = option.value();
+ if (NS_tstrcmp(arg, NS_T("backgroundtask")) == 0) {
+ return true;
+ }
+ }
+ }
+#endif // MOZ_BACKGROUNDTASKS
+
+#if defined(XP_WIN) || defined(XP_MACOSX)
+ if (EnvHasValue("MOZ_APP_SILENT_START")) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+int NS_main(int argc, NS_tchar** argv) {
+#ifdef MOZ_MAINTENANCE_SERVICE
+ sUsingService = EnvHasValue("MOZ_USING_SERVICE");
+ putenv(const_cast<char*>("MOZ_USING_SERVICE="));
+#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;
+
+ // `isDMGInstall` is only ever true for macOS, but we are declaring it here
+ // to avoid a ton of extra #ifdef's.
+ bool isDMGInstall = false;
+
+#ifdef XP_MACOSX
+ // We want to control file permissions explicitly, or else we could end up
+ // corrupting installs for other users on the system. Accordingly, set the
+ // umask to 0 for all file creations below and reset it on exit. See Bug
+ // 1337007
+ mozilla::UniquePtr<UmaskContext> umaskContext(new UmaskContext(0));
+
+ // This will be used to set `gIsElevated`, but we are going to do it later
+ // when we are ready to set it for every OS to avoid inconsistency.
+ bool isElevated =
+ strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") !=
+ 0;
+ if (isElevated) {
+ if (!ObtainUpdaterArguments(&argc, &argv)) {
+ // Won't actually get here because ObtainUpdaterArguments will terminate
+ // the current process on failure.
+ return 1;
+ }
+ }
+
+ if (argc == 4 && (strstr(argv[1], "-dmgInstall") != 0)) {
+ isDMGInstall = true;
+ if (isElevated) {
+ PerformInstallationFromDMG(argc, argv);
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ return 0;
+ }
+ }
+#endif
+
+ if (!isDMGInstall) {
+ // Skip update-related code path for DMG installs.
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && defined(MAR_NSS)
+ // If using NSS for signature verification, initialize NSS but minimize
+ // the portion we depend on by avoiding all of the NSS databases.
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ PRErrorCode error = PR_GetError();
+ fprintf(stderr, "Could not initialize NSS: %s (%d)",
+ PR_ErrorToName(error), (int)error);
+ _exit(1);
+ }
+#endif
+
+ // 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");
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+#if defined(TEST_UPDATER) && defined(XP_WIN)
+ // The tests use nsIProcess to launch the updater and it is simpler for the
+ // tests to just set an environment variable and have the test updater set
+ // the current working directory than it is to set the current working
+ // directory in the test itself.
+ if (EnvHasValue("CURWORKDIRPATH")) {
+ const WCHAR* val = _wgetenv(L"CURWORKDIRPATH");
+ NS_tchdir(val);
+ }
+#endif
+
+ } // if (!isDMGInstall)
+
+ // The directory containing the update information.
+ NS_tstrncpy(gPatchDirPath, argv[1], MAXPATHLEN);
+ gPatchDirPath[MAXPATHLEN - 1] = NS_T('\0');
+
+#ifdef XP_WIN
+ NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
+ NS_tsnprintf(elevatedLockFilePath,
+ sizeof(elevatedLockFilePath) / sizeof(elevatedLockFilePath[0]),
+ NS_T("%s\\update_elevated.lock"), gPatchDirPath);
+ gUseSecureOutputPath =
+ sUsingService || (NS_tremove(elevatedLockFilePath) && errno != ENOENT);
+
+ // Even if a file has no sharing access, you can still get its attributes
+ // If we are running elevated, this file will exist, having been opened by
+ // the unelevated updater that started this one.
+ gIsElevated =
+ GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
+#elif defined(XP_MACOSX)
+ // This is only ever true on macOS and Windows. We don't currently have a
+ // way of elevating on other platforms.
+ gIsElevated = isElevated;
+#endif
+
+ if (!isDMGInstall) {
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[1])) {
+ // Since the status file is written to the patch directory and the patch
+ // directory is invalid don't write the status file.
+ fprintf(stderr,
+ "The patch directory path is not valid for this "
+ "application (" LOG_S ")\n",
+ argv[1]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[2])) {
+ WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR);
+ fprintf(stderr,
+ "The install directory path is not valid for this "
+ "application (" LOG_S ")\n",
+ argv[2]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ } // if (!isDMGInstall)
+
+ // 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;
+ // Prevent the updater from falling back from updating with the Maintenance
+ // Service to updating without the Service. Used for Service tests.
+ // This is set below via the MOZ_NO_SERVICE_FALLBACK environment variable.
+ bool noServiceFallback = false;
+ // Force the updater to use the Maintenance Service incorrectly, causing it
+ // to fail. Used to test the mechanism that allows the updater to fall back
+ // from using the Maintenance Service to updating without it.
+ // This is set below via the MOZ_FORCE_SERVICE_FALLBACK environment variable.
+ bool forceServiceFallback = false;
+#endif
+
+ if (!isDMGInstall) {
+#ifdef XP_WIN
+ // We never want the service to be used unless we build with
+ // the maintenance service.
+# ifdef MOZ_MAINTENANCE_SERVICE
+ useService = IsUpdateStatusPendingService();
+# ifdef TEST_UPDATER
+ noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK");
+ putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
+ forceServiceFallback = EnvHasValue("MOZ_FORCE_SERVICE_FALLBACK");
+ putenv(const_cast<char*>("MOZ_FORCE_SERVICE_FALLBACK="));
+ // Our tests run with a different apply directory for each test.
+ // We use this registry key on our test machines to store the
+ // allowed name/issuers.
+ testOnlyFallbackKeyExists = DoesFallbackKeyExist();
+# endif
+# 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 (!isDMGInstall)
+
+ // If there is a PID specified and it is not '0' then wait for the process to
+ // exit.
+ NS_tpid pid = 0;
+ if (argc > 4) {
+ pid = NS_tatoi(argv[4]);
+ 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;
+ }
+ }
+
+ if (!isDMGInstall) {
+ // This check is also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (!IsValidFullPath(argv[3])) {
+ WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR);
+ fprintf(stderr,
+ "The working directory path is not valid for this "
+ "application (" LOG_S ")\n",
+ argv[3]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 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(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 (argc > callbackIndex) {
+ if (!IsValidFullPath(argv[callbackIndex])) {
+ WriteStatusFile(INVALID_CALLBACK_PATH_ERROR);
+ fprintf(stderr,
+ "The callback file path is not valid for this "
+ "application (" LOG_S ")\n",
+ argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ size_t len = NS_tstrlen(gInstallDirPath);
+ NS_tchar callbackInstallDir[MAXPATHLEN] = {NS_T('\0')};
+ NS_tstrncpy(callbackInstallDir, argv[callbackIndex], len);
+ if (NS_tstrcmp(gInstallDirPath, callbackInstallDir) != 0) {
+ WriteStatusFile(INVALID_CALLBACK_DIR_ERROR);
+ fprintf(stderr,
+ "The callback file must be located in the "
+ "installation directory (" LOG_S ")\n",
+ argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ return 1;
+ }
+
+ sUpdateSilently =
+ ShouldRunSilently(argc - callbackIndex, argv + callbackIndex);
+ }
+
+ } // if (!isDMGInstall)
+
+ if (!sUpdateSilently && !isDMGInstall
+#ifdef XP_MACOSX
+ && !isElevated
+#endif
+ ) {
+ InitProgressUI(&argc, &argv);
+ }
+
+#ifdef XP_MACOSX
+ if (!isElevated && (!IsRecursivelyWritable(argv[2]) || isDMGInstall)) {
+ // If the app directory isn't recursively writeable or if this is a DMG
+ // install, an elevated helper process is required.
+ if (sUpdateSilently) {
+ // An elevated update always requires an elevation dialog, so if we are
+ // updating silently, don't do an elevated update.
+ // This means that we cannot successfully perform silent updates from
+ // non-admin accounts on a Mac.
+ // It also means that we cannot silently perform the first update by an
+ // admin who was not the installing user. Once the first update has been
+ // installed, the permissions of the installation directory should be
+ // changed such that we don't need to elevate in the future.
+ // Firefox shouldn't actually launch the updater at all in this case. This
+ // is defense in depth.
+ WriteStatusFile(SILENT_UPDATE_NEEDED_ELEVATION_ERROR);
+ fprintf(stderr,
+ "Skipping update to avoid elevation prompt from silent update.");
+ } else {
+ UpdateServerThreadArgs threadArgs;
+ threadArgs.argc = argc;
+ threadArgs.argv = const_cast<const NS_tchar**>(argv);
+
+ Thread t1;
+ if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
+ // Show an indeterminate progress bar while an elevated update is in
+ // progress.
+ if (!isDMGInstall) {
+ ShowProgressUI(true);
+ }
+ }
+ t1.Join();
+ }
+
+ LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex,
+ std::move(umaskContext));
+ return gSucceeded ? 0 : 1;
+ }
+#endif
+
+#ifdef XP_WIN
+ HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
+#endif
+
+ if (!isDMGInstall) {
+ NS_tchar logFilePath[MAXPATHLEN + 1] = {L'\0'};
+#ifdef XP_WIN
+ if (gUseSecureOutputPath) {
+ // Remove the secure output files so it is easier to determine when new
+ // files are created in the unelevated updater.
+ RemoveSecureOutputFiles(gPatchDirPath);
+
+ (void)GetSecureOutputFilePath(gPatchDirPath, L".log", logFilePath);
+ } else {
+ NS_tsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
+ NS_T("%s\\%s"), gPatchDirPath, UpdateLogFilename());
+ }
+#else
+ NS_tsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
+ NS_T("%s/%s"), gPatchDirPath, UpdateLogFilename());
+#endif
+ LogInit(logFilePath);
+
+ LOG(("sUsingService=%s", sUsingService ? "true" : "false"));
+ LOG(("sUpdateSilently=%s", sUpdateSilently ? "true" : "false"));
+#ifdef XP_WIN
+ LOG(("gUseSecureOutputPath=%s", gUseSecureOutputPath ? "true" : "false"));
+ // Note that this is not the final value of useService
+ LOG(("useService=%s", useService ? "true" : "false"));
+#endif
+ LOG(("gIsElevated=%s", gIsElevated ? "true" : "false"));
+
+ if (!WriteStatusFile("applying")) {
+ LOG(("failed setting status to 'applying'"));
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ output_finish();
+ 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));
+
+#if defined(XP_WIN)
+ // These checks are also performed in workmonitor.cpp since the maintenance
+ // service can be called directly.
+ if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
+ if (!sStagedUpdate && !sReplaceRequest) {
+ WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
+ LOG(
+ ("Installation directory and working directory must be the same "
+ "for non-staged updates. Exiting."));
+ output_finish();
+ return 1;
+ }
+
+ NS_tchar workingDirParent[MAX_PATH];
+ NS_tsnprintf(workingDirParent,
+ sizeof(workingDirParent) / sizeof(workingDirParent[0]),
+ NS_T("%s"), gWorkingDirPath);
+ if (!PathRemoveFileSpecW(workingDirParent)) {
+ WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
+ LOG(("Error calling PathRemoveFileSpecW: %lu", GetLastError()));
+ output_finish();
+ return 1;
+ }
+
+ if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) {
+ WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
+ LOG(
+ ("The apply-to directory must be the same as or "
+ "a child of the installation directory! Exiting."));
+ output_finish();
+ return 1;
+ }
+ }
+#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;
+# ifdef TEST_UPDATER
+ if (EnvHasValue("MOZ_TEST_SHORTER_WAIT_PID")) {
+ // Use a shorter time to wait for the PID to exit for the test.
+ waitTime = 100;
+ }
+# endif
+ DWORD result = WaitForSingleObject(parent, waitTime);
+ CloseHandle(parent);
+ if (result != WAIT_OBJECT_0) {
+ // Continue to update since the parent application sometimes doesn't
+ // exit (see bug 1375242) so any fixes to the parent application will
+ // be applied instead of leaving the client in a broken state.
+ LOG(("The parent process didn't exit! Continuing with update."));
+ }
+ }
+ }
+#endif
+
+#ifdef XP_WIN
+ if (sReplaceRequest || sStagedUpdate) {
+ // On Windows, when performing a stage or replace request the current
+ // working directory for the process must be changed so it isn't locked.
+ NS_tchar sysDir[MAX_PATH + 1] = {L'\0'};
+ if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
+ NS_tchdir(sysDir);
+ }
+ }
+
+ // 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;
+
+ // Check whether a second instance of the updater should be launched by the
+ // maintenance service or with the 'runas' verb when write access is denied
+ // to the installation directory.
+ if (!sUsingService &&
+ (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
+ LOG(("Checking whether elevation is needed"));
+
+ 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 (NS_tremove(updateLockFilePath) && errno != ENOENT) {
+ // Try to fall back to the old way of doing updates if a staged
+ // update fails.
+ if (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");
+ } else if (sStagedUpdate) {
+ WriteStatusFile(DELETE_ERROR_STAGING_LOCK_FILE);
+ }
+ LOG(("Update already in progress! Exiting"));
+ output_finish();
+ return 1;
+ }
+
+ updateLockFileHandle =
+ CreateFileW(updateLockFilePath, GENERIC_READ | GENERIC_WRITE, 0,
+ nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
+
+ if (updateLockFileHandle == INVALID_HANDLE_VALUE) {
+ LOG(("Failed to open update lock file: %lu", GetLastError()));
+ } else {
+ LOG(("Successfully opened lock file"));
+ }
+
+ // 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 (gIsElevated) {
+ // 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 || forceServiceFallback))) {
+ LOG(("Can't open lock file - seems like we need elevation"));
+
+ HANDLE elevatedFileHandle;
+ if (NS_tremove(elevatedLockFilePath) && errno != ENOENT) {
+ LOG(("Unable to create elevated lock file! Exiting"));
+ output_finish();
+ return 1;
+ }
+
+ 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"));
+ output_finish();
+ return 1;
+ }
+
+ auto cmdLine = mozilla::MakeCommandLine(argc - 1, argv + 1);
+ if (!cmdLine) {
+ LOG(("Failed to make command line! Exiting"));
+ CloseHandle(elevatedFileHandle);
+ output_finish();
+ return 1;
+ }
+
+# ifdef MOZ_MAINTENANCE_SERVICE
+// Only invoke the service for installations in Program Files.
+// This check is duplicated in workmonitor.cpp because the service can
+// be invoked directly without going through the updater.
+# ifndef TEST_UPDATER
+ if (useService) {
+ useService = IsProgramFilesPath(gInstallDirPath);
+ LOG(("After checking IsProgramFilesPath, useService=%s",
+ useService ? "true" : "false"));
+ }
+# endif
+
+ // 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;
+ LOG(("After checking IsLocalFile, useService=%s",
+ useService ? "true" : "false"));
+ }
+
+ // 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;
+ LOG(("After checking IsUnpromptedElevation, useService=%s",
+ useService ? "true" : "false"));
+ }
+ }
+
+ // Make sure the service registry entries for the installation 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 {
+# ifdef TEST_UPDATER
+ useService = testOnlyFallbackKeyExists;
+ LOG(("After failing to open maintenanceServiceKey, useService=%s",
+ useService ? "true" : "false"));
+# endif
+ if (!useService) {
+ lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
+ }
+ }
+ } else {
+ useService = false;
+ lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
+ LOG(("Can't get registry certificate location. useService=false"));
+ }
+ }
+
+ // 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
+ // command for the update.
+ if (useService) {
+ // Get the secure ID before trying to update so it is possible to
+ // determine if the updater or the maintenance service has created a
+ // new one.
+ char uuidStringBefore[UUID_LEN] = {'\0'};
+ bool checkID = GetSecureID(uuidStringBefore);
+ // Write a catchall service failure status in case it fails without
+ // changing the status.
+ WriteStatusFile(SERVICE_UPDATE_STATUS_UNCHANGED);
+
+ int serviceArgc = argc;
+ if (forceServiceFallback && serviceArgc > 2) {
+ // To force the service to fail, we can just pass it too few
+ // arguments. However, we don't want to pass it no arguments,
+ // because then it won't have enough information to write out the
+ // update status file telling us that it failed.
+ serviceArgc = 2;
+ }
+
+ // If the update couldn't be started, then set useService to false so
+ // we do the update the old way.
+ DWORD ret =
+ LaunchServiceSoftwareUpdateCommand(serviceArgc, (LPCWSTR*)argv);
+ useService = (ret == ERROR_SUCCESS);
+ // If the command was launched then wait for the service to be done.
+ if (useService) {
+ LOG(("Launched service successfully"));
+ bool showProgressUI = false;
+ // Never show the progress UI when staging updates or in a
+ // background task.
+ if (!sStagedUpdate && !sUpdateSilently) {
+ // 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;
+ LOG(("Service didn't stop after 10 minutes. useService=false"));
+ } else {
+ LOG(("Service stop detected."));
+ // Copy the secure output files if the secure ID has changed.
+ gCopyOutputFiles = true;
+ char uuidStringAfter[UUID_LEN] = {'\0'};
+ if (checkID && GetSecureID(uuidStringAfter) &&
+ strncmp(uuidStringBefore, uuidStringAfter,
+ sizeof(uuidStringBefore)) == 0) {
+ LOG(
+ ("The secure ID hasn't changed after launching the updater "
+ "using the service"));
+ gCopyOutputFiles = false;
+ }
+ if (gCopyOutputFiles && !sStagedUpdate && !noServiceFallback) {
+ // If the Maintenance Service fails for a Service-specific
+ // reason, we ought to fall back to attempting to update
+ // without the Service.
+ // However, we need the secure output files to be able to be
+ // check the error code, and we can't fall back when we are
+ // staging, because we will need to show a UAC.
+ bool updateFailed;
+ mozilla::Maybe<int> maybeErrorCode;
+ bool success =
+ IsSecureUpdateStatusFailed(updateFailed, &maybeErrorCode);
+ if (success && updateFailed && maybeErrorCode.isSome() &&
+ IsServiceSpecificErrorCode(maybeErrorCode.value())) {
+ useService = false;
+ LOG(("Service-specific failure detected. useService=false"));
+ }
+ }
+ }
+ } else {
+ LOG(("Launching service failed. useService=false"));
+ lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
+ }
+ }
+# endif
+
+ // If the service can't be used when staging an update, make sure that
+ // the UAC prompt is not shown!
+ if (!useService && sStagedUpdate) {
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ // Set an error so the failure is reported. This will be reset
+ // to pending so the update can be applied during the next startup,
+ // see bug 1552853.
+ WriteStatusFile(UNEXPECTED_STAGING_ERROR);
+ LOG(
+ ("Non-critical update staging error! Falling back to non-staged "
+ "updates and exiting"));
+ output_finish();
+ // We don't have a callback when staging so we can just exit.
+ return 0;
+ }
+
+ // If the service can't be used when in a background task, make sure
+ // that the UAC prompt is not shown!
+ if (!useService && sUpdateSilently) {
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ // Set an error so we don't get into an update loop when the callback
+ // runs. This will be reset to pending by handleUpdateFailure in
+ // UpdateService.jsm.
+ WriteStatusFile(SILENT_UPDATE_NEEDED_ELEVATION_ERROR);
+ LOG(("Skipping update to avoid UAC prompt from background task."));
+ output_finish();
+
+ LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
+ sUsingService);
+ return 0;
+ }
+
+ // 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 ||
+ forceServiceFallback)) {
+ LOG(("Elevating via a UAC prompt"));
+ // Get the secure ID before trying to update so it is possible to
+ // determine if the updater has created a new one.
+ char uuidStringBefore[UUID_LEN] = {'\0'};
+ bool checkID = GetSecureID(uuidStringBefore);
+ // Write a catchall failure status in case it fails without changing
+ // the status.
+ WriteStatusFile(UPDATE_STATUS_UNCHANGED);
+
+ 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.get();
+ if (forceServiceFallback) {
+ // In testing, we don't actually want a UAC prompt. We should
+ // already have the permissions such that we shouldn't need it.
+ // And we don't have a good way of accepting the prompt in
+ // automation.
+ sinfo.lpVerb = L"open";
+ // This handle is what lets the updater that we spawn below know
+ // that it's the elevated updater. We are going to close it so that
+ // it doesn't know that and will run un-elevated. Doing this make
+ // this makes for an imperfect test of the service fallback
+ // functionality because it changes how the (usually) elevated
+ // updater runs. One of the effects of this is that the secure
+ // output files will not be used. So that functionality won't really
+ // be covered by testing. But we can't really have the updater run
+ // elevated, because that would require a UAC, which we have no way
+ // to deal with in automation.
+ CloseHandle(elevatedFileHandle);
+ // We need to let go of the update lock to let the un-elevated
+ // updater we are about to spawn update.
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+ } else {
+ sinfo.lpVerb = L"runas";
+ }
+ sinfo.nShow = SW_SHOWNORMAL;
+
+ bool result = ShellExecuteEx(&sinfo);
+
+ if (result) {
+ LOG(("Elevation successful. Waiting for elevated updater to run."));
+ WaitForSingleObject(sinfo.hProcess, INFINITE);
+ LOG(("Elevated updater has finished running."));
+ CloseHandle(sinfo.hProcess);
+
+ // Copy the secure output files if the secure ID has changed.
+ gCopyOutputFiles = true;
+ char uuidStringAfter[UUID_LEN] = {'\0'};
+ if (checkID && GetSecureID(uuidStringAfter) &&
+ strncmp(uuidStringBefore, uuidStringAfter,
+ sizeof(uuidStringBefore)) == 0) {
+ LOG(
+ ("The secure ID hasn't changed after launching the updater "
+ "using runas"));
+ gCopyOutputFiles = false;
+ }
+ } else {
+ // Don't copy the secure output files if the elevation request was
+ // canceled since the status file written below is in the patch
+ // directory. At this point it should already be set to false and
+ // this is set here to make it clear that it should be false at this
+ // point and to prevent future changes from regressing this code.
+ gCopyOutputFiles = false;
+ WriteStatusFile(ELEVATION_CANCELED);
+ LOG(("Elevation canceled."));
+ }
+ } else {
+ LOG(("Not showing a UAC prompt."));
+ LOG(("useService=%s", useService ? "true" : "false"));
+ LOG(("noServiceFallback=%s", noServiceFallback ? "true" : "false"));
+ LOG(("updateLockFileHandle%sINVALID_HANDLE_VALUE",
+ updateLockFileHandle == INVALID_HANDLE_VALUE ? "==" : "!="));
+ LOG(("forceServiceFallback=%s",
+ forceServiceFallback ? "true" : "false"));
+ }
+
+ // If we started the elevated updater, and it finished, check the secure
+ // update status file to make sure that it succeeded, and if it did we
+ // need to launch the PostUpdate process in the unelevated updater which
+ // is running in the current user's session. Note that we don't need to
+ // do this when staging an update since the PostUpdate step runs during
+ // the replace request.
+ if (!sStagedUpdate) {
+ bool updateStatusSucceeded = false;
+ if (IsSecureUpdateStatusSucceeded(updateStatusSucceeded) &&
+ updateStatusSucceeded) {
+ LOG(("Running LaunchWinPostProcess"));
+ if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
+ LOG(("Failed to run LaunchWinPostProcess"));
+ }
+ } else {
+ LOG(
+ ("Not running LaunchWinPostProcess because update status is not"
+ "'succeeded'."));
+ }
+ }
+
+ CloseHandle(elevatedFileHandle);
+
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+
+ if (!useService && noServiceFallback) {
+ // When the service command was not launched at all.
+ // We should only reach 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.
+ gCopyOutputFiles = false;
+ WriteStatusFile(lastFallbackError);
+ }
+
+ // The logging output needs to be finished before launching the callback
+ // application so the update status file contains the value from the
+ // secure directory used by the maintenance service and the elevated
+ // updater.
+ LOG(("Update complete"));
+ output_finish();
+ if (argc > callbackIndex) {
+ LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
+ sUsingService);
+ }
+ return 0;
+
+ // This is the end of the code block for launching another instance of
+ // the updater using either the maintenance service or with the 'runas'
+ // verb when the updater doesn't have write access to the installation
+ // directory.
+ }
+ // This is the end of the code block when the updater was not launched by
+ // the service that checks whether the updater has write access to the
+ // installation directory.
+ }
+ // If we made it this far this is the updater instance that will perform the
+ // actual update and gCopyOutputFiles will be false (e.g. the default
+ // value).
+ LOG(("Going to update via this updater instance."));
+#endif
+
+ if (sStagedUpdate) {
+#ifdef TEST_UPDATER
+ // This allows testing that the correct UI after an update staging failure
+ // that falls back to applying the update on startup. It is simulated due
+ // to the difficulty of creating the conditions for this type of staging
+ // failure.
+ if (EnvHasValue("MOZ_TEST_STAGING_ERROR")) {
+# ifdef XP_WIN
+ if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(updateLockFileHandle);
+ }
+# endif
+ // WRITE_ERROR is one of the cases where the staging failure falls back
+ // to applying the update on startup.
+ WriteStatusFile(WRITE_ERROR);
+ output_finish();
+ return 0;
+ }
+#endif
+ // When staging updates, blow away the old installation directory and
+ // create it from scratch.
+ ensure_remove_recursive(gWorkingDirPath);
+ }
+ if (!sReplaceRequest) {
+ // Try to create the destination directory if it doesn't exist
+ int rv = NS_tmkdir(gWorkingDirPath, 0755);
+ if (rv != OK && errno != EEXIST) {
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
+ output_finish();
+ return 1;
+ }
+ }
+
+#ifdef XP_WIN
+ NS_tchar applyDirLongPath[MAXPATHLEN];
+ if (!GetLongPathNameW(
+ gWorkingDirPath, applyDirLongPath,
+ sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) {
+ WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
+ LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
+ output_finish();
+ 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]))) {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
+ LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
+ output_finish();
+ 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;
+
+ const size_t callbackBackupPathBufSize =
+ sizeof(gCallbackBackupPath) / sizeof(gCallbackBackupPath[0]);
+ const int callbackBackupPathLen =
+ NS_tsnprintf(gCallbackBackupPath, callbackBackupPathBufSize,
+ NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
+
+ if (callbackBackupPathLen < 0 ||
+ callbackBackupPathLen >=
+ static_cast<int>(callbackBackupPathBufSize)) {
+ WriteStatusFile(USAGE_ERROR);
+ LOG(("NS_main: callback backup path truncated"));
+ output_finish();
+
+ // Don't attempt to launch the callback when the callback path is
+ // longer than expected.
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ return 1;
+ }
+
+ // Make a copy of the callback executable so it can be read when
+ // patching.
+ if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, false)) {
+ DWORD copyFileError = GetLastError();
+ if (copyFileError == ERROR_ACCESS_DENIED) {
+ WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+ } else {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
+ }
+ LOG(("NS_main: failed to copy callback file " LOG_S
+ " into place at " LOG_S,
+ argv[callbackIndex], gCallbackBackupPath));
+ output_finish();
+ EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
+ LaunchCallbackApp(argv[callbackIndex], argc - callbackIndex,
+ argv + callbackIndex, sUsingService);
+ return 1;
+ }
+
+ // 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: %lu",
+ 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) {
+ bool proceedWithoutExclusive = true;
+
+ // 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]));
+ if (lastWriteError == ERROR_ACCESS_DENIED) {
+ WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
+ } else {
+ WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
+ }
+
+ proceedWithoutExclusive = false;
+ }
+
+ // Fail even on sharing violation from a background task, since a
+ // background task has a higher risk of interfering with a running
+ // app. Note that this does not apply when staging (when an exclusive
+ // lock isn't necessary), as there is no callback.
+ if (lastWriteError == ERROR_SHARING_VIOLATION && sUpdateSilently) {
+ LOG((
+ "NS_main: callback app file in use, failed to exclusively open "
+ "executable file from background task: " LOG_S,
+ argv[callbackIndex]));
+ WriteStatusFile(WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION);
+
+ proceedWithoutExclusive = false;
+ }
+
+ if (!proceedWithoutExclusive) {
+ if (NS_tremove(gCallbackBackupPath) && errno != ENOENT) {
+ LOG(
+ ("NS_main: unable to remove backup of callback app file, "
+ "path: " LOG_S,
+ gCallbackBackupPath));
+ }
+ output_finish();
+ 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 performing a staged update or replace
+ // request; it can be used during a replace request but then it doesn't
+ // use gDeleteDirPath.
+ 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, on OS reboot
+ // using MoveFileEx if it contains files that are in use, or by the post
+ // update process after the update finishes. On Windows when performing a
+ // normal update (e.g. the update is not a staged update and is not a
+ // replace request) gWorkingDirPath is the same as gInstallDirPath and
+ // gWorkingDirPath is used because it is the destination directory.
+ NS_tsnprintf(gDeleteDirPath,
+ sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
+ NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR);
+
+ if (NS_taccess(gDeleteDirPath, F_OK)) {
+ NS_tmkdir(gDeleteDirPath, 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, or if
+ // this is an elevated process on OSX.
+ Thread t;
+ if (t.Run(UpdateThreadFunc, nullptr) == 0) {
+ if (!sStagedUpdate && !sReplaceRequest && !sUpdateSilently
+#ifdef XP_MACOSX
+ && !isElevated
+#endif
+ ) {
+ ShowProgressUI();
+ }
+ }
+ t.Join();
+
+#ifdef XP_WIN
+ if (argc > callbackIndex && !sReplaceRequest) {
+ if (callbackFile != INVALID_HANDLE_VALUE) {
+ CloseHandle(callbackFile);
+ }
+ // Remove the copy of the callback executable.
+ if (NS_tremove(gCallbackBackupPath) && errno != ENOENT) {
+ LOG(
+ ("NS_main: non-fatal error removing backup of callback app file, "
+ "path: " LOG_S,
+ gCallbackBackupPath));
+ }
+ }
+
+ if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) {
+ 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(gDeleteDirPath, 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 (!isDMGInstall)
+
+#ifdef XP_MACOSX
+ if (isElevated) {
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(false);
+ } else if (IsOwnedByGroupAdmin(gInstallDirPath)) {
+ // If the group ownership of the Firefox .app bundle was set to the "admin"
+ // group during a previous elevated update, we need to ensure that all files
+ // in the bundle have group ownership of "admin" as well as write permission
+ // for the group to not break updates in the future.
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ }
+#endif /* XP_MACOSX */
+
+ LOG(("Running LaunchCallbackAndPostProcessApps"));
+
+ int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
+#ifdef XP_WIN
+ ,
+ elevatedLockFilePath,
+ updateLockFileHandle
+#elif XP_MACOSX
+ ,
+ std::move(umaskContext)
+#endif
+ );
+
+ return retVal ? retVal : (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);
+ mozilla::UniquePtr<const NS_tchar> pszSpec(get_full_path(searchspec));
+
+ hFindFile = FindFirstFileW(pszSpec.get(), &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));
+ FindClose(hFindFile);
+ return rv;
+ }
+ } else {
+ // Add the file to be removed to the ActionList.
+ NS_tchar* quotedpath = get_quoted_path(foundpath);
+ if (!quotedpath) {
+ FindClose(hFindFile);
+ return PARSE_ERROR;
+ }
+
+ mozilla::UniquePtr<Action> action(new RemoveFile());
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ free(quotedpath);
+ FindClose(hFindFile);
+ return rv;
+ }
+ free(quotedpath);
+
+ list->Append(action.release());
+ }
+ } 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;
+ }
+
+ mozilla::UniquePtr<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.release());
+ }
+ free(quotedpath);
+ }
+ }
+
+ return rv;
+}
+
+#elif defined(HAVE_FTS_H)
+ int add_dir_entries(const NS_tchar* dirpath, ActionList* list) {
+ int rv = OK;
+ FTS* ftsdir;
+ FTSENT* ftsdirEntry;
+ mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(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.get()) - 1] = NS_T('\0');
+ char* const pathargv[] = {searchpath.get(), 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 = nullptr;
+ mozilla::UniquePtr<Action> action;
+
+ 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
+ [[fallthrough]];
+
+ // 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(get_relative_path(foundpath));
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+ action.reset(new RemoveFile());
+ rv = action->Parse(quotedpath);
+ free(quotedpath);
+ if (!rv) {
+ list->Append(action.release());
+ }
+ break;
+
+ // Directories
+ case FTS_DP:
+ // 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(get_relative_path(foundpath));
+ if (!quotedpath) {
+ rv = UPDATER_QUOTED_PATH_MEM_ERROR;
+ break;
+ }
+
+ action.reset(new RemoveDir());
+ rv = action->Parse(quotedpath);
+ free(quotedpath);
+ if (!rv) {
+ list->Append(action.release());
+ }
+ 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;
+ }
+ [[fallthrough]];
+
+ 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;
+ }
+
+#else
+
+int add_dir_entries(const NS_tchar* dirpath, ActionList* list) {
+ int rv = OK;
+ NS_tchar foundpath[PATH_MAX];
+ struct {
+ dirent dent_buffer;
+ char chars[NAME_MAX];
+ } ent_buf;
+ struct dirent* ent;
+ mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
+
+ DIR* dir = opendir(searchpath.get());
+ if (!dir) {
+ LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d",
+ searchpath.get(), 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"), searchpath.get(), 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%s/"), dirpath, ent->d_name);
+ // 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(get_relative_path(foundpath));
+ if (!quotedpath) {
+ closedir(dir);
+ return PARSE_ERROR;
+ }
+
+ mozilla::UniquePtr<Action> action(new RemoveFile());
+ rv = action->Parse(quotedpath);
+ if (rv) {
+ LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
+ quotedpath, rv));
+ free(quotedpath);
+ closedir(dir);
+ return rv;
+ }
+ free(quotedpath);
+
+ list->Append(action.release());
+ }
+ }
+ closedir(dir);
+
+ // Add the directory to be removed to the ActionList.
+ NS_tchar* quotedpath = get_quoted_path(get_relative_path(dirpath));
+ if (!quotedpath) {
+ return PARSE_ERROR;
+ }
+
+ mozilla::UniquePtr<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.release());
+ }
+ free(quotedpath);
+
+ return rv;
+}
+
+#endif
+
+/*
+ * Gets the contents of an update manifest file. The return value is malloc'd
+ * and it is the responsibility of the caller to free it.
+ *
+ * @param manifest
+ * The full path to the manifest file.
+ * @return On success the contents of the manifest and nullptr otherwise.
+ */
+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));
+ free(mbuf);
+ return nullptr;
+ }
+
+ r -= c;
+ rb += c;
+ }
+ *rb = '\0';
+
+#ifndef XP_WIN
+ return mbuf;
+#else
+ NS_tchar* wrb = (NS_tchar*)malloc((ms.st_size + 1) * sizeof(NS_tchar));
+ if (!wrb) {
+ free(mbuf);
+ return nullptr;
+ }
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbuf, -1, wrb,
+ ms.st_size + 1)) {
+ LOG(("GetManifestContents: error converting utf8 to utf16le: %lu",
+ GetLastError()));
+ free(mbuf);
+ free(wrb);
+ return nullptr;
+ }
+ free(mbuf);
+
+ return wrb;
+#endif
+}
+
+int AddPreCompleteActions(ActionList* list) {
+#ifdef XP_MACOSX
+ mozilla::UniquePtr<NS_tchar[]> manifestPath(
+ get_full_path(NS_T("Contents/Resources/precomplete")));
+#else
+ mozilla::UniquePtr<NS_tchar[]> manifestPath(
+ get_full_path(NS_T("precomplete")));
+#endif
+
+ NS_tchar* buf = GetManifestContents(manifestPath.get());
+ if (!buf) {
+ 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;
+ }
+ NS_tchar* rb = buf;
+
+ 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"));
+ free(buf);
+ 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));
+ free(buf);
+ return PARSE_ERROR;
+ }
+
+ if (!action) {
+ free(buf);
+ return BAD_ACTION_ERROR;
+ }
+
+ rv = action->Parse(line);
+ if (rv) {
+ delete action;
+ free(buf);
+ return rv;
+ }
+
+ list->Append(action);
+ }
+
+ free(buf);
+ 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) {
+ LOG(("DoUpdate: error extracting manifest file"));
+ return rv;
+ }
+
+ NS_tchar* buf = GetManifestContents(manifest);
+ // The manifest is located in the <working_dir>/updating directory which is
+ // removed after the update has finished so don't delete it here.
+ if (!buf) {
+ LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
+ return READ_ERROR;
+ }
+ NS_tchar* rb = buf;
+
+ 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"));
+ free(buf);
+ 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) {
+ free(buf);
+ 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) {
+ free(buf);
+ return PARSE_ERROR;
+ }
+
+ if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) {
+ free(buf);
+ return PARSE_ERROR;
+ }
+
+ rv = add_dir_entries(reldirpath, &list);
+ if (rv) {
+ free(buf);
+ 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));
+ free(buf);
+ return PARSE_ERROR;
+ }
+
+ if (!action) {
+ free(buf);
+ return BAD_ACTION_ERROR;
+ }
+
+ rv = action->Parse(line);
+ if (rv) {
+ delete action;
+ free(buf);
+ return rv;
+ }
+
+ list.Append(action);
+ }
+
+ rv = list.Prepare();
+ if (rv) {
+ free(buf);
+ return rv;
+ }
+
+ rv = list.Execute();
+
+ list.Finish(rv);
+ free(buf);
+ return rv;
+}
diff --git a/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest
new file mode 100644
index 0000000000..9df0057e64
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.exe.comctl32.manifest
@@ -0,0 +1,43 @@
+<?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}"/>
+ </application>
+</compatibility>
+<ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>True/PM</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
+ </ms_asmv3:windowsSettings>
+</ms_asmv3:application>
+</assembly>
diff --git a/toolkit/mozapps/update/updater/updater.exe.manifest b/toolkit/mozapps/update/updater/updater.exe.manifest
new file mode 100644
index 0000000000..6646ec6534
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.exe.manifest
@@ -0,0 +1,31 @@
+<?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}"/>
+ </application>
+</compatibility>
+<ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>True/PM</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
+ </ms_asmv3:windowsSettings>
+</ms_asmv3:application>
+</assembly>
diff --git a/toolkit/mozapps/update/updater/updater.ico b/toolkit/mozapps/update/updater/updater.ico
new file mode 100644
index 0000000000..48457029d6
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.ico
Binary files differ
diff --git a/toolkit/mozapps/update/updater/updater.png b/toolkit/mozapps/update/updater/updater.png
new file mode 100644
index 0000000000..6f1251bd03
--- /dev/null
+++ b/toolkit/mozapps/update/updater/updater.png
Binary files differ
diff --git a/toolkit/mozapps/update/updater/updater.rc b/toolkit/mozapps/update/updater/updater.rc
new file mode 100644
index 0000000000..aff834a597
--- /dev/null
+++ b/toolkit/mozapps/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.
+//
+#if defined(TEST_UPDATER) || defined(DEP_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/toolkit/mozapps/update/updater/xpcshellCertificate.der b/toolkit/mozapps/update/updater/xpcshellCertificate.der
new file mode 100644
index 0000000000..ea1fd47faa
--- /dev/null
+++ b/toolkit/mozapps/update/updater/xpcshellCertificate.der
Binary files differ