/* 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/. */ var EXPORTED_SYMBOLS = ["UpdateUtils"]; const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm"); const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm"); const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm"); XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]); /* globals fetch */ ChromeUtils.defineModuleGetter(this, "WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"); // The file that stores Application Update configuration settings. The file is // located in the update directory which makes it a common setting across all // application profiles and allows the Background Update Agent to read it. const FILE_UPDATE_CONFIG_JSON = "update-config.json"; const FILE_UPDATE_LOCALE = "update.locale"; const PREF_APP_DISTRIBUTION = "distribution.id"; const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; // Do not use the PREF_APP_UPDATE_AUTO preference directly! // Call getAppUpdateAutoEnabled or setAppUpdateAutoEnabled instead. const PREF_APP_UPDATE_AUTO = "app.update.auto"; const PREF_APP_UPDATE_AUTO_MIGRATED = "app.update.auto.migrated"; // The setting name in the FILE_UPDATE_CONFIG_JSON file for whether the // Application Update Service automatically downloads and installs updates. const CONFIG_APP_UPDATE_AUTO = "app.update.auto"; // The default value for the CONFIG_APP_UPDATE_AUTO setting and the // PREF_APP_UPDATE_AUTO preference. const DEFAULT_APP_UPDATE_AUTO = true; var UpdateUtils = { _locale: undefined, /** * Read the update channel from defaults only. We do this to ensure that * the channel is tightly coupled with the application and does not apply * to other instances of the application that may use the same profile. * * @param [optional] aIncludePartners * Whether or not to include the partner bits. Default: true. */ getUpdateChannel(aIncludePartners = true) { let defaults = Services.prefs.getDefaultBranch(null); let channel = defaults.getCharPref("app.update.channel", AppConstants.MOZ_UPDATE_CHANNEL); if (aIncludePartners) { try { let partners = Services.prefs.getChildList("app.partner.").sort(); if (partners.length) { channel += "-cck"; partners.forEach(function(prefName) { channel += "-" + Services.prefs.getCharPref(prefName); }); } } catch (e) { Cu.reportError(e); } } return channel; }, get UpdateChannel() { return this.getUpdateChannel(); }, /** * Formats a URL by replacing %...% values with OS, build and locale specific * values. * * @param url * The URL to format. * @return The formatted URL. */ async formatUpdateURL(url) { const locale = await this.getLocale(); return url.replace(/%(\w+)%/g, (match, name) => { switch (name) { case "PRODUCT": return Services.appinfo.name; case "VERSION": return Services.appinfo.version; case "BUILD_ID": return Services.appinfo.appBuildID; case "BUILD_TARGET": return Services.appinfo.OS + "_" + this.ABI; case "OS_VERSION": return this.OSVersion; case "LOCALE": return locale; case "CHANNEL": return this.UpdateChannel; case "PLATFORM_VERSION": return Services.appinfo.platformVersion; case "SYSTEM_CAPABILITIES": return getSystemCapabilities(); case "DISTRIBUTION": return getDistributionPrefValue(PREF_APP_DISTRIBUTION); case "DISTRIBUTION_VERSION": return getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION); } return match; }).replace(/\+/g, "%2B"); }, /** * Gets the locale from the update.locale file for replacing %LOCALE% in the * update url. The update.locale file can be located in the application * directory or the GRE directory with preference given to it being located in * the application directory. */ async getLocale() { if (this._locale !== undefined) { return this._locale; } for (let res of ["app", "gre"]) { const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE; let data; try { data = await fetch(url); } catch (e) { continue; } const locale = await data.text(); if (locale) { return this._locale = locale.trim(); } } Cu.reportError(FILE_UPDATE_LOCALE + " file doesn't exist in either the " + "application or GRE directories"); return this._locale = null; }, /** * Determines whether or not the Application Update Service automatically * downloads and installs updates. This corresponds to whether or not the user * has selected "Automatically install updates" in about:preferences. * * On Windows, this setting is shared across all profiles for the installation * and is read asynchrnously from the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * * @return A Promise that resolves with a boolean. */ getAppUpdateAutoEnabled() { if (AppConstants.platform != "win") { // On platforms other than Windows the setting is stored in a preference. let prefValue = Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO, DEFAULT_APP_UPDATE_AUTO); return Promise.resolve(prefValue); } // Justification for the empty catch statement below: // All promises returned by (get|set)AutoUpdateIsEnabled are part of a // single promise chain in order to serialize disk operations. We don't want // the entire promise chain to reject when one operation fails. // // There is only one situation when a promise in this chain should ever // reject, which is when writing fails and the error is logged and // re-thrown. All other possible exceptions are wrapped in try blocks, which // also log any exception that may occur. let readPromise = updateAutoIOPromise.catch(() => {}).then(async () => { try { let configValue = await readUpdateAutoConfig(); // If we read a value out of this file, don't later perform migration. // If the file is deleted, we don't want some stale pref getting // written to it just because a different profile performed migration. Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true); return configValue; } catch (e) { Cu.reportError("UpdateUtils.getAppUpdateAutoEnabled - Unable to read " + "app update configuration file. Exception: " + e); let valueMigrated = Services.prefs.getBoolPref( PREF_APP_UPDATE_AUTO_MIGRATED, false); if (!valueMigrated) { Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO_MIGRATED, true); let prefValue = Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO, DEFAULT_APP_UPDATE_AUTO); try { let writtenValue = await writeUpdateAutoConfig(prefValue); Services.prefs.clearUserPref(PREF_APP_UPDATE_AUTO); return writtenValue; } catch (e) { Cu.reportError("UpdateUtils.getAppUpdateAutoEnabled - Migration " + "failed. Exception: " + e); } } } // Fallthrough for if the value could not be read or migrated. return DEFAULT_APP_UPDATE_AUTO; }).then(maybeUpdateAutoConfigChanged.bind(this)); updateAutoIOPromise = readPromise; return readPromise; }, /** * Toggles whether the Update Service automatically downloads and installs * updates. This effectively selects between the "Automatically install * updates" and "Check for updates but let you choose to install them" options * in about:preferences. * * On Windows, this setting is shared across all profiles for the installation * and is written asynchrnously to the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * * @param enabled If set to true, automatic download and installation of * updates will be enabled. If set to false, this will be * disabled. * @return A Promise that, once the setting has been saved, resolves with the * boolean value that was saved. If the setting could not be * successfully saved, the Promise will reject. * On Windows, where this setting is stored in a file, this Promise * may reject with an I/O error. * On other operating systems, this promise should not reject as * this operation simply sets a pref. */ setAppUpdateAutoEnabled(enabledValue) { if (AppConstants.platform != "win") { // Only in Windows do we store the update config in the update directory let prefValue = !!enabledValue; Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, prefValue); maybeUpdateAutoConfigChanged(prefValue); return Promise.resolve(prefValue); } // Justification for the empty catch statement below: // All promises returned by (get|set)AutoUpdateIsEnabled are part of a // single promise chain in order to serialize disk operations. We don't want // the entire promise chain to reject when one operation fails. // // There is only one situation when a promise in this chain should ever // reject, which is when writing fails and the error is logged and // re-thrown. All other possible exceptions are wrapped in try blocks, which // also log any exception that may occur. let writePromise = updateAutoIOPromise.catch(() => {}).then(async () => { try { return await writeUpdateAutoConfig(enabledValue); } catch (e) { Cu.reportError("UpdateUtils.setAppUpdateAutoEnabled - App update " + "configuration file write failed. Exception: " + e); // Rethrow the error so the caller knows that writing the value in the // app update config file failed. throw e; } }).then(maybeUpdateAutoConfigChanged.bind(this)); updateAutoIOPromise = writePromise; return writePromise; }, }; // Used for serializing reads and writes of the app update json config file so // the writes don't happen out of order and the last write is the one that // the sets the value. var updateAutoIOPromise = Promise.resolve(); var updateAutoSettingCachedVal = null; async function readUpdateAutoConfig() { let configFile = FileUtils.getDir("UpdRootD", [], true); configFile.append(FILE_UPDATE_CONFIG_JSON); let binaryData = await OS.File.read(configFile.path); let jsonData = new TextDecoder().decode(binaryData); let configData = JSON.parse(jsonData); return !!configData[CONFIG_APP_UPDATE_AUTO]; } async function writeUpdateAutoConfig(enabledValue) { let enabledBoolValue = !!enabledValue; let configFile = FileUtils.getDir("UpdRootD", [], true); configFile.append(FILE_UPDATE_CONFIG_JSON); let configObject = {[CONFIG_APP_UPDATE_AUTO]: enabledBoolValue}; await OS.File.writeAtomic(configFile.path, JSON.stringify(configObject)); return enabledBoolValue; } // Notifies observers if the value of app.update.auto has changed and returns // the value for app.update.auto. function maybeUpdateAutoConfigChanged(newValue) { // Don't notify on the first read when updateAutoSettingCachedVal is null. if (updateAutoSettingCachedVal !== null && newValue != updateAutoSettingCachedVal) { updateAutoSettingCachedVal = newValue; Services.obs.notifyObservers(null, "auto-update-config-change", newValue.toString()); } return newValue; } /* Get the distribution pref values, from defaults only */ function getDistributionPrefValue(aPrefName) { return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default"); } function getSystemCapabilities() { return "ISET:" + gInstructionSet + ",MEM:" + getMemoryMB(); } /** * Gets the RAM size in megabytes. This will round the value because sysinfo * doesn't always provide RAM in multiples of 1024. */ function getMemoryMB() { let memoryMB = "unknown"; try { memoryMB = Services.sysinfo.getProperty("memsize"); if (memoryMB) { memoryMB = Math.round(memoryMB / 1024 / 1024); } } catch (e) { Cu.reportError("Error getting system info memsize property. " + "Exception: " + e); } return memoryMB; } /** * Gets the supported CPU instruction set. */ XPCOMUtils.defineLazyGetter(this, "gInstructionSet", function aus_gIS() { const CPU_EXTENSIONS = ["hasSSE4_2", "hasSSE4_1", "hasSSE4A", "hasSSSE3", "hasSSE3", "hasSSE2", "hasSSE", "hasMMX", "hasNEON", "hasARMv7", "hasARMv6"]; for (let ext of CPU_EXTENSIONS) { if (Services.sysinfo.getProperty(ext)) { return ext.substring(3); } } return "unknown"; }); /* Windows only getter that returns the processor architecture. */ XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() { // Get processor architecture let arch = "unknown"; const WORD = ctypes.uint16_t; const DWORD = ctypes.uint32_t; // This structure is described at: // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx const SYSTEM_INFO = new ctypes.StructType("SYSTEM_INFO", [ {wProcessorArchitecture: WORD}, {wReserved: WORD}, {dwPageSize: DWORD}, {lpMinimumApplicationAddress: ctypes.voidptr_t}, {lpMaximumApplicationAddress: ctypes.voidptr_t}, {dwActiveProcessorMask: DWORD.ptr}, {dwNumberOfProcessors: DWORD}, {dwProcessorType: DWORD}, {dwAllocationGranularity: DWORD}, {wProcessorLevel: WORD}, {wProcessorRevision: WORD}, ]); let kernel32 = false; try { kernel32 = ctypes.open("Kernel32"); } catch (e) { Cu.reportError("Unable to open kernel32! Exception: " + e); } if (kernel32) { try { let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo", ctypes.winapi_abi, ctypes.void_t, SYSTEM_INFO.ptr); let winSystemInfo = SYSTEM_INFO(); // Default to unknown winSystemInfo.wProcessorArchitecture = 0xffff; GetNativeSystemInfo(winSystemInfo.address()); switch (winSystemInfo.wProcessorArchitecture) { case 12: arch = "aarch64"; break; case 9: arch = "x64"; break; case 6: arch = "IA64"; break; case 0: arch = "x86"; break; } } catch (e) { Cu.reportError("Error getting processor architecture. " + "Exception: " + e); } finally { kernel32.close(); } } return arch; }); XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() { let abi = null; try { abi = Services.appinfo.XPCOMABI; } catch (e) { Cu.reportError("XPCOM ABI unknown"); } if (AppConstants.platform == "win") { // Windows build should report the CPU architecture that it's running on. abi += "-" + gWinCPUArch; } if (AppConstants.ASAN) { // Allow ASan builds to receive their own updates abi += "-asan"; } return abi; }); XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function() { let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (e) { Cu.reportError("OS Version unknown."); } if (osVersion) { if (AppConstants.platform == "win") { const BYTE = ctypes.uint8_t; const WORD = ctypes.uint16_t; const DWORD = ctypes.uint32_t; const WCHAR = ctypes.char16_t; const BOOL = ctypes.int; // This structure is described at: // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx const SZCSDVERSIONLENGTH = 128; const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW", [ {dwOSVersionInfoSize: DWORD}, {dwMajorVersion: DWORD}, {dwMinorVersion: DWORD}, {dwBuildNumber: DWORD}, {dwPlatformId: DWORD}, {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)}, {wServicePackMajor: WORD}, {wServicePackMinor: WORD}, {wSuiteMask: WORD}, {wProductType: BYTE}, {wReserved: BYTE}, ]); let kernel32 = false; try { kernel32 = ctypes.open("Kernel32"); } catch (e) { Cu.reportError("Unable to open kernel32! " + e); osVersion += ".unknown (unknown)"; } if (kernel32) { try { // Get Service pack info try { let GetVersionEx = kernel32.declare("GetVersionExW", ctypes.winapi_abi, BOOL, OSVERSIONINFOEXW.ptr); let winVer = OSVERSIONINFOEXW(); winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size; if (0 !== GetVersionEx(winVer.address())) { osVersion += "." + winVer.wServicePackMajor + "." + winVer.wServicePackMinor + "." + winVer.dwBuildNumber; } else { Cu.reportError("Unknown failure in GetVersionEX (returned 0)"); osVersion += ".unknown"; } } catch (e) { Cu.reportError("Error getting service pack information. Exception: " + e); osVersion += ".unknown"; } if (Services.vc.compare(Services.sysinfo.getProperty("version"), "10") >= 0) { const WINDOWS_UBR_KEY_PATH = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; let ubr = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, WINDOWS_UBR_KEY_PATH, "UBR", Ci.nsIWindowsRegKey.WOW64_64); osVersion += (ubr !== undefined) ? "." + ubr : ".unknown"; } } finally { kernel32.close(); } // Add processor architecture osVersion += " (" + gWinCPUArch + ")"; } } try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; });