# Application Update Concepts This page lays out many of the fundamental concepts underlying the design of Application Update. ## Staging Update installation needs to happen when Firefox is not running. While running, Firefox is still using those files and reacts poorly to having them swapped out from underneath it. See [Bug 1480452, Comment 8](https://bugzilla.mozilla.org/show_bug.cgi?id=1480452#c8) for more details on this. Applying updates can take some time. And since this happens at Firefox startup, the user is likely watching and waiting. To mitigate this, we default to staging updates. Staging involves doing most of the work of installing while Firefox is still running. Basically, it copies the installation into a different directory and updates the copy. Then, when Firefox is restarted, the updater does a "Replace Request" and swaps the installation with the updated copy and then deletes the original installation. Staging can be disabled by setting `app.update.staging.enabled` to `false`. ## Update Directory The update directory is where we put all update related files as well as some of the update related preferences. On Windows and Linux, the update directory is installation-specific. On macOS, the update directory is specific to both the user and installation. The path to the update directory can be found by navigating Firefox to `about:support` and finding the entry called "Update folder". Alternately, it can be looked up in the browser console using `Services.dirsvc.get("UpdRootD", Ci.nsIFile).path`. ### Install and Uninstall Cleanup When Firefox is uninstalled, we do our best to remove the files in the update directory in order to properly clean up after our installation. When someone performs a paveover install (installing Firefox without uninstalling it first), we don't delete the entire update directory, as this could cause settings to be lost. But we do delete any in-progress updates. This is to prevent situations such as a user disabling automatic update, downgrading, and then Firefox immediately installing an already-downloaded update. ## Elevation Firefox is often installed into directories with permissions that prevent regular, unprivileged processes from changing them. This means that Firefox may require elevation in order to update. We have several operating system-specific ways of dealing with this. ### Windows When updating on Windows, we have two options for elevation, the Mozilla Maintenance Service or a User Account Control (UAC) prompt. More details on the Mozilla Maintenance Service can be found [here](Implementation.md#mozilla-maintenance-service). If the Maintenance Service is not available and elevation is needed to update, Firefox must show a UAC prompt every time it updates. ### macOS Details on macOS elevated update can be found [here](MacElevatedUpdate.md). ### Other We do not currently have a method of elevating on other operating systems. We do provide a Linux package repository that can be used by Linux package managers which are better able to keep Linux installations up-to-date in a secure manner. These are the most highly recommended ways of updating on Linux. More information on using this method can be found [here](https://support.mozilla.org/en-US/kb/install-firefox-linux). ## BITS Windows has a component called the [Background Intelligent Transfer Service](https://learn.microsoft.com/en-us/windows/win32/bits/background-intelligent-transfer-service-portal), or BITS. This allows the operating system to download something for us. On Windows, we use BITS by default for update downloads, allowing the updates to continue downloading even after Firefox closes. BITS has a number of limitations. We cannot easily connect to a BITS transfer that was started by another user. We also cannot easily get it to use Firefox's proxy configuration. When we detect these situations, BITS will not be used. ## Update Mutex and Update Sync Manager The update mutex and the update sync manager are two similar but different components. Early in Firefox startup, we initialize the update sync manager. This essentially involves taking a non-exclusive lock on an installation-specific file. When other instances of Firefox launch, they take another non-exclusive lock on the same file. At certain stages in the update process, we check to see if other instances are running by briefly attempting to take an exclusive lock on the file. If we are unable to take it, we know that other Firefox processes must still be running and we introduce delays into the update process in an attempt to mitigate [Bug 1480452](https://bugzilla.mozilla.org/show_bug.cgi?id=1480452). This behavior can be disabled with `app.update.checkOnlyInstance.enabled`. The delay that we introduce into update can be changed by setting `app.update.checkOnlyInstance.timeout` to the desired number of milliseconds to delay for. Note that this timeout cannot be increased beyond 2 days. The update mutex serves a different purpose. It is designed to keep two instances of Firefox from running the update process at the same time, interfering with each other. This is accomplished by taking the mutex during update initialization. If the mutex cannot be obtained, we hold off on running update until we obtain it. Firefox checks again to see if it can get it each time it does an update check. Note that the User Interfaces for Firefox update cannot be used unless the current Firefox instance both has the mutex and is the only instance running. ## Update State In theory, the Application Update Service is a state machine. However, there are two state machines running in parallel: the state machine of the `update.status` file and the in-memory state machine of the Update Service itself. The `update.status` state machine is older and describes a number of states that are effectively variations of other states (ex: `pending-service` is basically a variation of `pending`): - `null`: When there are no updates in-progress, the state file is deleted. `null` represents this state. - `"downloading"`: An update download is in-progress. - `"pending"`: There is an update ready to be installed or staged. If Firefox starts with this state the update will be installed. Whether the updater runs staging or does the full installation depends on what arguments are passed. This state has some implications for any elevation that may be needed to install the update, which vary depending on the operating system. On Windows, this state means that the Mozilla Maintenance Service may not be used. If Windows elevation is needed to install the update, our only option is to display a User Account Control (UAC) prompt. On macOS, this state means that we have permission to update, even if elevation is needed. Since we do not support elevating on Linux, this has no elevation-related implications there. - `"pending-service"`: This state is relevant only on Windows. There is an update ready to be installed or staged, and the Mozilla Maintenance Service can be used to do this. - `"pending-elevate"`: This state is relevant only on macOS. There is an update ready to be installed, but we are not going to install it yet. On the next Firefox launch, we will display a notification telling the user that elevation is needed to install this update. Once this notification has been accepted, the state will be changed to `"pending"`. - `"applying"`: This state is only written by the updater binary, not Firefox, and indicates that it is currently staging or installing an update. - `"applied"`: The update has been successfully staged and is ready to be installed. Note that this state does not grant permission to use the Mozilla Maintenance Service. - `"applied-service"`: The update has been successfully staged and is ready to be installed. The Mozilla Maintenance Service can be used to install it. - `"failed"`: The update failed during staging or installation. The `update.status` file will generally represent this state with the state name followed by an error code from `toolkit/mozapps/update/common/updatererrors.h`. To make things slightly more complicated, these states are also used for the `state` property of update objects used in the Update Service. The Update Service currently tracks basically 3 types of updates. 1. Updates in the update history. These should generally always be in a `"succeeded"`, `"failed"`, or `"download-failed"` state. Note that `"download-failed"` should never get written out to `update.status` and thus was not listed above. 2. `downloadingUpdate` is the currently downloading update. This should always be in the `"downloading"` state. 3. `readyUpdate` is an update that we have finished downloading but we have not tried to install yet (though we may have staged it). This should be in one of the `"pending"` or `"applied"` states. However, while the updater binary writes state changes back to `update.status` to communicate them to Firefox, it has no awareness of these update objects so they will not be changed until Firefox updates them afterwards. Note that if there is a `readyUpdate` and a `downloadingUpdate` at the same time, `update.status` will reflect the status of the `readyUpdate`, not the `downloadingUpdate`. The Update Service state machine was added to resolve a couple of issues with the `update.status` state machine: - Having the canonical state of the Update Service live on disk incurs more disk reads than necessary. - There are multiple states that, for most purposes, represent the same state (ex: `"pending"` and `"pending-service"`). - The Update Service didn't really have an existing way of checking if staging was still in progress that didn't involve race conditions. - Multiple Firefox instances can exist simultaneously, but only one of them can drive update at once. This leads to a situation where the state on disk doesn't really match the state of some Firefox instances. - Once support for multiple downloads per session was added, there was no good way of representing aspects of this in the existing state machine. Because of this, a different but closely related state machine was added to `nsIApplicationUpdateService` with these states: - `STATE_IDLE`: An instance of Firefox that doesn't have the [update mutex](#update-mutex-and-update-sync-manager) will always be in this state until it manages to get it. If the mutex is held, this state means that there is no update downloading, staging, or ready to be installed. - `STATE_DOWNLOADING`: The update mutex is held and there is not a downloading, staging or ready-to-install update. - `STATE_STAGING`: The update mutex is held, an update has been downloaded, and it is now being staged. - `STATE_PENDING`: The update mutex is held and an update is ready to be installed. - `STATE_SWAP`: The update mutex is held, an update has been readied, and now a second update has finished downloading. Firefox is now swapping the old update with the new update. The following flowchart describes the update state transitions in terms of both types of update states. It describes the states from the perspective of the Firefox instance that holds the update mutex during this process. Note that some of the more unusual error cases are not shown here. The "happy path" (the simplest, most ideal flow) through the system has thicker arrows. ```{mermaid} flowchart TD %% If these state names aren't short and manual line-breaks aren't used, %% we seem to end up with very small boxes with invisible text in them. idle[AUS:IDLE
status:null] check{Update Check} waitToRetryDownload{{Wait}} downloading[AUS:DOWNLOADING
status:downloading] morePatchesToTryDownloading{More patches?} maybeStage{Staging?} stagingService[AUS: STAGING
status: pending-service] stagingNoService[AUS: STAGING
status: pending] launchStaging{{Run
Updater
binary}} stagingStart[[status: applying]] stagingSuccess[[status: applied]] stagingFail[[status: failed <code>]] stagingEnd{Firefox} appliedMaybeService{Use MMS?} appliedNoService[AUS:PENDING
status: applied] appliedService[AUS:PENDING
status: applied-service] doWeNeedElevation{Elevation
needed?} pendingNoElevation[AUS: PENDING
status: pending] pendingService[AUS: PENDING
status: pending-service] pendingElevate[AUS: PENDING
status: pending-elevate] waitForRestart{{Wait for
restart
or update}} additionalDownload{{Found
update}} additionalDownloadSuccessful[AUS: SWAP
status: null] restarts{{Firefox restarts}} askToElevate{Permission
to elevate?} askToElevateAccepted[AUS: PENDING
status: pending] waitForNewVersion{{Block this
version}} launchUpdate{{Run
Updater
binary}} updateStart[[status: applying]] updateSuccess[[status: succeeded]] updateFail[[status: failed <code>]] updateEnd{Firefox} completeMarFallback{Complete
MAR
available?} updateComplete([Update complete!]) idle==>check check== Update Found ==>downloading check-- No Updates Found -->idle downloading-- Transient
or BITS
Failure -->waitToRetryDownload-->downloading downloading-- Patch verification failed -->morePatchesToTryDownloading downloading-- Other failure -->idle downloading== Download success ==>maybeStage morePatchesToTryDownloading-- Yes -->downloading morePatchesToTryDownloading-- No -->idle maybeStage-- Yes, with Windows elevation -->stagingService maybeStage== Yes, without elevation ==>stagingNoService maybeStage-- No -->doWeNeedElevation stagingService-->launchStaging stagingNoService==>launchStaging launchStaging==>stagingStart stagingStart== Staging successful ==>stagingSuccess stagingStart-- Staging failure -->stagingFail stagingSuccess==>stagingEnd stagingFail-->stagingEnd stagingEnd== Success ==>appliedMaybeService stagingEnd-- Staging Specific Error -->doWeNeedElevation stagingEnd-- Maintenance Service Specific Error -->pendingNoElevation stagingEnd-- Other Error -->completeMarFallback appliedMaybeService-- Yes -->appliedService-->waitForRestart appliedMaybeService== No ==>appliedNoService==>waitForRestart doWeNeedElevation-- No or Maintenance Service is not available -->pendingNoElevation doWeNeedElevation-- Windows elevation, Maintenance Service is available -->pendingService doWeNeedElevation-- macOS elevation -->pendingElevate pendingNoElevation-->waitForRestart pendingService-->waitForRestart pendingElevate-->waitForRestart waitForRestart-->additionalDownload waitForRestart==>restarts additionalDownload-- Download Failed -->waitForRestart additionalDownload-- Download Successful -->additionalDownloadSuccessful additionalDownloadSuccessful-->maybeStage restarts-- status is pending-elevate -->askToElevate restarts== status is pending or pending-service ==>launchUpdate askToElevate-- User declines -->waitForNewVersion askToElevate-- User accepts -->askToElevateAccepted askToElevateAccepted-->restarts waitForNewVersion-->idle launchUpdate==>updateStart updateStart== Update successful ==>updateSuccess updateStart-- Update failure -->updateFail updateSuccess==>updateEnd updateFail-->updateEnd updateEnd-- Error writing or elevating -->doWeNeedElevation updateEnd-- Maintenance Service specific error -->pendingNoElevation updateEnd-- Other error -->completeMarFallback updateEnd== Success ==>updateComplete completeMarFallback-- Yes -->downloading completeMarFallback-- No -->idle ``` ## Update Channel There is one wrinkle that informs some confusing parts of update system and MAR design. We don't want the update to change the channel of the installation, even if the update appears to be from a different channel. There are two main reasons for this. The first is that QA uses this in their testing. The second is release candidates. Release candidates are slightly weird. They are built precisely as if they were meant for the Release channel. But we actually release them on the Beta channel. This is to help more thoroughly test for problems that might come from this slight change in the build process. Because of this, release candidate installers will not be advertised to users looking for the beta channel installer. But, when we serve a release candidate as an update, we expect the updater to produce an installation that is identical to a Release installation _except_ that it will continue to be on the Beta channel. In order to allow for this, we isolate the definition of our current update channel to just a few files. And we support having the update contain files that will only be installed if they do not already exist. This allows us to ensure that the updater will always leave those files untouched. Examples of these files include `default/pref/channel-prefs.js` and `update-settings.ini` on Windows and Linux. The macOS equivalents of these files are `Contents/Frameworks/ChannelPrefs.framework` and `Contents/MacOS/updater.app/Contents/Frameworks/UpdateSettings.framework`, respectively. ## precomplete Sometimes, we remove files from the Firefox installation. This is a bit problematic for complete MARs. They are intended to be able to update from almost any version to the current version. But it's tricky to have the complete MAR know what files should be removed when updating an arbitrary installed version. We don't want to just delete everything in the current installation, just in case there are, for some reason, files in our installation directory that do not really "belong to" our installation (i.e. files that the user placed there). Additionally, there are some files that are part of the installation that should never be deleted on update (see [Update Channel](#update-channel), above). This problem is solved by the `precomplete` file. When installing a complete MAR, the updater looks for this file in the existing installation. The `precomplete` file tells it what files and directories are a part of the current installation so that the updater knows what to remove to effectively "uninstall" the current installation before installing the updated installation. ## Background Update Unfortunately, background update has come to mean two things in Firefox. In the initial update design, we referred to updates without user interaction as background updates. This was to distinguish them from foreground updates, which involve user interaction. Later on, the Background Update Task (also known as the Background Update Agent) was added to update Firefox when it is not running. Confusingly, this is often shortened to "Background Update". While Firefox is running, background update (the in-app one, not the task) is initiated via the `TimerManager` in `toolkit/components/timermanager/UpdateTimerManager.sys.mjs`. The update timer is added to the timer manager implicitly, via `toolkit/mozapps/update/nsUpdateService.manifest`. The update checking interval can be changed by setting `app.update.interval` to the desired number of seconds. When the update timer expires, `UpdateService.notify()` is called, initiating an update check. The Background Update Task is a type of [Background Task](../../../../components/backgroundtasks/docs/index.md), which is basically just a copy of Firefox running in a stripped down, headless mode. It is described in detail, [here](BackgroundUpdates.rst), but a short summary will be provided below. The Background Update Task is registered, if possible, at Firefox startup. There are a number of reasons that it might not be registered, which are listed in the definition of `BackgroundUpdate.REASON`. We only re-register the task if it does not exist or if the task version has been changed since the last time we registered the task. In order to ensure that this happens, be sure to update `TASK_DEF_CURRENT_VERSION` when changing the definition of the task. The Background Update Task essentially just runs update the same way that the update UI would: via the `AppUpdater`. It requires that [BITS](#bits) be used for downloads so that it can keep its run time brief. Note that in order to prevent race conditions, even if the download completes instantly, the Update Service is prevented from proceeding with update since the Task may already be shutting down. It will continue the update process the next time the task runs. ## Langpacks One way of setting the language of a Firefox installation is via [langpacks](https://support.mozilla.org/en-US/kb/use-firefox-another-language#w_add-languages-to-the-firefox-interface). When using a langpack, the updated langpack ought to be staged before Firefox is updated. The Update Service currently takes care of this. When it starts an update download, it also starts the process of downloading and staging a langpack. It then waits for the langpack to be ready before it sets the [state](#update-state) to `STATE_PENDING`. ## Update XMLs There are actually three XMLs pertaining to update, each of which will be described here. The format of all of them is basically equivalent. Each of them are parsed into an array of `Update` objects (defined in `toolkit/mozapps/update/UpdateService.sys.mjs`). Each `Update` can contain multiple `UpdatePatch` objects. Generally speaking, an `Update` will contain a patch for a [complete MAR](MarFiles.md#types-of-mars) and possibly a patch for a partial MAR, if one is available. When Firefox checks for updates, the update server (Balrog) replies with an XML describing the most up-to-date update that is available from the installed version. While updates are in-progress, they will be stored in `active-update.xml`. The first update listed is the `readyUpdate` and the second is the `downloadingUpdate`. If there is only one update in the XML in the `downloading` state, it is the `downloadingUpdate`. If there is one update in another state, it is the `readyUpdate`. The update history is stored in `updates.xml`. It stores the last 10 successful and failed updates, starting with the most recent. ## Force Parameter Balrog supports throttling updates. This is implemented by picking a ratio, on the server side, to throttle the update at. Then, based on that probability, pick to return one of two updates (generally the newest update or the one before that). However, this can be overridden by passing the `force` query parameter. When an update is initiated by the user (from the update UI), `force=1` is sent, overriding the probability and retrieving the newer update. It is technically also possible to send `force=0` to specifically request the older update, but we do not use this in practice. ## Pinning Firefox has [an update version pinning mechanism](https://mozilla.github.io/policy-templates/#appupdatepin) available as an enterprise policy. This causes a query parameter to be sent to convey the update pin to the server (Balrog). The pinning logic is entirely implemented on the server side. It works by keeping a table of the most recent version for each major and minor version. It then uses the pin requested to do a lookup in that table. ## No Window Auto Restart On macOS, Firefox (like most other applications) continues running even when the last window is closed. Since the user isn't using the browser at this point, this is a good time for us to update. The problem is that Firefox can't really update while it is running. To address this, `RestartOnLastWindowClosed` was added to `toolkit/mozapps/update/UpdateService.sys.mjs`. When the last Firefox window is closed and an update is ready, `RestartOnLastWindowClosed` starts a timer. When this expires, it attempts to perform a silent update in which no UI is shown and we bail out if any kind of UI is needed. Firefox is then restarted in a special state where no windows open. This mechanism can be disabled by setting `app.update.noWindowAutoRestart.enabled` to `false`. The delay until the restart can be changed by setting `app.update.noWindowAutoRestart.delayMs` to the desired number of milliseconds.