diff options
Diffstat (limited to 'onlineupdate/source/update')
58 files changed, 8342 insertions, 0 deletions
diff --git a/onlineupdate/source/update/common/errors.h b/onlineupdate/source/update/common/errors.h new file mode 100644 index 000000000000..95bdcdfa73e5 --- /dev/null +++ b/onlineupdate/source/update/common/errors.h @@ -0,0 +1,96 @@ +/* -*- 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 Errors_h__ +#define Errors_h__ + +#define OK 0 + +// Error codes that are no longer used should not be used again unless they +// aren't used in client code (e.g. nsUpdateService.js, updates.js, +// UpdatePrompt.js, etc.). + +#define MAR_ERROR_EMPTY_ACTION_LIST 1 +#define LOADSOURCE_ERROR_WRONG_SIZE 2 + +// Error codes 3-16 are for general update problems. +#define USAGE_ERROR 3 +#define CRC_ERROR 4 +#define PARSE_ERROR 5 +#define READ_ERROR 6 +#define WRITE_ERROR 7 +// #define UNEXPECTED_ERROR 8 // Replaced with errors 38-42 +#define ELEVATION_CANCELED 9 +#define READ_STRINGS_MEM_ERROR 10 +#define ARCHIVE_READER_MEM_ERROR 11 +#define BSPATCH_MEM_ERROR 12 +#define UPDATER_MEM_ERROR 13 +#define UPDATER_QUOTED_PATH_MEM_ERROR 14 +#define BAD_ACTION_ERROR 15 +#define STRING_CONVERSION_ERROR 16 + +// Error codes 17-23 are related to security tasks for MAR +// signing and MAR protection. +#define CERT_LOAD_ERROR 17 +#define CERT_HANDLING_ERROR 18 +#define CERT_VERIFY_ERROR 19 +#define ARCHIVE_NOT_OPEN 20 +#define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21 +#define MAR_CHANNEL_MISMATCH_ERROR 22 +#define VERSION_DOWNGRADE_ERROR 23 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24 +#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25 +#define SERVICE_UPDATER_SIGN_ERROR 26 +#define SERVICE_UPDATER_COMPARE_ERROR 27 +#define SERVICE_UPDATER_IDENTITY_ERROR 28 +#define SERVICE_STILL_APPLYING_ON_SUCCESS 29 +#define SERVICE_STILL_APPLYING_ON_FAILURE 30 +#define SERVICE_UPDATER_NOT_FIXED_DRIVE 31 +#define SERVICE_COULD_NOT_LOCK_UPDATER 32 +#define SERVICE_INSTALLDIR_ERROR 33 + +#define NO_INSTALLDIR_ERROR 34 +#define WRITE_ERROR_ACCESS_DENIED 35 +// #define WRITE_ERROR_SHARING_VIOLATION 36 // Replaced with errors 46-48 +#define WRITE_ERROR_CALLBACK_APP 37 +#define UNEXPECTED_BZIP_ERROR 39 +#define UNEXPECTED_MAR_ERROR 40 +#define UNEXPECTED_BSPATCH_ERROR 41 +#define UNEXPECTED_FILE_OPERATION_ERROR 42 +#define FILESYSTEM_MOUNT_READWRITE_ERROR 43 +#define DELETE_ERROR_EXPECTED_DIR 46 +#define DELETE_ERROR_EXPECTED_FILE 47 +#define RENAME_ERROR_EXPECTED_FILE 48 + +// Error codes 24-33 and 49-51 are for the Windows maintenance service. +#define SERVICE_COULD_NOT_COPY_UPDATER 49 +#define SERVICE_STILL_APPLYING_TERMINATED 50 +#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51 + +#define WRITE_ERROR_FILE_COPY 61 +#define WRITE_ERROR_DELETE_FILE 62 +#define WRITE_ERROR_OPEN_PATCH_FILE 63 +#define WRITE_ERROR_PATCH_FILE 64 +#define WRITE_ERROR_APPLY_DIR_PATH 65 +#define WRITE_ERROR_CALLBACK_PATH 66 +#define WRITE_ERROR_FILE_ACCESS_DENIED 67 +#define WRITE_ERROR_DIR_ACCESS_DENIED 68 +#define WRITE_ERROR_DELETE_BACKUP 69 +#define WRITE_ERROR_EXTRACT 70 + +// Error codes 80 through 99 are reserved for nsUpdateService.js + +// The following error codes are only used by updater.exe +// when a fallback key exists for tests. +#define FALLBACKKEY_UNKNOWN_ERROR 100 +#define FALLBACKKEY_REGPATH_ERROR 101 +#define FALLBACKKEY_NOKEY_ERROR 102 +#define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103 +#define FALLBACKKEY_LAUNCH_ERROR 104 + +#endif // Errors_h__ diff --git a/onlineupdate/source/update/common/moz.build b/onlineupdate/source/update/common/moz.build new file mode 100644 index 000000000000..044433696efc --- /dev/null +++ b/onlineupdate/source/update/common/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'readstrings.h', + 'updatedefines.h', + 'updatelogging.h', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + EXPORTS += [ + 'pathhash.h', + 'uachelper.h', + 'updatehelper.cpp', + 'updatehelper.h', + ] + +Library('updatecommon') + +srcdir = '.' + +include('sources.mozbuild') + +FINAL_LIBRARY = 'xul' + +FAIL_ON_WARNINGS = True diff --git a/onlineupdate/source/update/common/pathhash.cpp b/onlineupdate/source/update/common/pathhash.cpp new file mode 100644 index 000000000000..540a8fd20903 --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.cpp @@ -0,0 +1,139 @@ +/* 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> +#include <wincrypt.h> +#include "pathhash.h" + + +/** + * Converts a binary sequence into a hex string + * + * @param hash The binary data sequence + * @param hashSize The size of the binary data sequence + * @param hexString A buffer to store the hex string, must be of + * size 2 * @hashSize +*/ +static void +BinaryDataToHexString(const BYTE *hash, DWORD &hashSize, + LPWSTR hexString) +{ + WCHAR *p = hexString; + for (DWORD i = 0; i < hashSize; ++i) { + wsprintfW(p, L"%.2x", hash[i]); + p += 2; + } +} + +/** + * Calculates an MD5 hash for the given input binary data + * + * @param data Any sequence of bytes + * @param dataSize The number of bytes inside @data + * @param hash Output buffer to store hash, must be freed by the caller + * @param hashSize The number of bytes in the output buffer + * @return TRUE on success +*/ +static BOOL +CalculateMD5(const char *data, DWORD dataSize, + BYTE **hash, DWORD &hashSize) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + if (NTE_BAD_KEYSET != GetLastError()) { + return FALSE; + } + + // Maybe it doesn't exist, try to create it. + if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET)) { + return FALSE; + } + } + + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { + return FALSE; + } + + if (!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data), + dataSize, 0)) { + return FALSE; + } + + DWORD dwCount = sizeof(DWORD); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&hashSize, + &dwCount, 0)) { + return FALSE; + } + + *hash = new BYTE[hashSize]; + ZeroMemory(*hash, hashSize); + if (!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) { + return FALSE; + } + + if (hHash) { + CryptDestroyHash(hHash); + } + + if (hProv) { + CryptReleaseContext(hProv,0); + } + + return TRUE; +} + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL +CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath) +{ + size_t filePathLen = wcslen(filePath); + if (!filePathLen) { + return FALSE; + } + + // If the file path ends in a slash, ignore that character + if (filePath[filePathLen -1] == L'\\' || + filePath[filePathLen - 1] == L'/') { + filePathLen--; + } + + // Copy in the full path into our own buffer. + // Copying in the extra slash is OK because we calculate the hash + // based on the filePathLen which excludes the slash. + // +2 to account for the possibly trailing slash and the null terminator. + WCHAR *lowercasePath = new WCHAR[filePathLen + 2]; + memset(lowercasePath, 0, (filePathLen + 2) * sizeof(WCHAR)); + wcsncpy(lowercasePath, filePath, filePathLen + 1); + _wcslwr(lowercasePath); + + BYTE *hash; + DWORD hashSize = 0; + if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath), + filePathLen * 2, + &hash, hashSize)) { + delete[] lowercasePath; + return FALSE; + } + delete[] lowercasePath; + + LPCWSTR baseRegPath = L"SOFTWARE\\Mozilla\\" + L"MaintenanceService\\"; + wcsncpy(registryPath, baseRegPath, MAX_PATH); + BinaryDataToHexString(hash, hashSize, + registryPath + wcslen(baseRegPath)); + delete[] hash; + return TRUE; +} diff --git a/onlineupdate/source/update/common/pathhash.h b/onlineupdate/source/update/common/pathhash.h new file mode 100644 index 000000000000..9856b4cf45aa --- /dev/null +++ b/onlineupdate/source/update/common/pathhash.h @@ -0,0 +1,19 @@ +/* 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 _PATHHASH_H_ +#define _PATHHASH_H_ + +/** + * Converts a file path into a unique registry location for cert storage + * + * @param filePath The input file path to get a registry path from + * @param registryPath A buffer to write the registry path to, must + * be of size in WCHARs MAX_PATH + 1 + * @return TRUE if successful +*/ +BOOL CalculateRegistryPathFromFilePath(const LPCWSTR filePath, + LPWSTR registryPath); + +#endif diff --git a/onlineupdate/source/update/common/readstrings.cpp b/onlineupdate/source/update/common/readstrings.cpp new file mode 100644 index 000000000000..428c91c0ba69 --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.cpp @@ -0,0 +1,236 @@ +/* -*- 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 <limits.h> +#include <string.h> +#include <stdio.h> +#include "readstrings.h" +#include "errors.h" + +#ifdef XP_WIN +# define NS_tfopen _wfopen +# define OPEN_MODE L"rb" +#else +# define NS_tfopen fopen +# define OPEN_MODE "r" +#endif + +// stack based FILE wrapper to ensure that fclose is called. +class AutoFILE { +public: + explicit AutoFILE(FILE *fp) : fp_(fp) {} + ~AutoFILE() { if (fp_) fclose(fp_); } + operator FILE *() { return fp_; } +private: + FILE *fp_; +}; + +class AutoCharArray { +public: + explicit AutoCharArray(size_t len) { ptr_ = new char[len]; } + ~AutoCharArray() { delete[] ptr_; } + operator char *() { return ptr_; } +private: + char *ptr_; +}; + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +static const char* +NS_strspnp(const char *delims, const char *str) +{ + const char *d; + do { + for (d = delims; *d != '\0'; ++d) { + if (*str == *d) { + ++str; + break; + } + } + } while (*d); + + return str; +} + +static char* +NS_strtok(const char *delims, char **str) +{ + if (!*str) + return nullptr; + + char *ret = (char*) NS_strspnp(delims, *str); + + if (!*ret) { + *str = ret; + return nullptr; + } + + char *i = ret; + do { + for (const char *d = delims; *d != '\0'; ++d) { + if (*i == *d) { + *i = '\0'; + *str = ++i; + return ret; + } + } + ++i; + } while (*i); + + *str = nullptr; + return ret; +} + +/** + * Find a key in a keyList containing zero-delimited keys ending with "\0\0". + * Returns a zero-based index of the key in the list, or -1 if the key is not found. + */ +static int +find_key(const char *keyList, char* key) +{ + if (!keyList) + return -1; + + int index = 0; + const char *p = keyList; + while (*p) + { + if (strcmp(key, p) == 0) + return index; + + p += strlen(p) + 1; + index++; + } + + // The key was not found if we came here + return -1; +} + +/** + * A very basic parser for updater.ini taken mostly from nsINIParser.cpp + * that can be used by standalone apps. + * + * @param path Path to the .ini file to read + * @param keyList List of zero-delimited keys ending with two zero characters + * @param numStrings Number of strings to read into results buffer - must be equal to the number of keys + * @param results Two-dimensional array of strings to be filled in the same order as the keys provided + * @param section Optional name of the section to read; defaults to "Strings" + */ +int +ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section) +{ + AutoFILE fp(NS_tfopen(path, OPEN_MODE)); + + if (!fp) + return READ_ERROR; + + /* get file size */ + if (fseek(fp, 0, SEEK_END) != 0) + return READ_ERROR; + + long len = ftell(fp); + if (len <= 0) + return READ_ERROR; + + size_t flen = size_t(len); + AutoCharArray fileContents(flen + 1); + if (!fileContents) + return READ_STRINGS_MEM_ERROR; + + /* read the file in one swoop */ + if (fseek(fp, 0, SEEK_SET) != 0) + return READ_ERROR; + + size_t rd = fread(fileContents, sizeof(char), flen, fp); + if (rd != flen) + return READ_ERROR; + + fileContents[flen] = '\0'; + + char *buffer = fileContents; + bool inStringsSection = false; + + unsigned int read = 0; + + while (char *token = NS_strtok(kNL, &buffer)) { + if (token[0] == '#' || token[0] == ';') // it's a comment + continue; + + token = (char*) NS_strspnp(kWhitespace, token); + if (!*token) // empty line + continue; + + if (token[0] == '[') { // section header! + ++token; + char const * currSection = token; + + char *rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + inStringsSection = false; + } + else { + if (section) + inStringsSection = strcmp(currSection, section) == 0; + else + inStringsSection = strcmp(currSection, "Strings") == 0; + } + + continue; + } + + if (!inStringsSection) { + // If we haven't found a section header (or we found a malformed + // section header), or this isn't the [Strings] section don't bother + // parsing this line. + continue; + } + + char *key = token; + char *e = NS_strtok(kEquals, &token); + if (!e) + continue; + + int keyIndex = find_key(keyList, key); + if (keyIndex >= 0 && (unsigned int)keyIndex < numStrings) + { + strncpy(results[keyIndex], token, MAX_TEXT_LEN - 1); + results[keyIndex][MAX_TEXT_LEN - 1] = '\0'; + read++; + } + } + + return (read == numStrings) ? OK : PARSE_ERROR; +} + +// A wrapper function to read strings for the updater. +// Added for compatibility with the original code. +int +ReadStrings(const NS_tchar *path, StringTable *results) +{ + const unsigned int kNumStrings = 2; + const char *kUpdaterKeys = "Title\0Info\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, updater_strings); + + strncpy(results->title, updater_strings[0], MAX_TEXT_LEN - 1); + results->title[MAX_TEXT_LEN - 1] = '\0'; + strncpy(results->info, updater_strings[1], MAX_TEXT_LEN - 1); + results->info[MAX_TEXT_LEN - 1] = '\0'; + + return result; +} diff --git a/onlineupdate/source/update/common/readstrings.h b/onlineupdate/source/update/common/readstrings.h new file mode 100644 index 000000000000..ccc26e3fe935 --- /dev/null +++ b/onlineupdate/source/update/common/readstrings.h @@ -0,0 +1,43 @@ +/* -*- 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 READSTRINGS_H__ +#define READSTRINGS_H__ + +#define MAX_TEXT_LEN 600 + +#ifdef XP_WIN +# include <windows.h> + typedef WCHAR NS_tchar; +#else + typedef char NS_tchar; +#endif + +#ifndef NULL +#define NULL 0 +#endif + +struct StringTable +{ + char title[MAX_TEXT_LEN]; + char info[MAX_TEXT_LEN]; +}; + +/** + * This function reads in localized strings from updater.ini + */ +int ReadStrings(const NS_tchar *path, StringTable *results); + +/** + * This function reads in localized strings corresponding to the keys from a given .ini + */ +int ReadStrings(const NS_tchar *path, + const char *keyList, + unsigned int numStrings, + char results[][MAX_TEXT_LEN], + const char *section = nullptr); + +#endif // READSTRINGS_H__ diff --git a/onlineupdate/source/update/common/sources.mozbuild b/onlineupdate/source/update/common/sources.mozbuild new file mode 100644 index 000000000000..3de907b32f05 --- /dev/null +++ b/onlineupdate/source/update/common/sources.mozbuild @@ -0,0 +1,19 @@ +# 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/. + +sources = [] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + sources += [ + 'pathhash.cpp', + 'uachelper.cpp', + 'updatehelper.cpp', + ] + +sources += [ + 'readstrings.cpp', + 'updatelogging.cpp', +] + +SOURCES += sorted(['%s/%s' % (srcdir, s) for s in sources]) diff --git a/onlineupdate/source/update/common/uachelper.cpp b/onlineupdate/source/update/common/uachelper.cpp new file mode 100644 index 000000000000..90ccdb76b4bd --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.cpp @@ -0,0 +1,222 @@ +/* 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> +#include <wtsapi32.h> +#include "uachelper.h" +#include "updatelogging.h" + +// See the MSDN documentation with title: Privilege Constants +// At the time of this writing, this documentation is located at: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx +LPCTSTR UACHelper::PrivsToDisable[] = { + SE_ASSIGNPRIMARYTOKEN_NAME, + SE_AUDIT_NAME, + SE_BACKUP_NAME, + // CreateProcess will succeed but the app will fail to launch on some WinXP + // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens + // for limited user accounts on those machines. The define is kept here as a + // reminder that it should never be re-added. + // This permission is for directory watching but also from MSDN: "This + // privilege also causes the system to skip all traversal access checks." + // SE_CHANGE_NOTIFY_NAME, + SE_CREATE_GLOBAL_NAME, + SE_CREATE_PAGEFILE_NAME, + SE_CREATE_PERMANENT_NAME, + SE_CREATE_SYMBOLIC_LINK_NAME, + SE_CREATE_TOKEN_NAME, + SE_DEBUG_NAME, + SE_ENABLE_DELEGATION_NAME, + SE_IMPERSONATE_NAME, + SE_INC_BASE_PRIORITY_NAME, + SE_INCREASE_QUOTA_NAME, + SE_INC_WORKING_SET_NAME, + SE_LOAD_DRIVER_NAME, + SE_LOCK_MEMORY_NAME, + SE_MACHINE_ACCOUNT_NAME, + SE_MANAGE_VOLUME_NAME, + SE_PROF_SINGLE_PROCESS_NAME, + SE_RELABEL_NAME, + SE_REMOTE_SHUTDOWN_NAME, + SE_RESTORE_NAME, + SE_SECURITY_NAME, + SE_SHUTDOWN_NAME, + SE_SYNC_AGENT_NAME, + SE_SYSTEM_ENVIRONMENT_NAME, + SE_SYSTEM_PROFILE_NAME, + SE_SYSTEMTIME_NAME, + SE_TAKE_OWNERSHIP_NAME, + SE_TCB_NAME, + SE_TIME_ZONE_NAME, + SE_TRUSTED_CREDMAN_ACCESS_NAME, + SE_UNDOCK_NAME, + SE_UNSOLICITED_INPUT_NAME +}; + +/** + * Opens a user token for the given session ID + * + * @param sessionID The session ID for the token to obtain + * @return A handle to the token to obtain which will be primary if enough + * permissions exist. Caller should close the handle. + */ +HANDLE +UACHelper::OpenUserToken(DWORD sessionID) +{ + HMODULE module = LoadLibraryW(L"wtsapi32.dll"); + HANDLE token = nullptr; + decltype(WTSQueryUserToken)* wtsQueryUserToken = + (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken"); + if (wtsQueryUserToken) { + wtsQueryUserToken(sessionID, &token); + } + FreeLibrary(module); + return token; +} + +/** + * Opens a linked token for the specified token. + * + * @param token The token to get the linked token from + * @return A linked token or nullptr if one does not exist. + * Caller should close the handle. + */ +HANDLE +UACHelper::OpenLinkedToken(HANDLE token) +{ + // Magic below... + // UAC creates 2 tokens. One is the restricted token which we have. + // the other is the UAC elevated one. Since we are running as a service + // as the system account we have access to both. + TOKEN_LINKED_TOKEN tlt; + HANDLE hNewLinkedToken = nullptr; + DWORD len; + if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, + &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) { + token = tlt.LinkedToken; + hNewLinkedToken = token; + } + return hNewLinkedToken; +} + + +/** + * Enables or disables a privilege for the specified token. + * + * @param token The token to adjust the privilege on. + * @param priv The privilege to adjust. + * @param enable Whether to enable or disable it + * @return TRUE if the token was adjusted to the specified value. + */ +BOOL +UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) +{ + LUID luidOfPriv; + if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) { + return FALSE; + } + + TOKEN_PRIVILEGES tokenPriv; + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Luid = luidOfPriv; + tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + SetLastError(ERROR_SUCCESS); + if (!AdjustTokenPrivileges(token, false, &tokenPriv, + sizeof(tokenPriv), nullptr, nullptr)) { + return FALSE; + } + + return GetLastError() == ERROR_SUCCESS; +} + +/** + * For each privilege that is specified, an attempt will be made to + * drop the privilege. + * + * @param token The token to adjust the privilege on. + * Pass nullptr for current token. + * @param unneededPrivs An array of unneeded privileges. + * @param count The size of the array + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, + size_t count) +{ + HANDLE obtainedToken = nullptr; + if (!token) { + // Note: This handle is a pseudo-handle and need not be closed + HANDLE process = GetCurrentProcess(); + if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) { + LOG_WARN(("Could not obtain token for current process, no " + "privileges changed. (%d)", GetLastError())); + return FALSE; + } + token = obtainedToken; + } + + BOOL result = TRUE; + for (size_t i = 0; i < count; i++) { + if (SetPrivilege(token, unneededPrivs[i], FALSE)) { + LOG(("Disabled unneeded token privilege: %s.", + unneededPrivs[i])); + } else { + LOG(("Could not disable token privilege value: %s. (%d)", + unneededPrivs[i], GetLastError())); + result = FALSE; + } + } + + if (obtainedToken) { + CloseHandle(obtainedToken); + } + return result; +} + +/** + * Disables privileges for the specified token. + * The privileges to disable are in PrivsToDisable. + * In the future there could be new privs and we are not sure if we should + * explicitly disable these or not. + * + * @param token The token to drop the privilege on. + * Pass nullptr for current token. + * @return TRUE if there were no errors + */ +BOOL +UACHelper::DisablePrivileges(HANDLE token) +{ + static const size_t PrivsToDisableSize = + sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]); + + return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, + PrivsToDisableSize); +} + +/** + * Check if the current user can elevate. + * + * @return true if the user can elevate. + * false otherwise. + */ +bool +UACHelper::CanUserElevate() +{ + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + return false; + } + + TOKEN_ELEVATION_TYPE elevationType; + DWORD len; + bool canElevate = GetTokenInformation(token, TokenElevationType, + &elevationType, + sizeof(elevationType), &len) && + (elevationType == TokenElevationTypeLimited); + CloseHandle(token); + + return canElevate; +} diff --git a/onlineupdate/source/update/common/uachelper.h b/onlineupdate/source/update/common/uachelper.h new file mode 100644 index 000000000000..810d79d79f24 --- /dev/null +++ b/onlineupdate/source/update/common/uachelper.h @@ -0,0 +1,23 @@ +/* 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 _UACHELPER_H_ +#define _UACHELPER_H_ + +class UACHelper +{ +public: + static HANDLE OpenUserToken(DWORD sessionID); + static HANDLE OpenLinkedToken(HANDLE token); + static BOOL DisablePrivileges(HANDLE token); + static bool CanUserElevate(); + +private: + static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable); + static BOOL DisableUnneededPrivileges(HANDLE token, + LPCTSTR *unneededPrivs, size_t count); + static LPCTSTR PrivsToDisable[]; +}; + +#endif diff --git a/onlineupdate/source/update/common/updatedefines.h b/onlineupdate/source/update/common/updatedefines.h new file mode 100644 index 000000000000..0f62dbe2c50b --- /dev/null +++ b/onlineupdate/source/update/common/updatedefines.h @@ -0,0 +1,155 @@ +/* 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 UPDATEDEFINES_H +#define UPDATEDEFINES_H + +#include "readstrings.h" + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#if defined(XP_WIN) +# include <windows.h> +# include <shlwapi.h> +# include <direct.h> +# include <io.h> +# include <stdio.h> +# include <stdarg.h> + +# define F_OK 00 +# define W_OK 02 +# define R_OK 04 +# define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG) + +# define access _access + +# define putenv _putenv +# if _MSC_VER < 1900 +# define stat _stat +# endif +# define DELETE_DIR L"tobedeleted" +# define CALLBACK_BACKUP_EXT L".moz-callback" + +# define LOG_S "%S" +# define NS_T(str) L ## str +# define NS_SLASH NS_T('\\') + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// On Windows, _snprintf and _snwprintf don't guarantee null termination. These +// macros always leave room in the buffer for null termination and set the end +// of the buffer to null in case the string is larger than the buffer. Having +// multiple nulls in a string is fine and this approach is simpler (possibly +// faster) than calculating the string length to place the null terminator and +// truncates the string as _snprintf and _snwprintf do on other platforms. +static inline int mysnprintf(char* dest, size_t count, const char* fmt, ...) +{ + size_t _count = count - 1; + va_list varargs; + va_start(varargs, fmt); + int result = _vsnprintf(dest, count - 1, fmt, varargs); + va_end(varargs); + dest[_count] = '\0'; + return result; +} +#define snprintf mysnprintf +#endif +static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...) +{ + size_t _count = count - 1; + va_list varargs; + va_start(varargs, fmt); + int result = _vsnwprintf(dest, count - 1, fmt, varargs); + va_end(varargs); + dest[_count] = L'\0'; + return result; +} +#define NS_tsnprintf mywcsprintf +# define NS_taccess _waccess +# define NS_tchdir _wchdir +# define NS_tchmod _wchmod +# define NS_tfopen _wfopen +# define NS_tmkdir(path, perms) _wmkdir(path) +# define NS_tremove _wremove +// _wrename is used to avoid the link tracking service. +# define NS_trename _wrename +# define NS_trmdir _wrmdir +# define NS_tstat _wstat +# define NS_tlstat _wstat // No symlinks on Windows +# define NS_tstat_t _stat +# define NS_tstrcat wcscat +# define NS_tstrcmp wcscmp +# define NS_tstricmp wcsicmp +# define NS_tstrcpy wcscpy +# define NS_tstrncpy wcsncpy +# define NS_tstrlen wcslen +# define NS_tstrchr wcschr +# define NS_tstrrchr wcsrchr +# define NS_tstrstr wcsstr +# include "win_dirent.h" +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#else +# include <sys/wait.h> +# include <unistd.h> + +#ifdef SOLARIS +# include <sys/stat.h> +#else +# include <fts.h> +#endif +# include <dirent.h> + +#ifdef XP_MACOSX +# include <sys/time.h> +#endif + +# define LOG_S "%s" +# define NS_T(str) str +# define NS_SLASH NS_T('/') +# define NS_tsnprintf snprintf +# define NS_taccess access +# define NS_tchdir chdir +# define NS_tchmod chmod +# define NS_tfopen fopen +# define NS_tmkdir mkdir +# define NS_tremove remove +# define NS_trename rename +# define NS_trmdir rmdir +# define NS_tstat stat +# define NS_tstat_t stat +# define NS_tlstat lstat +# define NS_tstrcat strcat +# define NS_tstrcmp strcmp +# define NS_tstricmp strcasecmp +# define NS_tstrcpy strcpy +# define NS_tstrncpy strncpy +# define NS_tstrlen strlen +# define NS_tstrrchr strrchr +# define NS_tstrstr strstr +# define NS_tDIR DIR +# define NS_tdirent dirent +# define NS_topendir opendir +# define NS_tclosedir closedir +# define NS_treaddir readdir +#endif + +#define BACKUP_EXT NS_T(".moz-backup") + +#endif diff --git a/onlineupdate/source/update/common/updatehelper.cpp b/onlineupdate/source/update/common/updatehelper.cpp new file mode 100644 index 000000000000..84ab331a6052 --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.cpp @@ -0,0 +1,751 @@ +/* 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> + +// Needed for CreateToolhelp32Snapshot +#include <tlhelp32.h> +#ifndef ONLY_SERVICE_LAUNCHING + +#include <stdio.h> +#include "shlobj.h" +#include "updatehelper.h" +#include "uachelper.h" +#include "pathhash.h" +#include "mozilla/UniquePtr.h" + +// Needed for PathAppendW +#include <shlwapi.h> + +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +WCHAR* MakeCommandLine(int argc, WCHAR **argv); +BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra); + +/** + * Obtains the path of a file in the same directory as the specified file. + * + * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result. + * @param siblingFIlePath The path of another file in the same directory + * @param newFileName The filename of another file in the same directory + * @return TRUE if successful + */ +BOOL +PathGetSiblingFilePath(LPWSTR destinationBuffer, + LPCWSTR siblingFilePath, + LPCWSTR newFileName) +{ + if (wcslen(siblingFilePath) >= MAX_PATH) { + return FALSE; + } + + wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH); + if (!PathRemoveFileSpecW(destinationBuffer)) { + return FALSE; + } + + if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) { + return FALSE; + } + + return PathAppendSafe(destinationBuffer, newFileName); +} + +/** + * Launch the post update application as the specified user (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. + * @param forceSync If true even if the ini file specifies async, the + * process will wait for termination of PostUpdate. + * @param userToken The user token to run as, if nullptr the current + * user will be used. + * @return TRUE if there was no error starting the process. + */ +BOOL +LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken) +{ + 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")) { + return FALSE; + } + + WCHAR exefile[MAX_PATH + 1]; + WCHAR exearg[MAX_PATH + 1]; + WCHAR exeasync[10]; + bool async = true; + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr, + exefile, MAX_PATH + 1, inifile)) { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg, + MAX_PATH + 1, inifile)) { + return FALSE; + } + + if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE", + exeasync, + sizeof(exeasync)/sizeof(exeasync[0]), + inifile)) { + return FALSE; + } + + WCHAR exefullpath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(exefullpath, installationDir, MAX_PATH); + if (!PathAppendSafe(exefullpath, exefile)) { + return false; + } + + WCHAR dlogFile[MAX_PATH + 1]; + if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) { + return FALSE; + } + + WCHAR slogFile[MAX_PATH + 1] = { L'\0' }; + wcsncpy(slogFile, updateInfoDir, MAX_PATH); + if (!PathAppendSafe(slogFile, L"update.log")) { + 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) { + return FALSE; + } + + wcsncpy(cmdline, dummyArg, len); + wcscat(cmdline, exearg); + + if (forceSync || + !_wcsnicmp(exeasync, L"false", 6) || + !_wcsnicmp(exeasync, L"0", 2)) { + async = false; + } + + // 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 = L""; + PROCESS_INFORMATION pi = {0}; + + bool ok; + if (userToken) { + ok = CreateProcessAsUserW(userToken, + 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); + } else { + 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) { + if (!async) + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return ok; +} + +/** + * Starts the upgrade process for update of the service if it is + * already installed. + * + * @param installDir the installation directory where + * maintenanceservice_installer.exe is located. + * @return TRUE if successful + */ +BOOL +StartServiceUpdate(LPCWSTR installDir) +{ + // Get a handle to the local computer SCM database + SC_HANDLE manager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_ALL_ACCESS); + if (!manager) { + return FALSE; + } + + // Open the service + SC_HANDLE svc = OpenServiceW(manager, SVC_NAME, + SERVICE_ALL_ACCESS); + if (!svc) { + CloseServiceHandle(manager); + return FALSE; + } + + // If we reach here, then the service is installed, so + // proceed with upgrading it. + + CloseServiceHandle(manager); + + // The service exists and we opened it, get the config bytes needed + DWORD bytesNeeded; + if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + CloseServiceHandle(svc); + return FALSE; + } + + // Get the service config information, in particular we want the binary + // path of the service. + UniquePtr<char[]> serviceConfigBuffer = MakeUnique<char[]>(bytesNeeded); + if (!QueryServiceConfigW(svc, + reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), + bytesNeeded, &bytesNeeded)) { + CloseServiceHandle(svc); + return FALSE; + } + + CloseServiceHandle(svc); + + QUERY_SERVICE_CONFIGW &serviceConfig = + *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()); + + PathUnquoteSpacesW(serviceConfig.lpBinaryPathName); + + // Obtain the temp path of the maintenance service binary + WCHAR tmpService[MAX_PATH + 1] = { L'\0' }; + if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName, + L"maintenanceservice_tmp.exe")) { + return FALSE; + } + + // Get the new maintenance service path from the install dir + WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(newMaintServicePath, installDir, MAX_PATH); + PathAppendSafe(newMaintServicePath, + L"maintenanceservice.exe"); + + // Copy the temp file in alongside the maintenace service. + // This is a requirement for maintenance service upgrades. + if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) { + return FALSE; + } + + // Start the upgrade comparison process + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + // No particular desktop because no UI + si.lpDesktop = L""; + PROCESS_INFORMATION pi = {0}; + WCHAR cmdLine[64] = { '\0' }; + wcsncpy(cmdLine, L"dummyparam.exe upgrade", + sizeof(cmdLine) / sizeof(cmdLine[0]) - 1); + BOOL svcUpdateProcessStarted = CreateProcessW(tmpService, + cmdLine, + nullptr, nullptr, FALSE, + 0, + nullptr, installDir, &si, &pi); + if (svcUpdateProcessStarted) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return svcUpdateProcessStarted; +} + +#endif + +/** + * Executes a maintenance service command + * + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the service, + * @return ERROR_SUCCESS if the service command was started. + * Less than 16000, a windows system error code from StartServiceW + * More than 20000, 20000 + the last state of the service constant if + * the last state is something other than stopped. + * 17001 if the SCM could not be opened + * 17002 if the service could not be opened +*/ +DWORD +StartServiceCommand(int argc, LPCWSTR* argv) +{ + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) { + return 20000 + lastState; + } + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) { + return 17001; + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + SVC_NAME, + SERVICE_START); + if (!service) { + CloseServiceHandle(serviceManager); + return 17002; + } + + // Wait at most 5 seconds trying to start the service in case of errors + // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT. + const DWORD maxWaitMS = 5000; + DWORD currentWaitMS = 0; + DWORD lastError = ERROR_SUCCESS; + while (currentWaitMS < maxWaitMS) { + BOOL result = StartServiceW(service, argc, argv); + if (result) { + lastError = ERROR_SUCCESS; + break; + } else { + lastError = GetLastError(); + } + Sleep(100); + currentWaitMS += 100; + } + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastError; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Launch a service initiated action for a software update with the + * specified arguments. + * + * @param exePath The path of the executable to run + * @param argc The total number of arguments in argv + * @param argv An array of null terminated strings to pass to the exePath, + * argv[0] must be the path to the updater.exe + * @return ERROR_SUCCESS if successful + */ +DWORD +LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv) +{ + // The service command is the same as the updater.exe command line except + // it has 2 extra args: 1) The Path to udpater.exe, and 2) the command + // being executed which is "software-update" + LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2]; + updaterServiceArgv[0] = L"MozillaMaintenance"; + updaterServiceArgv[1] = L"software-update"; + + for (int i = 0; i < argc; ++i) { + updaterServiceArgv[i + 2] = argv[i]; + } + + // Execute the service command by starting the service with + // the passed in arguments. + DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv); + delete[] updaterServiceArgv; + return ret; +} + +/** + * Joins a base directory path with a filename. + * + * @param base The base directory path of size MAX_PATH + 1 + * @param extra The filename to append + * @return TRUE if the file name was successful appended to base + */ +BOOL +PathAppendSafe(LPWSTR base, LPCWSTR extra) +{ + if (wcslen(base) + wcslen(extra) >= MAX_PATH) { + return FALSE; + } + + return PathAppendW(base, extra); +} + +/** + * Sets update.status to pending so that the next startup will not use + * the service and instead will attempt an update the with a UAC prompt. + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusPending(LPCWSTR updateDirPath) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { + return FALSE; + } + + const char pending[] = "pending"; + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + + DWORD wrote; + BOOL ok = WriteFile(statusFile, pending, + sizeof(pending) - 1, &wrote, nullptr); + CloseHandle(statusFile); + return ok && (wrote == sizeof(pending) - 1); +} + +/** + * Sets update.status to a specific failure code + * + * @param updateDirPath The path of the update directory + * @return TRUE if successful + */ +BOOL +WriteStatusFailure(LPCWSTR updateDirPath, int errorCode) +{ + WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' }; + wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); + if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { + return FALSE; + } + + HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, 0, nullptr); + if (statusFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + char failure[32]; + sprintf(failure, "failed: %d", errorCode); + + DWORD toWrite = strlen(failure); + DWORD wrote; + BOOL ok = WriteFile(statusFile, failure, + toWrite, &wrote, nullptr); + CloseHandle(statusFile); + return ok && wrote == toWrite; +} + +#endif + +/** + * Waits for a service to enter a stopped state. + * This function does not stop the service, it just blocks until the service + * is stopped. + * + * @param serviceName The service to wait for. + * @param maxWaitSeconds The maximum number of seconds to wait + * @return state of the service after a timeout or when stopped. + * A value of 255 is returned for an error. Typical values are: + * SERVICE_STOPPED 0x00000001 + * SERVICE_START_PENDING 0x00000002 + * SERVICE_STOP_PENDING 0x00000003 + * SERVICE_RUNNING 0x00000004 + * SERVICE_CONTINUE_PENDING 0x00000005 + * SERVICE_PAUSE_PENDING 0x00000006 + * SERVICE_PAUSED 0x00000007 + * last status not set 0x000000CF + * Could no query status 0x000000DF + * Could not open service, access denied 0x000000EB + * Could not open service, invalid handle 0x000000EC + * Could not open service, invalid name 0x000000ED + * Could not open service, does not exist 0x000000EE + * Could not open service, other error 0x000000EF + * Could not open SCM, access denied 0x000000FD + * Could not open SCM, database does not exist 0x000000FE; + * Could not open SCM, other error 0x000000FF; + * Note: The strange choice of error codes above SERVICE_PAUSED are chosen + * in case Windows comes out with other service stats higher than 7, they + * would likely call it 8 and above. JS code that uses this in TestAUSHelper + * only handles values up to 255 so that's why we don't use GetLastError + * directly. + */ +DWORD +WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds) +{ + // 0x000000CF is defined above to be not set + DWORD lastServiceState = 0x000000CF; + + // Get a handle to the SCM database. + SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | + SC_MANAGER_ENUMERATE_SERVICE); + if (!serviceManager) { + DWORD lastError = GetLastError(); + switch(lastError) { + case ERROR_ACCESS_DENIED: + return 0x000000FD; + case ERROR_DATABASE_DOES_NOT_EXIST: + return 0x000000FE; + default: + return 0x000000FF; + } + } + + // Get a handle to the service. + SC_HANDLE service = OpenServiceW(serviceManager, + serviceName, + SERVICE_QUERY_STATUS); + if (!service) { + DWORD lastError = GetLastError(); + CloseServiceHandle(serviceManager); + switch(lastError) { + case ERROR_ACCESS_DENIED: + return 0x000000EB; + case ERROR_INVALID_HANDLE: + return 0x000000EC; + case ERROR_INVALID_NAME: + return 0x000000ED; + case ERROR_SERVICE_DOES_NOT_EXIST: + return 0x000000EE; + default: + return 0x000000EF; + } + } + + DWORD currentWaitMS = 0; + SERVICE_STATUS_PROCESS ssp; + ssp.dwCurrentState = lastServiceState; + while (currentWaitMS < maxWaitSeconds * 1000) { + DWORD bytesNeeded; + if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) { + DWORD lastError = GetLastError(); + switch (lastError) { + case ERROR_INVALID_HANDLE: + ssp.dwCurrentState = 0x000000D9; + break; + case ERROR_ACCESS_DENIED: + ssp.dwCurrentState = 0x000000DA; + break; + case ERROR_INSUFFICIENT_BUFFER: + ssp.dwCurrentState = 0x000000DB; + break; + case ERROR_INVALID_PARAMETER: + ssp.dwCurrentState = 0x000000DC; + break; + case ERROR_INVALID_LEVEL: + ssp.dwCurrentState = 0x000000DD; + break; + case ERROR_SHUTDOWN_IN_PROGRESS: + ssp.dwCurrentState = 0x000000DE; + break; + // These 3 errors can occur when the service is not yet stopped but + // it is stopping. + case ERROR_INVALID_SERVICE_CONTROL: + case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: + case ERROR_SERVICE_NOT_ACTIVE: + currentWaitMS += 50; + Sleep(50); + continue; + default: + ssp.dwCurrentState = 0x000000DF; + } + + // We couldn't query the status so just break out + break; + } + + // The service is already in use. + if (ssp.dwCurrentState == SERVICE_STOPPED) { + break; + } + currentWaitMS += 50; + Sleep(50); + } + + lastServiceState = ssp.dwCurrentState; + CloseServiceHandle(service); + CloseServiceHandle(serviceManager); + return lastServiceState; +} + +#ifndef ONLY_SERVICE_LAUNCHING + +/** + * Determines if there is at least one process running for the specified + * application. A match will be found across any session for any user. + * + * @param process The process to check for existance + * @return ERROR_NOT_FOUND if the process was not found + * ERROR_SUCCESS if the process was found and there were no errors + * Other Win32 system error code for other errors +**/ +DWORD +IsProcessRunning(LPCWSTR filename) +{ + // Take a snapshot of all processes in the system. + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (INVALID_HANDLE_VALUE == snapshot) { + return GetLastError(); + } + + PROCESSENTRY32W processEntry; + processEntry.dwSize = sizeof(PROCESSENTRY32W); + if (!Process32FirstW(snapshot, &processEntry)) { + DWORD lastError = GetLastError(); + CloseHandle(snapshot); + return lastError; + } + + do { + if (wcsicmp(filename, processEntry.szExeFile) == 0) { + CloseHandle(snapshot); + return ERROR_SUCCESS; + } + } while (Process32NextW(snapshot, &processEntry)); + CloseHandle(snapshot); + return ERROR_NOT_FOUND; +} + +/** + * Waits for the specified applicaiton to exit. + * + * @param filename The application to wait for. + * @param maxSeconds The maximum amount of seconds to wait for all + * instances of the application to exit. + * @return ERROR_SUCCESS if no instances of the application exist + * WAIT_TIMEOUT if the process is still running after maxSeconds. + * Any other Win32 system error code. +*/ +DWORD +WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds) +{ + DWORD applicationRunningError = WAIT_TIMEOUT; + for(DWORD i = 0; i < maxSeconds; i++) { + DWORD applicationRunningError = IsProcessRunning(filename); + if (ERROR_NOT_FOUND == applicationRunningError) { + return ERROR_SUCCESS; + } + Sleep(1000); + } + + if (ERROR_SUCCESS == applicationRunningError) { + return WAIT_TIMEOUT; + } + + return applicationRunningError; +} + +/** + * Determines if the fallback key exists or not + * + * @return TRUE if the fallback key exists and there was no error checking +*/ +BOOL +DoesFallbackKeyExist() +{ + HKEY testOnlyFallbackKey; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + TEST_ONLY_FALLBACK_KEY_PATH, 0, + KEY_READ | KEY_WOW64_64KEY, + &testOnlyFallbackKey) != ERROR_SUCCESS) { + return FALSE; + } + + RegCloseKey(testOnlyFallbackKey); + return TRUE; +} + +#endif + +/** + * Determines if the file system for the specified file handle is local + * @param file path to check the filesystem type for, must be at most MAX_PATH + * @param isLocal out parameter which will hold TRUE if the drive is local + * @return TRUE if the call succeeded +*/ +BOOL +IsLocalFile(LPCWSTR file, BOOL &isLocal) +{ + WCHAR rootPath[MAX_PATH + 1] = { L'\0' }; + if (wcslen(file) > MAX_PATH) { + return FALSE; + } + + wcsncpy(rootPath, file, MAX_PATH); + PathStripToRootW(rootPath); + isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED; + return TRUE; +} + + +/** + * Determines the DWORD value of a registry key value + * + * @param key The base key to where the value name exists + * @param valueName The name of the value + * @param retValue Out parameter which will hold the value + * @return TRUE on success +*/ +static BOOL +GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue) +{ + DWORD regDWORDValueSize = sizeof(DWORD); + LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr, + reinterpret_cast<LPBYTE>(&retValue), + ®DWORDValueSize); + return ERROR_SUCCESS == retCode; +} + +/** + * Determines if the the system's elevation type allows + * unprmopted elevation. + * + * @param isUnpromptedElevation Out parameter which specifies if unprompted + * elevation is allowed. + * @return TRUE if the user can actually elevate and the value was obtained + * successfully. +*/ +BOOL +IsUnpromptedElevation(BOOL &isUnpromptedElevation) +{ + if (!UACHelper::CanUserElevate()) { + return FALSE; + } + + LPCWSTR UACBaseRegKey = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; + HKEY baseKey; + LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + UACBaseRegKey, 0, + KEY_READ, &baseKey); + if (retCode != ERROR_SUCCESS) { + return FALSE; + } + + DWORD consent, secureDesktop; + BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin", + consent); + success = success && + GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop); + isUnpromptedElevation = !consent && !secureDesktop; + + RegCloseKey(baseKey); + return success; +} diff --git a/onlineupdate/source/update/common/updatehelper.h b/onlineupdate/source/update/common/updatehelper.h new file mode 100644 index 000000000000..5ab076f0b37c --- /dev/null +++ b/onlineupdate/source/update/common/updatehelper.h @@ -0,0 +1,34 @@ +/* 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/. */ + +BOOL LaunchWinPostProcess(const WCHAR *installationDir, + const WCHAR *updateInfoDir, + bool forceSync, + HANDLE userToken); +BOOL StartServiceUpdate(LPCWSTR installDir); +BOOL GetUpdateDirectoryPath(LPWSTR path); +DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv); +BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); +BOOL WriteStatusPending(LPCWSTR updateDirPath); +DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds); +DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds); +BOOL DoesFallbackKeyExist(); +BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal); +DWORD StartServiceCommand(int argc, LPCWSTR* argv); +BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation); + +#define SVC_NAME L"MozillaMaintenance" + +#define BASE_SERVICE_REG_KEY \ + L"SOFTWARE\\Mozilla\\MaintenanceService" + +// The test only fallback key, as its name implies, is only present on machines +// that will use automated tests. Since automated tests always run from a +// different directory for each test, the presence of this key bypasses the +// "This is a valid installation directory" check. This key also stores +// the allowed name and issuer for cert checks so that the cert check +// code can still be run unchanged. +#define TEST_ONLY_FALLBACK_KEY_PATH \ + BASE_SERVICE_REG_KEY L"\\3932ecacee736d366d6436db0f55bce4" + diff --git a/onlineupdate/source/update/common/updatelogging.cpp b/onlineupdate/source/update/common/updatelogging.cpp new file mode 100644 index 000000000000..036c0b175ae6 --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.cpp @@ -0,0 +1,82 @@ +/* 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 defined(XP_WIN) +#include <windows.h> +#endif + + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "updatelogging.h" + +UpdateLog::UpdateLog() : logFP(nullptr) +{ +} + +void UpdateLog::Init(NS_tchar* sourcePath, + const NS_tchar* fileName, + const NS_tchar* alternateFileName, + bool append) +{ + if (logFP) + return; + + this->sourcePath = sourcePath; + NS_tchar logFile[MAXPATHLEN]; + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePath, fileName); + + if (alternateFileName && NS_taccess(logFile, F_OK)) { + NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]), + NS_T("%s/%s"), sourcePath, alternateFileName); + } + + logFP = NS_tfopen(logFile, append ? NS_T("a") : NS_T("w")); +} + +void UpdateLog::Finish() +{ + if (!logFP) + return; + + fclose(logFP); + logFP = nullptr; +} + +void UpdateLog::Flush() +{ + if (!logFP) + return; + + fflush(logFP); +} + +void UpdateLog::Printf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "\n"); + va_end(ap); +} + +void UpdateLog::WarnPrintf(const char *fmt, ... ) +{ + if (!logFP) + return; + + va_list ap; + va_start(ap, fmt); + fprintf(logFP, "*** Warning: "); + vfprintf(logFP, fmt, ap); + fprintf(logFP, "***\n"); + va_end(ap); +} diff --git a/onlineupdate/source/update/common/updatelogging.h b/onlineupdate/source/update/common/updatelogging.h new file mode 100644 index 000000000000..8cdf0396df95 --- /dev/null +++ b/onlineupdate/source/update/common/updatelogging.h @@ -0,0 +1,47 @@ +/* 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 UPDATELOGGING_H +#define UPDATELOGGING_H + +#include "updatedefines.h" +#include <stdio.h> + +class UpdateLog +{ +public: + static UpdateLog & GetPrimaryLog() + { + static UpdateLog primaryLog; + return primaryLog; + } + + void Init(NS_tchar* sourcePath, const NS_tchar* fileName, + const NS_tchar* alternateFileName, bool append); + void Finish(); + void Flush(); + void Printf(const char *fmt, ... ); + void WarnPrintf(const char *fmt, ... ); + + ~UpdateLog() + { + Finish(); + } + +protected: + UpdateLog(); + FILE *logFP; + NS_tchar* sourcePath; +}; + +#define LOG_WARN(args) UpdateLog::GetPrimaryLog().WarnPrintf args +#define LOG(args) UpdateLog::GetPrimaryLog().Printf args +#define LogInit(PATHNAME_, FILENAME_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, 0, false) +#define LogInitAppend(PATHNAME_, FILENAME_, ALTERNATE_) \ + UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, ALTERNATE_, true) +#define LogFinish() UpdateLog::GetPrimaryLog().Finish() +#define LogFlush() UpdateLog::GetPrimaryLog().Flush() + +#endif diff --git a/onlineupdate/source/update/common/win_dirent.h b/onlineupdate/source/update/common/win_dirent.h new file mode 100644 index 000000000000..28f5317ff3e1 --- /dev/null +++ b/onlineupdate/source/update/common/win_dirent.h @@ -0,0 +1,32 @@ +/* -*- 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 WINDIRENT_H__ +#define WINDIRENT_H__ + +#ifndef XP_WIN +#error This library should only be used on Windows +#endif + +#include <windows.h> + +struct DIR { + explicit DIR(const WCHAR* path); + ~DIR(); + HANDLE findHandle; + WCHAR name[MAX_PATH]; +}; + +struct dirent { + dirent(); + WCHAR d_name[MAX_PATH]; +}; + +DIR* opendir(const WCHAR* path); +int closedir(DIR* dir); +dirent* readdir(DIR* dir); + +#endif // WINDIRENT_H__ diff --git a/onlineupdate/source/update/updater/Makefile.in b/onlineupdate/source/update/updater/Makefile.in new file mode 100644 index 000000000000..b465031b2832 --- /dev/null +++ b/onlineupdate/source/update/updater/Makefile.in @@ -0,0 +1,61 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ./updater-xpcshell/Makefile.in + +ifndef MOZ_WINCONSOLE +ifdef MOZ_DEBUG +MOZ_WINCONSOLE = 1 +else +MOZ_WINCONSOLE = 0 +endif +endif + +include $(topsrcdir)/config/rules.mk + +ifneq (,$(filter beta release esr,$(MOZ_UPDATE_CHANNEL))) + PRIMARY_CERT = release_primary.der + SECONDARY_CERT = release_secondary.der +else ifneq (,$(filter nightly aurora nightly-elm nightly-profiling nightly-oak nightly-ux,$(MOZ_UPDATE_CHANNEL))) + PRIMARY_CERT = nightly_aurora_level3_primary.der + SECONDARY_CERT = nightly_aurora_level3_secondary.der +else + PRIMARY_CERT = dep1.der + SECONDARY_CERT = dep2.der +endif + +CERT_HEADERS := primaryCert.h secondaryCert.h xpcshellCert.h + +export:: $(CERT_HEADERS) + +primaryCert.h: $(PRIMARY_CERT) +secondaryCert.h: $(SECONDARY_CERT) + +# This is how the xpcshellCertificate.der file is generated, in case we ever +# have to regenerate it. +# ./certutil -L -d modules/libmar/tests/unit/data -n mycert -r > xpcshellCertificate.der +xpcshellCert.h: xpcshellCertificate.der + +$(CERT_HEADERS): gen_cert_header.py + $(PYTHON) $< $(@:.h=Data) $(filter-out $<,$^) > $@ + +ifdef MOZ_WIDGET_GTK +libs:: updater.png + $(NSINSTALL) -D $(DIST)/bin/icons + $(INSTALL) $(IFLAGS1) $^ $(DIST)/bin/icons +endif + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +libs:: + $(NSINSTALL) -D $(DIST)/bin/updater.app + rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS + $(NSINSTALL) $(DIST)/bin/updater $(DIST)/bin/updater.app/Contents/MacOS + rm -f $(DIST)/bin/updater +endif + +CXXFLAGS += $(MOZ_BZ2_CFLAGS) diff --git a/onlineupdate/source/update/updater/archivereader.cpp b/onlineupdate/source/update/updater/archivereader.cpp new file mode 100644 index 000000000000..6aa1f49fde9d --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.cpp @@ -0,0 +1,324 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include "bzlib.h" +#include "archivereader.h" +#include "errors.h" +#ifdef XP_WIN +#include "nsAlgorithm.h" // Needed by nsVersionComparator.cpp +#include "updatehelper.h" +#endif + +// These are generated at compile time based on the DER file for the channel +// being used +#ifdef MOZ_VERIFY_MAR_SIGNATURE +#ifdef TEST_UPDATER +#include "../xpcshellCert.h" +#else +#include "primaryCert.h" +#include "secondaryCert.h" +#endif +#endif + +#define UPDATER_NO_STRING_GLUE_STL +#include "nsVersionComparator.cpp" +#undef UPDATER_NO_STRING_GLUE_STL + +#if defined(XP_UNIX) +# include <sys/types.h> +#elif defined(XP_WIN) +# include <io.h> +#endif + +static int inbuf_size = 262144; +static int outbuf_size = 262144; +static char *inbuf = nullptr; +static char *outbuf = nullptr; + +/** + * Performs a verification on the opened MAR file with the passed in + * certificate name ID and type ID. + * + * @param archive The MAR file to verify the signature on. + * @param certData The certificate data. + * @return OK on success, CERT_VERIFY_ERROR on failure. +*/ +template<uint32_t SIZE> +int +VerifyLoadedCert(MarFile *archive, const uint8_t (&certData)[SIZE]) +{ + (void)archive; + (void)certData; + +#ifdef MOZ_VERIFY_MAR_SIGNATURE + const uint32_t size = SIZE; + const uint8_t* const data = &certData[0]; + if (mar_verify_signatures(archive, &data, &size, 1)) { + return CERT_VERIFY_ERROR; + } +#endif + + return OK; +} + +/** + * Performs a verification on the opened MAR file. Both the primary and backup + * keys stored are stored in the current process and at least the primary key + * will be tried. Success will be returned as long as one of the two + * signatures verify. + * + * @return OK on success +*/ +int +ArchiveReader::VerifySignature() +{ + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + +#ifndef MOZ_VERIFY_MAR_SIGNATURE + return OK; +#else +#ifdef TEST_UPDATER + int rv = VerifyLoadedCert(mArchive, xpcshellCertData); +#else + int rv = VerifyLoadedCert(mArchive, primaryCertData); + if (rv != OK) { + rv = VerifyLoadedCert(mArchive, secondaryCertData); + } +#endif + return rv; +#endif +} + +/** + * Verifies that the MAR file matches the current product, channel, and version + * + * @param MARChannelID The MAR channel name to use, only updates from MARs + * with a matching MAR channel name will succeed. + * If an empty string is passed, no check will be done + * for the channel name in the product information block. + * If a comma separated list of values is passed then + * one value must match. + * @param appVersion The application version to use, only MARs with an + * application version >= to appVersion will be applied. + * @return OK on success + * COULD_NOT_READ_PRODUCT_INFO_BLOCK if the product info block + * could not be read. + * MARCHANNEL_MISMATCH_ERROR if update-settings.ini's MAR + * channel ID doesn't match the MAR + * file's MAR channel ID. + * VERSION_DOWNGRADE_ERROR if the application version for + * this updater is newer than the + * one in the MAR. + */ +int +ArchiveReader::VerifyProductInformation(const char *MARChannelID, + const char *appVersion) +{ + if (!mArchive) { + return ARCHIVE_NOT_OPEN; + } + + ProductInformationBlock productInfoBlock; + int rv = mar_read_product_info_block(mArchive, + &productInfoBlock); + if (rv != OK) { + return COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR; + } + + // Only check the MAR channel name if specified, it should be passed in from + // the update-settings.ini file. + if (MARChannelID && strlen(MARChannelID)) { + // Check for at least one match in the comma separated list of values. + const char *delimiter = " ,\t"; + // Make a copy of the string in case a read only memory buffer + // was specified. strtok modifies the input buffer. + char channelCopy[512] = { 0 }; + strncpy(channelCopy, MARChannelID, sizeof(channelCopy) - 1); + char *channel = strtok(channelCopy, delimiter); + rv = MAR_CHANNEL_MISMATCH_ERROR; + while(channel) { + if (!strcmp(channel, productInfoBlock.MARChannelID)) { + rv = OK; + break; + } + channel = strtok(nullptr, delimiter); + } + } + + if (rv == OK) { + /* Compare both versions to ensure we don't have a downgrade + -1 if appVersion is older than productInfoBlock.productVersion + 1 if appVersion is newer than productInfoBlock.productVersion + 0 if appVersion is the same as productInfoBlock.productVersion + This even works with strings like: + - 12.0a1 being older than 12.0a2 + - 12.0a2 being older than 12.0b1 + - 12.0a1 being older than 12.0 + - 12.0 being older than 12.1a1 */ + int versionCompareResult = + mozilla::CompareVersions(appVersion, productInfoBlock.productVersion); + if (1 == versionCompareResult) { + rv = VERSION_DOWNGRADE_ERROR; + } + } + + free((void *)productInfoBlock.MARChannelID); + free((void *)productInfoBlock.productVersion); + return rv; +} + +int +ArchiveReader::Open(const NS_tchar *path) +{ + if (mArchive) + Close(); + + if (!inbuf) { + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) { + // Try again with a smaller buffer. + inbuf_size = 1024; + inbuf = (char *)malloc(inbuf_size); + if (!inbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + + if (!outbuf) { + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) { + // Try again with a smaller buffer. + outbuf_size = 1024; + outbuf = (char *)malloc(outbuf_size); + if (!outbuf) + return ARCHIVE_READER_MEM_ERROR; + } + } + +#ifdef XP_WIN + mArchive = mar_wopen(path); +#else + mArchive = mar_open(path); +#endif + if (!mArchive) + return READ_ERROR; + + return OK; +} + +void +ArchiveReader::Close() +{ + if (mArchive) { + mar_close(mArchive); + mArchive = nullptr; + } + + if (inbuf) { + free(inbuf); + inbuf = nullptr; + } + + if (outbuf) { + free(outbuf); + outbuf = nullptr; + } +} + +int +ArchiveReader::ExtractFile(const char *name, const NS_tchar *dest) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + +#ifdef XP_WIN + FILE* fp = _wfopen(dest, L"wb+"); +#else + int fd = creat(dest, item->flags); + if (fd == -1) + return WRITE_ERROR; + + FILE *fp = fdopen(fd, "wb"); +#endif + if (!fp) + return WRITE_ERROR; + + int rv = ExtractItemToStream(item, fp); + + fclose(fp); + return rv; +} + +int +ArchiveReader::ExtractFileToStream(const char *name, FILE *fp) +{ + const MarItem *item = mar_find_item(mArchive, name); + if (!item) + return READ_ERROR; + + return ExtractItemToStream(item, fp); +} + +int +ArchiveReader::ExtractItemToStream(const MarItem *item, FILE *fp) +{ + /* decompress the data chunk by chunk */ + + bz_stream strm; + int offset, inlen, outlen, ret = OK; + + memset(&strm, 0, sizeof(strm)); + if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) + return UNEXPECTED_BZIP_ERROR; + + offset = 0; + for (;;) { + if (!item->length) { + ret = UNEXPECTED_MAR_ERROR; + break; + } + + if (offset < (int) item->length && strm.avail_in == 0) { + inlen = mar_read(mArchive, item, offset, inbuf, inbuf_size); + if (inlen <= 0) + return READ_ERROR; + offset += inlen; + strm.next_in = inbuf; + strm.avail_in = inlen; + } + + strm.next_out = outbuf; + strm.avail_out = outbuf_size; + + ret = BZ2_bzDecompress(&strm); + if (ret != BZ_OK && ret != BZ_STREAM_END) { + ret = UNEXPECTED_BZIP_ERROR; + break; + } + + outlen = outbuf_size - strm.avail_out; + if (outlen) { + if (fwrite(outbuf, outlen, 1, fp) != 1) { + ret = WRITE_ERROR_EXTRACT; + break; + } + } + + if (ret == BZ_STREAM_END) { + ret = OK; + break; + } + } + + BZ2_bzDecompressEnd(&strm); + return ret; +} diff --git a/onlineupdate/source/update/updater/archivereader.h b/onlineupdate/source/update/updater/archivereader.h new file mode 100644 index 000000000000..6dccd8983eb5 --- /dev/null +++ b/onlineupdate/source/update/updater/archivereader.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ArchiveReader_h__ +#define ArchiveReader_h__ + +#include <stdio.h> +#include "mar.h" + +#ifdef XP_WIN + typedef WCHAR NS_tchar; +#else + typedef char NS_tchar; +#endif + +// This class provides an API to extract files from an update archive. +class ArchiveReader +{ +public: + ArchiveReader() : mArchive(nullptr) {} + ~ArchiveReader() { Close(); } + + int Open(const NS_tchar *path); + int VerifySignature(); + int VerifyProductInformation(const char *MARChannelID, + const char *appVersion); + void Close(); + + int ExtractFile(const char *item, const NS_tchar *destination); + int ExtractFileToStream(const char *item, FILE *fp); + +private: + int ExtractItemToStream(const MarItem *item, FILE *fp); + + MarFile *mArchive; +}; + +#endif // ArchiveReader_h__ diff --git a/onlineupdate/source/update/updater/automounter_gonk.cpp b/onlineupdate/source/update/updater/automounter_gonk.cpp new file mode 100644 index 000000000000..3dff2a1337a1 --- /dev/null +++ b/onlineupdate/source/update/updater/automounter_gonk.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <android/log.h> +#include <cutils/android_reboot.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/reboot.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "automounter_gonk.h" +#include "updatedefines.h" +#include "updatelogging.h" + +#define LOG_TAG "GonkAutoMounter" + +#define GONK_LOG(level, format, ...) \ + LOG((LOG_TAG ": " format "\n", ##__VA_ARGS__)); \ + __android_log_print(level, LOG_TAG, format, ##__VA_ARGS__) + +#define LOGI(format, ...) GONK_LOG(ANDROID_LOG_INFO, format, ##__VA_ARGS__) +#define LOGE(format, ...) GONK_LOG(ANDROID_LOG_ERROR, format, ##__VA_ARGS__) + +const char *kGonkMountsPath = "/proc/mounts"; +const char *kGonkSystemPath = "/system"; + +GonkAutoMounter::GonkAutoMounter() : mDevice(nullptr), mAccess(Unknown) +{ + if (!RemountSystem(ReadWrite)) { + LOGE("Could not remount %s as read-write.", kGonkSystemPath); + } +} + +GonkAutoMounter::~GonkAutoMounter() +{ + bool result = RemountSystem(ReadOnly); + free(mDevice); + + if (!result) { + // Don't take any chances when remounting as read-only fails, just reboot. + Reboot(); + } +} + +void +GonkAutoMounter::Reboot() +{ + // The android_reboot wrapper provides more safety, doing fancier read-only + // remounting and attempting to sync() the filesystem first. If this fails + // our only hope is to force a reboot directly without these protections. + // For more, see system/core/libcutils/android_reboot.c + LOGE("Could not remount %s as read-only, forcing a system reboot.", + kGonkSystemPath); + LogFlush(); + + if (android_reboot(ANDROID_RB_RESTART, 0, nullptr) != 0) { + LOGE("Safe system reboot failed, attempting to force"); + LogFlush(); + + if (reboot(RB_AUTOBOOT) != 0) { + LOGE("CRITICAL: Failed to force restart"); + } + } +} + +static const char * +MountAccessToString(MountAccess access) +{ + switch (access) { + case ReadOnly: return "read-only"; + case ReadWrite: return "read-write"; + default: return "unknown"; + } +} + +bool +GonkAutoMounter::RemountSystem(MountAccess access) +{ + if (!UpdateMountStatus()) { + return false; + } + + if (mAccess == access) { + return true; + } + + unsigned long flags = MS_REMOUNT; + if (access == ReadOnly) { + flags |= MS_RDONLY; + // Give the system a chance to flush file buffers + sync(); + } + + if (!MountSystem(flags)) { + return false; + } + + // Check status again to verify /system has been properly remounted + if (!UpdateMountStatus()) { + return false; + } + + if (mAccess != access) { + LOGE("Updated mount status %s should be %s", + MountAccessToString(mAccess), + MountAccessToString(access)); + return false; + } + + return true; +} + +bool +GonkAutoMounter::UpdateMountStatus() +{ + FILE *mountsFile = NS_tfopen(kGonkMountsPath, "r"); + + if (mountsFile == nullptr) { + LOGE("Error opening %s: %s", kGonkMountsPath, strerror(errno)); + return false; + } + + // /proc/mounts returns a 0 size from fstat, so we use the same + // pre-allocated buffer size that ADB does here + const int mountsMaxSize = 4096; + char mountData[mountsMaxSize]; + size_t read = fread(mountData, 1, mountsMaxSize - 1, mountsFile); + mountData[read + 1] = '\0'; + + if (ferror(mountsFile)) { + LOGE("Error reading %s, %s", kGonkMountsPath, strerror(errno)); + fclose(mountsFile); + return false; + } + + char *token, *tokenContext; + bool foundSystem = false; + + for (token = strtok_r(mountData, "\n", &tokenContext); + token; + token = strtok_r(nullptr, "\n", &tokenContext)) + { + if (ProcessMount(token)) { + foundSystem = true; + break; + } + } + + fclose(mountsFile); + + if (!foundSystem) { + LOGE("Couldn't find %s mount in %s", kGonkSystemPath, kGonkMountsPath); + } + return foundSystem; +} + +bool +GonkAutoMounter::ProcessMount(const char *mount) +{ + const int strSize = 256; + char mountDev[strSize]; + char mountDir[strSize]; + char mountAccess[strSize]; + + int rv = sscanf(mount, "%255s %255s %*s %255s %*d %*d\n", + mountDev, mountDir, mountAccess); + mountDev[strSize - 1] = '\0'; + mountDir[strSize - 1] = '\0'; + mountAccess[strSize - 1] = '\0'; + + if (rv != 3) { + return false; + } + + if (strcmp(kGonkSystemPath, mountDir) != 0) { + return false; + } + + free(mDevice); + mDevice = strdup(mountDev); + mAccess = Unknown; + + char *option, *optionContext; + for (option = strtok_r(mountAccess, ",", &optionContext); + option; + option = strtok_r(nullptr, ",", &optionContext)) + { + if (strcmp("ro", option) == 0) { + mAccess = ReadOnly; + break; + } else if (strcmp("rw", option) == 0) { + mAccess = ReadWrite; + break; + } + } + + return true; +} + +/* + * Mark the given block device as read-write or read-only, using the BLKROSET + * ioctl. + */ +static void SetBlockReadWriteStatus(const char *blockdev, bool setReadOnly) { + int fd; + int roMode = setReadOnly ? 1 : 0; + + fd = open(blockdev, O_RDONLY); + if (fd < 0) { + return; + } + + if (ioctl(fd, BLKROSET, &roMode) == -1) { + LOGE("Error setting read-only mode on %s to %s: %s", blockdev, + setReadOnly ? "true": "false", strerror(errno)); + } + close(fd); +} + + +bool +GonkAutoMounter::MountSystem(unsigned long flags) +{ + if (!mDevice) { + LOGE("No device was found for %s", kGonkSystemPath); + return false; + } + + // Without setting the block device ro mode to false, we get a permission + // denied error while trying to remount it in read-write. + SetBlockReadWriteStatus(mDevice, (flags & MS_RDONLY)); + + const char *readOnly = flags & MS_RDONLY ? "read-only" : "read-write"; + int result = mount(mDevice, kGonkSystemPath, "none", flags, nullptr); + + if (result != 0) { + LOGE("Error mounting %s as %s: %s", kGonkSystemPath, readOnly, + strerror(errno)); + return false; + } + + LOGI("Mounted %s partition as %s", kGonkSystemPath, readOnly); + return true; +} diff --git a/onlineupdate/source/update/updater/automounter_gonk.h b/onlineupdate/source/update/updater/automounter_gonk.h new file mode 100644 index 000000000000..1300d39003aa --- /dev/null +++ b/onlineupdate/source/update/updater/automounter_gonk.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AUTOMOUNTER_GONK_H__ +#define AUTOMOUNTER_GONK_H__ + +typedef enum { + ReadOnly, + ReadWrite, + Unknown +} MountAccess; + +/** + * This class will remount the /system partition as read-write in Gonk to allow + * the updater write access. Upon destruction, /system will be remounted back to + * read-only. If something causes /system to remain read-write, this class will + * reboot the device and allow the system to mount as read-only. + * + * Code inspired from AOSP system/core/adb/remount_service.c + */ +class GonkAutoMounter +{ +public: + GonkAutoMounter(); + ~GonkAutoMounter(); + + const MountAccess GetAccess() + { + return mAccess; + } + +private: + bool RemountSystem(MountAccess access); + bool ForceRemountReadOnly(); + bool UpdateMountStatus(); + bool ProcessMount(const char *mount); + bool MountSystem(unsigned long flags); + void Reboot(); + +private: + char *mDevice; + MountAccess mAccess; +}; + +#endif // AUTOMOUNTER_GONK_H__ diff --git a/onlineupdate/source/update/updater/bspatch.cpp b/onlineupdate/source/update/updater/bspatch.cpp new file mode 100644 index 000000000000..e4b5706ef2de --- /dev/null +++ b/onlineupdate/source/update/updater/bspatch.cpp @@ -0,0 +1,187 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg <benjamin@smedbergs.us> + */ + +#include "bspatch.h" +#include "errors.h" + +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <limits.h> + +#if defined(XP_WIN) +# include <io.h> +#else +# include <unistd.h> +#endif + +#ifdef XP_WIN +# include <winsock2.h> +#else +# include <arpa/inet.h> +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +int +MBS_ReadHeader(FILE* file, MBSPatchHeader *header) +{ + size_t s = fread(header, 1, sizeof(MBSPatchHeader), file); + if (s != sizeof(MBSPatchHeader)) + return READ_ERROR; + + header->slen = ntohl(header->slen); + header->scrc32 = ntohl(header->scrc32); + header->dlen = ntohl(header->dlen); + header->cblen = ntohl(header->cblen); + header->difflen = ntohl(header->difflen); + header->extralen = ntohl(header->extralen); + + struct stat hs; + s = fstat(fileno(file), &hs); + if (s) + return READ_ERROR; + + if (memcmp(header->tag, "MBDIFF10", 8) != 0) + return UNEXPECTED_BSPATCH_ERROR; + + if (sizeof(MBSPatchHeader) + + header->cblen + + header->difflen + + header->extralen != uint32_t(hs.st_size)) + return UNEXPECTED_BSPATCH_ERROR; + + return OK; +} + +int +MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file) +{ + unsigned char *fbufend = fbuffer + header->slen; + + unsigned char *buf = (unsigned char*) malloc(header->cblen + + header->difflen + + header->extralen); + if (!buf) + return BSPATCH_MEM_ERROR; + + int rv = OK; + + size_t r = header->cblen + header->difflen + header->extralen; + unsigned char *wb = buf; + while (r) { + const size_t count = (r > SSIZE_MAX) ? SSIZE_MAX : r; + size_t c = fread(wb, 1, count, patchFile); + if (c != count) { + rv = READ_ERROR; + goto end; + } + + r -= c; + wb += c; + } + + { + MBSPatchTriple *ctrlsrc = (MBSPatchTriple*) buf; + unsigned char *diffsrc = buf + header->cblen; + unsigned char *extrasrc = diffsrc + header->difflen; + + MBSPatchTriple *ctrlend = (MBSPatchTriple*) diffsrc; + unsigned char *diffend = extrasrc; + unsigned char *extraend = extrasrc + header->extralen; + + do { + ctrlsrc->x = ntohl(ctrlsrc->x); + ctrlsrc->y = ntohl(ctrlsrc->y); + ctrlsrc->z = ntohl(ctrlsrc->z); + +#ifdef DEBUG_bsmedberg + printf("Applying block:\n" + " x: %u\n" + " y: %u\n" + " z: %i\n", + ctrlsrc->x, + ctrlsrc->y, + ctrlsrc->z); +#endif + + /* Add x bytes from oldfile to x bytes from the diff block */ + + if (fbuffer + ctrlsrc->x > fbufend || + diffsrc + ctrlsrc->x > diffend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + for (uint32_t i = 0; i < ctrlsrc->x; ++i) { + diffsrc[i] += fbuffer[i]; + } + if ((uint32_t) fwrite(diffsrc, 1, ctrlsrc->x, file) != ctrlsrc->x) { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + fbuffer += ctrlsrc->x; + diffsrc += ctrlsrc->x; + + /* Copy y bytes from the extra block */ + + if (extrasrc + ctrlsrc->y > extraend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + if ((uint32_t) fwrite(extrasrc, 1, ctrlsrc->y, file) != ctrlsrc->y) { + rv = WRITE_ERROR_PATCH_FILE; + goto end; + } + extrasrc += ctrlsrc->y; + + /* "seek" forwards in oldfile by z bytes */ + + if (fbuffer + ctrlsrc->z > fbufend) { + rv = UNEXPECTED_BSPATCH_ERROR; + goto end; + } + fbuffer += ctrlsrc->z; + + /* and on to the next control block */ + + ++ctrlsrc; + } while (ctrlsrc < ctrlend); + } + +end: + free(buf); + return rv; +} diff --git a/onlineupdate/source/update/updater/bspatch.h b/onlineupdate/source/update/updater/bspatch.h new file mode 100644 index 000000000000..2b5fb338726f --- /dev/null +++ b/onlineupdate/source/update/updater/bspatch.h @@ -0,0 +1,93 @@ +/*- + * Copyright 2003,2004 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Changelog: + * 2005-04-26 - Define the header as a C structure, add a CRC32 checksum to + * the header, and make all the types 32-bit. + * --Benjamin Smedberg <benjamin@smedbergs.us> + */ + +#ifndef bspatch_h__ +#define bspatch_h__ + +#include <stdint.h> +#include <stdio.h> + +typedef struct MBSPatchHeader_ { + /* "MBDIFF10" */ + char tag[8]; + + /* Length of the file to be patched */ + uint32_t slen; + + /* CRC32 of the file to be patched */ + uint32_t scrc32; + + /* Length of the result file */ + uint32_t dlen; + + /* Length of the control block in bytes */ + uint32_t cblen; + + /* Length of the diff block in bytes */ + uint32_t difflen; + + /* Length of the extra block in bytes */ + uint32_t extralen; + + /* Control block (MBSPatchTriple[]) */ + /* Diff block (binary data) */ + /* Extra block (binary data) */ +} MBSPatchHeader; + +/** + * Read the header of a patch file into the MBSPatchHeader structure. + * + * @param fd Must have been opened for reading, and be at the beginning + * of the file. + */ +int MBS_ReadHeader(FILE* file, MBSPatchHeader *header); + +/** + * Apply a patch. This method does not validate the checksum of the original + * file: client code should validate the checksum before calling this method. + * + * @param patchfd Must have been processed by MBS_ReadHeader + * @param fbuffer The original file read into a memory buffer of length + * header->slen. + * @param filefd Must have been opened for writing. Should be truncated + * to header->dlen if it is an existing file. The offset + * should be at the beginning of the file. + */ +int MBS_ApplyPatch(const MBSPatchHeader *header, FILE* patchFile, + unsigned char *fbuffer, FILE* file); + +typedef struct MBSPatchTriple_ { + uint32_t x; /* add x bytes from oldfile to x bytes from the diff block */ + uint32_t y; /* copy y bytes from the extra block */ + int32_t z; /* seek forwards in oldfile by z bytes */ +} MBSPatchTriple; + +#endif // bspatch_h__ diff --git a/onlineupdate/source/update/updater/dep1.der b/onlineupdate/source/update/updater/dep1.der Binary files differnew file mode 100644 index 000000000000..95b4ef38c6bf --- /dev/null +++ b/onlineupdate/source/update/updater/dep1.der diff --git a/onlineupdate/source/update/updater/dep2.der b/onlineupdate/source/update/updater/dep2.der Binary files differnew file mode 100644 index 000000000000..a460d6a16dd1 --- /dev/null +++ b/onlineupdate/source/update/updater/dep2.der diff --git a/onlineupdate/source/update/updater/gen_cert_header.py b/onlineupdate/source/update/updater/gen_cert_header.py new file mode 100644 index 000000000000..182e98b64443 --- /dev/null +++ b/onlineupdate/source/update/updater/gen_cert_header.py @@ -0,0 +1,25 @@ +import sys +import binascii + +def file_byte_generator(filename, block_size = 512): + with open(filename, "rb") as f: + while True: + block = f.read(block_size) + if block: + for byte in block: + yield byte + else: + break + +def create_header(array_name, in_filename): + hexified = ["0x" + binascii.hexlify(byte) for byte in file_byte_generator(in_filename)] + print "const uint8_t " + array_name + "[] = {" + print ", ".join(hexified) + print "};" + return 0 + +if __name__ == '__main__': + if len(sys.argv) < 3: + print 'ERROR: usage: gen_cert_header.py array_name in_filename' + sys.exit(1); + sys.exit(create_header(sys.argv[1], sys.argv[2])) diff --git a/onlineupdate/source/update/updater/launchchild_osx.mm b/onlineupdate/source/update/updater/launchchild_osx.mm new file mode 100644 index 000000000000..9e4e08d372fe --- /dev/null +++ b/onlineupdate/source/update/updater/launchchild_osx.mm @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <Cocoa/Cocoa.h> +#include <CoreServices/CoreServices.h> +#include <crt_externs.h> +#include <stdlib.h> +#include <stdio.h> +#include <spawn.h> +#include "readstrings.h" + +// Prefer the currently running architecture (this is the same as the +// architecture that launched the updater) and fallback to CPU_TYPE_ANY if it +// is no longer available after the update. +static cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY }; + +void LaunchChild(int argc, char **argv) +{ + // Initialize spawn attributes. + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) { + printf("Failed to init posix spawn attribute."); + return; + } + + // Set spawn attributes. + size_t attr_count = 2; + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types, &attr_ocount) != 0 || + attr_ocount != attr_count) { + printf("Failed to set binary preference on posix spawn attribute."); + posix_spawnattr_destroy(&spawnattr); + return; + } + + // "posix_spawnp" uses null termination for arguments rather than a count. + // Note that we are not duplicating the argument strings themselves. + char** argv_copy = (char**)malloc((argc + 1) * sizeof(char*)); + if (!argv_copy) { + printf("Failed to allocate memory for arguments."); + posix_spawnattr_destroy(&spawnattr); + return; + } + for (int i = 0; i < argc; i++) { + argv_copy[i] = argv[i]; + } + argv_copy[argc] = NULL; + + // Pass along our environment. + char** envp = NULL; + char*** cocoaEnvironment = _NSGetEnviron(); + if (cocoaEnvironment) { + envp = *cocoaEnvironment; + } + + int result = posix_spawnp(NULL, argv_copy[0], NULL, &spawnattr, argv_copy, envp); + + free(argv_copy); + posix_spawnattr_destroy(&spawnattr); + + if (result != 0) { + printf("Process spawn failed with code %d!", result); + } +} + +void +LaunchMacPostProcess(const char* aAppBundle) +{ + // Launch helper to perform post processing for the update; this is the Mac + // analogue of LaunchWinPostProcess (PostUpdateWin). + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString* iniPath = [NSString stringWithUTF8String:aAppBundle]; + iniPath = + [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"]; + + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:iniPath]) { + // the file does not exist; there is nothing to run + [pool release]; + return; + } + + int readResult; + char values[2][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeRelPath\0ExeArg\0", + 2, + values, + "PostUpdateMac"); + if (readResult) { + [pool release]; + return; + } + + NSString *exeRelPath = [NSString stringWithUTF8String:values[0]]; + NSString *exeArg = [NSString stringWithUTF8String:values[1]]; + if (!exeArg || !exeRelPath) { + [pool release]; + return; + } + + NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle]; + exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath]; + + char optVals[1][MAX_TEXT_LEN]; + readResult = ReadStrings([iniPath UTF8String], + "ExeAsync\0", + 1, + optVals, + "PostUpdateMac"); + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:exeFullPath]; + [task setArguments:[NSArray arrayWithObject:exeArg]]; + [task launch]; + if (!readResult) { + NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]]; + if ([exeAsync isEqualToString:@"false"]) { + [task waitUntilExit]; + } + } + // ignore the return value of the task, there's nothing we can do with it + [task release]; + + [pool release]; +} diff --git a/onlineupdate/source/update/updater/loaddlls.cpp b/onlineupdate/source/update/updater/loaddlls.cpp new file mode 100644 index 000000000000..b4291a5df6bf --- /dev/null +++ b/onlineupdate/source/update/updater/loaddlls.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> + +// Delayed load libraries are loaded when the first symbol is used. +// The following ensures that we load the delayed loaded libraries from the +// system directory. +struct AutoLoadSystemDependencies +{ + AutoLoadSystemDependencies() + { + // Remove the current directory from the search path for dynamically loaded + // DLLs as a precaution. This call has no effect for delay load DLLs. + SetDllDirectory(L""); + + HMODULE module = ::GetModuleHandleW(L"kernel32.dll"); + if (module) { + // SetDefaultDllDirectories is always available on Windows 8 and above. It + // is also available on Windows Vista, Windows Server 2008, and + // Windows 7 when MS KB2533623 has been applied. + decltype(SetDefaultDllDirectories)* setDefaultDllDirectories = + (decltype(SetDefaultDllDirectories)*) GetProcAddress(module, "SetDefaultDllDirectories"); + if (setDefaultDllDirectories) { + setDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); + return; + } + } + + // When SetDefaultDllDirectories is not available, fallback to preloading + // dlls. The order that these are loaded does not matter since they are + // loaded using the LOAD_WITH_ALTERED_SEARCH_PATH flag. +#ifdef HAVE_64BIT_BUILD + // DLLs for Firefox x64 on Windows 7 (x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"sspicli.dll", + L"wsock32.dll" }; + +#else + // DLLs for Firefox x86 on Windows XP through Windows 7 (x86 and x64). + // Note: dwmapi.dll is preloaded since a crash will try to load it from the + // application's directory. + static LPCWSTR delayDLLs[] = { L"apphelp.dll", + L"crypt32.dll", + L"cryptbase.dll", + L"cryptsp.dll", + L"dwmapi.dll", + L"mpr.dll", + L"msasn1.dll", + L"ntmarta.dll", + L"profapi.dll", + L"propsys.dll", + L"psapi.dll", + L"secur32.dll", + L"sspicli.dll", + L"userenv.dll", + L"uxtheme.dll", + L"ws2_32.dll", + L"ws2help.dll", + L"wsock32.dll" }; +#endif + + WCHAR systemDirectory[MAX_PATH + 1] = { L'\0' }; + // If GetSystemDirectory fails we accept that we'll load the DLLs from the + // normal search path. + GetSystemDirectoryW(systemDirectory, MAX_PATH + 1); + size_t systemDirLen = wcslen(systemDirectory); + + // Make the system directory path terminate with a slash + if (systemDirectory[systemDirLen - 1] != L'\\' && systemDirLen) { + systemDirectory[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-null terminate + } + + // For each known DLL ensure it is loaded from the system32 directory + for (size_t i = 0; i < sizeof(delayDLLs) / sizeof(delayDLLs[0]); ++i) { + size_t fileLen = wcslen(delayDLLs[i]); + wcsncpy(systemDirectory + systemDirLen, delayDLLs[i], + MAX_PATH - systemDirLen); + if (systemDirLen + fileLen <= MAX_PATH) { + systemDirectory[systemDirLen + fileLen] = L'\0'; + } else { + systemDirectory[MAX_PATH] = L'\0'; + } + LPCWSTR fullModulePath = systemDirectory; // just for code readability + // LOAD_WITH_ALTERED_SEARCH_PATH makes a dll look in its own directory for + // dependencies and is only available on Win 7 and below. + LoadLibraryExW(fullModulePath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } +} loadDLLs; diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Info.plist b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist new file mode 100644 index 000000000000..f104b55b9920 --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Info.plist @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>updater</string> + <key>CFBundleIconFile</key> + <string>updater.icns</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.updater</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>LSMinimumSystemVersion</key> + <string>10.5</string> + <key>LSMinimumSystemVersionByArchitecture</key> + <dict> + <key>i386</key> + <string>10.5.0</string> + <key>x86_64</key> + <string>10.6.0</string> + </dict> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo new file mode 100644 index 000000000000..bd04210fb49f --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/PkgInfo @@ -0,0 +1 @@ +APPL????
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in new file mode 100644 index 000000000000..bca4022e755a --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Localized versions of Info.plist keys */ + +CFBundleName = "%APP_NAME% Software Update"; diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 000000000000..6cfb50406bcf --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,19 @@ +{ + IBClasses = ( + { + CLASS = FirstResponder; + LANGUAGE = ObjC; + SUPERCLASS = NSObject; +}, + { + CLASS = UpdaterUI; + LANGUAGE = ObjC; + OUTLETS = { + progressBar = NSProgressIndicator; + progressTextField = NSTextField; + }; + SUPERCLASS = NSObject; +} + ); + IBVersion = 1; +}
\ No newline at end of file diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 000000000000..15091783707b --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBDocumentLocation</key> + <string>111 162 356 240 0 0 1440 878 </string> + <key>IBEditorPositions</key> + <dict> + <key>29</key> + <string>106 299 84 44 0 0 1440 878 </string> + </dict> + <key>IBFramework Version</key> + <string>489.0</string> + <key>IBOpenObjects</key> + <array> + <integer>21</integer> + <integer>29</integer> + </array> + <key>IBSystem Version</key> + <string>10J567</string> +</dict> +</plist> diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 000000000000..61ff026009dc --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns Binary files differnew file mode 100644 index 000000000000..d7499c6692db --- /dev/null +++ b/onlineupdate/source/update/updater/macbuild/Contents/Resources/updater.icns diff --git a/onlineupdate/source/update/updater/module.ver b/onlineupdate/source/update/updater/module.ver new file mode 100644 index 000000000000..771416bb115d --- /dev/null +++ b/onlineupdate/source/update/updater/module.ver @@ -0,0 +1 @@ +WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@ Software Updater diff --git a/onlineupdate/source/update/updater/moz.build b/onlineupdate/source/update/updater/moz.build new file mode 100644 index 000000000000..d5b1d5046062 --- /dev/null +++ b/onlineupdate/source/update/updater/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program('updater') + +updater_rel_path = '' +include('updater-common.build') +if CONFIG['ENABLE_TESTS']: + DIRS += ['updater-xpcshell'] +FAIL_ON_WARNINGS = True diff --git a/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der b/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der Binary files differnew file mode 100644 index 000000000000..b22124798d43 --- /dev/null +++ b/onlineupdate/source/update/updater/nightly_aurora_level3_primary.der diff --git a/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der b/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der Binary files differnew file mode 100644 index 000000000000..2dffbd02da0f --- /dev/null +++ b/onlineupdate/source/update/updater/nightly_aurora_level3_secondary.der diff --git a/onlineupdate/source/update/updater/progressui.h b/onlineupdate/source/update/updater/progressui.h new file mode 100644 index 000000000000..6dc20e06bcc7 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef PROGRESSUI_H__ +#define PROGRESSUI_H__ + +#include "updatedefines.h" + +#if defined(XP_WIN) + typedef WCHAR NS_tchar; + #define NS_main wmain +#else + typedef char NS_tchar; + #define NS_main main +#endif + +// Called to perform any initialization of the widget toolkit +int InitProgressUI(int *argc, NS_tchar ***argv); + +#if defined(XP_WIN) + // Called on the main thread at startup + int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true); + int InitProgressUIStrings(); +#else + // Called on the main thread at startup + int ShowProgressUI(); +#endif +// May be called from any thread +void QuitProgressUI(); + +// May be called from any thread: progress is a number between 0 and 100 +void UpdateProgressUI(float progress); + +#endif // PROGRESSUI_H__ diff --git a/onlineupdate/source/update/updater/progressui_gonk.cpp b/onlineupdate/source/update/updater/progressui_gonk.cpp new file mode 100644 index 000000000000..f77d0af6338a --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gonk.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <assert.h> +#include <stdio.h> + +#include <string> + +#include "android/log.h" + +#include "progressui.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoUpdater" , ## args) + +using namespace std; + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + LOG("Starting to apply update ...\n"); + return 0; +} + +void QuitProgressUI() +{ + LOG("Finished applying update\n"); +} + +void UpdateProgressUI(float progress) +{ + assert(0.0f <= progress && progress <= 100.0f); + + static const size_t kProgressBarLength = 50; + static size_t sLastNumBars; + size_t numBars = size_t(float(kProgressBarLength) * progress / 100.0f); + if (numBars == sLastNumBars) { + return; + } + sLastNumBars = numBars; + + size_t numSpaces = kProgressBarLength - numBars; + string bars(numBars, '='); + string spaces(numSpaces, ' '); + LOG("Progress [ %s%s ]\n", bars.c_str(), spaces.c_str()); +} diff --git a/onlineupdate/source/update/updater/progressui_gtk.cpp b/onlineupdate/source/update/updater/progressui_gtk.cpp new file mode 100644 index 000000000000..f3c537047f25 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_gtk.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <gtk/gtk.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 100 + +static float sProgressVal; // between 0 and 100 +static gboolean sQuit = FALSE; +static gboolean sEnableUI; +static guint sTimerID; + +static GtkWidget *sWin; +static GtkWidget *sLabel; +static GtkWidget *sProgressBar; + +static const char *sProgramPath; + +static gboolean +UpdateDialog(gpointer data) +{ + if (sQuit) + { + gtk_widget_hide(sWin); + gtk_main_quit(); + } + + float progress = sProgressVal; + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sProgressBar), + progress / 100.0); + + return TRUE; +} + +static gboolean +OnDeleteEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + return TRUE; +} + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sProgramPath = (*pargv)[0]; + + sEnableUI = gtk_init_check(pargc, pargv); + return 0; +} + +int +ShowProgressUI() +{ + if (!sEnableUI) + return -1; + + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char ini_path[PATH_MAX]; + snprintf(ini_path, sizeof(ini_path), "%s.ini", sProgramPath); + + StringTable strings; + if (ReadStrings(ini_path, &strings) != OK) + return -1; + + sWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + if (!sWin) + return -1; + + static GdkPixbuf *pixbuf; + char icon_path[PATH_MAX]; + snprintf(icon_path, sizeof(icon_path), "%s.png", sProgramPath); + + g_signal_connect(G_OBJECT(sWin), "delete_event", + G_CALLBACK(OnDeleteEvent), nullptr); + + gtk_window_set_title(GTK_WINDOW(sWin), strings.title); + gtk_window_set_type_hint(GTK_WINDOW(sWin), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position(GTK_WINDOW(sWin), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_resizable(GTK_WINDOW(sWin), FALSE); + gtk_window_set_decorated(GTK_WINDOW(sWin), TRUE); + gtk_window_set_deletable(GTK_WINDOW(sWin),FALSE); + pixbuf = gdk_pixbuf_new_from_file (icon_path, nullptr); + gtk_window_set_icon(GTK_WINDOW(sWin), pixbuf); + g_object_unref(pixbuf); + + GtkWidget *vbox = gtk_vbox_new(TRUE, 6); + sLabel = gtk_label_new(strings.info); + gtk_misc_set_alignment(GTK_MISC(sLabel), 0.0f, 0.0f); + sProgressBar = gtk_progress_bar_new(); + + gtk_box_pack_start(GTK_BOX(vbox), sLabel, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), sProgressBar, TRUE, TRUE, 0); + + sTimerID = g_timeout_add(TIMER_INTERVAL, UpdateDialog, nullptr); + + gtk_container_set_border_width(GTK_CONTAINER(sWin), 10); + gtk_container_add(GTK_CONTAINER(sWin), vbox); + gtk_widget_show_all(sWin); + + gtk_main(); + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/onlineupdate/source/update/updater/progressui_null.cpp b/onlineupdate/source/update/updater/progressui_null.cpp new file mode 100644 index 000000000000..cb3ac6369593 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_null.cpp @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "progressui.h" + +int InitProgressUI(int *argc, char ***argv) +{ + return 0; +} + +int ShowProgressUI() +{ + return 0; +} + +void QuitProgressUI() +{ +} + +void UpdateProgressUI(float progress) +{ +} diff --git a/onlineupdate/source/update/updater/progressui_osx.mm b/onlineupdate/source/update/updater/progressui_osx.mm new file mode 100644 index 000000000000..8e3ce01eb836 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_osx.mm @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <Cocoa/Cocoa.h> +#include <stdio.h> +#include <unistd.h> +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_INTERVAL 0.2 + +static float sProgressVal; // between 0 and 100 +static BOOL sQuit = FALSE; +static StringTable sLabels; +static const char *sUpdatePath; + +@interface UpdaterUI : NSObject +{ + IBOutlet NSProgressIndicator *progressBar; + IBOutlet NSTextField *progressTextField; +} +@end + +@implementation UpdaterUI + +-(void)awakeFromNib +{ + NSWindow *w = [progressBar window]; + + [w setTitle:[NSString stringWithUTF8String:sLabels.title]]; + [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]]; + + NSRect origTextFrame = [progressTextField frame]; + [progressTextField sizeToFit]; + + int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width; + + if (widthAdjust > 0) { + NSRect f; + f.size.width = w.frame.size.width + widthAdjust; + f.size.height = w.frame.size.height; + [w setFrame:f display:YES]; + } + + [w center]; + + [progressBar setIndeterminate:NO]; + [progressBar setDoubleValue:0.0]; + + [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self + selector:@selector(updateProgressUI:) + userInfo:nil repeats:YES] retain]; + + // Make sure we are on top initially + [NSApp activateIgnoringOtherApps:YES]; +} + +// called when the timer goes off +-(void)updateProgressUI:(NSTimer *)aTimer +{ + if (sQuit) { + [aTimer invalidate]; + [aTimer release]; + + // It seems to be necessary to activate and hide ourselves before we stop, + // otherwise the "run" method will not return until the user focuses some + // other app. The activate step is necessary if we are not the active app. + // This is a big hack, but it seems to do the trick. + [NSApp activateIgnoringOtherApps:YES]; + [NSApp hide:self]; + [NSApp stop:self]; + } + + float progress = sProgressVal; + + [progressBar setDoubleValue:(double)progress]; +} + +// leave this as returning a BOOL instead of NSApplicationTerminateReply +// for backward compatibility +- (BOOL)applicationShouldTerminate:(NSApplication *)sender +{ + return sQuit; +} + +@end + +int +InitProgressUI(int *pargc, char ***pargv) +{ + sUpdatePath = (*pargv)[1]; + + return 0; +} + +int +ShowProgressUI() +{ + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + usleep(500000); + + if (sQuit || sProgressVal > 70.0f) + return 0; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/updater.ini", sUpdatePath); + if (ReadStrings(path, &sLabels) != OK) + return -1; + + // Continue the update without showing the Progress UI if any of the supplied + // strings are larger than MAX_TEXT_LEN (Bug 628829). + if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 && + strlen(sLabels.info) < MAX_TEXT_LEN - 1)) + return -1; + + [NSApplication sharedApplication]; + [NSBundle loadNibNamed:@"MainMenu" owner:NSApp]; + [NSApp run]; + + return 0; +} + +// Called on a background thread +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +// Called on a background thread +void +UpdateProgressUI(float progress) +{ + sProgressVal = progress; // 32-bit writes are atomic +} diff --git a/onlineupdate/source/update/updater/progressui_win.cpp b/onlineupdate/source/update/updater/progressui_win.cpp new file mode 100644 index 000000000000..66232902a4e8 --- /dev/null +++ b/onlineupdate/source/update/updater/progressui_win.cpp @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <windows.h> +#include <commctrl.h> +#include <process.h> +#include <io.h> + +#include "resource.h" +#include "progressui.h" +#include "readstrings.h" +#include "errors.h" + +#define TIMER_ID 1 +#define TIMER_INTERVAL 100 + +#define RESIZE_WINDOW(hwnd, extrax, extray) \ + { \ + RECT windowSize; \ + GetWindowRect(hwnd, &windowSize); \ + SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \ + windowSize.bottom - windowSize.top + extray, \ + SWP_NOMOVE | SWP_NOZORDER); \ + } + +#define MOVE_WINDOW(hwnd, dx, dy) \ + { \ + RECT rc; \ + POINT pt; \ + GetWindowRect(hwnd, &rc); \ + pt.x = rc.left; \ + pt.y = rc.top; \ + ScreenToClient(GetParent(hwnd), &pt); \ + SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \ + SWP_NOSIZE | SWP_NOZORDER); \ + } + +static float sProgress; // between 0 and 100 +static BOOL sQuit = FALSE; +static BOOL sIndeterminate = FALSE; +static StringTable sUIStrings; + +static BOOL +GetStringsFile(WCHAR filename[MAX_PATH]) +{ + if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) + return FALSE; + + WCHAR *dot = wcsrchr(filename, '.'); + if (!dot || wcsicmp(dot + 1, L"exe")) + return FALSE; + + wcscpy(dot + 1, L"ini"); + return TRUE; +} + +static void +UpdateDialog(HWND hDlg) +{ + int pos = int(sProgress + 0.5f); + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETPOS, pos, 0L); +} + +// The code in this function is from MSDN: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp +static void +CenterDialog(HWND hDlg) +{ + RECT rc, rcOwner, rcDlg; + + // Get the owner window and dialog box rectangles. + HWND desktop = GetDesktopWindow(); + + GetWindowRect(desktop, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + + // Offset the owner and dialog box rectangles so that + // right and bottom values represent the width and + // height, and then offset the owner again to discard + // space taken up by the dialog box. + + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + + // The new position is the sum of half the remaining + // space and the owner's original position. + + SetWindowPos(hDlg, + HWND_TOP, + rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2), + 0, 0, // ignores size arguments + SWP_NOSIZE); +} + +static void +InitDialog(HWND hDlg) +{ + WCHAR szwTitle[MAX_TEXT_LEN]; + WCHAR szwInfo[MAX_TEXT_LEN]; + + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle, + sizeof(szwTitle)/sizeof(szwTitle[0])); + MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo, + sizeof(szwInfo)/sizeof(szwInfo[0])); + + SetWindowTextW(hDlg, szwTitle); + SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo); + + // Set dialog icon + HICON hIcon = LoadIcon(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDI_DIALOG)); + if (hIcon) + SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM) hIcon); + + HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS); + SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + if (sIndeterminate) { + LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE); + SetWindowLongPtr(hWndPro, GWL_STYLE, val|PBS_MARQUEE); + SendMessage(hWndPro,(UINT) PBM_SETMARQUEE,(WPARAM) TRUE,(LPARAM)50 ); + } + + // Resize the dialog to fit all of the text if necessary. + RECT infoSize, textSize; + HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO); + + // Get the control's font for calculating the new size for the control + HDC hDCInfo = GetDC(hWndInfo); + HFONT hInfoFont, hOldFont; + hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0); + + if (hInfoFont) + hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont); + + // Measure the space needed for the text on a single line. DT_CALCRECT means + // nothing is drawn. + if (DrawText(hDCInfo, szwInfo, -1, &textSize, + DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) { + GetClientRect(hWndInfo, &infoSize); + SIZE extra; + // Calculate the additional space needed for the text by subtracting from + // the rectangle returned by DrawText the existing client rectangle's width + // and height. + extra.cx = (textSize.right - textSize.left) - \ + (infoSize.right - infoSize.left); + extra.cy = (textSize.bottom - textSize.top) - \ + (infoSize.bottom - infoSize.top); + if (extra.cx < 0) + extra.cx = 0; + if (extra.cy < 0) + extra.cy = 0; + if ((extra.cx > 0) || (extra.cy > 0)) { + RESIZE_WINDOW(hDlg, extra.cx, extra.cy); + RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy); + RESIZE_WINDOW(hWndPro, extra.cx, 0); + MOVE_WINDOW(hWndPro, 0, extra.cy); + } + } + + if (hOldFont) + SelectObject(hDCInfo, hOldFont); + + ReleaseDC(hWndInfo, hDCInfo); + + CenterDialog(hDlg); // make dialog appear in the center of the screen + + SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr); +} + +// Message handler for update dialog. +static LRESULT CALLBACK +DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + InitDialog(hDlg); + return TRUE; + + case WM_TIMER: + if (sQuit) { + EndDialog(hDlg, 0); + } else { + UpdateDialog(hDlg); + } + return TRUE; + + case WM_COMMAND: + return TRUE; + } + return FALSE; +} + +int +InitProgressUI(int *argc, WCHAR ***argv) +{ + return 0; +} + +/** + * Initializes the progress UI strings + * + * @return 0 on success, -1 on error +*/ +int +InitProgressUIStrings() { + // If we do not have updater.ini, then we should not bother showing UI. + WCHAR filename[MAX_PATH]; + if (!GetStringsFile(filename)) { + return -1; + } + + if (_waccess(filename, 04)) { + return -1; + } + + // If the updater.ini doesn't have the required strings, then we should not + // bother showing UI. + if (ReadStrings(filename, &sUIStrings) != OK) { + return -1; + } + + return 0; +} + +int +ShowProgressUI(bool indeterminate, bool initUIStrings) +{ + sIndeterminate = indeterminate; + if (!indeterminate) { + // Only show the Progress UI if the process is taking a significant amount of + // time where a significant amount of time is defined as .5 seconds after + // ShowProgressUI is called sProgress is less than 70. + Sleep(500); + + if (sQuit || sProgress > 70.0f) + return 0; + } + + // Don't load the UI if there's an <exe_name>.Local directory for redirection. + WCHAR appPath[MAX_PATH + 1] = { L'\0' }; + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) { + return -1; + } + + if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) { + return -1; + } + + wcscat(appPath, L".Local"); + + if (!_waccess(appPath, 04)) { + return -1; + } + + // Don't load the UI if the strings for the UI are not provided. + if (initUIStrings && InitProgressUIStrings() == -1) { + return -1; + } + + if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) { + return -1; + } + + // Use an activation context that supports visual styles for the controls. + ACTCTXW actx = {0}; + actx.cbSize = sizeof(ACTCTXW); + actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID; + actx.hModule = GetModuleHandle(NULL); // Use the embedded manifest + // This is needed only for Win XP but doesn't cause a problem with other + // versions of Windows. + actx.lpSource = appPath; + actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST); + + HANDLE hactx = INVALID_HANDLE_VALUE; + hactx = CreateActCtxW(&actx); + ULONG_PTR actxCookie = NULL; + if (hactx != INVALID_HANDLE_VALUE) { + // Push the specified activation context to the top of the activation stack. + ActivateActCtx(hactx, &actxCookie); + } + + INITCOMMONCONTROLSEX icc = { + sizeof(INITCOMMONCONTROLSEX), + ICC_PROGRESS_CLASS + }; + InitCommonControlsEx(&icc); + + DialogBox(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDD_DIALOG), nullptr, + (DLGPROC) DialogProc); + + if (hactx != INVALID_HANDLE_VALUE) { + // Deactivate the context now that the comctl32.dll is loaded. + DeactivateActCtx(0, actxCookie); + } + + return 0; +} + +void +QuitProgressUI() +{ + sQuit = TRUE; +} + +void +UpdateProgressUI(float progress) +{ + sProgress = progress; // 32-bit writes are atomic +} diff --git a/onlineupdate/source/update/updater/release_primary.der b/onlineupdate/source/update/updater/release_primary.der Binary files differnew file mode 100644 index 000000000000..11417c35e7ff --- /dev/null +++ b/onlineupdate/source/update/updater/release_primary.der diff --git a/onlineupdate/source/update/updater/release_secondary.der b/onlineupdate/source/update/updater/release_secondary.der Binary files differnew file mode 100644 index 000000000000..16a7ef6d91d9 --- /dev/null +++ b/onlineupdate/source/update/updater/release_secondary.der diff --git a/onlineupdate/source/update/updater/resource.h b/onlineupdate/source/update/updater/resource.h new file mode 100644 index 000000000000..6b6091c7b8bb --- /dev/null +++ b/onlineupdate/source/update/updater/resource.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by updater.rc +// +#define IDD_DIALOG 101 +#define IDC_PROGRESS 1000 +#define IDC_INFO 1002 +#define IDI_DIALOG 1003 +#define TYPE_CERT 512 +#define IDR_PRIMARY_CERT 1004 +#define IDR_BACKUP_CERT 1005 +#define IDS_UPDATER_IDENTITY 1006 +#define IDR_XPCSHELL_CERT 1007 +#define IDR_COMCTL32_MANIFEST 17 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1008 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/onlineupdate/source/update/updater/updater-common.build b/onlineupdate/source/update/updater/updater-common.build new file mode 100644 index 000000000000..ae1d680a630b --- /dev/null +++ b/onlineupdate/source/update/updater/updater-common.build @@ -0,0 +1,130 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +srcs = [ + 'archivereader.cpp', + 'bspatch.cpp', + 'updater.cpp', +] + +have_progressui = 0 + +if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'verifymar', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + have_progressui = 1 + srcs += [ + 'loaddlls.cpp', + 'progressui_win.cpp', + 'win_dirent.cpp', + ] + RCINCLUDE = '%supdater.rc' % updater_rel_path + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + DEFINES['NOMINMAX'] = True + USE_STATIC_LIBS = True + + # Pick up nsWindowsRestart.cpp + LOCAL_INCLUDES += [ + '/toolkit/xre', + ] + USE_LIBS += [ + 'updatecommon-standalone', + ] + OS_LIBS += [ + 'comctl32', + 'ws2_32', + 'shell32', + 'shlwapi', + 'crypt32', + 'advapi32', + ] +elif CONFIG['OS_ARCH'] == 'Linux' and CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'nss', + 'signmar', + 'updatecommon', + ] + OS_LIBS += CONFIG['NSPR_LIBS'] +else: + USE_LIBS += [ + 'updatecommon', + ] + +USE_LIBS += [ + 'mar', +] + +if CONFIG['MOZ_NATIVE_BZ2']: + OS_LIBS += CONFIG['MOZ_BZ2_LIBS'] +else: + USE_LIBS += [ + 'bz2', + ] + +if CONFIG['MOZ_ENABLE_GTK']: + have_progressui = 1 + srcs += [ + 'progressui_gtk.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + have_progressui = 1 + srcs += [ + 'launchchild_osx.mm', + 'progressui_osx.mm', + ] + OS_LIBS += [ + '-framework Cocoa', + '-framework Security', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + have_progressui = 1 + srcs += [ + 'automounter_gonk.cpp', + 'progressui_gonk.cpp', + ] + DISABLE_STL_WRAPPING = True + OS_LIBS += [ + 'cutils', + 'sysutils', + ] + +if have_progressui == 0: + srcs += [ + 'progressui_null.cpp', + ] + +SOURCES += sorted(srcs) + +DEFINES['NS_NO_XPCOM'] = True +DISABLE_STL_WRAPPING = True +for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'): + DEFINES[var] = '"%s"' % CONFIG[var] + +LOCAL_INCLUDES += [ + '/toolkit/mozapps/update/common', + '/xpcom/glue', +] + +DELAYLOAD_DLLS += [ + 'crypt32.dll', + 'comctl32.dll', + 'userenv.dll', + 'wsock32.dll', +] + +if CONFIG['_MSC_VER']: + WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup'] +elif CONFIG['OS_ARCH'] == 'WINNT': + WIN32_EXE_LDFLAGS += ['-municode'] + +if CONFIG['MOZ_WIDGET_GTK']: + CXXFLAGS += CONFIG['TK_CFLAGS'] + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in new file mode 100644 index 000000000000..13b4e7f35d5a --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/Makefile.in @@ -0,0 +1,42 @@ +# vim:set ts=8 sw=8 sts=8 noet: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For changes here, also consider ../Makefile.in + +XPCSHELLTESTROOT = $(abspath $(DEPTH))/_tests/xpcshell/toolkit/mozapps/update/tests +MOCHITESTROOT = $(abspath $(DEPTH))/_tests/testing/mochitest/chrome/toolkit/mozapps/update/tests + +include $(topsrcdir)/config/rules.mk + +ifndef MOZ_WINCONSOLE +ifdef MOZ_DEBUG +MOZ_WINCONSOLE = 1 +else +MOZ_WINCONSOLE = 0 +endif +endif + +libs:: +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) + # Copy for xpcshell tests + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + rsync -a -C --exclude '*.in' $(srcdir)/../macbuild/Contents $(XPCSHELLTESTROOT)/data/updater-xpcshell.app + sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/../macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \ + iconv -f UTF-8 -t UTF-16 > $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/Resources/English.lproj/InfoPlist.strings + $(NSINSTALL) -D $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS/updater-xpcshell + $(NSINSTALL) $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater-xpcshell.app/Contents/MacOS + rm -f $(PROGRAM) + rm -Rf $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater-xpcshell.app $(XPCSHELLTESTROOT)/data/updater.app + mv $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater-xpcshell $(XPCSHELLTESTROOT)/data/updater.app/Contents/MacOS/updater + + # Copy for mochitest chrome tests + rsync -a -C $(XPCSHELLTESTROOT)/data/updater.app $(MOCHITESTROOT)/data/updater.app +else + cp $(PROGRAM) $(XPCSHELLTESTROOT)/data/updater$(BIN_SUFFIX) + cp $(PROGRAM) $(MOCHITESTROOT)/data/updater$(BIN_SUFFIX) +endif + +CXXFLAGS += $(MOZ_BZ2_CFLAGS) diff --git a/onlineupdate/source/update/updater/updater-xpcshell/moz.build b/onlineupdate/source/update/updater/updater-xpcshell/moz.build new file mode 100644 index 000000000000..ba5c844cfc10 --- /dev/null +++ b/onlineupdate/source/update/updater/updater-xpcshell/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program('updater-xpcshell') + +updater_rel_path = '../' +DIST_INSTALL = False +DEFINES['TEST_UPDATER'] = True +include('../updater-common.build') +FAIL_ON_WARNINGS = True diff --git a/onlineupdate/source/update/updater/updater.cpp b/onlineupdate/source/update/updater/updater.cpp new file mode 100644 index 000000000000..62237ade7bd1 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.cpp @@ -0,0 +1,3847 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Manifest Format + * --------------- + * + * contents = 1*( line ) + * line = method LWS *( param LWS ) CRLF + * CRLF = "\r\n" + * LWS = 1*( " " | "\t" ) + * + * Available methods for the manifest file: + * + * updatev2.manifest + * ----------------- + * method = "add" | "add-if" | "patch" | "patch-if" | "remove" | + * "rmdir" | "rmrfdir" | type + * + * 'type' is the update type (e.g. complete or partial) and when present MUST + * be the first entry in the update manifest. The type is used to support + * downgrades by causing the actions defined in precomplete to be performed. + * + * updatev3.manifest + * ----------------- + * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | + * "remove" | "rmdir" | "rmrfdir" | type + * + * 'add-if-not' adds a file if it doesn't exist. + * + * precomplete + * ----------- + * method = "remove" | "rmdir" + */ +#include "bspatch.h" +#include "progressui.h" +#include "archivereader.h" +#include "readstrings.h" +#include "errors.h" +#include "bzlib.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <algorithm> + +#include "updatelogging.h" + +#include "mozilla/Compiler.h" +#include "mozilla/Types.h" + +// Amount of the progress bar to use in each of the 3 update stages, +// should total 100.0. +#define PROGRESS_PREPARE_SIZE 20.0f +#define PROGRESS_EXECUTE_SIZE 75.0f +#define PROGRESS_FINISH_SIZE 5.0f + +// Amount of time in ms to wait for the parent process to close +#define PARENT_WAIT 5000 + +#if defined(XP_MACOSX) +// These functions are defined in launchchild_osx.mm +void LaunchChild(int argc, char **argv); +void LaunchMacPostProcess(const char* aAppBundle); +#endif + +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#ifndef NULL +# define NULL (0) +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX LONG_MAX +#endif + +// We want to use execv to invoke the callback executable on platforms where +// we were launched using execv. See nsUpdateDriver.cpp. +#if defined(XP_UNIX) && !defined(XP_MACOSX) +#define USE_EXECV +#endif + +#if defined(MOZ_WIDGET_GONK) +# include "automounter_gonk.h" +# include <unistd.h> +# include <android/log.h> +# include <linux/ioprio.h> +# include <sys/resource.h> + +#if ANDROID_VERSION < 21 +// The only header file in bionic which has a function prototype for ioprio_set +// is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts +// badly with unistd.h, so we declare the prototype for ioprio_set directly. +extern "C" MOZ_EXPORT int ioprio_set(int which, int who, int ioprio); +#else +# include <sys/syscall.h> +static int ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} +#endif + +# define MAYBE_USE_HARD_LINKS 1 +static bool sUseHardLinks = true; +#else +# define MAYBE_USE_HARD_LINKS 0 +#endif + +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX) +#include "nss.h" +#include "prerror.h" +#endif + +#ifdef XP_WIN +#include "updatehelper.h" + +// Closes the handle if valid and if the updater is elevated returns with the +// return code specified. This prevents multiple launches of the callback +// application by preventing the elevated process from launching the callback. +#define EXIT_WHEN_ELEVATED(path, handle, retCode) \ + { \ + if (handle != INVALID_HANDLE_VALUE) { \ + CloseHandle(handle); \ + } \ + if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \ + return retCode; \ + } \ + } +#endif + +//----------------------------------------------------------------------------- + +// This variable lives in libbz2. It's declared in bzlib_private.h, so we just +// declare it here to avoid including that entire header file. +#define BZ2_CRC32TABLE_UNDECLARED + +#if MOZ_IS_GCC +extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +extern "C" __global unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#endif +#if defined(BZ2_CRC32TABLE_UNDECLARED) +extern "C" unsigned int BZ2_crc32Table[256]; +#undef BZ2_CRC32TABLE_UNDECLARED +#endif + +static unsigned int +crc32(const unsigned char *buf, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + + const unsigned char *end = buf + len; + for (; buf != end; ++buf) + crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; + + crc = ~crc; + return crc; +} + +//----------------------------------------------------------------------------- + +// A simple stack based container for a FILE struct that closes the +// file descriptor from its destructor. +class AutoFile +{ +public: + explicit AutoFile(FILE* file = nullptr) + : mFile(file) { + } + + ~AutoFile() { + if (mFile != nullptr) + fclose(mFile); + } + + AutoFile &operator=(FILE* file) { + if (mFile != 0) + fclose(mFile); + mFile = file; + return *this; + } + + operator FILE*() { + return mFile; + } + + FILE* get() { + return mFile; + } + +private: + FILE* mFile; +}; + +struct MARChannelStringTable { + MARChannelStringTable() + { + MARChannelID[0] = '\0'; + } + + char MARChannelID[MAX_TEXT_LEN]; +}; + +//----------------------------------------------------------------------------- + +typedef void (* ThreadFunc)(void *param); + +#ifdef XP_WIN +#include <process.h> + +class Thread +{ +public: + int Run(ThreadFunc func, void *param) + { + mThreadFunc = func; + mThreadParam = param; + + unsigned int threadID; + + mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0, + &threadID); + + return mThread ? 0 : -1; + } + int Join() + { + WaitForSingleObject(mThread, INFINITE); + CloseHandle(mThread); + return 0; + } +private: + static unsigned __stdcall ThreadMain(void *p) + { + Thread *self = (Thread *) p; + self->mThreadFunc(self->mThreadParam); + return 0; + } + HANDLE mThread; + ThreadFunc mThreadFunc; + void *mThreadParam; +}; + +#elif defined(XP_UNIX) +#include <pthread.h> + +class Thread +{ +public: + int Run(ThreadFunc func, void *param) + { + return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param); + } + int Join() + { + void *result; + return pthread_join(thr, &result); + } +private: + pthread_t thr; +}; + +#else +#error "Unsupported platform" +#endif + +//----------------------------------------------------------------------------- + +static NS_tchar* gPatchDirPath; +static NS_tchar gInstallDirPath[MAXPATHLEN]; +static NS_tchar gWorkingDirPath[MAXPATHLEN]; +static ArchiveReader gArchiveReader; +static bool gSucceeded = false; +static bool sStagedUpdate = false; +static bool sReplaceRequest = false; +static bool sUsingService = false; +static bool sIsOSUpdate = false; + +#ifdef XP_WIN +// The current working directory specified in the command line. +static NS_tchar* gDestPath; +static NS_tchar gCallbackRelPath[MAXPATHLEN]; +static NS_tchar gCallbackBackupPath[MAXPATHLEN]; +#endif + +static const NS_tchar kWhitespace[] = NS_T(" \t"); +static const NS_tchar kNL[] = NS_T("\r\n"); +static const NS_tchar kQuote[] = NS_T("\""); + +static inline size_t +mmin(size_t a, size_t b) +{ + return (a > b) ? b : a; +} + +static NS_tchar* +mstrtok(const NS_tchar *delims, NS_tchar **str) +{ + if (!*str || !**str) { + *str = nullptr; + return nullptr; + } + + // skip leading "whitespace" + NS_tchar *ret = *str; + const NS_tchar *d; + do { + for (d = delims; *d != NS_T('\0'); ++d) { + if (*ret == *d) { + ++ret; + break; + } + } + } while (*d); + + if (!*ret) { + *str = ret; + return nullptr; + } + + NS_tchar *i = ret; + do { + for (d = delims; *d != NS_T('\0'); ++d) { + if (*i == *d) { + *i = NS_T('\0'); + *str = ++i; + return ret; + } + } + ++i; + } while (*i); + + *str = nullptr; + return ret; +} + +static bool +EnvHasValue(const char *name) +{ + const char *val = getenv(name); + return (val && *val); +} + +#ifdef XP_WIN +/** + * Coverts a relative update path to a full path for Windows. + * + * @param relpath + * The relative path to convert to a full path. + * @return valid filesystem full path or nullptr if memory allocation fails. + */ +static NS_tchar* +get_full_path(const NS_tchar *relpath) +{ + size_t lendestpath = NS_tstrlen(gDestPath); + size_t lenrelpath = NS_tstrlen(relpath); + NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar)); + if (!s) + return nullptr; + + NS_tchar *c = s; + + NS_tstrcpy(c, gDestPath); + c += lendestpath; + NS_tstrcat(c, relpath); + c += lenrelpath; + *c = NS_T('\0'); + c++; + return s; +} +#endif + +/** + * Gets the platform specific path and performs simple checks to the path. If + * the path checks don't pass nullptr will be returned. + * + * @param line + * The line from the manifest that contains the path. + * @param isdir + * Whether the path is a directory path. Defaults to false. + * @return valid filesystem path or nullptr if the path checks fail. + */ +static NS_tchar* +get_valid_path(NS_tchar **line, bool isdir = false) +{ + NS_tchar *path = mstrtok(kQuote, line); + if (!path) { + LOG(("get_valid_path: unable to determine path: " LOG_S, line)); + return nullptr; + } + + // All paths must be relative from the current working directory + if (path[0] == NS_T('/')) { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } + +#ifdef XP_WIN + // All paths must be relative from the current working directory + if (path[0] == NS_T('\\') || path[1] == NS_T(':')) { + LOG(("get_valid_path: path must be relative: " LOG_S, path)); + return nullptr; + } +#endif + + if (isdir) { + // Directory paths must have a trailing forward slash. + if (path[NS_tstrlen(path) - 1] != NS_T('/')) { + LOG(("get_valid_path: directory paths must have a trailing forward " \ + "slash: " LOG_S, path)); + return nullptr; + } + + // Remove the trailing forward slash because stat on Windows will return + // ENOENT if the path has a trailing slash. + path[NS_tstrlen(path) - 1] = NS_T('\0'); + } + + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } + + return path; +} + +static NS_tchar* +get_quoted_path(const NS_tchar *path) +{ + size_t lenQuote = NS_tstrlen(kQuote); + size_t lenPath = NS_tstrlen(path); + size_t len = lenQuote + lenPath + lenQuote + 1; + + NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar)); + if (!s) + return nullptr; + + NS_tchar *c = s; + NS_tstrcpy(c, kQuote); + c += lenQuote; + NS_tstrcat(c, path); + c += lenPath; + NS_tstrcat(c, kQuote); + c += lenQuote; + *c = NS_T('\0'); + c++; + return s; +} + +static void ensure_write_permissions(const NS_tchar *path) +{ +#ifdef XP_WIN + (void) _wchmod(path, _S_IREAD | _S_IWRITE); +#else + struct stat fs; + if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) { + (void)chmod(path, fs.st_mode | S_IWUSR); + } +#endif +} + +static int ensure_remove(const NS_tchar *path) +{ + ensure_write_permissions(path); + int rv = NS_tremove(path); + if (rv) + LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return rv; +} + +// Remove the directory pointed to by path and all of its files and sub-directories. +static int ensure_remove_recursive(const NS_tchar *path, + bool continueEnumOnFailure = false) +{ + // We use lstat rather than stat here so that we can successfully remove + // symlinks. + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + // This error is benign + return rv; + } + if (!S_ISDIR(sInfo.st_mode)) { + return ensure_remove(path); + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("ensure_remove_recursive: unable to open directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + rv = ensure_remove_recursive(childPath); + if (rv && !continueEnumOnFailure) { + break; + } + } + } + + NS_tclosedir(dir); + + if (rv == OK) { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) { + LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} + +static bool is_read_only(const NS_tchar *flags) +{ + size_t length = NS_tstrlen(flags); + if (length == 0) + return false; + + // Make sure the string begins with "r" + if (flags[0] != NS_T('r')) + return false; + + // Look for "r+" or "r+b" + if (length > 1 && flags[1] == NS_T('+')) + return false; + + // Look for "rb+" + if (NS_tstrcmp(flags, NS_T("rb+")) == 0) + return false; + + return true; +} + +static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options) +{ + ensure_write_permissions(path); + FILE* f = NS_tfopen(path, flags); + if (is_read_only(flags)) { + // Don't attempt to modify the file permissions if the file is being opened + // in read-only mode. + return f; + } + if (NS_tchmod(path, options) != 0) { + if (f != nullptr) { + fclose(f); + } + return nullptr; + } + struct NS_tstat_t ss; + if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) { + if (f != nullptr) { + fclose(f); + } + return nullptr; + } + return f; +} + +// Ensure that the directory containing this file exists. +static int ensure_parent_dir(const NS_tchar *path) +{ + int rv = OK; + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/')); + if (slash) { + *slash = NS_T('\0'); + rv = ensure_parent_dir(path); + // Only attempt to create the directory if we're not at the root + if (rv == OK && *path) { + rv = NS_tmkdir(path, 0755); + // If the directory already exists, then ignore the error. + if (rv < 0 && errno != EEXIST) { + LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \ + "err: %d", path, errno)); + rv = WRITE_ERROR; + } else { + rv = OK; + } + } + *slash = NS_T('/'); + } + return rv; +} + +#ifdef XP_UNIX +static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest) +{ + // Copy symlinks by creating a new symlink to the same target + NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')}; + int rv = readlink(path, target, MAXPATHLEN); + if (rv == -1) { + LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + rv = symlink(target, dest); + if (rv == -1) { + LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d", + dest, target, errno)); + return READ_ERROR; + } + return 0; +} +#endif + +#if MAYBE_USE_HARD_LINKS +/* + * Creates a hardlink (destFilename) which points to the existing file + * (srcFilename). + * + * @return 0 if successful, an error otherwise + */ + +static int +create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename) +{ + if (link(srcFilename, destFilename) < 0) { + LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno)); + return WRITE_ERROR; + } + return OK; +} +#endif + +// Copy the file named path onto a new file named dest. +static int ensure_copy(const NS_tchar *path, const NS_tchar *dest) +{ +#ifdef XP_WIN + // Fast path for Windows + bool result = CopyFileW(path, dest, false); + if (!result) { + LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x", + path, dest, GetLastError())); + return WRITE_ERROR_FILE_COPY; + } + return OK; +#else + struct NS_tstat_t ss; + int rv = NS_tlstat(path, &ss); + if (rv) { + LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + +#ifdef XP_UNIX + if (S_ISLNK(ss.st_mode)) { + return ensure_copy_symlink(path, dest); + } +#endif + +#if MAYBE_USE_HARD_LINKS + if (sUseHardLinks) { + if (!create_hard_link(path, dest)) { + return OK; + } + // Since we failed to create the hard link, fall through and copy the file. + sUseHardLinks = false; + } +#endif + + AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); + if (!infile) { + LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d", + path, errno)); + return READ_ERROR; + } + AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode)); + if (!outfile) { + LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d", + dest, errno)); + return WRITE_ERROR; + } + + // This block size was chosen pretty arbitrarily but seems like a reasonable + // compromise. For example, the optimal block size on a modern OS X machine + // is 100k */ + const int blockSize = 32 * 1024; + void* buffer = malloc(blockSize); + if (!buffer) + return UPDATER_MEM_ERROR; + + while (!feof(infile.get())) { + size_t read = fread(buffer, 1, blockSize, infile); + if (ferror(infile.get())) { + LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", + path, errno)); + free(buffer); + return READ_ERROR; + } + + size_t written = 0; + + while (written < read) { + size_t chunkWritten = fwrite(buffer, 1, read - written, outfile); + if (chunkWritten <= 0) { + LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", + dest, errno)); + free(buffer); + return WRITE_ERROR_FILE_COPY; + } + + written += chunkWritten; + } + } + + rv = NS_tchmod(dest, ss.st_mode); + + free(buffer); + return rv; +#endif +} + +template <unsigned N> +struct copy_recursive_skiplist { + NS_tchar paths[N][MAXPATHLEN]; + + void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) { + NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix); + } + + bool find(const NS_tchar *path) { + for (int i = 0; i < static_cast<int>(N); ++i) { + if (!NS_tstricmp(paths[i], path)) { + return true; + } + } + return false; + } +}; + +// Copy all of the files and subdirectories under path to a new directory named dest. +// The path names in the skiplist will be skipped and will not be copied. +template <unsigned N> +static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest, + copy_recursive_skiplist<N>& skiplist) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + +#ifdef XP_UNIX + if (S_ISLNK(sInfo.st_mode)) { + return ensure_copy_symlink(path, dest); + } +#endif + + if (!S_ISDIR(sInfo.st_mode)) { + return ensure_copy(path, dest); + } + + rv = NS_tmkdir(dest, sInfo.st_mode); + if (rv < 0 && errno != EEXIST) { + LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return WRITE_ERROR; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", + path, rv, errno)); + return READ_ERROR; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + if (skiplist.find(childPath)) { + continue; + } + NS_tchar childPathDest[MAXPATHLEN]; + NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]), + NS_T("%s/%s"), dest, entry->d_name); + rv = ensure_copy_recursive(childPath, childPathDest, skiplist); + if (rv) { + break; + } + } + } + NS_tclosedir(dir); + return rv; +} + +// Renames the specified file to the new file specified. If the destination file +// exists it is removed. +static int rename_file(const NS_tchar *spath, const NS_tchar *dpath, + bool allowDirs = false) +{ + int rv = ensure_parent_dir(dpath); + if (rv) + return rv; + + struct NS_tstat_t spathInfo; + rv = NS_tstat(spath, &spathInfo); + if (rv) { + LOG(("rename_file: failed to read file status info: " LOG_S ", " \ + "err: %d", spath, errno)); + return READ_ERROR; + } + + if (!S_ISREG(spathInfo.st_mode)) { + if (allowDirs && !S_ISDIR(spathInfo.st_mode)) { + LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", + spath, errno)); + return RENAME_ERROR_EXPECTED_FILE; + } else { + LOG(("rename_file: proceeding to rename the directory")); + } + } + + if (!NS_taccess(dpath, F_OK)) { + if (ensure_remove(dpath)) { + LOG(("rename_file: destination file exists and could not be " \ + "removed: " LOG_S, dpath)); + return WRITE_ERROR_DELETE_FILE; + } + } + + if (NS_trename(spath, dpath) != 0) { + LOG(("rename_file: failed to rename file - src: " LOG_S ", " \ + "dst:" LOG_S ", err: %d", spath, dpath, errno)); + return WRITE_ERROR; + } + + return OK; +} + +#ifdef XP_WIN +// Remove the directory pointed to by path and all of its files and +// sub-directories. If a file is in use move it to the tobedeleted directory +// and attempt to schedule removal of the file on reboot +static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir) +{ + struct NS_tstat_t sInfo; + int rv = NS_tlstat(path, &sInfo); + if (rv) { + // This error is benign + return rv; + } + + if (!S_ISDIR(sInfo.st_mode)) { + NS_tchar tmpDeleteFile[MAXPATHLEN]; + GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile); + NS_tremove(tmpDeleteFile); + rv = rename_file(path, tmpDeleteFile, false); + if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: " + LOG_S, rv ? path : tmpDeleteFile)); + } else { + LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of " + "file: " LOG_S, rv ? path : tmpDeleteFile)); + } + return rv; + } + + NS_tDIR *dir; + NS_tdirent *entry; + + dir = NS_topendir(path); + if (!dir) { + LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S + ", rv: %d, err: %d", + path, rv, errno)); + return rv; + } + + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childPath[MAXPATHLEN]; + NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), + NS_T("%s/%s"), path, entry->d_name); + // There is no need to check the return value of this call since this + // function is only called after an update is successful and there is not + // much that can be done to recover if it isn't successful. There is also + // no need to log the value since it will have already been logged. + remove_recursive_on_reboot(childPath, deleteDir); + } + } + + NS_tclosedir(dir); + + if (rv == OK) { + ensure_write_permissions(path); + rv = NS_trmdir(path); + if (rv) { + LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S + ", rv: %d, err: %d", path, rv, errno)); + } + } + return rv; +} +#endif + +//----------------------------------------------------------------------------- + +// Create a backup of the specified file by renaming it. +static int backup_create(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + return rename_file(path, backup); +} + +// Rename the backup of the specified file that was created by renaming it back +// to the original file. +static int backup_restore(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + if (NS_taccess(backup, F_OK)) { + LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup)); + return OK; + } + + return rename_file(backup, path); +} + +// Discard the backup of the specified file that was created by renaming it. +static int backup_discard(const NS_tchar *path) +{ + NS_tchar backup[MAXPATHLEN]; + NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), + NS_T("%s") BACKUP_EXT, path); + + // Nothing to discard + if (NS_taccess(backup, F_OK)) { + return OK; + } + + int rv = ensure_remove(backup); +#if defined(XP_WIN) + if (rv && !sStagedUpdate && !sReplaceRequest) { + LOG(("backup_discard: unable to remove: " LOG_S, backup)); + NS_tchar path[MAXPATHLEN]; + GetTempFileNameW(DELETE_DIR, L"moz", 0, path); + if (rename_file(backup, path)) { + LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S, + backup, path)); + return WRITE_ERROR_DELETE_BACKUP; + } + // The MoveFileEx call to remove the file on OS reboot will fail if the + // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key + // but this is ok since the installer / uninstaller will delete the + // directory containing the file along with its contents after an update is + // applied, on reinstall, and on uninstall. + if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("backup_discard: file renamed and will be removed on OS " \ + "reboot: " LOG_S, path)); + } else { + LOG(("backup_discard: failed to schedule OS reboot removal of " \ + "file: " LOG_S, path)); + } + } +#else + if (rv) + return WRITE_ERROR_DELETE_BACKUP; +#endif + + return OK; +} + +// Helper function for post-processing a temporary backup. +static void backup_finish(const NS_tchar *path, int status) +{ + if (status == OK) + backup_discard(path); + else + backup_restore(path); +} + +//----------------------------------------------------------------------------- + +static int DoUpdate(); + +class Action +{ +public: + Action() : mProgressCost(1), mNext(nullptr) { } + virtual ~Action() { } + + virtual int Parse(NS_tchar *line) = 0; + + // Do any preprocessing to ensure that the action can be performed. Execute + // will be called if this Action and all others return OK from this method. + virtual int Prepare() = 0; + + // Perform the operation. Return OK to indicate success. After all actions + // have been executed, Finish will be called. A requirement of Execute is + // that its operation be reversable from Finish. + virtual int Execute() = 0; + + // Finish is called after execution of all actions. If status is OK, then + // all actions were successfully executed. Otherwise, some action failed. + virtual void Finish(int status) = 0; + + int mProgressCost; +private: + Action* mNext; + + friend class ActionList; +}; + +class RemoveFile : public Action +{ +public: + RemoveFile() : mFile(nullptr), mSkip(0) { } + + int Parse(NS_tchar *line); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + const NS_tchar *mFile; + int mSkip; +}; + +int +RemoveFile::Parse(NS_tchar *line) +{ + // format "<deadfile>" + + mFile = get_valid_path(&line); + if (!mFile) + return PARSE_ERROR; + + return OK; +} + +int +RemoveFile::Prepare() +{ + // Skip the file if it already doesn't exist. + int rv = NS_taccess(mFile, F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEFILE " LOG_S, mFile)); + + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile, &fileInfo); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile, + errno)); + return READ_ERROR; + } + + if (!S_ISREG(fileInfo.st_mode)) { + LOG(("path present, but not a file: " LOG_S, mFile)); + return DELETE_ERROR_EXPECTED_FILE; + } + + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/')); + if (slash) { + *slash = NS_T('\0'); + rv = NS_taccess(mFile, W_OK); + *slash = NS_T('/'); + } else { + rv = NS_taccess(NS_T("."), W_OK); + } + + if (rv) { + LOG(("access failed: %d", errno)); + return WRITE_ERROR_FILE_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveFile::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEFILE " LOG_S, mFile)); + + // The file is checked for existence here and in Prepare since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mFile, F_OK); + if (rv) { + LOG(("file cannot be removed because it does not exist; skipping")); + mSkip = 1; + return OK; + } + + // Rename the old file. It will be removed in Finish. + rv = backup_create(mFile); + if (rv) { + LOG(("backup_create failed: %d", rv)); + return rv; + } + + return OK; +} + +void +RemoveFile::Finish(int status) +{ + if (mSkip) + return; + + LOG(("FINISH REMOVEFILE " LOG_S, mFile)); + + backup_finish(mFile, status); +} + +class RemoveDir : public Action +{ +public: + RemoveDir() : mDir(nullptr), mSkip(0) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // check that the source dir exists + virtual int Execute(); + virtual void Finish(int status); + +private: + const NS_tchar *mDir; + int mSkip; +}; + +int +RemoveDir::Parse(NS_tchar *line) +{ + // format "<deaddir>/" + + mDir = get_valid_path(&line, true); + if (!mDir) + return PARSE_ERROR; + + return OK; +} + +int +RemoveDir::Prepare() +{ + // We expect the directory to exist if we are to remove it. + int rv = NS_taccess(mDir, F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } + + LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir)); + + // Make sure that we're actually a dir. + struct NS_tstat_t dirInfo; + rv = NS_tstat(mDir, &dirInfo); + if (rv) { + LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir, + errno)); + return READ_ERROR; + } + + if (!S_ISDIR(dirInfo.st_mode)) { + LOG(("path present, but not a directory: " LOG_S, mDir)); + return DELETE_ERROR_EXPECTED_DIR; + } + + rv = NS_taccess(mDir, W_OK); + if (rv) { + LOG(("access failed: %d, %d", rv, errno)); + return WRITE_ERROR_DIR_ACCESS_DENIED; + } + + return OK; +} + +int +RemoveDir::Execute() +{ + if (mSkip) + return OK; + + LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir)); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir, F_OK); + if (rv) { + LOG(("directory no longer exists; skipping")); + mSkip = 1; + } + + return OK; +} + +void +RemoveDir::Finish(int status) +{ + if (mSkip || status != OK) + return; + + LOG(("FINISH REMOVEDIR " LOG_S "/", mDir)); + + // The directory is checked for existence at every step since it might have + // been removed by a separate instruction: bug 311099. + int rv = NS_taccess(mDir, F_OK); + if (rv) { + LOG(("directory no longer exists; skipping")); + return; + } + + + if (status == OK) { + if (NS_trmdir(mDir)) { + LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d", + mDir, rv, errno)); + } + } +} + +class AddFile : public Action +{ +public: + AddFile() : mFile(nullptr) + , mAdded(false) + { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +private: + const NS_tchar *mFile; + bool mAdded; +}; + +int +AddFile::Parse(NS_tchar *line) +{ + // format "<newfile>" + + mFile = get_valid_path(&line); + if (!mFile) + return PARSE_ERROR; + + return OK; +} + +int +AddFile::Prepare() +{ + LOG(("PREPARE ADD " LOG_S, mFile)); + + return OK; +} + +int +AddFile::Execute() +{ + LOG(("EXECUTE ADD " LOG_S, mFile)); + + int rv; + + // First make sure that we can actually get rid of any existing file. + rv = NS_taccess(mFile, F_OK); + if (rv == 0) { + rv = backup_create(mFile); + if (rv) + return rv; + } else { + rv = ensure_parent_dir(mFile); + if (rv) + return rv; + } + +#ifdef XP_WIN + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN, + nullptr, nullptr)) { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + rv = gArchiveReader.ExtractFile(sourcefile, mFile); +#else + rv = gArchiveReader.ExtractFile(mFile, mFile); +#endif + if (!rv) { + mAdded = true; + } + return rv; +} + +void +AddFile::Finish(int status) +{ + LOG(("FINISH ADD " LOG_S, mFile)); + // When there is an update failure and a file has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) + NS_tremove(mFile); + backup_finish(mFile, status); +} + +class PatchFile : public Action +{ +public: + PatchFile() : mPatchIndex(-1), buf(nullptr) { } + + virtual ~PatchFile(); + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + int LoadSourceFile(FILE* ofile); + + static int sPatchIndex; + + const NS_tchar *mPatchFile; + const NS_tchar *mFile; + int mPatchIndex; + MBSPatchHeader header; + unsigned char *buf; + NS_tchar spath[MAXPATHLEN]; +}; + +int PatchFile::sPatchIndex = 0; + +PatchFile::~PatchFile() +{ + // delete the temporary patch file + if (spath[0]) + NS_tremove(spath); + + if (buf) + free(buf); +} + +int +PatchFile::LoadSourceFile(FILE* ofile) +{ + struct stat os; + int rv = fstat(fileno((FILE *)ofile), &os); + if (rv) { + LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \ + "err: %d", mFile, errno)); + return READ_ERROR; + } + + if (uint32_t(os.st_size) != header.slen) { + LOG(("LoadSourceFile: destination file size %d does not match expected size %d", + uint32_t(os.st_size), header.slen)); + return LOADSOURCE_ERROR_WRONG_SIZE; + } + + buf = (unsigned char *) malloc(header.slen); + if (!buf) + return UPDATER_MEM_ERROR; + + size_t r = header.slen; + unsigned char *rb = buf; + while (r) { + const size_t count = mmin(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, ofile); + if (c != count) { + LOG(("LoadSourceFile: error reading destination file: " LOG_S, + mFile)); + return READ_ERROR; + } + + r -= c; + rb += c; + } + + // Verify that the contents of the source file correspond to what we expect. + + unsigned int crc = crc32(buf, header.slen); + + if (crc != header.scrc32) { + LOG(("LoadSourceFile: destination file crc %d does not match expected " \ + "crc %d", crc, header.scrc32)); + return CRC_ERROR; + } + + return OK; +} + +int +PatchFile::Parse(NS_tchar *line) +{ + // format "<patchfile>" "<filetopatch>" + + // Get the path to the patch file inside of the mar + mPatchFile = mstrtok(kQuote, &line); + if (!mPatchFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + mFile = get_valid_path(&line); + if (!mFile) + return PARSE_ERROR; + + return OK; +} + +int +PatchFile::Prepare() +{ + LOG(("PREPARE PATCH " LOG_S, mFile)); + + // extract the patch to a temporary file + mPatchIndex = sPatchIndex++; + + NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]), + NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex); + + NS_tremove(spath); + + FILE *fp = NS_tfopen(spath, NS_T("wb")); + if (!fp) + return WRITE_ERROR; + +#ifdef XP_WIN + char sourcefile[MAXPATHLEN]; + if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN, + nullptr, nullptr)) { + LOG(("error converting wchar to utf8: %d", GetLastError())); + return STRING_CONVERSION_ERROR; + } + + int rv = gArchiveReader.ExtractFileToStream(sourcefile, fp); +#else + int rv = gArchiveReader.ExtractFileToStream(mPatchFile, fp); +#endif + fclose(fp); + return rv; +} + +int +PatchFile::Execute() +{ + LOG(("EXECUTE PATCH " LOG_S, mFile)); + + AutoFile pfile(NS_tfopen(spath, NS_T("rb"))); + if (pfile == nullptr) + return READ_ERROR; + + int rv = MBS_ReadHeader(pfile, &header); + if (rv) + return rv; + + FILE *origfile = nullptr; +#ifdef XP_WIN + if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) { + // Read from the copy of the callback when patching since the callback can't + // be opened for reading to prevent the application from being launched. + origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb")); + } else { + origfile = NS_tfopen(mFile, NS_T("rb")); + } +#else + origfile = NS_tfopen(mFile, NS_T("rb")); +#endif + + if (!origfile) { + LOG(("unable to open destination file: " LOG_S ", err: %d", mFile, + errno)); + return READ_ERROR; + } + + rv = LoadSourceFile(origfile); + fclose(origfile); + if (rv) { + LOG(("LoadSourceFile failed")); + return rv; + } + + // Rename the destination file if it exists before proceeding so it can be + // used to restore the file to its original state if there is an error. + struct NS_tstat_t ss; + rv = NS_tstat(mFile, &ss); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile, + errno)); + return READ_ERROR; + } + + rv = backup_create(mFile); + if (rv) + return rv; + +#if defined(HAVE_POSIX_FALLOCATE) + AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode)); + posix_fallocate(fileno((FILE *)ofile), 0, header.dlen); +#elif defined(XP_WIN) + bool shouldTruncate = true; + // Creating the file, setting the size, and then closing the file handle + // lessens fragmentation more than any other method tested. Other methods that + // have been tested are: + // 1. _chsize / _chsize_s reduced fragmentation though not completely. + // 2. _get_osfhandle and then setting the size reduced fragmentation though + // not completely. There are also reports of _get_osfhandle failing on + // mingw. + HANDLE hfile = CreateFileW(mFile, + GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (hfile != INVALID_HANDLE_VALUE) { + if (SetFilePointer(hfile, header.dlen, + nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER && + SetEndOfFile(hfile) != 0) { + shouldTruncate = false; + } + CloseHandle(hfile); + } + + AutoFile ofile(ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"), + ss.st_mode)); +#elif defined(XP_MACOSX) + AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode)); + // Modified code from FileUtils.cpp + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen}; + // Try to get a continous chunk of disk space + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + if (rv == -1) { + // OK, perhaps we are too fragmented, allocate non-continuous + store.fst_flags = F_ALLOCATEALL; + rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); + } + + if (rv != -1) { + ftruncate(fileno((FILE *)ofile), header.dlen); + } +#else + AutoFile ofile(ensure_open(mFile, NS_T("wb+"), ss.st_mode)); +#endif + + if (ofile == nullptr) { + LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno)); + return WRITE_ERROR_OPEN_PATCH_FILE; + } + +#ifdef XP_WIN + if (!shouldTruncate) { + fseek(ofile, 0, SEEK_SET); + } +#endif + + rv = MBS_ApplyPatch(&header, pfile, buf, ofile); + + // Go ahead and do a bit of cleanup now to minimize runtime overhead. + // Set pfile to nullptr to make AutoFile close the file so it can be deleted + // on Windows. + pfile = nullptr; + NS_tremove(spath); + spath[0] = NS_T('\0'); + free(buf); + buf = nullptr; + + return rv; +} + +void +PatchFile::Finish(int status) +{ + LOG(("FINISH PATCH " LOG_S, mFile)); + + backup_finish(mFile, status); +} + +class AddIfFile : public AddFile +{ +public: + AddIfFile() : mTestFile(nullptr) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + const NS_tchar *mTestFile; +}; + +int +AddIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile = get_valid_path(&line); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile, F_OK)) { + mTestFile = nullptr; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class AddIfNotFile : public AddFile +{ +public: + AddIfNotFile() : mTestFile(NULL) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + +protected: + const NS_tchar *mTestFile; +}; + +int +AddIfNotFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<newfile>" + + mTestFile = get_valid_path(&line); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return AddFile::Parse(line); +} + +int +AddIfNotFile::Prepare() +{ + // If the test file exists, then skip this action. + if (!NS_taccess(mTestFile, F_OK)) { + mTestFile = NULL; + return OK; + } + + return AddFile::Prepare(); +} + +int +AddIfNotFile::Execute() +{ + if (!mTestFile) + return OK; + + return AddFile::Execute(); +} + +void +AddIfNotFile::Finish(int status) +{ + if (!mTestFile) + return; + + AddFile::Finish(status); +} + +class PatchIfFile : public PatchFile +{ +public: + PatchIfFile() : mTestFile(nullptr) { } + + virtual int Parse(NS_tchar *line); + virtual int Prepare(); // should check for patch file and for checksum here + virtual int Execute(); + virtual void Finish(int status); + +private: + const NS_tchar *mTestFile; +}; + +int +PatchIfFile::Parse(NS_tchar *line) +{ + // format "<testfile>" "<patchfile>" "<filetopatch>" + + mTestFile = get_valid_path(&line); + if (!mTestFile) + return PARSE_ERROR; + + // consume whitespace between args + NS_tchar *q = mstrtok(kQuote, &line); + if (!q) + return PARSE_ERROR; + + return PatchFile::Parse(line); +} + +int +PatchIfFile::Prepare() +{ + // If the test file does not exist, then skip this action. + if (NS_taccess(mTestFile, F_OK)) { + mTestFile = nullptr; + return OK; + } + + return PatchFile::Prepare(); +} + +int +PatchIfFile::Execute() +{ + if (!mTestFile) + return OK; + + return PatchFile::Execute(); +} + +void +PatchIfFile::Finish(int status) +{ + if (!mTestFile) + return; + + PatchFile::Finish(status); +} + +//----------------------------------------------------------------------------- + +#ifdef XP_WIN +#include "nsWindowsRestart.cpp" +#include "nsWindowsHelpers.h" +#include "uachelper.h" +#include "pathhash.h" +#endif + +static void +LaunchCallbackApp(const NS_tchar *workingDir, + int argc, + NS_tchar **argv, + bool usingService) +{ + putenv(const_cast<char*>("NO_EM_RESTART=")); + putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1")); + + // Run from the specified working directory (see bug 312360). This is not + // necessary on Windows CE since the application that launches the updater + // passes the working directory as an --environ: command line argument. + if (NS_tchdir(workingDir) != 0) { + LOG(("Warning: chdir failed")); + } + +#if defined(USE_EXECV) + execv(argv[0], argv); +#elif defined(XP_MACOSX) + LaunchChild(argc, argv); +#elif defined(XP_WIN) + // Do not allow the callback to run when running an update through the + // service as session 0. The unelevated updater.exe will do the launching. + if (!usingService) { + WinLaunchChild(argv[0], argc, argv, nullptr); + } +#else +# warning "Need implementaton of LaunchCallbackApp" +#endif +} + +static bool +WriteStatusFile(const char* aStatus) +{ + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + // Make sure that the directory for the update status file exists + if (ensure_parent_dir(filename)) + return false; + + AutoFile file(NS_tfopen(filename, NS_T("wb+"))); + if (file == nullptr) + return false; + + if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) + return false; + + return true; +} + +static void +WriteStatusFile(int status) +{ + const char *text; + + char buf[32]; + if (status == OK) { + if (sStagedUpdate) { + text = "applied\n"; + } else { + text = "succeeded\n"; + } + } else { + snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status); + text = buf; + } + + WriteStatusFile(text); +} + +#ifdef MOZ_MAINTENANCE_SERVICE +/* + * Read the update.status file and sets isPendingService to true if + * the status is set to pending-service. + * + * @param isPendingService Out parameter for specifying if the status + * is set to pending-service or not. + * @return true if the information was retrieved and it is pending + * or pending-service. +*/ +static bool +IsUpdateStatusPendingService() +{ + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kPendingService[] = "pending-service"; + const char kAppliedService[] = "applied-service"; + + return (strncmp(buf, kPendingService, + sizeof(kPendingService) - 1) == 0) || + (strncmp(buf, kAppliedService, + sizeof(kAppliedService) - 1) == 0); +} +#endif + +#ifdef XP_WIN +/* + * Read the update.status file and sets isSuccess to true if + * the status is set to succeeded. + * + * @param isSucceeded Out parameter for specifying if the status + * is set to succeeded or not. + * @return true if the information was retrieved and it is succeeded. +*/ +static bool +IsUpdateStatusSucceeded(bool &isSucceeded) +{ + isSucceeded = false; + NS_tchar filename[MAXPATHLEN]; + NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), + NS_T("%s/update.status"), gPatchDirPath); + + AutoFile file(NS_tfopen(filename, NS_T("rb"))); + if (file == nullptr) + return false; + + char buf[32] = { 0 }; + fread(buf, sizeof(buf), 1, file); + + const char kSucceeded[] = "succeeded"; + isSucceeded = strncmp(buf, kSucceeded, + sizeof(kSucceeded) - 1) == 0; + return true; +} +#endif + +/* + * Copy the entire contents of the application installation directory to the + * destination directory for the update process. + * + * @return 0 if successful, an error code otherwise. + */ +static int +CopyInstallDirToDestDir() +{ + // These files should not be copied over to the updated app +#ifdef XP_WIN +#define SKIPLIST_COUNT 3 +#elif XP_MACOSX +#define SKIPLIST_COUNT 0 +#else +#define SKIPLIST_COUNT 2 +#endif + copy_recursive_skiplist<SKIPLIST_COUNT> skiplist; +#ifndef XP_MACOSX + skiplist.append(0, gInstallDirPath, NS_T("updated")); + skiplist.append(1, gInstallDirPath, NS_T("updates/0")); +#ifdef XP_WIN + skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock")); +#endif +#endif + + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); +} + +/* + * Replace the application installation directory with the destination + * directory in order to finish a staged update task + * + * @return 0 if successful, an error code otherwise. + */ +static int +ProcessReplaceRequest() +{ + // The replacement algorithm is like this: + // 1. Move destDir to tmpDir. In case of failure, abort. + // 2. Move newDir to destDir. In case of failure, revert step 1 and abort. + // 3. Delete tmpDir (or defer it to the next reboot). + +#ifdef XP_MACOSX + NS_tchar destDir[MAXPATHLEN]; + NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]), + NS_T("%s/Contents"), gInstallDirPath); +#elif XP_WIN + // Windows preserves the case of the file/directory names. We use the + // GetLongPathName API in order to get the correct case for the directory + // name, so that if the user has used a different case when launching the + // application, the installation directory's name does not change. + NS_tchar destDir[MAXPATHLEN]; + if (!GetLongPathNameW(gInstallDirPath, destDir, + sizeof(destDir)/sizeof(destDir[0]))) { + return NO_INSTALLDIR_ERROR; + } +#else + NS_tchar* destDir = gInstallDirPath; +#endif + + NS_tchar tmpDir[MAXPATHLEN]; + NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]), + NS_T("%s.bak"), destDir); + + NS_tchar newDir[MAXPATHLEN]; + NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]), +#ifdef XP_MACOSX + NS_T("%s/Contents"), + gWorkingDirPath); +#else + NS_T("%s.bak/updated"), + gInstallDirPath); +#endif + + // First try to remove the possibly existing temp directory, because if this + // directory exists, we will fail to rename destDir. + // No need to error check here because if this fails, we will fail in the + // next step anyways. + ensure_remove_recursive(tmpDir); + + LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")", + destDir, tmpDir)); + int rv = rename_file(destDir, tmpDir, true); +#ifdef XP_WIN + // On Windows, if Firefox is launched using the shortcut, it will hold a handle + // to its installation directory open, which might not get released in time. + // Therefore we wait a little bit here to see if the handle is released. + // If it's not released, we just fail to perform the replace request. + const int max_retries = 10; + int retries = 0; + while (rv == WRITE_ERROR && (retries++ < max_retries)) { + LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \ + "File: " LOG_S ". Last error: %d, err: %d", retries, + destDir, GetLastError(), rv)); + + Sleep(100); + + rv = rename_file(destDir, tmpDir, true); + } +#endif + if (rv) { + // The status file will have 'pending' written to it so there is no value in + // returning an error specific for this failure. + LOG(("Moving destDir to tmpDir failed, err: %d", rv)); + return rv; + } + + LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + rv = rename_file(newDir, destDir, true); +#ifdef XP_MACOSX + if (rv) { + LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")", + newDir, destDir)); + copy_recursive_skiplist<0> skiplist; + rv = ensure_copy_recursive(newDir, destDir, skiplist); + } +#endif + if (rv) { + LOG(("Moving newDir to destDir failed, err: %d", rv)); + LOG(("Now, try to move tmpDir back to destDir")); + ensure_remove_recursive(destDir); + int rv2 = rename_file(tmpDir, destDir, true); + if (rv2) { + LOG(("Moving tmpDir back to destDir failed, err: %d", rv2)); + } + // The status file will be have 'pending' written to it so there is no value + // in returning an error specific for this failure. + return rv; + } + + LOG(("Now, remove the tmpDir")); + rv = ensure_remove_recursive(tmpDir, true); + if (rv) { + LOG(("Removing tmpDir failed, err: %d", rv)); +#ifdef XP_WIN + NS_tchar deleteDir[MAXPATHLEN]; + NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]), + NS_T("%s\\%s"), destDir, DELETE_DIR); + // Attempt to remove the tobedeleted directory and then recreate it if it + // was successfully removed. + _wrmdir(deleteDir); + if (NS_taccess(deleteDir, F_OK)) { + NS_tmkdir(deleteDir, 0755); + } + remove_recursive_on_reboot(tmpDir, deleteDir); +#endif + } + +#ifdef XP_MACOSX + // On OS X, we we need to remove the staging directory after its Contents + // directory has been moved. + NS_tchar updatedAppDir[MAXPATHLEN]; + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gPatchDirPath); + ensure_remove_recursive(updatedAppDir); +#endif + + gSucceeded = true; + + return 0; +} + +#ifdef XP_WIN +static void +WaitForServiceFinishThread(void *param) +{ + // We wait at most 10 minutes, we already waited 5 seconds previously + // before deciding to show this UI. + WaitForServiceStop(SVC_NAME, 595); + QuitProgressUI(); +} +#endif + +#ifdef MOZ_VERIFY_MAR_SIGNATURE +/** + * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini + * + * @param path The path to the ini file that is to be read + * @param results A pointer to the location to store the read strings + * @return OK on success + */ +static int +ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) +{ + const unsigned int kNumStrings = 1; + const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0"; + char updater_strings[kNumStrings][MAX_TEXT_LEN]; + + int result = ReadStrings(path, kUpdaterKeys, kNumStrings, + updater_strings, "Settings"); + + strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1); + results->MARChannelID[MAX_TEXT_LEN - 1] = 0; + + return result; +} +#endif + +static int +GetUpdateFileName(NS_tchar *fileName, int maxChars) +{ +#if defined(MOZ_WIDGET_GONK) + // If an update.link file exists, then it will contain the name + // of the update file (terminated by a newline). + + NS_tchar linkFileName[MAXPATHLEN]; + NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]), + NS_T("%s/update.link"), gPatchDirPath); + AutoFile linkFile(NS_tfopen(linkFileName, NS_T("rb"))); + if (linkFile == nullptr) { + NS_tsnprintf(fileName, maxChars, + NS_T("%s/update.mar"), gPatchDirPath); + return OK; + } + + char dataFileName[MAXPATHLEN]; + size_t bytesRead; + + if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) { + *fileName = NS_T('\0'); + return READ_ERROR; + } + if (dataFileName[bytesRead-1] == '\n') { + // Strip trailing newline (for \n and \r\n) + bytesRead--; + } + if (dataFileName[bytesRead-1] == '\r') { + // Strip trailing CR (for \r, \r\n) + bytesRead--; + } + dataFileName[bytesRead] = '\0'; + + strncpy(fileName, dataFileName, maxChars-1); + fileName[maxChars-1] = '\0'; +#else + // We currently only support update.link files under GONK + NS_tsnprintf(fileName, maxChars, + NS_T("%s/update.mar"), gPatchDirPath); +#endif + return OK; +} + +static void +UpdateThreadFunc(void *param) +{ + // open ZIP archive and process... + int rv; + if (sReplaceRequest) { + rv = ProcessReplaceRequest(); + } else { + NS_tchar dataFile[MAXPATHLEN]; + rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0])); + if (rv == OK) { + rv = gArchiveReader.Open(dataFile); + } + +#ifdef MOZ_VERIFY_MAR_SIGNATURE + if (rv == OK) { +#ifdef XP_WIN + HKEY baseKey = nullptr; + wchar_t valueName[] = L"Image Path"; + wchar_t rasenh[] = L"rsaenh.dll"; + bool reset = false; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0", + 0, KEY_READ | KEY_WRITE, + &baseKey) == ERROR_SUCCESS) { + wchar_t path[MAX_PATH + 1]; + DWORD size = sizeof(path); + DWORD type; + if (RegQueryValueExW(baseKey, valueName, 0, &type, + (LPBYTE)path, &size) == ERROR_SUCCESS) { + if (type == REG_SZ && wcscmp(path, rasenh) == 0) { + wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll"; + if (RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenhFullPath, + sizeof(rasenhFullPath)) == ERROR_SUCCESS) { + reset = true; + } + } + } + } +#endif + rv = gArchiveReader.VerifySignature(); +#ifdef XP_WIN + if (baseKey) { + if (reset) { + RegSetValueExW(baseKey, valueName, 0, REG_SZ, + (const BYTE*)rasenh, + sizeof(rasenh)); + } + RegCloseKey(baseKey); + } +#endif + } + + if (rv == OK) { + if (rv == OK) { + NS_tchar updateSettingsPath[MAX_TEXT_LEN]; + NS_tsnprintf(updateSettingsPath, + sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), +#ifdef XP_MACOSX + NS_T("%s/Contents/Resources/update-settings.ini"), +#else + NS_T("%s/update-settings.ini"), +#endif + gWorkingDirPath); + MARChannelStringTable MARStrings; + if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { + // If we can't read from update-settings.ini then we shouldn't impose + // a MAR restriction. Some installations won't even include this file. + MARStrings.MARChannelID[0] = '\0'; + } + + rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, + MOZ_APP_VERSION); + } + } +#endif + + if (rv == OK && sStagedUpdate && !sIsOSUpdate) { +#ifdef TEST_UPDATER + // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying + // the files in dist/bin in the test updater when staging an update since + // this can cause tests to timeout. + if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) { + rv = OK; + } else { + rv = CopyInstallDirToDestDir(); + } +#else + rv = CopyInstallDirToDestDir(); +#endif + } + + if (rv == OK) { + rv = DoUpdate(); + gArchiveReader.Close(); + NS_tchar updatingDir[MAXPATHLEN]; + NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), + NS_T("%s/updating"), gWorkingDirPath); + ensure_remove_recursive(updatingDir); + } + } + + if (sReplaceRequest && rv) { + // When attempting to replace the application, we should fall back + // to non-staged updates in case of a failure. We do this by + // setting the status to pending, exiting the updater, and + // launching the callback application. The callback application's + // startup path will see the pending status, and will start the + // updater application again in order to apply the update without + // staging. + ensure_remove_recursive(gWorkingDirPath); + WriteStatusFile(sUsingService ? "pending-service" : "pending"); +#ifdef TEST_UPDATER + // Some tests need to use --test-process-updates again. + putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES=")); +#endif + } else { + if (rv) { + LOG(("failed: %d", rv)); + } else { +#ifdef XP_MACOSX + // If the update was successful we need to update the timestamp on the + // top-level Mac OS X bundle directory so that Mac OS X's Launch Services + // picks up any major changes when the bundle is updated. + if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) { + LOG(("Couldn't set access/modification time on application bundle.")); + } +#endif + + LOG(("succeeded")); + } + WriteStatusFile(rv); + } + + LOG(("calling QuitProgressUI")); + QuitProgressUI(); +} + +int NS_main(int argc, NS_tchar **argv) +{ +#if defined(MOZ_WIDGET_GONK) + if (EnvHasValue("LD_PRELOAD")) { + // If the updater is launched with LD_PRELOAD set, then we wind up + // preloading libmozglue.so. Under some circumstances, this can cause + // the remount of /system to fail when going from rw to ro, so if we + // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it. + // This will cause the offending preloaded library to be closed. + // + // For a variety of reasons, this is really hard to do in a safe manner + // in the parent process, so we do it here. + unsetenv("LD_PRELOAD"); + execv(argv[0], argv); + __android_log_print(ANDROID_LOG_INFO, "updater", + "execve failed: errno: %d. Exiting...", errno); + _exit(1); + } +#endif + +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX) + // On Windows and Mac we rely on native APIs to do verifications so we don't + // need to initialize NSS at all there. + // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS + // databases. + if (NSS_NoDB_Init(NULL) != SECSuccess) { + PRErrorCode error = PR_GetError(); + fprintf(stderr, "Could not initialize NSS: %s (%d)", + PR_ErrorToName(error), (int) error); + _exit(1); + } +#endif + + InitProgressUI(&argc, &argv); + + // To process an update the updater command line must at a minimum have the + // directory path containing the updater.mar file to process as the first + // argument, the install directory as the second argument, and the directory + // to apply the update to as the third argument. When the updater is launched + // by another process the PID of the parent process should be provided in the + // optional fourth argument and the updater will wait on the parent process to + // exit if the value is non-zero and the process is present. This is necessary + // due to not being able to update files that are in use on Windows. The + // optional fifth argument is the callback's working directory and the + // optional sixth argument is the callback path. The callback is the + // application to launch after updating and it will be launched when these + // arguments are provided whether the update was successful or not. All + // remaining arguments are optional and are passed to the callback when it is + // launched. + if (argc < 4) { + fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n"); + return 1; + } + + // The directory containing the update information. + gPatchDirPath = argv[1]; + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN); + gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0'); + NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH); + if (slash && !slash[1]) { + *slash = NS_T('\0'); + } + +#ifdef XP_WIN + bool useService = false; + bool testOnlyFallbackKeyExists = false; + bool noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK"); + putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK=")); + + // We never want the service to be used unless we build with + // the maintenance service. +#ifdef MOZ_MAINTENANCE_SERVICE + useService = IsUpdateStatusPendingService(); + // Our tests run with a different apply directory for each test. + // We use this registry key on our test slaves to store the + // allowed name/issuers. + testOnlyFallbackKeyExists = DoesFallbackKeyExist(); +#endif + + // Remove everything except close window from the context menu + { + HKEY hkApp = nullptr; + RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", + 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr); + RegCloseKey(hkApp); + if (RegCreateKeyExW(HKEY_CURRENT_USER, + L"Software\\Classes\\Applications\\updater.exe", + 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, + &hkApp, nullptr) == ERROR_SUCCESS) { + RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); + RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); + RegCloseKey(hkApp); + } + } +#endif + + // If there is a PID specified and it is not '0' then wait for the process to exit. +#ifdef XP_WIN + __int64 pid = 0; +#else + int pid = 0; +#endif + if (argc > 4) { +#ifdef XP_WIN + pid = _wtoi64(argv[4]); +#else + pid = atoi(argv[4]); +#endif + if (pid == -1) { + // This is a signal from the parent process that the updater should stage + // the update. + sStagedUpdate = true; + } else if (NS_tstrstr(argv[4], NS_T("/replace"))) { + // We're processing a request to replace the application with a staged + // update. + sReplaceRequest = true; + } + } + + // The directory we're going to update to. + // We copy this string because we need to remove trailing slashes. The C++ + // standard says that it's always safe to write to strings pointed to by argv + // elements, but I don't necessarily believe it. + NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN); + gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0'); + slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH); + if (slash && !slash[1]) { + *slash = NS_T('\0'); + } + + if (EnvHasValue("MOZ_OS_UPDATE")) { + sIsOSUpdate = true; + putenv(const_cast<char*>("MOZ_OS_UPDATE=")); + } + + if (sReplaceRequest) { + // If we're attempting to replace the application, try to append to the + // log generated when staging the staged update. +#ifdef XP_WIN + NS_tchar* logDir = gPatchDirPath; +#else +#ifdef XP_MACOSX + NS_tchar* logDir = gPatchDirPath; +#else + NS_tchar logDir[MAXPATHLEN]; + NS_tsnprintf(logDir, sizeof(logDir)/sizeof(logDir[0]), + NS_T("%s/updated/updates"), + gInstallDirPath); +#endif +#endif + + LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log")); + } else { + LogInit(gPatchDirPath, NS_T("update.log")); + } + + if (!WriteStatusFile("applying")) { + LOG(("failed setting status to 'applying'")); + return 1; + } + + if (sStagedUpdate) { + LOG(("Performing a staged update")); + } else if (sReplaceRequest) { + LOG(("Performing a replace request")); + } + + LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath)); + LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath)); + LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath)); + +#ifdef MOZ_WIDGET_GONK + const char *prioEnv = getenv("MOZ_UPDATER_PRIO"); + if (prioEnv) { + int32_t prioVal; + int32_t oomScoreAdj; + int32_t ioprioClass; + int32_t ioprioLevel; + if (sscanf(prioEnv, "%d/%d/%d/%d", + &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) { + LOG(("MOZ_UPDATER_PRIO=%s", prioEnv)); + if (setpriority(PRIO_PROCESS, 0, prioVal)) { + LOG(("setpriority(%d) failed, errno = %d", prioVal, errno)); + } + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, + IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) { + LOG(("ioprio_set(%d,%d) failed: errno = %d", + ioprioClass, ioprioLevel, errno)); + } + FILE *fs = fopen("/proc/self/oom_score_adj", "w"); + if (fs) { + fprintf(fs, "%d", oomScoreAdj); + fclose(fs); + } else { + LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d", + errno)); + } + } + } +#endif + +#ifdef XP_WIN + if (pid > 0) { + HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid); + // May return nullptr if the parent process has already gone away. + // Otherwise, wait for the parent process to exit before starting the + // update. + if (parent) { + DWORD waitTime = PARENT_WAIT; + DWORD result = WaitForSingleObject(parent, waitTime); + CloseHandle(parent); + if (result != WAIT_OBJECT_0) + return 1; + } + } +#else + if (pid > 0) + waitpid(pid, nullptr, 0); +#endif + + if (sReplaceRequest) { +#ifdef XP_WIN + // On Windows, the current working directory of the process should be changed + // so that it's not locked. + NS_tchar sysDir[MAX_PATH + 1] = { L'\0' }; + if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) { + NS_tchdir(sysDir); + } +#endif + } + + // The callback is the remaining arguments starting at callbackIndex. + // The argument specified by callbackIndex is the callback executable and the + // argument prior to callbackIndex is the working directory. + const int callbackIndex = 6; + +#if defined(XP_WIN) + sUsingService = EnvHasValue("MOZ_USING_SERVICE"); + putenv(const_cast<char*>("MOZ_USING_SERVICE=")); + // lastFallbackError keeps track of the last error for the service not being + // used, in case of an error when fallback is not enabled we write the + // error to the update.status file. + // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then + // we will instead fallback to not using the service and display a UAC prompt. + int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR; + + // Launch a second instance of the updater with the runas verb on Windows + // when write access is denied to the installation directory. + HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE; + NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')}; + if (!sUsingService && + (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) { + NS_tchar updateLockFilePath[MAXPATHLEN]; + if (sStagedUpdate) { + // When staging an update, the lock file is: + // <install_dir>\updated.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath); + } else if (sReplaceRequest) { + // When processing a replace request, the lock file is: + // <install_dir>\..\moz_update_in_progress.lock + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); + *slash = NS_T('\0'); + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s\\moz_update_in_progress.lock"), installDir); + } else { + // In the non-staging update case, the lock file is: + // <install_dir>\<app_name>.exe.update_in_progress.lock + NS_tsnprintf(updateLockFilePath, + sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), + NS_T("%s.update_in_progress.lock"), argv[callbackIndex]); + } + + // The update_in_progress.lock file should only exist during an update. In + // case it exists attempt to remove it and exit if that fails to prevent + // simultaneous updates occurring. + if (!_waccess(updateLockFilePath, F_OK) && + NS_tremove(updateLockFilePath) != 0) { + // Try to fall back to the old way of doing updates if a staged + // update fails. + if (sStagedUpdate || sReplaceRequest) { + // Note that this could fail, but if it does, there isn't too much we + // can do in order to recover anyways. + WriteStatusFile("pending"); + } + LOG(("Update already in progress! Exiting")); + return 1; + } + + updateLockFileHandle = CreateFileW(updateLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + NS_tsnprintf(elevatedLockFilePath, + sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]), + NS_T("%s/update_elevated.lock"), gPatchDirPath); + + // Even if a file has no sharing access, you can still get its attributes + bool startedFromUnelevatedUpdater = + GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES; + + // If we're running from the service, then we were started with the same + // token as the service so the permissions are already dropped. If we're + // running from an elevated updater that was started from an unelevated + // updater, then we drop the permissions here. We do not drop the + // permissions on the originally called updater because we use its token + // to start the callback application. + if (startedFromUnelevatedUpdater) { + // Disable every privilege we don't need. Processes started using + // CreateProcess will use the same token as this process. + UACHelper::DisablePrivileges(nullptr); + } + + if (updateLockFileHandle == INVALID_HANDLE_VALUE || + (useService && testOnlyFallbackKeyExists && noServiceFallback)) { + if (!_waccess(elevatedLockFilePath, F_OK) && + NS_tremove(elevatedLockFilePath) != 0) { + fprintf(stderr, "Unable to create elevated lock file! Exiting\n"); + return 1; + } + + HANDLE elevatedFileHandle; + elevatedFileHandle = CreateFileW(elevatedLockFilePath, + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (elevatedFileHandle == INVALID_HANDLE_VALUE) { + LOG(("Unable to create elevated lock file! Exiting")); + return 1; + } + + wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1); + if (!cmdLine) { + CloseHandle(elevatedFileHandle); + return 1; + } + + // Make sure the path to the updater to use for the update is on local. + // We do this check to make sure that file locking is available for + // race condition security checks. + if (useService) { + BOOL isLocal = FALSE; + useService = IsLocalFile(argv[0], isLocal) && isLocal; + } + + // If we have unprompted elevation we should NOT use the service + // for the update. Service updates happen with the SYSTEM account + // which has more privs than we need to update with. + // Windows 8 provides a user interface so users can configure this + // behavior and it can be configured in the registry in all Windows + // versions that support UAC. + if (useService) { + BOOL unpromptedElevation; + if (IsUnpromptedElevation(unpromptedElevation)) { + useService = !unpromptedElevation; + } + } + + // Make sure the service registry entries for the instsallation path + // are available. If not don't use the service. + if (useService) { + WCHAR maintenanceServiceKey[MAX_PATH + 1]; + if (CalculateRegistryPathFromFilePath(gInstallDirPath, + maintenanceServiceKey)) { + HKEY baseKey = nullptr; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + maintenanceServiceKey, 0, + KEY_READ | KEY_WOW64_64KEY, + &baseKey) == ERROR_SUCCESS) { + RegCloseKey(baseKey); + } else { + useService = testOnlyFallbackKeyExists; + if (!useService) { + lastFallbackError = FALLBACKKEY_NOKEY_ERROR; + } + } + } else { + useService = false; + lastFallbackError = FALLBACKKEY_REGPATH_ERROR; + } + } + + // Originally we used to write "pending" to update.status before + // launching the service command. This is no longer needed now + // since the service command is launched from updater.exe. If anything + // fails in between, we can fall back to using the normal update process + // on our own. + + // If we still want to use the service try to launch the service + // comamnd for the update. + if (useService) { + // If the update couldn't be started, then set useService to false so + // we do the update the old way. + DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv); + useService = (ret == ERROR_SUCCESS); + // If the command was launched then wait for the service to be done. + if (useService) { + bool showProgressUI = false; + // Never show the progress UI when staging updates. + if (!sStagedUpdate) { + // We need to call this separately instead of allowing ShowProgressUI + // to initialize the strings because the service will move the + // ini file out of the way when running updater. + showProgressUI = !InitProgressUIStrings(); + } + + // Wait for the service to stop for 5 seconds. If the service + // has still not stopped then show an indeterminate progress bar. + DWORD lastState = WaitForServiceStop(SVC_NAME, 5); + if (lastState != SERVICE_STOPPED) { + Thread t1; + if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 && + showProgressUI) { + ShowProgressUI(true, false); + } + t1.Join(); + } + + lastState = WaitForServiceStop(SVC_NAME, 1); + if (lastState != SERVICE_STOPPED) { + // If the service doesn't stop after 10 minutes there is + // something seriously wrong. + lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR; + useService = false; + } + } else { + lastFallbackError = FALLBACKKEY_LAUNCH_ERROR; + } + } + + // If the service can't be used when staging and update, make sure that + // the UAC prompt is not shown! In this case, just set the status to + // pending and the update will be applied during the next startup. + if (!useService && sStagedUpdate) { + if (updateLockFileHandle != INVALID_HANDLE_VALUE) { + CloseHandle(updateLockFileHandle); + } + WriteStatusPending(gPatchDirPath); + return 0; + } + + // If we started the service command, and it finished, check the + // update.status file to make sure it succeeded, and if it did + // we need to manually start the PostUpdate process from the + // current user's session of this unelevated updater.exe the + // current process is running as. + // Note that we don't need to do this if we're just staging the update, + // as the PostUpdate step runs when performing the replacing in that case. + if (useService && !sStagedUpdate) { + bool updateStatusSucceeded = false; + if (IsUpdateStatusSucceeded(updateStatusSucceeded) && + updateStatusSucceeded) { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, + nullptr)) { + fprintf(stderr, "The post update process which runs as the user" + " for service update could not be launched."); + } + } + } + + // If we didn't want to use the service at all, or if an update was + // already happening, or launching the service command failed, then + // launch the elevated updater.exe as we do without the service. + // We don't launch the elevated updater in the case that we did have + // write access all along because in that case the only reason we're + // using the service is because we are testing. + if (!useService && !noServiceFallback && + updateLockFileHandle == INVALID_HANDLE_VALUE) { + SHELLEXECUTEINFO sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFO); + sinfo.fMask = SEE_MASK_FLAG_NO_UI | + SEE_MASK_FLAG_DDEWAIT | + SEE_MASK_NOCLOSEPROCESS; + sinfo.hwnd = nullptr; + sinfo.lpFile = argv[0]; + sinfo.lpParameters = cmdLine; + sinfo.lpVerb = L"runas"; + sinfo.nShow = SW_SHOWNORMAL; + + bool result = ShellExecuteEx(&sinfo); + free(cmdLine); + + if (result) { + WaitForSingleObject(sinfo.hProcess, INFINITE); + CloseHandle(sinfo.hProcess); + } else { + WriteStatusFile(ELEVATION_CANCELED); + } + } + + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + + CloseHandle(elevatedFileHandle); + + if (!useService && !noServiceFallback && + INVALID_HANDLE_VALUE == updateLockFileHandle) { + // We didn't use the service and we did run the elevated updater.exe. + // The elevated updater.exe is responsible for writing out the + // update.status file. + return 0; + } else if(useService) { + // The service command was launched. The service is responsible for + // writing out the update.status file. + if (updateLockFileHandle != INVALID_HANDLE_VALUE) { + CloseHandle(updateLockFileHandle); + } + return 0; + } else { + // Otherwise the service command was not launched at all. + // We are only reaching this code path because we had write access + // all along to the directory and a fallback key existed, and we + // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists). + // We only currently use this env var from XPCShell tests. + CloseHandle(updateLockFileHandle); + WriteStatusFile(lastFallbackError); + return 0; + } + } + } +#endif + +#if defined(MOZ_WIDGET_GONK) + // In gonk, the master b2g process sets its umask to 0027 because + // there's no reason for it to ever create world-readable files. + // The updater binary, however, needs to do this, and it inherits + // the master process's cautious umask. So we drop down a bit here. + umask(0022); + + // Remount the /system partition as read-write for gonk. The destructor will + // remount /system as read-only. We add an extra level of scope here to avoid + // calling LogFinish() before the GonkAutoMounter destructor has a chance + // to be called + { + GonkAutoMounter mounter; + if (mounter.GetAccess() != MountAccess::ReadWrite) { + WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR); + return 1; + } +#endif + + if (sStagedUpdate) { + // When staging updates, blow away the old installation directory and create + // it from scratch. + ensure_remove_recursive(gWorkingDirPath); + } + if (!sReplaceRequest) { + // Change current directory to the directory where we need to apply the update. + if (NS_tchdir(gWorkingDirPath) != 0) { + // Try to create the destination directory if it doesn't exist + int rv = NS_tmkdir(gWorkingDirPath, 0755); + if (rv == OK && errno != EEXIST) { + // Try changing the current directory again + if (NS_tchdir(gWorkingDirPath) != 0) { + // OK, time to give up! + return 1; + } + } else { + // Failed to create the directory, bail out + return 1; + } + } + } + +#ifdef XP_WIN + // For replace requests, we don't need to do any real updates, so this is not + // necessary. + if (!sReplaceRequest) { + // Allocate enough space for the length of the path an optional additional + // trailing slash and null termination. + NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar)); + if (!destpath) + return 1; + + NS_tchar *c = destpath; + NS_tstrcpy(c, gWorkingDirPath); + c += NS_tstrlen(gWorkingDirPath); + if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') && + gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\')) { + NS_tstrcat(c, NS_T("/")); + c += NS_tstrlen(NS_T("/")); + } + *c = NS_T('\0'); + c++; + + gDestPath = destpath; + } + + NS_tchar applyDirLongPath[MAXPATHLEN]; + if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath, + sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) { + LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], argc - callbackIndex, + argv + callbackIndex, sUsingService); + } + return 1; + } + + HANDLE callbackFile = INVALID_HANDLE_VALUE; + if (argc > callbackIndex) { + // If the callback executable is specified it must exist for a successful + // update. It is important we null out the whole buffer here because later + // we make the assumption that the callback application is inside the + // apply-to dir. If we don't have a fully null'ed out buffer it can lead + // to stack corruption which causes crashes and other problems. + NS_tchar callbackLongPath[MAXPATHLEN]; + ZeroMemory(callbackLongPath, sizeof(callbackLongPath)); + NS_tchar *targetPath = argv[callbackIndex]; + NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') }; + size_t bufferLeft = MAXPATHLEN * 2; + if (sReplaceRequest) { + // In case of replace requests, we should look for the callback file in + // the destination directory. + size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex], + gInstallDirPath, + nullptr); + NS_tchar *p = buffer; + NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength); + p += commonPrefixLength; + bufferLeft -= commonPrefixLength; + NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft); + + size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength); + p += len; + bufferLeft -= len; + *p = NS_T('\\'); + ++p; + bufferLeft--; + *p = NS_T('\0'); + NS_tchar installDir[MAXPATHLEN]; + NS_tstrcpy(installDir, gInstallDirPath); + size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex], + installDir, + nullptr); + NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, + commonPrefixLength), bufferLeft); + targetPath = buffer; + } + if (!GetLongPathNameW(targetPath, callbackLongPath, + sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) { + LOG(("NS_main: unable to find callback file: " LOG_S, targetPath)); + LogFinish(); + WriteStatusFile(WRITE_ERROR_CALLBACK_PATH); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + if (argc > callbackIndex) { + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + } + return 1; + } + + // Doing this is only necessary when we're actually applying a patch. + if (!sReplaceRequest) { + int len = NS_tstrlen(applyDirLongPath); + NS_tchar *s = callbackLongPath; + NS_tchar *d = gCallbackRelPath; + // advance to the apply to directory and advance past the trailing backslash + // if present. + s += len; + if (*s == NS_T('\\')) + ++s; + + // Copy the string and replace backslashes with forward slashes along the + // way. + do { + if (*s == NS_T('\\')) + *d = NS_T('/'); + else + *d = *s; + ++s; + ++d; + } while (*s); + *d = NS_T('\0'); + ++d; + + // Make a copy of the callback executable so it can be read when patching. + NS_tsnprintf(gCallbackBackupPath, + sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]), + NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]); + NS_tremove(gCallbackBackupPath); + CopyFileW(argv[callbackIndex], gCallbackBackupPath, false); + + // Since the process may be signaled as exited by WaitForSingleObject before + // the release of the executable image try to lock the main executable file + // multiple times before giving up. If we end up giving up, we won't + // fail the update. + const int max_retries = 10; + int retries = 1; + DWORD lastWriteError = 0; + do { + // By opening a file handle wihout FILE_SHARE_READ to the callback + // executable, the OS will prevent launching the process while it is + // being updated. + callbackFile = CreateFileW(targetPath, + DELETE | GENERIC_WRITE, + // allow delete, rename, and write + FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr); + if (callbackFile != INVALID_HANDLE_VALUE) + break; + + lastWriteError = GetLastError(); + LOG(("NS_main: callback app file open attempt %d failed. " \ + "File: " LOG_S ". Last error: %d", retries, + targetPath, lastWriteError)); + + Sleep(100); + } while (++retries <= max_retries); + + // CreateFileW will fail if the callback executable is already in use. + if (callbackFile == INVALID_HANDLE_VALUE) { + // Only fail the update if the last error was not a sharing violation. + if (lastWriteError != ERROR_SHARING_VIOLATION) { + LOG(("NS_main: callback app file in use, failed to exclusively open " \ + "executable file: " LOG_S, argv[callbackIndex])); + LogFinish(); + if (lastWriteError == ERROR_ACCESS_DENIED) { + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); + } else { + WriteStatusFile(WRITE_ERROR_CALLBACK_APP); + } + + NS_tremove(gCallbackBackupPath); + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + return 1; + } + LOG(("NS_main: callback app file in use, continuing without " \ + "exclusive access for executable file: " LOG_S, + argv[callbackIndex])); + } + } + } + + // DELETE_DIR is not required when staging an update. + if (!sStagedUpdate && !sReplaceRequest) { + // The directory to move files that are in use to on Windows. This directory + // will be deleted after the update is finished or on OS reboot using + // MoveFileEx if it contains files that are in use. + if (NS_taccess(DELETE_DIR, F_OK)) { + NS_tmkdir(DELETE_DIR, 0755); + } + } +#endif /* XP_WIN */ + + // Run update process on a background thread. ShowProgressUI may return + // before QuitProgressUI has been called, so wait for UpdateThreadFunc to + // terminate. Avoid showing the progress UI when staging an update. + Thread t; + if (t.Run(UpdateThreadFunc, nullptr) == 0) { + if (!sStagedUpdate && !sReplaceRequest) { + ShowProgressUI(); + } + } + t.Join(); + +#ifdef XP_WIN + if (argc > callbackIndex && !sReplaceRequest) { + if (callbackFile != INVALID_HANDLE_VALUE) { + CloseHandle(callbackFile); + } + // Remove the copy of the callback executable. + NS_tremove(gCallbackBackupPath); + } + + if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) { + LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", + DELETE_DIR, errno)); + // The directory probably couldn't be removed due to it containing files + // that are in use and will be removed on OS reboot. The call to remove the + // directory on OS reboot is done after the calls to remove the files so the + // files are removed first on OS reboot since the directory must be empty + // for the directory removal to be successful. The MoveFileEx call to remove + // the directory on OS reboot will fail if the process doesn't have write + // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the + // installer / uninstaller will delete the directory along with its contents + // after an update is applied, on reinstall, and on uninstall. + if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { + LOG(("NS_main: directory will be removed on OS reboot: " LOG_S, + DELETE_DIR)); + } else { + LOG(("NS_main: failed to schedule OS reboot removal of " \ + "directory: " LOG_S, DELETE_DIR)); + } + } +#endif /* XP_WIN */ + +#if defined(MOZ_WIDGET_GONK) + } // end the extra level of scope for the GonkAutoMounter +#endif + +#ifdef XP_MACOSX + // When the update is successful remove the precomplete file in the root of + // the application bundle and move the distribution directory from + // Contents/MacOS to Contents/Resources and if both exist delete the + // directory under Contents/MacOS (see Bug 1068439). + if (gSucceeded && !sStagedUpdate) { + NS_tchar oldPrecomplete[MAXPATHLEN]; + NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]), + NS_T("%s/precomplete"), gInstallDirPath); + NS_tremove(oldPrecomplete); + + NS_tchar oldDistDir[MAXPATHLEN]; + NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]), + NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath); + int rv = NS_taccess(oldDistDir, F_OK); + if (!rv) { + NS_tchar newDistDir[MAXPATHLEN]; + NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]), + NS_T("%s/Contents/Resources/distribution"), gInstallDirPath); + rv = NS_taccess(newDistDir, F_OK); + if (!rv) { + LOG(("New distribution directory already exists... removing old " \ + "distribution directory: " LOG_S, oldDistDir)); + rv = ensure_remove_recursive(oldDistDir); + if (rv) { + LOG(("Removing old distribution directory failed - err: %d", rv)); + } + } else { + LOG(("Moving old distribution directory to new location. src: " LOG_S \ + ", dst:" LOG_S, oldDistDir, newDistDir)); + rv = rename_file(oldDistDir, newDistDir, true); + if (rv) { + LOG(("Moving old distribution directory to new location failed - " \ + "err: %d", rv)); + } + } + } + } +#endif /* XP_MACOSX */ + + LogFinish(); + + if (argc > callbackIndex) { +#if defined(XP_WIN) + if (gSucceeded) { + // The service update will only be executed if it is already installed. + // For first time installs of the service, the install will happen from + // the PostUpdate process. We do the service update process here + // because it's possible we are updating with updater.exe without the + // service if the service failed to apply the update. We want to update + // the service to a newer version in that case. If we are not running + // through the service, then MOZ_USING_SERVICE will not exist. + if (!sUsingService) { + if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath, false, nullptr)) { + LOG(("NS_main: The post update process could not be launched.")); + } + + StartServiceUpdate(gInstallDirPath); + } + } + EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); +#endif /* XP_WIN */ +#ifdef XP_MACOSX + if (gSucceeded) { + LaunchMacPostProcess(gInstallDirPath); + } +#endif /* XP_MACOSX */ + LaunchCallbackApp(argv[5], + argc - callbackIndex, + argv + callbackIndex, + sUsingService); + } + + return gSucceeded ? 0 : 1; +} + +class ActionList +{ +public: + ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { } + ~ActionList(); + + void Append(Action* action); + int Prepare(); + int Execute(); + void Finish(int status); + +private: + Action *mFirst; + Action *mLast; + int mCount; +}; + +ActionList::~ActionList() +{ + Action* a = mFirst; + while (a) { + Action *b = a; + a = a->mNext; + delete b; + } +} + +void +ActionList::Append(Action *action) +{ + if (mLast) + mLast->mNext = action; + else + mFirst = action; + + mLast = action; + mCount++; +} + +int +ActionList::Prepare() +{ + // If the action list is empty then we should fail in order to signal that + // something has gone wrong. Otherwise we report success when nothing is + // actually done. See bug 327140. + if (mCount == 0) { + LOG(("empty action list")); + return MAR_ERROR_EMPTY_ACTION_LIST; + } + + Action *a = mFirst; + int i = 0; + while (a) { + int rv = a->Prepare(); + if (rv) + return rv; + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +int +ActionList::Execute() +{ + int currentProgress = 0, maxProgress = 0; + Action *a = mFirst; + while (a) { + maxProgress += a->mProgressCost; + a = a->mNext; + } + + a = mFirst; + while (a) { + int rv = a->Execute(); + if (rv) { + LOG(("### execution failed")); + return rv; + } + + currentProgress += a->mProgressCost; + float percent = float(currentProgress) / float(maxProgress); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE * percent); + + a = a->mNext; + } + + return OK; +} + +void +ActionList::Finish(int status) +{ + Action *a = mFirst; + int i = 0; + while (a) { + a->Finish(status); + + float percent = float(++i) / float(mCount); + UpdateProgressUI(PROGRESS_PREPARE_SIZE + + PROGRESS_EXECUTE_SIZE + + PROGRESS_FINISH_SIZE * percent); + + a = a->mNext; + } + + if (status == OK) + gSucceeded = true; +} + + +#ifdef XP_WIN +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + WIN32_FIND_DATAW finddata; + HANDLE hFindFile; + NS_tchar searchspec[MAXPATHLEN]; + NS_tchar foundpath[MAXPATHLEN]; + + NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]), + NS_T("%s*"), dirpath); + const NS_tchar *pszSpec = get_full_path(searchspec); + + hFindFile = FindFirstFileW(pszSpec, &finddata); + if (hFindFile != INVALID_HANDLE_VALUE) { + do { + // Don't process the current or parent directory. + if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 || + NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), dirpath, finddata.cFileName); + if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + return rv; + } + } else { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(foundpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + return rv; + } + + list->Append(action); + } + } while (FindNextFileW(hFindFile, &finddata) != 0); + + FindClose(hFindFile); + { + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(dirpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + else + list->Append(action); + } + } + + return rv; +} + +#elif defined(SOLARIS) +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + NS_tchar searchpath[MAXPATHLEN]; + NS_tchar foundpath[MAXPATHLEN]; + struct { + dirent dent_buffer; + char chars[MAXNAMLEN]; + } ent_buf; + struct dirent* ent; + + + NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"), + dirpath); + // Remove the trailing slash so the paths don't contain double slashes. The + // existence of the slash has already been checked in DoUpdate. + searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0'); + + DIR* dir = opendir(searchpath); + if (!dir) { + LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath, + errno)); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + + while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) + continue; + + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s%s"), dirpath, ent->d_name); + struct stat64 st_buf; + int test = stat64(foundpath, &st_buf); + if (test) { + closedir(dir); + return UNEXPECTED_FILE_OPERATION_ERROR; + } + if (S_ISDIR(st_buf.st_mode)) { + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), foundpath); + // Recurse into the directory. + rv = add_dir_entries(foundpath, list); + if (rv) { + LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); + closedir(dir); + return rv; + } + } else { + // Add the file to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(foundpath); + if (!quotedpath) { + closedir(dir); + return PARSE_ERROR; + } + + Action *action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", + quotedpath, rv)); + closedir(dir); + return rv; + } + + list->Append(action); + } + } + closedir(dir); + + // Add the directory to be removed to the ActionList. + NS_tchar *quotedpath = get_quoted_path(dirpath); + if (!quotedpath) + return PARSE_ERROR; + + Action *action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (rv) { + LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", + quotedpath, rv)); + } + else { + list->Append(action); + } + + return rv; +} + +#else + +int add_dir_entries(const NS_tchar *dirpath, ActionList *list) +{ + int rv = OK; + FTS *ftsdir; + FTSENT *ftsdirEntry; + NS_tchar searchpath[MAXPATHLEN]; + + NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"), + dirpath); + // Remove the trailing slash so the paths don't contain double slashes. The + // existence of the slash has already been checked in DoUpdate. + searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0'); + char* const pathargv[] = {searchpath, nullptr}; + + // FTS_NOCHDIR is used so relative paths from the destination directory are + // returned. + if (!(ftsdir = fts_open(pathargv, + FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR, + nullptr))) + return UNEXPECTED_FILE_OPERATION_ERROR; + + while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) { + NS_tchar foundpath[MAXPATHLEN]; + NS_tchar *quotedpath; + Action *action = nullptr; + + switch (ftsdirEntry->fts_info) { + // Filesystem objects that shouldn't be in the application's directories + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + LOG(("add_dir_entries: found a non-standard file: " LOG_S, + ftsdirEntry->fts_path)); + // Fall through and try to remove as a file + + // Files + case FTS_F: + case FTS_NSOK: + // Add the file to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(foundpath); + if (!quotedpath) { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + action = new RemoveFile(); + rv = action->Parse(quotedpath); + if (!rv) + list->Append(action); + break; + + // Directories + case FTS_DP: + rv = OK; + // Add the directory to be removed to the ActionList. + NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), + NS_T("%s/"), ftsdirEntry->fts_accpath); + quotedpath = get_quoted_path(foundpath); + if (!quotedpath) { + rv = UPDATER_QUOTED_PATH_MEM_ERROR; + break; + } + + action = new RemoveDir(); + rv = action->Parse(quotedpath); + if (!rv) + list->Append(action); + break; + + // Errors + case FTS_DNR: + case FTS_NS: + // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that + // we're racing with ourselves. Though strange, the entry will be + // removed anyway. + if (ENOENT == ftsdirEntry->fts_errno) { + rv = OK; + break; + } + // Fall through + + case FTS_ERR: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d", + ftsdirEntry->fts_path, ftsdirEntry->fts_errno)); + break; + + case FTS_DC: + rv = UNEXPECTED_FILE_OPERATION_ERROR; + LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S, + ftsdirEntry->fts_path)); + break; + + default: + // FTS_D is ignored and FTS_DP is used instead (post-order). + rv = OK; + break; + } + + if (rv != OK) + break; + } + + fts_close(ftsdir); + + return rv; +} +#endif + +static NS_tchar* +GetManifestContents(const NS_tchar *manifest) +{ + AutoFile mfile(NS_tfopen(manifest, NS_T("rb"))); + if (mfile == nullptr) { + LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest)); + return nullptr; + } + + struct stat ms; + int rv = fstat(fileno((FILE *)mfile), &ms); + if (rv) { + LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest)); + return nullptr; + } + + char *mbuf = (char *) malloc(ms.st_size + 1); + if (!mbuf) + return nullptr; + + size_t r = ms.st_size; + char *rb = mbuf; + while (r) { + const size_t count = mmin(SSIZE_MAX, r); + size_t c = fread(rb, 1, count, mfile); + if (c != count) { + LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest)); + return nullptr; + } + + r -= c; + rb += c; + } + mbuf[ms.st_size] = '\0'; + rb = mbuf; + +#ifndef XP_WIN + return rb; +#else + NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar)); + if (!wrb) + return nullptr; + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb, + ms.st_size + 1)) { + LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError())); + free(mbuf); + free(wrb); + return nullptr; + } + free(mbuf); + + return wrb; +#endif +} + +int AddPreCompleteActions(ActionList *list) +{ + if (sIsOSUpdate) { + return OK; + } + +#ifdef XP_MACOSX + NS_tchar *rb = GetManifestContents(NS_T("Contents/Resources/precomplete")); +#else + NS_tchar *rb = GetManifestContents(NS_T("precomplete")); +#endif + if (rb == nullptr) { + LOG(("AddPreCompleteActions: error getting contents of precomplete " \ + "manifest")); + // Applications aren't required to have a precomplete manifest. The mar + // generation scripts enforce the presence of a precomplete manifest. + return OK; + } + + int rv; + NS_tchar *line; + while((line = mstrtok(kNL, &rb)) != 0) { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) { + LOG(("AddPreCompleteActions: token not found in manifest")); + return PARSE_ERROR; + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported + continue; + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty + action = new RemoveDir(); + } + else { + LOG(("AddPreCompleteActions: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list->Append(action); + } + + return OK; +} + +int DoUpdate() +{ + NS_tchar manifest[MAXPATHLEN]; + NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]), + NS_T("%s/updating/update.manifest"), gWorkingDirPath); + ensure_parent_dir(manifest); + + // extract the manifest + int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest); + if (rv) { + rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest); + if (rv) { + LOG(("DoUpdate: error extracting manifest file")); + return rv; + } + } + + NS_tchar *rb = GetManifestContents(manifest); + NS_tremove(manifest); + if (rb == nullptr) { + LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest)); + return READ_ERROR; + } + + + ActionList list; + NS_tchar *line; + bool isFirstAction = true; + + while((line = mstrtok(kNL, &rb)) != 0) { + // skip comments + if (*line == NS_T('#')) + continue; + + NS_tchar *token = mstrtok(kWhitespace, &line); + if (!token) { + LOG(("DoUpdate: token not found in manifest")); + return PARSE_ERROR; + } + + if (isFirstAction) { + isFirstAction = false; + // The update manifest isn't required to have a type declaration. The mar + // generation scripts enforce the presence of the type declaration. + if (NS_tstrcmp(token, NS_T("type")) == 0) { + const NS_tchar *type = mstrtok(kQuote, &line); + LOG(("UPDATE TYPE " LOG_S, type)); + if (NS_tstrcmp(type, NS_T("complete")) == 0) { + rv = AddPreCompleteActions(&list); + if (rv) + return rv; + } + continue; + } + } + + Action *action = nullptr; + if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file + action = new RemoveFile(); + } + else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty + action = new RemoveDir(); + } + else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive + const NS_tchar *reldirpath = mstrtok(kQuote, &line); + if (!reldirpath) + return PARSE_ERROR; + + if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) + return PARSE_ERROR; + + rv = add_dir_entries(reldirpath, &list); + if (rv) + return rv; + + continue; + } + else if (NS_tstrcmp(token, NS_T("add")) == 0) { + action = new AddFile(); + } + else if (NS_tstrcmp(token, NS_T("patch")) == 0) { + action = new PatchFile(); + } + else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists + action = new AddIfFile(); + } + else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists + action = new AddIfNotFile(); + } + else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists + action = new PatchIfFile(); + } + else { + LOG(("DoUpdate: unknown token: " LOG_S, token)); + return PARSE_ERROR; + } + + if (!action) + return BAD_ACTION_ERROR; + + rv = action->Parse(line); + if (rv) + return rv; + + list.Append(action); + } + + rv = list.Prepare(); + if (rv) + return rv; + + rv = list.Execute(); + + list.Finish(rv); + return rv; +} diff --git a/onlineupdate/source/update/updater/updater.exe.comctl32.manifest b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest new file mode 100644 index 000000000000..9a6cdb565fe1 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.comctl32.manifest @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.exe.manifest b/onlineupdate/source/update/updater/updater.exe.manifest new file mode 100644 index 000000000000..cd229c954109 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.exe.manifest @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="Updater" + type="win32" +/> +<description>Updater</description> +<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> +</ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/onlineupdate/source/update/updater/updater.ico b/onlineupdate/source/update/updater/updater.ico Binary files differnew file mode 100644 index 000000000000..48457029d6aa --- /dev/null +++ b/onlineupdate/source/update/updater/updater.ico diff --git a/onlineupdate/source/update/updater/updater.png b/onlineupdate/source/update/updater/updater.png Binary files differnew file mode 100644 index 000000000000..7b5e78907785 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.png diff --git a/onlineupdate/source/update/updater/updater.rc b/onlineupdate/source/update/updater/updater.rc new file mode 100644 index 000000000000..bfc80142cea4 --- /dev/null +++ b/onlineupdate/source/update/updater/updater.rc @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Microsoft Visual C++ generated resource script. +// +#ifdef TEST_UPDATER +#include "../resource.h" +#define MANIFEST_PATH "../updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "../updater.exe.comctl32.manifest" +#define ICON_PATH "../updater.ico" +#else +#include "resource.h" +#define MANIFEST_PATH "updater.exe.manifest" +#define COMCTL32_MANIFEST_PATH "updater.exe.comctl32.manifest" +#define ICON_PATH "updater.ico" +#endif + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winresrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST MANIFEST_PATH +IDR_COMCTL32_MANIFEST RT_MANIFEST COMCTL32_MANIFEST_PATH + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +IDI_DIALOG ICON ICON_PATH + + +///////////////////////////////////////////////////////////////////////////// +// +// Embedded an identifier to uniquely identiy this as a Mozilla updater. +// + +STRINGTABLE +{ + IDS_UPDATER_IDENTITY, "moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49" +} + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG DIALOGEX 0, 0, 253, 41 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,24,239,10 + LTEXT "",IDC_INFO,7,8,239,13,SS_NOPREFIX +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 246 + TOPMARGIN, 7 + BOTTOMMARGIN, 39 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winresrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/onlineupdate/source/update/updater/win_dirent.cpp b/onlineupdate/source/update/updater/win_dirent.cpp new file mode 100644 index 000000000000..b0807ba5e193 --- /dev/null +++ b/onlineupdate/source/update/updater/win_dirent.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "win_dirent.h" +#include <errno.h> +#include <string.h> + +// This file implements the minimum set of dirent APIs used by updater.cpp on +// Windows. If updater.cpp is modified to use more of this API, we need to +// implement those parts here too. + +static dirent gDirEnt; + +DIR::DIR(const WCHAR* path) + : findHandle(INVALID_HANDLE_VALUE) +{ + memset(name, 0, sizeof(name)); + wcsncpy(name, path, sizeof(name)/sizeof(name[0])); + wcsncat(name, L"\\*", sizeof(name)/sizeof(name[0]) - wcslen(name) - 1); +} + +DIR::~DIR() +{ + if (findHandle != INVALID_HANDLE_VALUE) { + FindClose(findHandle); + } +} + +dirent::dirent() +{ + d_name[0] = L'\0'; +} + +DIR* +opendir(const WCHAR* path) +{ + return new DIR(path); +} + +int +closedir(DIR* dir) +{ + delete dir; + return 0; +} + +dirent* readdir(DIR* dir) +{ + WIN32_FIND_DATAW data; + if (dir->findHandle != INVALID_HANDLE_VALUE) { + BOOL result = FindNextFileW(dir->findHandle, &data); + if (!result) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + errno = ENOENT; + } + return 0; + } + } else { + // Reading the first directory entry + dir->findHandle = FindFirstFileW(dir->name, &data); + if (dir->findHandle == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) { + errno = ENOENT; + } else { + errno = EBADF; + } + return 0; + } + } + memset(gDirEnt.d_name, 0, sizeof(gDirEnt.d_name)); + wcsncpy(gDirEnt.d_name, data.cFileName, + sizeof(gDirEnt.d_name)/sizeof(gDirEnt.d_name[0])); + return &gDirEnt; +} + diff --git a/onlineupdate/source/update/updater/xpcshellCertificate.der b/onlineupdate/source/update/updater/xpcshellCertificate.der Binary files differnew file mode 100644 index 000000000000..185b2dff4a69 --- /dev/null +++ b/onlineupdate/source/update/updater/xpcshellCertificate.der |