summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPale Moon <git-repo@palemoon.org>2016-10-13 09:28:23 +0200
committerPale Moon <git-repo@palemoon.org>2016-10-13 09:43:49 +0200
commitf39a7473bb52ebfd9a6571ab6c2ac7f6ba0a71ea (patch)
treedb92f2994eaac281d2d0962412dfc69dd6009d13
parentae4c8cc4559be4c8820b20111339ef702e5b4dd7 (diff)
downloadpalemoon-gre-f39a7473bb52ebfd9a6571ab6c2ac7f6ba0a71ea.tar.gz
Revert "Remove the maintenance service (take 2)."
This reverts commit 009fdf9eb83e29aceec430ccc6c1132e32b23185. Tag #492.
-rw-r--r--browser/installer/package-manifest.in7
-rw-r--r--browser/installer/windows/nsis/installer.nsi19
-rw-r--r--configure.in18
-rw-r--r--toolkit/components/maintenanceservice/Makefile.in15
-rw-r--r--toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi278
-rw-r--r--toolkit/components/maintenanceservice/maintenanceservice.cpp386
-rw-r--r--toolkit/components/maintenanceservice/maintenanceservice.exe.manifest26
-rw-r--r--toolkit/components/maintenanceservice/maintenanceservice.h10
-rw-r--r--toolkit/components/maintenanceservice/maintenanceservice.rc86
-rw-r--r--toolkit/components/maintenanceservice/moz.build53
-rw-r--r--toolkit/components/maintenanceservice/resource.h20
-rw-r--r--toolkit/components/maintenanceservice/servicebase.cpp86
-rw-r--r--toolkit/components/maintenanceservice/servicebase.h22
-rw-r--r--toolkit/components/maintenanceservice/serviceinstall.cpp733
-rw-r--r--toolkit/components/maintenanceservice/serviceinstall.h21
-rw-r--r--toolkit/components/maintenanceservice/workmonitor.cpp635
-rw-r--r--toolkit/components/maintenanceservice/workmonitor.h5
-rw-r--r--toolkit/moz.build3
-rw-r--r--toolkit/mozapps/installer/windows/nsis/makensis.mk7
-rw-r--r--toolkit/mozapps/update/moz.build11
-rw-r--r--toolkit/mozapps/update/nsUpdateService.js47
-rw-r--r--toolkit/mozapps/update/tests/Makefile.in7
-rw-r--r--toolkit/mozapps/update/tests/moz.build6
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js33
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js99
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js320
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js286
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js160
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseFallbackStageFailureCompleteSvc_win.js54
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js55
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailureCompleteSvc_win.js55
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailurePartialSvc_win.js55
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js46
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js46
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailureCompleteSvc_win.js64
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailurePartialSvc_win.js64
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailureCompleteSvc_win.js66
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailurePartialSvc_win.js64
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js67
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js65
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js56
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js54
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js55
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js160
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js101
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js70
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js76
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini83
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp295
61 files changed, 5633 insertions, 31 deletions
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index 35f7a1816..c9aae3841 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -806,6 +806,13 @@ bin/libfreebl_32int64_3.so
#endif
#endif
+; [MaintenanceService]
+;
+#ifdef MOZ_MAINTENANCE_SERVICE
+@BINPATH@/maintenanceservice.exe
+@BINPATH@/maintenanceservice_installer.exe
+#endif
+
@RESPATH@/components/DataStore.manifest
@RESPATH@/components/DataStoreImpl.js
@RESPATH@/components/dom_datastore.xpt
diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
index b5bd62605..8afccb91b 100644
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -1018,6 +1018,25 @@ Function .onInit
WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" State "1"
${EndUnless}
+ ; Setup the components.ini file for the Components Page
+ WriteINIStr "$PLUGINSDIR\components.ini" "Settings" NumFields "2"
+
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Text "$(OPTIONAL_COMPONENTS_DESC)"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Bottom "25"
+
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Text "$(MAINTENANCE_SERVICE_CHECKBOX_DESC)"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Top "27"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Bottom "37"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Flags "GROUP"
+
; There must always be a core directory.
${GetSize} "$EXEDIR\core\" "/S=0K" $R5 $R7 $R8
SectionSetSize ${APP_IDX} $R5
diff --git a/configure.in b/configure.in
index 8fb2d0017..242a955ca 100644
--- a/configure.in
+++ b/configure.in
@@ -6184,6 +6184,23 @@ if test -n "$MOZ_SIGN_CMD"; then
fi
dnl ========================================================
+dnl Maintenance Service
+dnl ========================================================
+
+MOZ_ARG_ENABLE_BOOL(maintenance-service,
+[ --enable-maintenance-service Enable building of maintenanceservice],
+ MOZ_MAINTENANCE_SERVICE=1,
+ MOZ_MAINTENANCE_SERVICE= )
+
+if test -n "$MOZ_MAINTENANCE_SERVICE"; then
+ if test "$OS_ARCH" = "WINNT"; then
+ AC_DEFINE(MOZ_MAINTENANCE_SERVICE)
+ else
+ AC_MSG_ERROR([Can only build with --enable-maintenance-service with a Windows target])
+ fi
+fi
+
+dnl ========================================================
dnl Bundled fonts on desktop platforms
dnl ========================================================
@@ -8244,6 +8261,7 @@ AC_SUBST(ACCESSIBILITY)
AC_SUBST(MOZ_SPELLCHECK)
AC_SUBST(MOZ_ANDROID_APZ)
AC_SUBST(MOZ_ANDROID_ANR_REPORTER)
+AC_SUBST(MOZ_MAINTENANCE_SERVICE)
AC_SUBST(MOZ_VERIFY_MAR_SIGNATURE)
AC_SUBST(MOZ_ENABLE_SIGNMAR)
AC_SUBST(MOZ_UPDATER)
diff --git a/toolkit/components/maintenanceservice/Makefile.in b/toolkit/components/maintenanceservice/Makefile.in
new file mode 100644
index 000000000..32f2dff91
--- /dev/null
+++ b/toolkit/components/maintenanceservice/Makefile.in
@@ -0,0 +1,15 @@
+# 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/.
+
+DIST_PROGRAM = maintenanceservice$(BIN_SUFFIX)
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
diff --git a/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi b/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi
new file mode 100644
index 000000000..9e831dc9c
--- /dev/null
+++ b/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi
@@ -0,0 +1,278 @@
+# 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/.
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel admin
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+; Variables
+Var TempMaintServiceName
+Var BrandFullNameDA
+Var BrandFullName
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetOptions
+!insertmacro GetParameters
+!insertmacro GetSize
+
+; The test slaves use this fallback key to run tests.
+; And anyone that wants to run tests themselves should already have
+; this installed.
+!define FallbackKey \
+ "SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4"
+
+!define CompanyName "Mozilla Corporation"
+!define BrandFullNameInternal ""
+
+; The following includes are custom.
+!include defines.nsi
+; We keep defines.nsi defined so that we get other things like
+; the version number, but we redefine BrandFullName
+!define MaintFullName "Mozilla Maintenance Service"
+!undef BrandFullName
+!define BrandFullName "${MaintFullName}"
+
+!include common.nsh
+!include locales.nsi
+
+VIAddVersionKey "FileDescription" "${MaintFullName} Installer"
+VIAddVersionKey "OriginalFilename" "maintenanceservice_installer.exe"
+
+Name "${MaintFullName}"
+OutFile "maintenanceservice_installer.exe"
+
+; Get installation folder from registry if available
+InstallDirRegKey HKLM "Software\Mozilla\MaintenanceService" ""
+
+SetOverwrite on
+
+!define MaintUninstallKey \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
+
+; Always install into the 32-bit location even if we have a 64-bit build.
+; This is because we use only 1 service for all Firefox channels.
+; Allow either x86 and x64 builds to exist at this location, depending on
+; what is the latest build.
+InstallDir "$PROGRAMFILES32\${MaintFullName}\"
+ShowUnInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+;Interface Settings
+!define MUI_ABORTWARNING
+
+; Uninstaller Pages
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale
+; using " " for BrandingText will hide the "Nullsoft Install System..." branding
+BrandingText " "
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ SetSilent silent
+ ; On Windows 2000 we do not install the maintenance service.
+ ; We won't run this installer from the parent installer, but just in case
+ ; someone tries to execute it on Windows 2000...
+ ${Unless} ${AtLeastWinXP}
+ Abort
+ ${EndUnless}
+FunctionEnd
+
+Function un.onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ StrCpy $BrandFullNameDA "${MaintFullName}"
+ StrCpy $BrandFullName "${MaintFullName}"
+FunctionEnd
+
+Section "MaintenanceService"
+ AllowSkipFiles off
+
+ CreateDirectory $INSTDIR
+ SetOutPath $INSTDIR
+
+ ; If the service already exists, then it will be stopped when upgrading it
+ ; via the maintenanceservice_tmp.exe command executed below.
+ ; The maintenanceservice_tmp.exe command will rename the file to
+ ; maintenanceservice.exe if maintenanceservice_tmp.exe is newer.
+ ; If the service does not exist yet, we install it and drop the file on
+ ; disk as maintenanceservice.exe directly.
+ StrCpy $TempMaintServiceName "maintenanceservice.exe"
+ IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists
+ StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe"
+ skipAlreadyExists:
+
+ ; We always write out a copy and then decide whether to install it or
+ ; not via calling its 'install' cmdline which works by version comparison.
+ CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName"
+
+ ; The updater.ini file is only used when performing an install or upgrade,
+ ; and only if that install or upgrade is successful. If an old updater.ini
+ ; happened to be copied into the maintenance service installation directory
+ ; but the service was not newer, the updater.ini file would be unused.
+ ; It is used to fill the description of the service on success.
+ CopyFiles "$EXEDIR\updater.ini" "$INSTDIR\updater.ini"
+
+ ; Install the application maintenance service.
+ ; If a service already exists, the command line parameter will stop the
+ ; service and only install itself if it is newer than the already installed
+ ; service. If successful it will remove the old maintenanceservice.exe
+ ; and replace it with maintenanceservice_tmp.exe.
+ ClearErrors
+ ;${GetParameters} $0
+ ;${GetOptions} "$0" "/Upgrade" $0
+ ;${If} ${Errors}
+ ExecWait '"$INSTDIR\$TempMaintServiceName" forceinstall'
+ ;${Else}
+ ; The upgrade cmdline is the same as install except
+ ; It will fail if the service isn't already installed.
+ ; ExecWait '"$INSTDIR\$TempMaintServiceName" upgrade'
+ ;${EndIf}
+
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
+ WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
+ '"$INSTDIR\uninstall.exe"'
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
+ "$INSTDIR\Uninstall.exe,0"
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}"
+ WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla"
+ WriteRegStr HKLM "${MaintUninstallKey}" "Comments" "${BrandFullName}"
+ WriteRegDWORD HKLM "${MaintUninstallKey}" "NoModify" 1
+ ${GetSize} "$INSTDIR" "/S=0K" $R2 $R3 $R4
+ WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2
+
+ ; Write out that a maintenance service was attempted.
+ ; We do this because on upgrades we will check this value and we only
+ ; want to install once on the first upgrade to maintenance service.
+ ; Also write out that we are currently installed, preferences will check
+ ; this value to determine if we should show the service update pref.
+ ; Since the Maintenance service can be installed either x86 or x64,
+ ; always use the 64-bit registry for checking if an attempt was made.
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
+ WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
+
+ ; Included here for debug purposes only.
+ ; These keys are used to bypass the installation dir is a valid installation
+ ; check from the service so that tests can be run.
+ WriteRegStr HKLM "${FallbackKey}\0" "name" "Mozilla Corporation"
+ WriteRegStr HKLM "${FallbackKey}\0" "issuer" "DigiCert SHA2 Assured ID Code Signing CA"
+ WriteRegStr HKLM "${FallbackKey}\1" "name" "Mozilla Fake SPC"
+ WriteRegStr HKLM "${FallbackKey}\1" "issuer" "Mozilla Fake CA"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+SectionEnd
+
+; By renaming before deleting we improve things slightly in case
+; there is a file in use error. In this case a new install can happen.
+Function un.RenameDelete
+ Pop $9
+ ; If the .moz-delete file already exists previously, delete it
+ ; If it doesn't exist, the call is ignored.
+ ; We don't need to pass /REBOOTOK here since it was already marked that way
+ ; if it exists.
+ Delete "$9.moz-delete"
+ Rename "$9" "$9.moz-delete"
+ ${If} ${Errors}
+ Delete /REBOOTOK "$9"
+ ${Else}
+ Delete /REBOOTOK "$9.moz-delete"
+ ${EndIf}
+ ClearErrors
+FunctionEnd
+
+Section "Uninstall"
+ ; Delete the service so that no updates will be attempted
+ ExecWait '"$INSTDIR\maintenanceservice.exe" uninstall'
+
+ Push "$INSTDIR\updater.ini"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice_tmp.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice.old"
+ Call un.RenameDelete
+ Push "$INSTDIR\Uninstall.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\update\updater.ini"
+ Call un.RenameDelete
+ Push "$INSTDIR\update\updater.exe"
+ Call un.RenameDelete
+ RMDir /REBOOTOK "$INSTDIR\update"
+ RMDir /REBOOTOK "$INSTDIR"
+ DeleteRegKey HKLM "${MaintUninstallKey}"
+
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed"
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
+ DeleteRegKey HKLM "${FallbackKey}\"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+SectionEnd
+
diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp
new file mode 100644
index 000000000..e79df071f
--- /dev/null
+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ -0,0 +1,386 @@
+/* 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 <shlwapi.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <shlobj.h>
+
+#include "serviceinstall.h"
+#include "maintenanceservice.h"
+#include "servicebase.h"
+#include "workmonitor.h"
+#include "uachelper.h"
+#include "updatehelper.h"
+
+// Link w/ subsystem window so we don't get a console when executing
+// this binary through the installer.
+#pragma comment(linker, "/SUBSYSTEM:windows")
+
+SERVICE_STATUS gSvcStatus = { 0 };
+SERVICE_STATUS_HANDLE gSvcStatusHandle = nullptr;
+HANDLE gWorkDoneEvent = nullptr;
+HANDLE gThread = nullptr;
+bool gServiceControlStopping = false;
+
+// logs are pretty small, about 20 lines, so 10 seems reasonable.
+#define LOGS_TO_KEEP 10
+
+BOOL GetLogDirectoryPath(WCHAR *path);
+
+int
+wmain(int argc, WCHAR **argv)
+{
+ // If command-line parameter is "install", install the service
+ // or upgrade if already installed
+ // If command line parameter is "forceinstall", install the service
+ // even if it is older than what is already installed.
+ // If command-line parameter is "upgrade", upgrade the service
+ // but do not install it if it is not already installed.
+ // If command line parameter is "uninstall", uninstall the service.
+ // Otherwise, the service is probably being started by the SCM.
+ bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
+ if (!lstrcmpi(argv[1], L"install") || forceInstall) {
+ WCHAR updatePath[MAX_PATH + 1];
+ if (GetLogDirectoryPath(updatePath)) {
+ LogInit(updatePath, L"maintenanceservice-install.log");
+ }
+
+ SvcInstallAction action = InstallSvc;
+ if (forceInstall) {
+ action = ForceInstallSvc;
+ LOG(("Installing service with force specified..."));
+ } else {
+ LOG(("Installing service..."));
+ }
+
+ bool ret = SvcInstall(action);
+ if (!ret) {
+ LOG_WARN(("Could not install service. (%d)", GetLastError()));
+ LogFinish();
+ return 1;
+ }
+
+ LOG(("The service was installed successfully"));
+ LogFinish();
+ return 0;
+ }
+
+ if (!lstrcmpi(argv[1], L"upgrade")) {
+ WCHAR updatePath[MAX_PATH + 1];
+ if (GetLogDirectoryPath(updatePath)) {
+ LogInit(updatePath, L"maintenanceservice-install.log");
+ }
+
+ LOG(("Upgrading service if installed..."));
+ if (!SvcInstall(UpgradeSvc)) {
+ LOG_WARN(("Could not upgrade service. (%d)", GetLastError()));
+ LogFinish();
+ return 1;
+ }
+
+ LOG(("The service was upgraded successfully"));
+ LogFinish();
+ return 0;
+ }
+
+ if (!lstrcmpi(argv[1], L"uninstall")) {
+ WCHAR updatePath[MAX_PATH + 1];
+ if (GetLogDirectoryPath(updatePath)) {
+ LogInit(updatePath, L"maintenanceservice-uninstall.log");
+ }
+ LOG(("Uninstalling service..."));
+ if (!SvcUninstall()) {
+ LOG_WARN(("Could not uninstall service. (%d)", GetLastError()));
+ LogFinish();
+ return 1;
+ }
+ LOG(("The service was uninstalled successfully"));
+ LogFinish();
+ return 0;
+ }
+
+ SERVICE_TABLE_ENTRYW DispatchTable[] = {
+ { SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
+ { nullptr, nullptr }
+ };
+
+ // This call returns when the service has stopped.
+ // The process should simply terminate when the call returns.
+ if (!StartServiceCtrlDispatcherW(DispatchTable)) {
+ LOG_WARN(("StartServiceCtrlDispatcher failed. (%d)", GetLastError()));
+ }
+
+ return 0;
+}
+
+/**
+ * Obtains the base path where logs should be stored
+ *
+ * @param path The out buffer for the backup log path of size MAX_PATH + 1
+ * @return TRUE if successful.
+ */
+BOOL
+GetLogDirectoryPath(WCHAR *path)
+{
+ if (!GetModuleFileNameW(nullptr, path, MAX_PATH)) {
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(path)) {
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(path, L"logs")) {
+ return FALSE;
+ }
+ CreateDirectoryW(path, nullptr);
+ return TRUE;
+}
+
+/**
+ * Calculated a backup path based on the log number.
+ *
+ * @param path The out buffer to store the log path of size MAX_PATH + 1
+ * @param basePath The base directory where the calculated path should go
+ * @param logNumber The log number, 0 == updater.log
+ * @return TRUE if successful.
+ */
+BOOL
+GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
+{
+ WCHAR logName[64] = { L'\0' };
+ wcsncpy(path, basePath, sizeof(logName) / sizeof(logName[0]) - 1);
+ if (logNumber <= 0) {
+ swprintf(logName, sizeof(logName) / sizeof(logName[0]),
+ L"maintenanceservice.log");
+ } else {
+ swprintf(logName, sizeof(logName) / sizeof(logName[0]),
+ L"maintenanceservice-%d.log", logNumber);
+ }
+ return PathAppendSafe(path, logName);
+}
+
+/**
+ * Moves the old log files out of the way before a new one is written.
+ * If you for example keep 3 logs, then this function will do:
+ * updater2.log -> updater3.log
+ * updater1.log -> updater2.log
+ * updater.log -> updater1.log
+ * Which clears room for a new updater.log in the basePath directory
+ *
+ * @param basePath The base directory path where log files are stored
+ * @param numLogsToKeep The number of logs to keep
+ */
+void
+BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
+{
+ WCHAR oldPath[MAX_PATH + 1];
+ WCHAR newPath[MAX_PATH + 1];
+ for (int i = numLogsToKeep; i >= 1; i--) {
+ if (!GetBackupLogPath(oldPath, basePath, i -1)) {
+ continue;
+ }
+
+ if (!GetBackupLogPath(newPath, basePath, i)) {
+ continue;
+ }
+
+ if (!MoveFileExW(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) {
+ continue;
+ }
+ }
+}
+
+/**
+ * Ensures the service is shutdown once all work is complete.
+ * There is an issue on XP SP2 and below where the service can hang
+ * in a stop pending state even though the SCM is notified of a stopped
+ * state. Control *should* be returned to StartServiceCtrlDispatcher from the
+ * call to SetServiceStatus on a stopped state in the wmain thread.
+ * Sometimes this is not the case though. This thread will terminate the process
+ * if it has been 5 seconds after all work is done and the process is still not
+ * terminated. This thread is only started once a stopped state was sent to the
+ * SCM. The stop pending hang can be reproduced intermittently even if you set
+ * a stopped state dirctly and never set a stop pending state. It is safe to
+ * forcefully terminate the process ourselves since all work is done once we
+ * start this thread.
+*/
+DWORD WINAPI
+EnsureProcessTerminatedThread(LPVOID)
+{
+ Sleep(5000);
+ exit(0);
+ return 0;
+}
+
+void
+StartTerminationThread()
+{
+ // If the process does not self terminate like it should, this thread
+ // will terminate the process after 5 seconds.
+ HANDLE thread = CreateThread(nullptr, 0, EnsureProcessTerminatedThread,
+ nullptr, 0, nullptr);
+ if (thread) {
+ CloseHandle(thread);
+ }
+}
+
+/**
+ * Main entry point when running as a service.
+ */
+void WINAPI
+SvcMain(DWORD argc, LPWSTR *argv)
+{
+ // Setup logging, and backup the old logs
+ WCHAR updatePath[MAX_PATH + 1];
+ if (GetLogDirectoryPath(updatePath)) {
+ BackupOldLogs(updatePath, LOGS_TO_KEEP);
+ LogInit(updatePath, L"maintenanceservice.log");
+ }
+
+ // Disable every privilege we don't need. Processes started using
+ // CreateProcess will use the same token as this process.
+ UACHelper::DisablePrivileges(nullptr);
+
+ // Register the handler function for the service
+ gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
+ if (!gSvcStatusHandle) {
+ LOG_WARN(("RegisterServiceCtrlHandler failed. (%d)", GetLastError()));
+ ExecuteServiceCommand(argc, argv);
+ LogFinish();
+ exit(1);
+ }
+
+ // These values will be re-used later in calls involving gSvcStatus
+ gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ gSvcStatus.dwServiceSpecificExitCode = 0;
+
+ // Report initial status to the SCM
+ ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
+
+ // This event will be used to tell the SvcCtrlHandler when the work is
+ // done for when a stop comamnd is manually issued.
+ gWorkDoneEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ if (!gWorkDoneEvent) {
+ ReportSvcStatus(SERVICE_STOPPED, 1, 0);
+ StartTerminationThread();
+ return;
+ }
+
+ // Initialization complete and we're about to start working on
+ // the actual command. Report the service state as running to the SCM.
+ ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
+
+ // The service command was executed, stop logging and set an event
+ // to indicate the work is done in case someone is waiting on a
+ // service stop operation.
+ ExecuteServiceCommand(argc, argv);
+ LogFinish();
+
+ SetEvent(gWorkDoneEvent);
+
+ // If we aren't already in a stopping state then tell the SCM we're stopped
+ // now. If we are already in a stopping state then the SERVICE_STOPPED state
+ // will be set by the SvcCtrlHandler.
+ if (!gServiceControlStopping) {
+ ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
+ StartTerminationThread();
+ }
+}
+
+/**
+ * Sets the current service status and reports it to the SCM.
+ *
+ * @param currentState The current state (see SERVICE_STATUS)
+ * @param exitCode The system error code
+ * @param waitHint Estimated time for pending operation in milliseconds
+ */
+void
+ReportSvcStatus(DWORD currentState,
+ DWORD exitCode,
+ DWORD waitHint)
+{
+ static DWORD dwCheckPoint = 1;
+
+ gSvcStatus.dwCurrentState = currentState;
+ gSvcStatus.dwWin32ExitCode = exitCode;
+ gSvcStatus.dwWaitHint = waitHint;
+
+ if (SERVICE_START_PENDING == currentState ||
+ SERVICE_STOP_PENDING == currentState) {
+ gSvcStatus.dwControlsAccepted = 0;
+ } else {
+ gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
+ SERVICE_ACCEPT_SHUTDOWN;
+ }
+
+ if ((SERVICE_RUNNING == currentState) ||
+ (SERVICE_STOPPED == currentState)) {
+ gSvcStatus.dwCheckPoint = 0;
+ } else {
+ gSvcStatus.dwCheckPoint = dwCheckPoint++;
+ }
+
+ // Report the status of the service to the SCM.
+ SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
+}
+
+/**
+ * Since the SvcCtrlHandler should only spend at most 30 seconds before
+ * returning, this function does the service stop work for the SvcCtrlHandler.
+*/
+DWORD WINAPI
+StopServiceAndWaitForCommandThread(LPVOID)
+{
+ do {
+ ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
+ } while(WaitForSingleObject(gWorkDoneEvent, 100) == WAIT_TIMEOUT);
+ CloseHandle(gWorkDoneEvent);
+ gWorkDoneEvent = nullptr;
+ ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
+ StartTerminationThread();
+ return 0;
+}
+
+/**
+ * Called by SCM whenever a control code is sent to the service
+ * using the ControlService function.
+ */
+void WINAPI
+SvcCtrlHandler(DWORD dwCtrl)
+{
+ // After a SERVICE_CONTROL_STOP there should be no more commands sent to
+ // the SvcCtrlHandler.
+ if (gServiceControlStopping) {
+ return;
+ }
+
+ // Handle the requested control code.
+ switch(dwCtrl) {
+ case SERVICE_CONTROL_SHUTDOWN:
+ case SERVICE_CONTROL_STOP: {
+ gServiceControlStopping = true;
+ ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 1000);
+
+ // The SvcCtrlHandler thread should not spend more than 30 seconds in
+ // shutdown so we spawn a new thread for stopping the service
+ HANDLE thread = CreateThread(nullptr, 0,
+ StopServiceAndWaitForCommandThread,
+ nullptr, 0, nullptr);
+ if (thread) {
+ CloseHandle(thread);
+ } else {
+ // Couldn't start the thread so just call the stop ourselves.
+ // If it happens to take longer than 30 seconds the caller will
+ // get an error.
+ StopServiceAndWaitForCommandThread(nullptr);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
diff --git a/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest b/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest
new file mode 100644
index 000000000..cb317c47d
--- /dev/null
+++ b/toolkit/components/maintenanceservice/maintenanceservice.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="MaintenanceService"
+ type="win32"
+/>
+<description>MaintenanceService</description>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="requireAdministrator" 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/toolkit/components/maintenanceservice/maintenanceservice.h b/toolkit/components/maintenanceservice/maintenanceservice.h
new file mode 100644
index 000000000..9e02914a0
--- /dev/null
+++ b/toolkit/components/maintenanceservice/maintenanceservice.h
@@ -0,0 +1,10 @@
+/* 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/. */
+
+void WINAPI SvcMain(DWORD dwArgc, LPWSTR *lpszArgv);
+void SvcInit(DWORD dwArgc, LPWSTR *lpszArgv);
+void WINAPI SvcCtrlHandler(DWORD dwCtrl);
+void ReportSvcStatus(DWORD dwCurrentState,
+ DWORD dwWin32ExitCode,
+ DWORD dwWaitHint);
diff --git a/toolkit/components/maintenanceservice/maintenanceservice.rc b/toolkit/components/maintenanceservice/maintenanceservice.rc
new file mode 100644
index 000000000..ddd3e942b
--- /dev/null
+++ b/toolkit/components/maintenanceservice/maintenanceservice.rc
@@ -0,0 +1,86 @@
+/* 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.
+//
+#include "resource.h"
+
+#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 "maintenanceservice.exe.manifest"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+END
+#endif // APSTUDIO_INVOKED
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winresrc.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/toolkit/components/maintenanceservice/moz.build b/toolkit/components/maintenanceservice/moz.build
new file mode 100644
index 000000000..21990b5ab
--- /dev/null
+++ b/toolkit/components/maintenanceservice/moz.build
@@ -0,0 +1,53 @@
+# -*- 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('maintenanceservice')
+
+SOURCES += [
+ 'maintenanceservice.cpp',
+ 'servicebase.cpp',
+ 'serviceinstall.cpp',
+ 'workmonitor.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ USE_LIBS += [
+ 'updatecommon-standalone',
+ ]
+else:
+ USE_LIBS += [
+ 'updatecommon',
+ ]
+
+# For debugging purposes only
+#DEFINES['DISABLE_UPDATER_AUTHENTICODE_CHECK'] = True
+
+DEFINES['UNICODE'] = True
+DEFINES['_UNICODE'] = True
+DEFINES['NS_NO_XPCOM'] = True
+
+# Pick up nsWindowsRestart.cpp
+LOCAL_INCLUDES += [
+ '/toolkit/mozapps/update/common',
+ '/toolkit/xre',
+]
+
+USE_STATIC_LIBS = True
+
+if CONFIG['_MSC_VER']:
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+
+RCINCLUDE = 'maintenanceservice.rc'
+
+DISABLE_STL_WRAPPING = True
+
+OS_LIBS += [
+ 'comctl32',
+ 'ws2_32',
+ 'shell32',
+]
+
+FAIL_ON_WARNINGS = True
diff --git a/toolkit/components/maintenanceservice/resource.h b/toolkit/components/maintenanceservice/resource.h
new file mode 100644
index 000000000..45619457c
--- /dev/null
+++ b/toolkit/components/maintenanceservice/resource.h
@@ -0,0 +1,20 @@
+/* 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 IDI_DIALOG 1003
+
+// 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 1003
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/toolkit/components/maintenanceservice/servicebase.cpp b/toolkit/components/maintenanceservice/servicebase.cpp
new file mode 100644
index 000000000..a858c4537
--- /dev/null
+++ b/toolkit/components/maintenanceservice/servicebase.cpp
@@ -0,0 +1,86 @@
+/* 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 "servicebase.h"
+#include "nsWindowsHelpers.h"
+
+// Shared code between applications and updater.exe
+#include "nsWindowsRestart.cpp"
+
+/**
+ * Verifies if 2 files are byte for byte equivalent.
+ *
+ * @param file1Path The first file to verify.
+ * @param file2Path The second file to verify.
+ * @param sameContent Out parameter, TRUE if the files are equal
+ * @return TRUE If there was no error checking the files.
+ */
+BOOL
+VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent)
+{
+ sameContent = FALSE;
+ nsAutoHandle file1(CreateFileW(file1Path, GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+ if (INVALID_HANDLE_VALUE == file1) {
+ return FALSE;
+ }
+ nsAutoHandle file2(CreateFileW(file2Path, GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+ if (INVALID_HANDLE_VALUE == file2) {
+ return FALSE;
+ }
+
+ DWORD fileSize1 = GetFileSize(file1, nullptr);
+ DWORD fileSize2 = GetFileSize(file2, nullptr);
+ if (INVALID_FILE_SIZE == fileSize1 || INVALID_FILE_SIZE == fileSize2) {
+ return FALSE;
+ }
+
+ if (fileSize1 != fileSize2) {
+ // sameContent is already set to FALSE
+ return TRUE;
+ }
+
+ char buf1[COMPARE_BLOCKSIZE];
+ char buf2[COMPARE_BLOCKSIZE];
+ DWORD numBlocks = fileSize1 / COMPARE_BLOCKSIZE;
+ DWORD leftOver = fileSize1 % COMPARE_BLOCKSIZE;
+ DWORD readAmount;
+ for (DWORD i = 0; i < numBlocks; i++) {
+ if (!ReadFile(file1, buf1, COMPARE_BLOCKSIZE, &readAmount, nullptr) ||
+ readAmount != COMPARE_BLOCKSIZE) {
+ return FALSE;
+ }
+
+ if (!ReadFile(file2, buf2, COMPARE_BLOCKSIZE, &readAmount, nullptr) ||
+ readAmount != COMPARE_BLOCKSIZE) {
+ return FALSE;
+ }
+
+ if (memcmp(buf1, buf2, COMPARE_BLOCKSIZE)) {
+ // sameContent is already set to FALSE
+ return TRUE;
+ }
+ }
+
+ if (leftOver) {
+ if (!ReadFile(file1, buf1, leftOver, &readAmount, nullptr) ||
+ readAmount != leftOver) {
+ return FALSE;
+ }
+
+ if (!ReadFile(file2, buf2, leftOver, &readAmount, nullptr) ||
+ readAmount != leftOver) {
+ return FALSE;
+ }
+
+ if (memcmp(buf1, buf2, leftOver)) {
+ // sameContent is already set to FALSE
+ return TRUE;
+ }
+ }
+
+ sameContent = TRUE;
+ return TRUE;
+}
diff --git a/toolkit/components/maintenanceservice/servicebase.h b/toolkit/components/maintenanceservice/servicebase.h
new file mode 100644
index 000000000..dfe04ed29
--- /dev/null
+++ b/toolkit/components/maintenanceservice/servicebase.h
@@ -0,0 +1,22 @@
+/* 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 "updatelogging.h"
+
+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
+BOOL VerifySameFiles(LPCWSTR file1Path, LPCWSTR file2Path, BOOL &sameContent);
+
+// 32KiB for comparing files at a time seems reasonable.
+// The bigger the better for speed, but this will be used
+// on the stack so I don't want it to be too big.
+#define COMPARE_BLOCKSIZE 32768
+
+// The following string resource value is used to uniquely identify the signed
+// Mozilla application as an updater. Before the maintenance service will
+// execute the updater it must have this updater identity string in its string
+// table. No other signed Mozilla product will have this string table value.
+#define UPDATER_IDENTITY_STRING \
+ "moz-updater.exe-4cdccec4-5ee0-4a06-9817-4cd899a9db49"
+#define IDS_UPDATER_IDENTITY 1006
diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
new file mode 100644
index 000000000..fda3e7bbd
--- /dev/null
+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
@@ -0,0 +1,733 @@
+/* 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 <aclapi.h>
+#include <stdlib.h>
+#include <shlwapi.h>
+
+// Used for DNLEN and UNLEN
+#include <lm.h>
+
+#include <nsWindowsHelpers.h>
+#include "mozilla/UniquePtr.h"
+
+#include "serviceinstall.h"
+#include "servicebase.h"
+#include "updatehelper.h"
+#include "shellapi.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#pragma comment(lib, "version.lib")
+
+/**
+ * A wrapper function to read strings for the maintenance service.
+ *
+ * @param path The path of the ini file to read from
+ * @param results The maintenance service strings that were read
+ * @return OK on success
+*/
+static int
+ReadMaintenanceServiceStrings(LPCWSTR path,
+ MaintenanceServiceStringTable *results)
+{
+ // Read in the maintenance service description string if specified.
+ const unsigned int kNumStrings = 1;
+ const char *kServiceKeys = "MozillaMaintenanceDescription\0";
+ char serviceStrings[kNumStrings][MAX_TEXT_LEN];
+ int result = ReadStrings(path, kServiceKeys,
+ kNumStrings, serviceStrings);
+ if (result != OK) {
+ serviceStrings[0][0] = '\0';
+ }
+ strncpy(results->serviceDescription,
+ serviceStrings[0], MAX_TEXT_LEN - 1);
+ results->serviceDescription[MAX_TEXT_LEN - 1] = '\0';
+ return result;
+}
+
+/**
+ * Obtains the version number from the specified PE file's version information
+ * Version Format: A.B.C.D (Example 10.0.0.300)
+ *
+ * @param path The path of the file to check the version on
+ * @param A The first part of the version number
+ * @param B The second part of the version number
+ * @param C The third part of the version number
+ * @param D The fourth part of the version number
+ * @return TRUE if successful
+ */
+static BOOL
+GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B,
+ DWORD &C, DWORD &D)
+{
+ DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
+ mozilla::UniquePtr<char[]> fileVersionInfo(new char[fileVersionInfoSize]);
+ if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
+ fileVersionInfo.get())) {
+ LOG_WARN(("Could not obtain file info of old service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ VS_FIXEDFILEINFO *fixedFileInfo =
+ reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
+ UINT size;
+ if (!VerQueryValueW(fileVersionInfo.get(), L"\\",
+ reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) {
+ LOG_WARN(("Could not query file version info of old service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ A = HIWORD(fixedFileInfo->dwFileVersionMS);
+ B = LOWORD(fixedFileInfo->dwFileVersionMS);
+ C = HIWORD(fixedFileInfo->dwFileVersionLS);
+ D = LOWORD(fixedFileInfo->dwFileVersionLS);
+ return TRUE;
+}
+
+/**
+ * Updates the service description with what is stored in updater.ini
+ * at the same path as the currently executing module binary.
+ *
+ * @param serviceHandle A handle to an opened service with
+ * SERVICE_CHANGE_CONFIG access right
+ * @param TRUE on succcess.
+*/
+BOOL
+UpdateServiceDescription(SC_HANDLE serviceHandle)
+{
+ WCHAR updaterINIPath[MAX_PATH + 1];
+ if (!GetModuleFileNameW(nullptr, updaterINIPath,
+ sizeof(updaterINIPath) /
+ sizeof(updaterINIPath[0]))) {
+ LOG_WARN(("Could not obtain module filename when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(updaterINIPath)) {
+ LOG_WARN(("Could not remove file spec when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(updaterINIPath, L"updater.ini")) {
+ LOG_WARN(("Could not append updater.ini filename when attempting to "
+ "modify service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES) {
+ LOG_WARN(("updater.ini file does not exist, will not modify "
+ "service description. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ MaintenanceServiceStringTable serviceStrings;
+ int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
+ if (rv != OK || !strlen(serviceStrings.serviceDescription)) {
+ LOG_WARN(("updater.ini file does not contain a maintenance "
+ "service description."));
+ return FALSE;
+ }
+
+ WCHAR serviceDescription[MAX_TEXT_LEN];
+ if (!MultiByteToWideChar(CP_UTF8, 0,
+ serviceStrings.serviceDescription, -1,
+ serviceDescription,
+ sizeof(serviceDescription) /
+ sizeof(serviceDescription[0]))) {
+ LOG_WARN(("Could not convert description to wide string format. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ SERVICE_DESCRIPTIONW descriptionConfig;
+ descriptionConfig.lpDescription = serviceDescription;
+ if (!ChangeServiceConfig2W(serviceHandle,
+ SERVICE_CONFIG_DESCRIPTION,
+ &descriptionConfig)) {
+ LOG_WARN(("Could not change service config. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("The service description was updated successfully."));
+ return TRUE;
+}
+
+/**
+ * Determines if the MozillaMaintenance service path needs to be updated
+ * and fixes it if it is wrong.
+ *
+ * @param service A handle to the service to fix.
+ * @param currentServicePath The current (possibly wrong) path that is used.
+ * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed.
+ * @return TRUE if the service path is now correct.
+*/
+BOOL
+FixServicePath(SC_HANDLE service,
+ LPCWSTR currentServicePath,
+ BOOL &servicePathWasWrong)
+{
+ // When we originally upgraded the MozillaMaintenance service we
+ // would uninstall the service on each upgrade. This had an
+ // intermittent error which could cause the service to use the file
+ // maintenanceservice_tmp.exe as the install path. Only a small number
+ // of Nightly users would be affected by this, but we check for this
+ // state here and fix the user if they are affected.
+ //
+ // We also fix the path in the case of the path not being quoted.
+ size_t currentServicePathLen = wcslen(currentServicePath);
+ bool doesServiceHaveCorrectPath =
+ currentServicePathLen > 2 &&
+ !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") &&
+ currentServicePath[0] == L'\"' &&
+ currentServicePath[currentServicePathLen - 1] == L'\"';
+
+ if (doesServiceHaveCorrectPath) {
+ LOG(("The MozillaMaintenance service path is correct."));
+ servicePathWasWrong = FALSE;
+ return TRUE;
+ }
+ // This is a recoverable situation so not logging as a warning
+ LOG(("The MozillaMaintenance path is NOT correct. It was: %ls",
+ currentServicePath));
+
+ servicePathWasWrong = TRUE;
+ WCHAR fixedPath[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(fixedPath, currentServicePath, MAX_PATH);
+ PathUnquoteSpacesW(fixedPath);
+ if (!PathRemoveFileSpecW(fixedPath)) {
+ LOG_WARN(("Couldn't remove file spec. (%d)", GetLastError()));
+ return FALSE;
+ }
+ if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe")) {
+ LOG_WARN(("Couldn't append file spec. (%d)", GetLastError()));
+ return FALSE;
+ }
+ PathQuoteSpacesW(fixedPath);
+
+
+ if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
+ SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr)) {
+ LOG_WARN(("Could not fix service path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("Fixed service path to: %ls.", fixedPath));
+ return TRUE;
+}
+
+/**
+ * Installs or upgrades the SVC_NAME service.
+ * If an existing service is already installed, we replace it with the
+ * currently running process.
+ *
+ * @param action The action to perform.
+ * @return TRUE if the service was installed/upgraded
+ */
+BOOL
+SvcInstall(SvcInstallAction action)
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager) {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ WCHAR newServiceBinaryPath[MAX_PATH + 1];
+ if (!GetModuleFileNameW(nullptr, newServiceBinaryPath,
+ sizeof(newServiceBinaryPath) /
+ sizeof(newServiceBinaryPath[0]))) {
+ LOG_WARN(("Could not obtain module filename when attempting to "
+ "install service. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Check if we already have the service installed.
+ nsAutoServiceHandle schService(OpenServiceW(schSCManager,
+ SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ DWORD lastError = GetLastError();
+ if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) {
+ // The service exists but we couldn't open it
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (schService) {
+ // The service exists but it may not have the correct permissions.
+ // This could happen if the permissions were not set correctly originally
+ // or have been changed after the installation. This will reset the
+ // permissions back to allow limited user accounts.
+ if (!SetUserAccessServiceDACL(schService)) {
+ LOG_WARN(("Could not reset security ACE on service handle. It might not be "
+ "possible to start the service. This error should never "
+ "happen. (%d)", GetLastError()));
+ }
+
+ // The service exists and we opened it
+ DWORD bytesNeeded;
+ if (!QueryServiceConfigW(schService, nullptr, 0, &bytesNeeded) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ LOG_WARN(("Could not determine buffer size for query service config. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+
+ // Get the service config information, in particular we want the binary
+ // path of the service.
+ mozilla::UniquePtr<char[]> serviceConfigBuffer(new char[bytesNeeded]);
+ if (!QueryServiceConfigW(schService,
+ reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
+ bytesNeeded, &bytesNeeded)) {
+ LOG_WARN(("Could open service but could not query service config. (%d)",
+ GetLastError()));
+ return FALSE;
+ }
+ QUERY_SERVICE_CONFIGW &serviceConfig =
+ *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
+
+ // Check if we need to fix the service path
+ BOOL servicePathWasWrong;
+ static BOOL alreadyCheckedFixServicePath = FALSE;
+ if (!alreadyCheckedFixServicePath) {
+ if (!FixServicePath(schService, serviceConfig.lpBinaryPathName,
+ servicePathWasWrong)) {
+ LOG_WARN(("Could not fix service path. This should never happen. (%d)",
+ GetLastError()));
+ // True is returned because the service is pointing to
+ // maintenanceservice_tmp.exe so it actually was upgraded to the
+ // newest installed service.
+ return TRUE;
+ } else if (servicePathWasWrong) {
+ // Now that the path is fixed we should re-attempt the install.
+ // This current process' image path is maintenanceservice_tmp.exe.
+ // The service used to point to maintenanceservice_tmp.exe.
+ // The service was just fixed to point to maintenanceservice.exe.
+ // Re-attempting an install from scratch will work as normal.
+ alreadyCheckedFixServicePath = TRUE;
+ LOG(("Restarting install action: %d", action));
+ return SvcInstall(action);
+ }
+ }
+
+ // Ensure the service path is not quoted. We own this memory and know it to
+ // be large enough for the quoted path, so it is large enough for the
+ // unquoted path. This function cannot fail.
+ PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
+
+ // Obtain the existing maintenanceservice file's version number and
+ // the new file's version number. Versions are in the format of
+ // A.B.C.D.
+ DWORD existingA, existingB, existingC, existingD;
+ DWORD newA, newB, newC, newD;
+ BOOL obtainedExistingVersionInfo =
+ GetVersionNumberFromPath(serviceConfig.lpBinaryPathName,
+ existingA, existingB,
+ existingC, existingD);
+ if (!GetVersionNumberFromPath(newServiceBinaryPath, newA,
+ newB, newC, newD)) {
+ LOG_WARN(("Could not obtain version number from new path"));
+ return FALSE;
+ }
+
+ // Check if we need to replace the old binary with the new one
+ // If we couldn't get the old version info then we assume we should
+ // replace it.
+ if (ForceInstallSvc == action ||
+ !obtainedExistingVersionInfo ||
+ (existingA < newA) ||
+ (existingA == newA && existingB < newB) ||
+ (existingA == newA && existingB == newB &&
+ existingC < newC) ||
+ (existingA == newA && existingB == newB &&
+ existingC == newC && existingD < newD)) {
+
+ // We have a newer updater, so update the description from the INI file.
+ UpdateServiceDescription(schService);
+
+ schService.reset();
+ if (!StopService()) {
+ return FALSE;
+ }
+
+ if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName)) {
+ LOG(("File is already in the correct location, no action needed for "
+ "upgrade. The path is: \"%ls\"", newServiceBinaryPath));
+ return TRUE;
+ }
+
+ BOOL result = TRUE;
+
+ // Attempt to copy the new binary over top the existing binary.
+ // If there is an error we try to move it out of the way and then
+ // copy it in. First try the safest / easiest way to overwrite the file.
+ if (!CopyFileW(newServiceBinaryPath,
+ serviceConfig.lpBinaryPathName, FALSE)) {
+ LOG_WARN(("Could not overwrite old service binary file. "
+ "This should never happen, but if it does the next "
+ "upgrade will fix it, the service is not a critical "
+ "component that needs to be installed for upgrades "
+ "to work. (%d)", GetLastError()));
+
+ // We rename the last 3 filename chars in an unsafe way. Manually
+ // verify there are more than 3 chars for safe failure in MoveFileExW.
+ const size_t len = wcslen(serviceConfig.lpBinaryPathName);
+ if (len > 3) {
+ // Calculate the temp file path that we're moving the file to. This
+ // is the same as the proper service path but with a .old extension.
+ LPWSTR oldServiceBinaryTempPath =
+ new WCHAR[len + 1];
+ memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR));
+ wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len);
+ // Rename the last 3 chars to 'old'
+ wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3);
+
+ // Move the current (old) service file to the temp path.
+ if (MoveFileExW(serviceConfig.lpBinaryPathName,
+ oldServiceBinaryTempPath,
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
+ // The old binary is moved out of the way, copy in the new one.
+ if (!CopyFileW(newServiceBinaryPath,
+ serviceConfig.lpBinaryPathName, FALSE)) {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("The new service binary could not be copied in."
+ " The service will not be upgraded."));
+ result = FALSE;
+ } else {
+ LOG(("The new service binary was copied in by first moving the"
+ " old one out of the way."));
+ }
+
+ // Attempt to get rid of the old service temp path.
+ if (DeleteFileW(oldServiceBinaryTempPath)) {
+ LOG(("The old temp service path was deleted: %ls.",
+ oldServiceBinaryTempPath));
+ } else {
+ // The old temp path could not be removed. It will be removed
+ // the next time the user can't copy the binary in or on uninstall.
+ LOG_WARN(("The old temp service path was not deleted."));
+ }
+ } else {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("Could not move old service file out of the way from:"
+ " \"%ls\" to \"%ls\". Service will not be upgraded. (%d)",
+ serviceConfig.lpBinaryPathName,
+ oldServiceBinaryTempPath, GetLastError()));
+ result = FALSE;
+ }
+ delete[] oldServiceBinaryTempPath;
+ } else {
+ // It is best to leave the old service binary in this condition.
+ LOG_WARN(("Service binary path was less than 3, service will"
+ " not be updated. This should never happen."));
+ result = FALSE;
+ }
+ } else {
+ LOG(("The new service binary was copied in."));
+ }
+
+ // We made a copy of ourselves to the existing location.
+ // The tmp file (the process of which we are executing right now) will be
+ // left over. Attempt to delete the file on the next reboot.
+ if (MoveFileExW(newServiceBinaryPath, nullptr,
+ MOVEFILE_DELAY_UNTIL_REBOOT)) {
+ LOG(("Deleting the old file path on the next reboot: %ls.",
+ newServiceBinaryPath));
+ } else {
+ LOG_WARN(("Call to delete the old file path failed: %ls.",
+ newServiceBinaryPath));
+ }
+
+ return result;
+ }
+
+ // We don't need to copy ourselves to the existing location.
+ // The tmp file (the process of which we are executing right now) will be
+ // left over. Attempt to delete the file on the next reboot.
+ MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
+
+ // nothing to do, we already have a newer service installed
+ return TRUE;
+ }
+
+ // If the service does not exist and we are upgrading, don't install it.
+ if (UpgradeSvc == action) {
+ // The service does not exist and we are upgrading, so don't install it
+ return TRUE;
+ }
+
+ // Quote the path only if it contains spaces.
+ PathQuoteSpacesW(newServiceBinaryPath);
+ // The service does not already exist so create the service as on demand
+ schService.own(CreateServiceW(schSCManager, SVC_NAME, SVC_DISPLAY_NAME,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ newServiceBinaryPath, nullptr, nullptr,
+ nullptr, nullptr, nullptr));
+ if (!schService) {
+ LOG_WARN(("Could not create Windows service. "
+ "This error should never happen since a service install "
+ "should only be called when elevated. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!SetUserAccessServiceDACL(schService)) {
+ LOG_WARN(("Could not set security ACE on service handle, the service will not "
+ "be able to be started from unelevated processes. "
+ "This error should never happen. (%d)",
+ GetLastError()));
+ }
+
+ UpdateServiceDescription(schService);
+
+ return TRUE;
+}
+
+/**
+ * Stops the Maintenance service.
+ *
+ * @return TRUE if successful.
+ */
+BOOL
+StopService()
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager) {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ if (!schService) {
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ LOG(("Sending stop request..."));
+ SERVICE_STATUS status;
+ SetLastError(ERROR_SUCCESS);
+ if (!ControlService(schService, SERVICE_CONTROL_STOP, &status) &&
+ GetLastError() != ERROR_SERVICE_NOT_ACTIVE) {
+ LOG_WARN(("Error sending stop request. (%d)", GetLastError()));
+ }
+
+ schSCManager.reset();
+ schService.reset();
+
+ LOG(("Waiting for service stop..."));
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 30);
+
+ // The service can be in a stopped state but the exe still in use
+ // so make sure the process is really gone before proceeding
+ WaitForProcessExit(L"maintenanceservice.exe", 30);
+ LOG(("Done waiting for service stop, last service state: %d", lastState));
+
+ return lastState == SERVICE_STOPPED;
+}
+
+/**
+ * Uninstalls the Maintenance service.
+ *
+ * @return TRUE if successful.
+ */
+BOOL
+SvcUninstall()
+{
+ // Get a handle to the local computer SCM database with full access rights.
+ nsAutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
+ SC_MANAGER_ALL_ACCESS));
+ if (!schSCManager) {
+ LOG_WARN(("Could not open service manager. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ // Open the service
+ nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME,
+ SERVICE_ALL_ACCESS));
+ if (!schService) {
+ LOG_WARN(("Could not open service. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ //Stop the service so it deletes faster and so the uninstaller
+ // can actually delete its EXE.
+ DWORD totalWaitTime = 0;
+ SERVICE_STATUS status;
+ static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
+ if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
+ do {
+ Sleep(status.dwWaitHint);
+ totalWaitTime += (status.dwWaitHint + 10);
+ if (status.dwCurrentState == SERVICE_STOPPED) {
+ break;
+ } else if (totalWaitTime > maxWaitTime) {
+ break;
+ }
+ } while (QueryServiceStatus(schService, &status));
+ }
+
+ // Delete the service or mark it for deletion
+ BOOL deleted = DeleteService(schService);
+ if (!deleted) {
+ deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE);
+ }
+
+ return deleted;
+}
+
+/**
+ * Sets the access control list for user access for the specified service.
+ *
+ * @param hService The service to set the access control list on
+ * @return TRUE if successful
+ */
+BOOL
+SetUserAccessServiceDACL(SC_HANDLE hService)
+{
+ PACL pNewAcl = nullptr;
+ PSECURITY_DESCRIPTOR psd = nullptr;
+ DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd);
+ if (pNewAcl) {
+ LocalFree((HLOCAL)pNewAcl);
+ }
+ if (psd) {
+ LocalFree((LPVOID)psd);
+ }
+ return ERROR_SUCCESS == lastError;
+}
+
+/**
+ * Sets the access control list for user access for the specified service.
+ *
+ * @param hService The service to set the access control list on
+ * @param pNewAcl The out param ACL which should be freed by caller
+ * @param psd out param security descriptor, should be freed by caller
+ * @return ERROR_SUCCESS if successful
+ */
+DWORD
+SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
+ PSECURITY_DESCRIPTOR psd)
+{
+ // Get the current security descriptor needed size
+ DWORD needed = 0;
+ if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
+ &psd, 0, &needed)) {
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ LOG_WARN(("Could not query service object security size. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ DWORD size = needed;
+ psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
+ if (!psd) {
+ LOG_WARN(("Could not allocate security descriptor. (%d)",
+ GetLastError()));
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+
+ // Get the actual security descriptor now
+ if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
+ psd, size, &needed)) {
+ LOG_WARN(("Could not allocate security descriptor. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+ }
+
+ // Get the current DACL from the security descriptor.
+ PACL pacl = nullptr;
+ BOOL bDaclPresent = FALSE;
+ BOOL bDaclDefaulted = FALSE;
+ if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl,
+ &bDaclDefaulted)) {
+ LOG_WARN(("Could not obtain DACL. (%d)", GetLastError()));
+ return GetLastError();
+ }
+
+ PSID sid;
+ DWORD SIDSize = SECURITY_MAX_SID_SIZE;
+ sid = LocalAlloc(LMEM_FIXED, SIDSize);
+ if (!sid) {
+ LOG_WARN(("Could not allocate SID memory. (%d)", GetLastError()));
+ return GetLastError();
+ }
+
+ if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize)) {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create well known SID. (%d)", lastError));
+ LocalFree(sid);
+ return lastError;
+ }
+
+ // Lookup the account name, the function fails if you don't pass in
+ // a buffer for the domain name but it's not used since we're using
+ // the built in account Sid.
+ SID_NAME_USE accountType;
+ WCHAR accountName[UNLEN + 1] = { L'\0' };
+ WCHAR domainName[DNLEN + 1] = { L'\0' };
+ DWORD accountNameSize = UNLEN + 1;
+ DWORD domainNameSize = DNLEN + 1;
+ if (!LookupAccountSidW(nullptr, sid, accountName,
+ &accountNameSize,
+ domainName, &domainNameSize, &accountType)) {
+ LOG_WARN(("Could not lookup account Sid, will try Users. (%d)",
+ GetLastError()));
+ wcsncpy(accountName, L"Users", UNLEN);
+ }
+
+ // We already have the group name so we can get rid of the SID
+ FreeSid(sid);
+ sid = nullptr;
+
+ // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged.
+ EXPLICIT_ACCESS ea;
+ BuildExplicitAccessWithNameW(&ea, accountName,
+ SERVICE_START | SERVICE_STOP | GENERIC_READ,
+ SET_ACCESS, NO_INHERITANCE);
+ DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl);
+ if (ERROR_SUCCESS != lastError) {
+ LOG_WARN(("Could not set entries in ACL. (%d)", lastError));
+ return lastError;
+ }
+
+ // Initialize a new security descriptor.
+ SECURITY_DESCRIPTOR sd;
+ if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
+ LOG_WARN(("Could not initialize security descriptor. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Set the new DACL in the security descriptor.
+ if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE)) {
+ LOG_WARN(("Could not set security descriptor DACL. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Set the new security descriptor for the service object.
+ if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd)) {
+ LOG_WARN(("Could not set object security. (%d)",
+ GetLastError()));
+ return GetLastError();
+ }
+
+ // Woohoo, raise the roof
+ LOG(("User access was set successfully on the service."));
+ return ERROR_SUCCESS;
+}
diff --git a/toolkit/components/maintenanceservice/serviceinstall.h b/toolkit/components/maintenanceservice/serviceinstall.h
new file mode 100644
index 000000000..d8532a968
--- /dev/null
+++ b/toolkit/components/maintenanceservice/serviceinstall.h
@@ -0,0 +1,21 @@
+/* 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 "readstrings.h"
+
+#define SVC_DISPLAY_NAME L"Mozilla Maintenance Service"
+
+enum SvcInstallAction { UpgradeSvc, InstallSvc, ForceInstallSvc };
+BOOL SvcInstall(SvcInstallAction action);
+BOOL SvcUninstall();
+BOOL StopService();
+BOOL SetUserAccessServiceDACL(SC_HANDLE hService);
+DWORD SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
+ PSECURITY_DESCRIPTOR psd);
+
+struct MaintenanceServiceStringTable
+{
+ char serviceDescription[MAX_TEXT_LEN];
+};
+
diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
new file mode 100644
index 000000000..39044a6c4
--- /dev/null
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -0,0 +1,635 @@
+/* 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 <shlobj.h>
+#include <shlwapi.h>
+#include <wtsapi32.h>
+#include <userenv.h>
+#include <shellapi.h>
+
+#pragma comment(lib, "wtsapi32.lib")
+#pragma comment(lib, "userenv.lib")
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "rpcrt4.lib")
+
+#include "nsWindowsHelpers.h"
+
+#include "workmonitor.h"
+#include "serviceinstall.h"
+#include "servicebase.h"
+#include "registrycertificates.h"
+#include "uachelper.h"
+#include "updatehelper.h"
+#include "errors.h"
+
+// Wait 15 minutes for an update operation to run at most.
+// Updates usually take less than a minute so this seems like a
+// significantly large and safe amount of time to wait.
+static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
+wchar_t* MakeCommandLine(int argc, wchar_t** argv);
+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
+ LPCWSTR newFileName);
+
+/*
+ * Read the update.status file and sets isApplying to true if
+ * the status is set to applying.
+ *
+ * @param updateDirPath The directory where update.status is stored
+ * @param isApplying Out parameter for specifying if the status
+ * is set to applying or not.
+ * @return TRUE if the information was filled.
+*/
+static BOOL
+IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying)
+{
+ isApplying = FALSE;
+ WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'};
+ wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
+ if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
+ LOG_WARN(("Could not append path for update.status file"));
+ return FALSE;
+ }
+
+ nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ,
+ FILE_SHARE_READ |
+ FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+
+ if (INVALID_HANDLE_VALUE == statusFile) {
+ LOG_WARN(("Could not open update.status file"));
+ return FALSE;
+ }
+
+ char buf[32] = { 0 };
+ DWORD read;
+ if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) {
+ LOG_WARN(("Could not read from update.status file"));
+ return FALSE;
+ }
+
+ LOG(("updater.exe returned status: %s", buf));
+
+ const char kApplying[] = "applying";
+ isApplying = strncmp(buf, kApplying,
+ sizeof(kApplying) - 1) == 0;
+ return TRUE;
+}
+
+/**
+ * Determines whether we're staging an update.
+ *
+ * @param argc The argc value normally sent to updater.exe
+ * @param argv The argv value normally sent to updater.exe
+ * @return boolean True if we're staging an update
+ */
+static bool
+IsUpdateBeingStaged(int argc, LPWSTR *argv)
+{
+ // PID will be set to -1 if we're supposed to stage an update.
+ return argc == 4 && !wcscmp(argv[3], L"-1") ||
+ argc == 5 && !wcscmp(argv[4], L"-1");
+}
+
+/**
+ * Determines whether the param only contains digits.
+ *
+ * @param str The string to check
+ * @param boolean True if the param only contains digits
+ */
+static bool
+IsDigits(WCHAR *str)
+{
+ while (*str) {
+ if (!iswdigit(*str++)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Determines whether the command line contains just the directory to apply the
+ * update to (old command line) or if it contains the installation directory and
+ * the directory to apply the update to.
+ *
+ * @param argc The argc value normally sent to updater.exe
+ * @param argv The argv value normally sent to updater.exe
+ * @param boolean True if the command line contains just the directory to apply
+ * the update to
+ */
+static bool
+IsOldCommandline(int argc, LPWSTR *argv)
+{
+ return argc == 4 && !wcscmp(argv[3], L"-1") ||
+ argc >= 4 && (wcsstr(argv[3], L"/replace") || IsDigits(argv[3]));
+}
+
+/**
+ * Gets the installation directory from the arguments passed to updater.exe.
+ *
+ * @param argcTmp The argc value normally sent to updater.exe
+ * @param argvTmp The argv value normally sent to updater.exe
+ * @param aResultDir Buffer to hold the installation directory.
+ */
+static BOOL
+GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1])
+{
+ int index = 3;
+ if (IsOldCommandline(argcTmp, argvTmp)) {
+ index = 2;
+ }
+
+ if (argcTmp < index) {
+ return FALSE;
+ }
+
+ wcsncpy(aResultDir, argvTmp[2], MAX_PATH);
+ WCHAR* backSlash = wcsrchr(aResultDir, L'\\');
+ // Make sure that the path does not include trailing backslashes
+ if (backSlash && (backSlash[1] == L'\0')) {
+ *backSlash = L'\0';
+ }
+
+ // The new command line's argv[2] is always the installation directory.
+ if (index == 2) {
+ bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp);
+ bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace"));
+ if (backgroundUpdate || replaceRequest) {
+ return PathRemoveFileSpecW(aResultDir);
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Runs an update process as the service using the SYSTEM account.
+ *
+ * @param argc The number of arguments in argv
+ * @param argv The arguments normally passed to updater.exe
+ * argv[0] must be the path to updater.exe
+ * @param processStarted Set to TRUE if the process was started.
+ * @return TRUE if the update process was run had a return code of 0.
+ */
+BOOL
+StartUpdateProcess(int argc,
+ LPWSTR *argv,
+ LPCWSTR installDir,
+ BOOL &processStarted)
+{
+ LOG(("Starting update process as the service in session 0."));
+ STARTUPINFO si = {0};
+ si.cb = sizeof(STARTUPINFO);
+ si.lpDesktop = L"winsta0\\Default";
+ PROCESS_INFORMATION pi = {0};
+
+ // The updater command line is of the form:
+ // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
+ LPWSTR cmdLine = MakeCommandLine(argc, argv);
+
+ int index = 3;
+ if (IsOldCommandline(argc, argv)) {
+ index = 2;
+ }
+
+ // If we're about to start the update process from session 0,
+ // then we should not show a GUI. This only really needs to be done
+ // on Vista and higher, but it's better to keep everything consistent
+ // across all OS if it's of no harm.
+ if (argc >= index) {
+ // Setting the desktop to blank will ensure no GUI is displayed
+ si.lpDesktop = L"";
+ si.dwFlags |= STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ }
+
+ // Add an env var for MOZ_USING_SERVICE so the updater.exe can
+ // do anything special that it needs to do for service updates.
+ // Search in updater.cpp for more info on MOZ_USING_SERVICE.
+ putenv(const_cast<char*>("MOZ_USING_SERVICE=1"));
+ LOG(("Starting service with cmdline: %ls", cmdLine));
+ processStarted = CreateProcessW(argv[0], cmdLine,
+ nullptr, nullptr, FALSE,
+ CREATE_DEFAULT_ERROR_MODE,
+ nullptr,
+ nullptr, &si, &pi);
+
+ BOOL updateWasSuccessful = FALSE;
+ if (processStarted) {
+ // Wait for the updater process to finish
+ LOG(("Process was started... waiting on result."));
+ DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
+ if (WAIT_TIMEOUT == waitRes) {
+ // We waited a long period of time for updater.exe and it never finished
+ // so kill it.
+ TerminateProcess(pi.hProcess, 1);
+ } else {
+ // Check the return code of updater.exe to make sure we get 0
+ DWORD returnCode;
+ if (GetExitCodeProcess(pi.hProcess, &returnCode)) {
+ LOG(("Process finished with return code %d.", returnCode));
+ // updater returns 0 if successful.
+ updateWasSuccessful = (returnCode == 0);
+ } else {
+ LOG_WARN(("Process finished but could not obtain return code."));
+ }
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ // Check just in case updater.exe didn't change the status from
+ // applying. If this is the case we report an error.
+ BOOL isApplying = FALSE;
+ if (IsStatusApplying(argv[1], isApplying) && isApplying) {
+ if (updateWasSuccessful) {
+ LOG(("update.status is still applying even know update "
+ " was successful."));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_STILL_APPLYING_ON_SUCCESS)) {
+ LOG_WARN(("Could not write update.status still applying on"
+ " success error."));
+ }
+ // Since we still had applying we know updater.exe didn't do its
+ // job correctly.
+ updateWasSuccessful = FALSE;
+ } else {
+ LOG_WARN(("update.status is still applying and update was not successful."));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_STILL_APPLYING_ON_FAILURE)) {
+ LOG_WARN(("Could not write update.status still applying on"
+ " success error."));
+ }
+ }
+ }
+ } else {
+ DWORD lastError = GetLastError();
+ LOG_WARN(("Could not create process as current user, "
+ "updaterPath: %ls; cmdLine: %ls. (%d)",
+ argv[0], cmdLine, lastError));
+ }
+ // Empty value on putenv is how you remove an env variable in Windows
+ putenv(const_cast<char*>("MOZ_USING_SERVICE="));
+
+ free(cmdLine);
+ return updateWasSuccessful;
+}
+
+/**
+ * Processes a software update command
+ *
+ * @param argc The number of arguments in argv
+ * @param argv The arguments normally passed to updater.exe
+ * argv[0] must be the path to updater.exe
+ * @return TRUE if the update was successful.
+ */
+BOOL
+ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
+{
+ BOOL result = TRUE;
+ if (argc < 3) {
+ LOG_WARN(("Not enough command line parameters specified. "
+ "Updating update.status."));
+
+ // We can only update update.status if argv[1] exists. argv[1] is
+ // the directory where the update.status file exists.
+ if (argc < 2 ||
+ !WriteStatusFailure(argv[1],
+ SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ GetLastError()));
+ }
+ return FALSE;
+ }
+
+ WCHAR installDir[MAX_PATH + 1] = {L'\0'};
+ if (!GetInstallationDir(argc, argv, installDir)) {
+ LOG_WARN(("Could not get the installation directory"));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_INSTALLDIR_ERROR)) {
+ LOG_WARN(("Could not write update.status for GetInstallationDir failure."));
+ }
+ return FALSE;
+ }
+
+ // Make sure the path to the updater to use for the update is local.
+ // We do this check to make sure that file locking is available for
+ // race condition security checks.
+ BOOL isLocal = FALSE;
+ if (!IsLocalFile(argv[0], isLocal) || !isLocal) {
+ LOG_WARN(("Filesystem in path %ls is not supported (%d)",
+ argv[0], GetLastError()));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_NOT_FIXED_DRIVE)) {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ GetLastError()));
+ }
+ return FALSE;
+ }
+
+ nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, 0, nullptr));
+ if (INVALID_HANDLE_VALUE == noWriteLock) {
+ LOG_WARN(("Could not set no write sharing access on file. (%d)",
+ GetLastError()));
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_COULD_NOT_LOCK_UPDATER)) {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ GetLastError()));
+ }
+ return FALSE;
+ }
+
+ // Verify that the updater.exe that we are executing is the same
+ // as the one in the installation directory which we are updating.
+ // The installation dir that we are installing to is installDir.
+ WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(installDirUpdater, installDir, MAX_PATH);
+ if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
+ LOG_WARN(("Install directory updater could not be determined."));
+ result = FALSE;
+ }
+
+ BOOL updaterIsCorrect;
+ if (result && !VerifySameFiles(argv[0], installDirUpdater,
+ updaterIsCorrect)) {
+ LOG_WARN(("Error checking if the updaters are the same.\n"
+ "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater));
+ result = FALSE;
+ }
+
+ if (result && !updaterIsCorrect) {
+ LOG_WARN(("The updaters do not match, updater will not run.\n"
+ "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater));
+ result = FALSE;
+ }
+
+ if (result) {
+ LOG(("updater.exe was compared successfully to the installation directory"
+ " updater.exe."));
+ } else {
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_COMPARE_ERROR)) {
+ LOG_WARN(("Could not write update.status updater compare failure."));
+ }
+ return FALSE;
+ }
+
+ // Check to make sure the updater.exe module has the unique updater identity.
+ // This is a security measure to make sure that the signed executable that
+ // we will run is actually an updater.
+ HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr,
+ LOAD_LIBRARY_AS_DATAFILE);
+ if (!updaterModule) {
+ LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError()));
+ result = FALSE;
+ } else {
+ char updaterIdentity[64];
+ if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY,
+ updaterIdentity, sizeof(updaterIdentity))) {
+ LOG_WARN(("The updater.exe application does not contain the Mozilla"
+ " updater identity."));
+ result = FALSE;
+ }
+
+ if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) {
+ LOG_WARN(("The updater.exe identity string is not valid."));
+ result = FALSE;
+ }
+ FreeLibrary(updaterModule);
+ }
+
+ if (result) {
+ LOG(("The updater.exe application contains the Mozilla"
+ " updater identity."));
+ } else {
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_IDENTITY_ERROR)) {
+ LOG_WARN(("Could not write update.status no updater identity."));
+ }
+ return TRUE;
+ }
+
+ // Check for updater.exe sign problems
+ BOOL updaterSignProblem = FALSE;
+#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
+ updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir,
+ argv[0]);
+#endif
+
+ // Only proceed with the update if we have no signing problems
+ if (!updaterSignProblem) {
+ BOOL updateProcessWasStarted = FALSE;
+ if (StartUpdateProcess(argc, argv, installDir,
+ updateProcessWasStarted)) {
+ LOG(("updater.exe was launched and run successfully!"));
+ LogFlush();
+
+ // Don't attempt to update the service when the update is being staged.
+ if (!IsUpdateBeingStaged(argc, argv)) {
+ // We might not execute code after StartServiceUpdate because
+ // the service installer will stop the service if it is running.
+ StartServiceUpdate(installDir);
+ }
+ } else {
+ result = FALSE;
+ LOG_WARN(("Error running update process. Updating update.status (%d)",
+ GetLastError()));
+ LogFlush();
+
+ // If the update process was started, then updater.exe is responsible for
+ // setting the failure code. If it could not be started then we do the
+ // work. We set an error instead of directly setting status pending
+ // so that the app.update.service.errors pref can be updated when
+ // the callback app restarts.
+ if (!updateProcessWasStarted) {
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_COULD_NOT_BE_STARTED)) {
+ LOG_WARN(("Could not write update.status service update failure. (%d)",
+ GetLastError()));
+ }
+ }
+ }
+ } else {
+ result = FALSE;
+ LOG_WARN(("Could not start process due to certificate check error on "
+ "updater.exe. Updating update.status. (%d)", GetLastError()));
+
+ // When there is a certificate check error on the updater.exe application,
+ // we want to write out the error.
+ if (!WriteStatusFailure(argv[1],
+ SERVICE_UPDATER_SIGN_ERROR)) {
+ LOG_WARN(("Could not write pending state to update.status. (%d)",
+ GetLastError()));
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Obtains the updater path alongside a subdir of the service binary.
+ * The purpose of this function is to return a path that is likely high
+ * integrity and therefore more safe to execute code from.
+ *
+ * @param serviceUpdaterPath Out parameter for the path where the updater
+ * should be copied to.
+ * @return TRUE if a file path was obtained.
+ */
+BOOL
+GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1])
+{
+ if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) {
+ LOG_WARN(("Could not obtain module filename when attempting to "
+ "use a secure updater path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathRemoveFileSpecW(serviceUpdaterPath)) {
+ LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
+ "updater path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (!PathAppendSafe(serviceUpdaterPath, L"update")) {
+ LOG_WARN(("Couldn't append file spec when attempting to use a secure "
+ "updater path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ CreateDirectoryW(serviceUpdaterPath, nullptr);
+
+ if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) {
+ LOG_WARN(("Couldn't append file spec when attempting to use a secure "
+ "updater path. (%d)", GetLastError()));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Deletes the passed in updater path and the associated updater.ini file.
+ *
+ * @param serviceUpdaterPath The path to delete.
+ * @return TRUE if a file was deleted.
+ */
+BOOL
+DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1])
+{
+ BOOL result = FALSE;
+ if (serviceUpdaterPath[0]) {
+ result = DeleteFileW(serviceUpdaterPath);
+ if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
+ GetLastError() != ERROR_FILE_NOT_FOUND) {
+ LOG_WARN(("Could not delete service updater path: '%ls'.",
+ serviceUpdaterPath));
+ }
+
+ WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' };
+ if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath,
+ L"updater.ini")) {
+ result = DeleteFileW(updaterINIPath);
+ if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
+ GetLastError() != ERROR_FILE_NOT_FOUND) {
+ LOG_WARN(("Could not delete service updater INI path: '%ls'.",
+ updaterINIPath));
+ }
+ }
+ }
+ return result;
+}
+
+/**
+ * Executes a service command.
+ *
+ * @param argc The number of arguments in argv
+ * @param argv The service command line arguments, argv[0] and argv[1]
+ * and automatically included by Windows. argv[2] is the
+ * service command.
+ *
+ * @return FALSE if there was an error executing the service command.
+ */
+BOOL
+ExecuteServiceCommand(int argc, LPWSTR *argv)
+{
+ if (argc < 3) {
+ LOG_WARN(("Not enough command line arguments to execute a service command"));
+ return FALSE;
+ }
+
+ // The tests work by making sure the log has changed, so we put a
+ // unique ID in the log.
+ RPC_WSTR guidString = RPC_WSTR(L"");
+ GUID guid;
+ HRESULT hr = CoCreateGuid(&guid);
+ if (SUCCEEDED(hr)) {
+ UuidToString(&guid, &guidString);
+ }
+ LOG(("Executing service command %ls, ID: %ls",
+ argv[2], reinterpret_cast<LPCWSTR>(guidString)));
+ RpcStringFree(&guidString);
+
+ BOOL result = FALSE;
+ if (!lstrcmpi(argv[2], L"software-update")) {
+
+ // Use the passed in command line arguments for the update, except for the
+ // path to updater.exe. We copy updater.exe to a the directory of the
+ // MozillaMaintenance service so that a low integrity process cannot
+ // replace the updater.exe at any point and use that for the update.
+ // It also makes DLL injection attacks harder.
+ LPWSTR oldUpdaterPath = argv[3];
+ WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
+ result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging
+ if (result) {
+ LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
+ oldUpdaterPath, secureUpdaterPath));
+ DeleteSecureUpdater(secureUpdaterPath);
+ result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE);
+ }
+
+ if (!result) {
+ LOG_WARN(("Could not copy path to secure location. (%d)",
+ GetLastError()));
+ if (argc > 4 && !WriteStatusFailure(argv[4],
+ SERVICE_COULD_NOT_COPY_UPDATER)) {
+ LOG_WARN(("Could not write update.status could not copy updater error"));
+ }
+ } else {
+
+ // We obtained the path and copied it successfully, update the path to
+ // use for the service update.
+ argv[3] = secureUpdaterPath;
+
+ WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
+ WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
+ if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath,
+ L"updater.ini") &&
+ PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath,
+ L"updater.ini")) {
+ // This is non fatal if it fails there is no real harm
+ if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) {
+ LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
+ oldUpdaterINIPath, secureUpdaterINIPath, GetLastError()));
+ }
+ }
+
+ result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
+ DeleteSecureUpdater(secureUpdaterPath);
+ }
+
+ // We might not reach here if the service install succeeded
+ // because the service self updates itself and the service
+ // installer will stop the service.
+ LOG(("Service command %ls complete.", argv[2]));
+ } else {
+ LOG_WARN(("Service command not recognized: %ls.", argv[2]));
+ // result is already set to FALSE
+ }
+
+ LOG(("service command %ls complete with result: %ls.",
+ argv[1], (result ? L"Success" : L"Failure")));
+ return TRUE;
+}
diff --git a/toolkit/components/maintenanceservice/workmonitor.h b/toolkit/components/maintenanceservice/workmonitor.h
new file mode 100644
index 000000000..ac4cd679b
--- /dev/null
+++ b/toolkit/components/maintenanceservice/workmonitor.h
@@ -0,0 +1,5 @@
+/* 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 ExecuteServiceCommand(int argc, LPWSTR *argv);
diff --git a/toolkit/moz.build b/toolkit/moz.build
index f5f2e288f..fd0f50dcc 100644
--- a/toolkit/moz.build
+++ b/toolkit/moz.build
@@ -25,6 +25,9 @@ DIRS += [
DIRS += ['mozapps/update']
+if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ DIRS += ['components/maintenanceservice']
+
DIRS += ['xre']
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'qt'):
diff --git a/toolkit/mozapps/installer/windows/nsis/makensis.mk b/toolkit/mozapps/installer/windows/nsis/makensis.mk
index 121819f6a..49aeca5cf 100644
--- a/toolkit/mozapps/installer/windows/nsis/makensis.mk
+++ b/toolkit/mozapps/installer/windows/nsis/makensis.mk
@@ -75,3 +75,10 @@ uninstaller::
cd $(CONFIG_DIR) && $(MAKENSISU) uninstaller.nsi
$(NSINSTALL) -D $(DIST)/bin/uninstall
cp $(CONFIG_DIR)/helper.exe $(DIST)/bin/uninstall
+
+ifdef MOZ_MAINTENANCE_SERVICE
+maintenanceservice_installer::
+ cd $(CONFIG_DIR) && $(MAKENSISU) maintenanceservice_installer.nsi
+ $(NSINSTALL) -D $(DIST)/bin/
+ cp $(CONFIG_DIR)/maintenanceservice_installer.exe $(DIST)/bin
+endif \ No newline at end of file
diff --git a/toolkit/mozapps/update/moz.build b/toolkit/mozapps/update/moz.build
index 94de7d42a..136b3c91e 100644
--- a/toolkit/mozapps/update/moz.build
+++ b/toolkit/mozapps/update/moz.build
@@ -5,11 +5,18 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
- if CONFIG['MOZ_UPDATER']:
- DIRS += ['common', 'updater']
+ if CONFIG['MOZ_UPDATER'] or CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ # If only the maintenance service is installed and not
+ # the updater, then the maintenance service may still be
+ # used for other things. We need to build update/common
+ # which the maintenance service uses.
+ DIRS += ['common']
if CONFIG['OS_ARCH'] == 'WINNT':
DIRS += ['common-standalone']
+ if CONFIG['MOZ_UPDATER']:
+ DIRS += ['updater']
+
XPIDL_MODULE = 'update'
XPCSHELL_TESTS_MANIFESTS += ['tests/unit_timermanager/xpcshell.ini']
diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
index 61de54caf..977a1aa61 100644
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -53,6 +53,9 @@ const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
+const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
+const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
+const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
@@ -167,6 +170,10 @@ const DOWNLOAD_FOREGROUND_INTERVAL = 0;
const UPDATE_WINDOW_NAME = "Update:Wizard";
+// The number of consecutive failures when updating using the service before
+// setting the app.update.service.enabled preference to false.
+const DEFAULT_SERVICE_MAX_ERRORS = 10;
+
// The number of consecutive socket errors to allow before falling back to
// downloading a different MAR file or failing if already downloading the full.
const DEFAULT_SOCKET_MAX_ERRORS = 10;
@@ -715,6 +722,15 @@ function getCanStageUpdates() {
}
#endif
+#ifdef MOZ_WIDGET_GONK
+ // For Gonk, the updater will remount the /system partition to move staged
+ // files into place.
+ if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
+ LOG("getCanStageUpdates - able to stage updates because this is gonk");
+ return true;
+ }
+#endif
+
if (!hasUpdateMutex()) {
LOG("getCanStageUpdates - unable to apply updates because another " +
"instance of the application is already handling updates for this " +
@@ -1143,7 +1159,12 @@ function releaseSDCardMountLock() {
* @return true if the service should be used for updates.
*/
function shouldUseService() {
+#ifdef MOZ_MAINTENANCE_SERVICE
+ return getPref("getBoolPref",
+ PREF_APP_UPDATE_SERVICE_ENABLED, false);
+#else
return false;
+#endif
}
/**
@@ -1496,6 +1517,24 @@ function handleUpdateFailure(update, errorCode) {
update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER ||
update.errorCode == SERVICE_INSTALLDIR_ERROR) {
+ var failCount = getPref("getIntPref",
+ PREF_APP_UPDATE_SERVICE_ERRORS, 0);
+ var maxFail = getPref("getIntPref",
+ PREF_APP_UPDATE_SERVICE_MAX_ERRORS,
+ DEFAULT_SERVICE_MAX_ERRORS);
+
+ // As a safety, when the service reaches maximum failures, it will
+ // disable itself and fallback to using the normal update mechanism
+ // without the service.
+ if (failCount >= maxFail) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
+ } else {
+ failCount++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
+ failCount);
+ }
+
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
try {
Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
@@ -2579,6 +2618,14 @@ UpdateService.prototype = {
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
"UPDATER_STAGE_ENABLED");
+#ifdef XP_WIN
+ this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
+ "UPDATER_SERVICE_ENABLED");
+ this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
+ "UPDATER_SERVICE_ERRORS");
+ this._sendServiceInstalledTelemetryPing();
+#endif
+
this._checkForBackgroundUpdates(true);
},
diff --git a/toolkit/mozapps/update/tests/Makefile.in b/toolkit/mozapps/update/tests/Makefile.in
index d3cd98c94..b9310233d 100644
--- a/toolkit/mozapps/update/tests/Makefile.in
+++ b/toolkit/mozapps/update/tests/Makefile.in
@@ -23,6 +23,13 @@ base-updater-head_TARGET := misc
base-updater-head_DEST := $(XPCSHELLTESTROOT)/unit_base_updater
base-updater-head_FILES := $(XPCSHELLTESTROOT)/unit_aus_update/head_update.js
+ifdef MOZ_MAINTENANCE_SERVICE
+INSTALL_TARGETS += service-updater-head
+service-updater-head_TARGET := misc
+service-updater-head_DEST := $(XPCSHELLTESTROOT)/unit_service_updater
+service-updater-head_FILES := $(XPCSHELLTESTROOT)/unit_aus_update/head_update.js
+endif # MOZ_MAINTENANCE_SERVICE
+
ifndef MOZ_PROFILE_GENERATE
ifdef COMPILE_ENVIRONMENT
INSTALL_TARGETS += xpcshell-test-helper
diff --git a/toolkit/mozapps/update/tests/moz.build b/toolkit/mozapps/update/tests/moz.build
index 15373e75a..9081e5be4 100644
--- a/toolkit/mozapps/update/tests/moz.build
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -12,6 +12,9 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
XPCSHELL_TESTS_MANIFESTS += ['unit_base_updater/xpcshell.ini']
+ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ XPCSHELL_TESTS_MANIFESTS += ['unit_service_updater/xpcshell.ini']
+
SimplePrograms([
'TestAUSHelper',
'TestAUSReadStrings',
@@ -43,6 +46,9 @@ for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_DISPLAYNAME',
DEFINES['NS_NO_XPCOM'] = True
+if CONFIG['MOZ_MAINTENANCE_SERVICE']:
+ DEFINES['MOZ_MAINTENANCE_SERVICE'] = CONFIG['MOZ_MAINTENANCE_SERVICE']
+
# For debugging purposes only
#DEFINES['DISABLE_UPDATER_AUTHENTICODE_CHECK'] = True
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
new file mode 100644
index 000000000..ffa207ebb
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Bootstrap the tests using the service by installing our own version of the service */
+
+function run_test() {
+ if (!shouldRunServiceTest(true)) {
+ return;
+ }
+
+ setupTestCommon();
+ // We don't actually care if the MAR has any data, we only care about the
+ // application return code and update.status result.
+ gTestFiles = gTestFilesCommon;
+ gTestDirs = [];
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED, false);
+}
+
+function checkUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+
+ // We need to check the service log even though this is a bootstrap
+ // because the app bin could be in use by this test by the time the next
+ // test runs.
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
new file mode 100644
index 000000000..869c3634b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+function run_test() {
+ if (MOZ_APP_NAME == "xulrunner") {
+ logTestInfo("Unable to run this test on xulrunner");
+ return;
+ }
+
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ createUpdaterINI(false);
+
+ if (IS_WIN) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true);
+ }
+
+ let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ STATE_PENDING_SVC);
+ let updates = getLocalUpdateString(patches, null, null, null, null, null,
+ null, null, null, null, null, null,
+ null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(getAppVersion());
+ writeStatusFile(STATE_PENDING_SVC);
+
+ reloadUpdateManagerData();
+ do_check_true(!!gUpdateManager.activeUpdate);
+
+ lockDirectory(getAppBaseDir());
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ stageUpdate();
+}
+
+function end_test() {
+ resetEnvironment();
+}
+
+/**
+ * Checks if staging the update has failed.
+ */
+function checkUpdateApplied() {
+ // Don't proceed until the update has failed, and reset to pending.
+ if (gUpdateManager.activeUpdate.state != STATE_PENDING) {
+ if (++gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for update to equal: " +
+ STATE_PENDING +
+ ", current state: " + gUpdateManager.activeUpdate.state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ do_timeout(TEST_CHECK_TIMEOUT, finishTest);
+}
+
+function finishTest() {
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ do_check_eq(readStatusState(), STATE_PENDING);
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ unlockDirectory(getAppBaseDir());
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
new file mode 100644
index 000000000..ba063244e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
@@ -0,0 +1,320 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+function run_test() {
+ if (MOZ_APP_NAME == "xulrunner") {
+ logTestInfo("Unable to run this test on xulrunner");
+ return;
+ }
+
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI(true);
+
+ if (IS_WIN) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true);
+ }
+
+ let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ STATE_PENDING_SVC);
+ let updates = getLocalUpdateString(patches, null, null, null, null, null,
+ null, null, null, null, null, null,
+ null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(getAppVersion());
+ writeStatusFile(STATE_PENDING_SVC);
+
+ reloadUpdateManagerData();
+ do_check_true(!!gUpdateManager.activeUpdate);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ stageUpdate();
+}
+
+function customLaunchAppToApplyUpdate() {
+ logTestInfo("start - locking installation directory");
+ const LPCWSTR = ctypes.char16_t.ptr;
+ const DWORD = ctypes.uint32_t;
+ const LPVOID = ctypes.voidptr_t;
+ const GENERIC_READ = 0x80000000;
+ const FILE_SHARE_READ = 1;
+ const FILE_SHARE_WRITE = 2;
+ const OPEN_EXISTING = 3;
+ const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+ const INVALID_HANDLE_VALUE = LPVOID(0xffffffff);
+ let kernel32 = ctypes.open("kernel32");
+ let CreateFile = kernel32.declare("CreateFileW", ctypes.default_abi,
+ LPVOID, LPCWSTR, DWORD, DWORD,
+ LPVOID, DWORD, DWORD, LPVOID);
+ gHandle = CreateFile(getAppBaseDir().path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, LPVOID(0),
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, LPVOID(0));
+ do_check_neq(gHandle.toString(), INVALID_HANDLE_VALUE.toString());
+ kernel32.close();
+ logTestInfo("finish - locking installation directory");
+}
+
+/**
+ * Checks if the update has finished being staged.
+ */
+function checkUpdateApplied() {
+ gTimeoutRuns++;
+ // Don't proceed until the active update's state is the expected value.
+ if (gUpdateManager.activeUpdate.state != STATE_APPLIED_SVC) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for update to equal: " +
+ STATE_APPLIED_SVC +
+ ", current state: " + gUpdateManager.activeUpdate.state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ // Don't proceed until the update's status state is the expected value.
+ let state = readStatusState();
+ if (state != STATE_APPLIED_SVC) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status state to equal: " +
+ STATE_APPLIED_SVC +
+ ", current status state: " + state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ // Don't proceed until the last update log has been created.
+ let log;
+ if (IS_WIN || IS_MACOSX) {
+ log = getUpdatesDir();
+ } else {
+ log = getStageDirFile(null, true);
+ log.append(DIR_UPDATES);
+ }
+ log.append(FILE_LAST_LOG);
+ if (!log.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update log " +
+ "to be created. Path: " + log.path);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+
+ log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_LAST_LOG);
+ if (IS_WIN || IS_MACOSX) {
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+ } else {
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+ }
+
+ log = getUpdatesDir();
+ log.append(FILE_BACKUP_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ let updatesDir = getStageDirFile(DIR_UPDATES + "/0", true);
+ logTestInfo("testing " + updatesDir.path + " shouldn't exist");
+ do_check_false(updatesDir.exists());
+
+ log = getStageDirFile(DIR_UPDATES + "/0/" + FILE_UPDATE_LOG, true);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getStageDirFile(DIR_UPDATES + "/" + FILE_LAST_LOG, true);
+ if (IS_WIN || IS_MACOSX) {
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+ } else {
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+ }
+
+ log = getStageDirFile(DIR_UPDATES + "/" + FILE_BACKUP_LOG, true);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ // Switch the application to the staged application that was updated by
+ // launching the application.
+ do_timeout(TEST_CHECK_TIMEOUT, launchAppToApplyUpdate);
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateFinished() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateFinished;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateFinished();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateFinished);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateFinished() {
+ gTimeoutRuns++;
+ // Don't proceed until the update's status state is the expected value.
+ let state = readStatusState();
+ if (state != STATE_SUCCEEDED) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status state to equal: " +
+ STATE_SUCCEEDED +
+ ", current status state: " + state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ // Don't proceed until the application was switched out with the staged update
+ // successfully.
+ let updatedDir = getStageDirFile(null, true);
+ if (updatedDir.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for updated dir to not exist. Path: " +
+ updatedDir.path);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ if (IS_WIN) {
+ // Don't proceed until the updater binary is no longer in use.
+ let updater = getUpdatesPatchDir();
+ updater.append(FILE_UPDATER_BIN);
+ if (updater.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for updater binary to no longer be " +
+ "in use");
+ } else {
+ try {
+ updater.remove(false);
+ } catch (e) {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ return;
+ }
+ }
+ }
+ }
+
+// Added to make this test pass on esr38
+ if (IS_WIN) {
+ // Don't proceed until the tobedeleted dir no longer exists.
+ let toBeDeletedDir = getApplyDirFile(DIR_TOBEDELETED, true);
+ if (toBeDeletedDir.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for the tobedeleted dir to no " +
+ "longer exist");
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ return;
+ }
+ }
+ }
+
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// let running = getPostUpdateFile(".running");
+// logTestInfo("checking that the post update process running file exists. " +
+// "Path: " + running.path);
+// do_check_true(running.exists());
+// }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackAppLog();
+
+ standardInit();
+
+ let update = gUpdateManager.getUpdateAt(0);
+ do_check_eq(update.state, STATE_SUCCEEDED);
+
+ let updatesDir = getUpdatesPatchDir();
+ logTestInfo("testing " + updatesDir.path + " should exist");
+ do_check_true(updatesDir.exists());
+
+ let log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_LAST_LOG);
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_BACKUP_LOG);
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
new file mode 100644
index 000000000..a4188fa3f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
@@ -0,0 +1,286 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+function run_test() {
+ if (MOZ_APP_NAME == "xulrunner") {
+ logTestInfo("Unable to run this test on xulrunner");
+ return;
+ }
+
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI(false);
+
+ if (IS_WIN) {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, true);
+ }
+
+ let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ STATE_PENDING_SVC);
+ let updates = getLocalUpdateString(patches, null, null, null, null, null,
+ null, null, null, null, null, null,
+ null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(getAppVersion());
+ writeStatusFile(STATE_PENDING_SVC);
+
+ reloadUpdateManagerData();
+ do_check_true(!!gUpdateManager.activeUpdate);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ stageUpdate();
+}
+
+/**
+ * Checks if the update has finished being staged.
+ */
+function checkUpdateApplied() {
+ gTimeoutRuns++;
+ // Don't proceed until the active update's state is the expected value.
+ if (gUpdateManager.activeUpdate.state != STATE_APPLIED_SVC) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for update to equal: " +
+ STATE_APPLIED_SVC +
+ ", current state: " + gUpdateManager.activeUpdate.state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ // Don't proceed until the update's status state is the expected value.
+ let state = readStatusState();
+ if (state != STATE_APPLIED_SVC) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status state to equal: " +
+ STATE_APPLIED_SVC +
+ ", current status state: " + state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ // Don't proceed until the last update log has been created.
+ let log;
+ if (IS_WIN || IS_MACOSX) {
+ log = getUpdatesDir();
+ } else {
+ log = getStageDirFile(null, true);
+ log.append(DIR_UPDATES);
+ }
+ log.append(FILE_LAST_LOG);
+ if (!log.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update log " +
+ "to be created. Path: " + log.path);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateApplied);
+ }
+ return;
+ }
+
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+
+ log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_LAST_LOG);
+ if (IS_WIN || IS_MACOSX) {
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+ } else {
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+ }
+
+ log = getUpdatesDir();
+ log.append(FILE_BACKUP_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ let updatesDir = getStageDirFile(DIR_UPDATES + "/0", true);
+ logTestInfo("testing " + updatesDir.path + " shouldn't exist");
+ do_check_false(updatesDir.exists());
+
+ log = getStageDirFile(DIR_UPDATES + "/0/" + FILE_UPDATE_LOG, true);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getStageDirFile(DIR_UPDATES + "/" + FILE_LAST_LOG, true);
+ if (IS_WIN || IS_MACOSX) {
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+ } else {
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+ }
+
+ log = getStageDirFile(DIR_UPDATES + "/" + FILE_BACKUP_LOG, true);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ // Switch the application to the staged application that was updated by
+ // launching the application.
+ do_timeout(TEST_CHECK_TIMEOUT, launchAppToApplyUpdate);
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateFinished() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateApplied;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateApplied();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateApplied);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateApplied() {
+ gTimeoutRuns++;
+ // Don't proceed until the update's status state is the expected value.
+ let state = readStatusState();
+ if (state != STATE_SUCCEEDED) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status state to equal: " + STATE_SUCCEEDED +
+ ", current status state: " + state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ // Don't proceed until the application was switched out with the staged update
+ // successfully.
+ let updatedDir = getStageDirFile(null, true);
+ if (updatedDir.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for updated dir to not exist. Path: " +
+ updatedDir.path);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ if (IS_WIN) {
+ // Don't proceed until the updater binary is no longer in use.
+ let updater = getUpdatesPatchDir();
+ updater.append(FILE_UPDATER_BIN);
+ if (updater.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for updater binary to no longer be " +
+ "in use");
+ } else {
+ try {
+ updater.remove(false);
+ } catch (e) {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ return;
+ }
+ }
+ }
+ }
+
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// let running = getPostUpdateFile(".running");
+// logTestInfo("checking that the post update process running file exists. " +
+// "Path: " + running.path);
+// do_check_true(running.exists());
+// }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ gSwitchApp = true;
+ checkUpdateLogContents();
+ gSwitchApp = false;
+ checkCallbackAppLog();
+
+ standardInit();
+
+ let update = gUpdateManager.getUpdateAt(0);
+ do_check_eq(update.state, STATE_SUCCEEDED);
+
+ let updatesDir = getUpdatesPatchDir();
+ logTestInfo("testing " + updatesDir.path + " should exist");
+ do_check_true(updatesDir.exists());
+
+ let log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_LAST_LOG);
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_BACKUP_LOG);
+ if (IS_WIN || IS_MACOSX) {
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+ } else {
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
new file mode 100644
index 000000000..a6749bb7e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+function run_test() {
+ if (MOZ_APP_NAME == "xulrunner") {
+ logTestInfo("Unable to run this test on xulrunner");
+ return;
+ }
+
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI();
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ let patches = getLocalPatchString(null, null, null, null, null, "true",
+ STATE_PENDING);
+ let updates = getLocalUpdateString(patches, null, null, null, null, null,
+ null, null, null, null, null, null,
+ null, "true", channel);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(getAppVersion());
+ writeStatusFile(STATE_PENDING_SVC);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateFinished() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateFinished;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateFinished();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateFinished);
+
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateFinished() {
+ gTimeoutRuns++;
+ // Don't proceed until the update's status state is the expected value.
+ let state = readStatusState();
+ if (state != STATE_SUCCEEDED) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update " +
+ "status state to equal: " + STATE_SUCCEEDED +
+ ", current status state: " + state);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ // Don't proceed until the update log has been created.
+ let log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ if (!log.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded MAX_TIMEOUT_RUNS while waiting for the update log " +
+ "to be created. Path: " + log.path);
+ } else {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ }
+ return;
+ }
+
+ if (IS_WIN) {
+ // Don't proceed until the updater binary is no longer in use.
+ let updater = getUpdatesPatchDir();
+ updater.append(FILE_UPDATER_BIN);
+ if (updater.exists()) {
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ do_throw("Exceeded while waiting for updater binary to no longer be " +
+ "in use");
+ } else {
+ try {
+ updater.remove(false);
+ } catch (e) {
+ do_timeout(TEST_CHECK_TIMEOUT, checkUpdateFinished);
+ return;
+ }
+ }
+ }
+ }
+
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackAppLog();
+
+ standardInit();
+
+ let update = gUpdateManager.getUpdateAt(0);
+ do_check_eq(update.state, STATE_SUCCEEDED);
+
+ let updatesPatchDir = getUpdatesPatchDir();
+ logTestInfo("testing " + updatesPatchDir.path + " should exist");
+ do_check_true(updatesPatchDir.exists());
+
+ log = getUpdatesPatchDir();
+ log.append(FILE_UPDATE_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_LAST_LOG);
+ logTestInfo("testing " + log.path + " should exist");
+ do_check_true(log.exists());
+
+ log = getUpdatesDir();
+ log.append(FILE_BACKUP_LOG);
+ logTestInfo("testing " + log.path + " shouldn't exist");
+ do_check_false(log.exists());
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseFallbackStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseFallbackStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..dc3dcc810
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseFallbackStageFailureCompleteSvc_win.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch the callback helper application so it is in use during the update.
+ let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ callbackAppProcess.init(callbackApp);
+ callbackAppProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..1d5d3c516
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch the callback helper application so it is in use during the update.
+ let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ callbackAppProcess.init(callbackApp);
+ callbackAppProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
new file mode 100644
index 000000000..552fd2d95
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch the callback helper application so it is in use during the update.
+ let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
+ callbackApp.permissions = PERMS_DIRECTORY;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ callbackAppProcess.init(callbackApp);
+ callbackAppProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let applyToDir = getApplyDirFile();
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..c698b885b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file staged patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ gCallbackBinFile = "exe0.exe";
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(0, STATE_SUCCEEDED);
+}
+
+function checkUpdateApplied() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
new file mode 100644
index 000000000..ab1d753e4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file staged patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ gCallbackBinFile = "exe0.exe";
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(0, STATE_SUCCEEDED);
+}
+
+function checkUpdateApplied() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..c09ac63fc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ gCallbackBinFile = "exe0.exe";
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
new file mode 100644
index 000000000..639743f3c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ gCallbackBinFile = "exe0.exe";
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
new file mode 100644
index 000000000..525ded0aa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
@@ -0,0 +1,63 @@
+/* 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/.
+ */
+
+/* General Partial MAR File Patch Apply Failure Test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ createUpdaterINI();
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on all updates since
+ // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function checkUpdateFinished() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..2fa86e798
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailureCompleteSvc_win.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[13].relPathDir +
+ gTestFiles[13].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..1463df65b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseFallbackStageFailurePartialSvc_win.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[11].relPathDir +
+ gTestFiles[11].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..f8c7a6a58
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[13].relPathDir +
+ gTestFiles[13].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..a52aa937d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[11].relPathDir +
+ gTestFiles[11].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..a535ac0c6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[13].relPathDir +
+ gTestFiles[13].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 000000000..68ffdff58
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Launch an existing file so it is in use during the update.
+ let fileInUseBin = getApplyDirFile(gTestFiles[11].relPathDir +
+ gTestFiles[11].fileName);
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
new file mode 100644
index 000000000..ed22b1d48
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[3].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[3].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
new file mode 100644
index 000000000..d4562a9e2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[2].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[2].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..7612db387
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailureCompleteSvc_win.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[3].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[3].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..366bb3632
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFallbackStageFailurePartialSvc_win.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[2].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[2].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..4600150eb
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[3].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[3].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..6b318375a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = gTestFiles[2].relPathDir.split("/");
+ if (IS_MACOSX) {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + gTestFiles[2].fileName;
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT, lockFileRelPath];
+ let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ lockFileProcess.init(helperBin);
+ lockFileProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..f6729e06a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailureCompleteSvc_win.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0]);
+ helperBin.copyTo(fileInUseDir, gTestDirs[4].subDirFiles[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..176b55ba5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseFallbackStageFailurePartialSvc_win.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ fallback test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[2].relPathDir +
+ gTestDirs[2].files[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[2].relPathDir);
+ helperBin.copyTo(fileInUseDir, gTestDirs[2].files[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ runUpdate(1, STATE_PENDING);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, false, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 000000000..a63644cfa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0]);
+ helperBin.copyTo(fileInUseDir, gTestDirs[4].subDirFiles[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 000000000..cc8067c8f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[2].relPathDir +
+ gTestDirs[2].files[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[2].relPathDir);
+ helperBin.copyTo(fileInUseDir, gTestDirs[2].files[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ gDisableReplaceFallback = true;
+ runUpdate(1, STATE_FAILED_WRITE_ERROR);
+}
+
+function checkUpdateApplied() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 000000000..3d042a672
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0]);
+ helperBin.copyTo(fileInUseDir, gTestDirs[4].subDirFiles[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 000000000..b62c2af4f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file patch apply success test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ let fileInUseBin = getApplyDirFile(gTestDirs[2].relPathDir +
+ gTestDirs[2].files[0]);
+ // Remove the empty file created for the test so the helper application can
+ // replace it.
+ fileInUseBin.remove(false);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseDir = getApplyDirFile(gTestDirs[2].relPathDir);
+ helperBin.copyTo(fileInUseDir, gTestDirs[2].files[0]);
+
+ // Launch an existing file so it is in use during the update.
+ let args = [getApplyDirPath() + DIR_RESOURCES, "input", "output", "-s",
+ HELPER_SLEEP_TIMEOUT];
+ let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+function checkUpdateFinished() {
+ setupHelperFinish();
+}
+
+function checkUpdate() {
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
new file mode 100644
index 000000000..71500f69f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
@@ -0,0 +1,55 @@
+/* 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/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Failure Test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+ createUpdaterINI(true);
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on all updates since
+ // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function checkUpdateFinished() {
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ checkFilesAfterUpdateFailure(getApplyDirFile, true, false);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
new file mode 100644
index 000000000..5dbc0fca9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
@@ -0,0 +1,160 @@
+/* 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/.
+ */
+
+/* General Complete MAR File Staged Patch Apply Test */
+
+function run_test() {
+ if (!shouldRunServiceTest(false, true)) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI(false);
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (IS_UNIX && !IS_MACOSX) {
+ removeSymlink();
+ createSymlink();
+ do_register_cleanup(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0,
+ {
+ description : "Readable symlink",
+ fileName : "link",
+ relPathDir : DIR_RESOURCES,
+ originalContents : "test",
+ compareContents : "test",
+ originalFile : null,
+ compareFile : null,
+ originalPerms : 0o666,
+ comparePerms : 0o666
+ });
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ do_timeout(TEST_CHECK_TIMEOUT, function() {
+ runUpdate(0, STATE_SUCCEEDED);
+ });
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateApplied() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateApplied;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateApplied();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateApplied);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateApplied() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// let running = getPostUpdateFile(".running");
+// logTestInfo("checking that the post update process running file exists. " +
+// "Path: " + running.path);
+// do_check_true(running.exists());
+// }
+
+ if (IS_UNIX && !IS_MACOSX) {
+ checkSymlink();
+ }
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackAppLog();
+}
+
+function runHelperProcess(args) {
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let process = AUS_Cc["@mozilla.org/process/util;1"].
+ createInstance(AUS_Ci.nsIProcess);
+ process.init(helperBin);
+ logTestInfo("Running " + helperBin.path + " " + args.join(" "));
+ process.run(true, args, args.length);
+ do_check_eq(process.exitValue, 0);
+}
+
+function createSymlink() {
+ let args = ["setup-symlink", "moz-foo", "moz-bar", "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ runHelperProcess(args);
+ getApplyDirFile(DIR_RESOURCES + "link", false).permissions = 0o666;
+
+ args = ["setup-symlink", "moz-foo2", "moz-bar2", "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2", "change-perm"];
+ runHelperProcess(args);
+}
+
+function removeSymlink() {
+ let args = ["remove-symlink", "moz-foo", "moz-bar", "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ runHelperProcess(args);
+ args = ["remove-symlink", "moz-foo2", "moz-bar2", "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2"];
+ runHelperProcess(args);
+}
+
+function checkSymlink() {
+ let args = ["check-symlink",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link"];
+ runHelperProcess(args);
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
new file mode 100644
index 000000000..272c468e4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
@@ -0,0 +1,101 @@
+/* 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/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Test */
+
+function run_test() {
+ if (!shouldRunServiceTest(false, true)) {
+ return;
+ }
+
+ gStageUpdate = true;
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI(false);
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on all updates since
+ // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED);
+}
+
+function checkUpdateFinished() {
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+
+ if (IS_WIN || IS_MACOSX) {
+ let running = getPostUpdateFile(".running");
+ logTestInfo("checking that the post update process running file doesn't " +
+ "exist. Path: " + running.path);
+ do_check_false(running.exists());
+ }
+
+ // Switch the application to the staged application that was updated.
+ gStageUpdate = false;
+ gSwitchApp = true;
+ do_timeout(TEST_CHECK_TIMEOUT, function() {
+ runUpdate(0, STATE_SUCCEEDED);
+ });
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateApplied() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateApplied;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateApplied();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateApplied);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateApplied() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// let running = getPostUpdateFile(".running");
+// logTestInfo("checking that the post update process running file exists. " +
+// "Path: " + running.path);
+// do_check_true(running.exists());
+// }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkCallbackAppLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
new file mode 100644
index 000000000..c6eafcaaf
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
@@ -0,0 +1,70 @@
+/* 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/.
+ */
+
+/* General Complete MAR File Patch Apply Test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupUpdaterTest(FILE_COMPLETE_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI();
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on a successful
+ // update (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateFinished() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateFinished;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateFinished();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateFinished);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateFinished() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
new file mode 100644
index 000000000..c241f67ec
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
@@ -0,0 +1,76 @@
+/* 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/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+function run_test() {
+ if (!shouldRunServiceTest()) {
+ return;
+ }
+
+ setupTestCommon();
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestFiles[gTestFiles.length - 2].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 2].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ setupUpdaterTest(FILE_PARTIAL_MAR);
+
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// createUpdaterINI(true);
+
+ // For Mac OS X set the last modified time for the root directory to a date in
+ // the past to test that the last modified time is updated on all updates since
+ // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
+ if (IS_MACOSX) {
+ let now = Date.now();
+ let yesterday = now - (1000 * 60 * 60 * 24);
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+ }
+
+ setupAppFilesAsync();
+}
+
+function setupAppFilesFinished() {
+ runUpdateUsingService(STATE_PENDING_SVC, STATE_SUCCEEDED);
+}
+
+/**
+ * Checks if the post update binary was properly launched for the platforms that
+ * support launching post update process.
+ */
+function checkUpdateFinished() {
+// This is commented out on mozilla-esr38 since it doesn't have the test updater
+// if (IS_WIN || IS_MACOSX) {
+// gCheckFunc = finishCheckUpdateFinished;
+// checkPostUpdateAppLog();
+// } else {
+// finishCheckUpdateFinished();
+// }
+ do_timeout(TEST_HELPER_TIMEOUT, finishCheckUpdateFinished);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function finishCheckUpdateFinished() {
+ if (IS_MACOSX) {
+ logTestInfo("testing last modified time on the apply to directory has " +
+ "changed after a successful update (bug 600098)");
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ do_check_true(timeDiff < MAC_MAX_TIME_DIFFERENCE);
+ }
+
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ checkCallbackServiceLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
new file mode 100644
index 000000000..43b24c783
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
@@ -0,0 +1,83 @@
+; 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/.
+
+; Tests that require the updater binary and the maintenance service.
+
+[DEFAULT]
+head = head_update.js
+tail =
+generated-files = head_update.js
+
+[bootstrapSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessPartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFailurePartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessPartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageFailurePartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseFallbackStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFallbackStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFallbackStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseFallbackStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseFallbackStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseFallbackStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseFallbackStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyDirLockedStageFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateSuccessSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateStageSuccessSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 766d278bc..1a5a17f36 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -291,6 +291,7 @@ 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
@@ -1823,7 +1824,8 @@ IsUpdateFromMetro(int argc, NS_tchar **argv)
/**
* Launch the post update application (helper.exe). It takes in the path of the
- * callback application to calculate the path of helper.exe.
+ * 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.
@@ -1876,6 +1878,14 @@ LaunchWinPostProcess(const WCHAR *installationDir,
return false;
}
+// TEST_UPDATER is not available on esr38
+//#if !defined(TEST_UPDATER)
+ if (sUsingService &&
+ !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
+ return false;
+ }
+//#endif
+
WCHAR dlogFile[MAX_PATH + 1];
if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
return false;
@@ -1899,7 +1909,8 @@ LaunchWinPostProcess(const WCHAR *installationDir,
wcsncpy(cmdline, dummyArg, len);
wcscat(cmdline, exearg);
- if (!_wcsnicmp(exeasync, L"false", 6) ||
+ if (sUsingService ||
+ !_wcsnicmp(exeasync, L"false", 6) ||
!_wcsnicmp(exeasync, L"0", 2)) {
async = false;
}
@@ -1939,7 +1950,8 @@ LaunchWinPostProcess(const WCHAR *installationDir,
static void
LaunchCallbackApp(const NS_tchar *workingDir,
int argc,
- NS_tchar **argv)
+ NS_tchar **argv,
+ bool usingService)
{
putenv(const_cast<char*>("NO_EM_RESTART="));
putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
@@ -1956,15 +1968,19 @@ LaunchCallbackApp(const NS_tchar *workingDir,
#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) {
#if defined(MOZ_METRO)
- // If our callback application is the default metro browser, then
- // launch it now.
- if (IsUpdateFromMetro(argc, argv)) {
- LaunchDefaultMetroBrowser();
- return;
- }
+ // If our callback application is the default metro browser, then
+ // launch it now.
+ if (IsUpdateFromMetro(argc, argv)) {
+ LaunchDefaultMetroBrowser();
+ return;
+ }
#endif
- WinLaunchChild(argv[0], argc, argv, nullptr);
+ WinLaunchChild(argv[0], argc, argv, nullptr);
+ }
#else
# warning "Need implementaton of LaunchCallbackApp"
#endif
@@ -2033,6 +2049,40 @@ WriteStatusFile(int status)
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
@@ -2223,6 +2273,17 @@ ProcessReplaceRequest()
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
@@ -2392,7 +2453,7 @@ UpdateThreadFunc(void *param)
// The only special thing which we should do here is to remove the
// staged directory as it won't be useful any more.
ensure_remove_recursive(gWorkingDirPath);
- WriteStatusFile("pending");
+ WriteStatusFile(sUsingService ? "pending-service" : "pending");
putenv(const_cast<char*>("MOZ_PROCESS_UPDATES=")); // We need to use --process-updates again in the tests
reportRealResults = false; // pretend success
}
@@ -2474,6 +2535,21 @@ int NS_main(int argc, NS_tchar **argv)
}
#ifdef XP_WIN
+ bool useService = false;
+ bool testOnlyFallbackKeyExists = false;
+ bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr;
+ 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;
@@ -2660,11 +2736,21 @@ int NS_main(int argc, NS_tchar **argv)
const int callbackIndex = 6;
#if defined(XP_WIN)
+ sUsingService = getenv("MOZ_USING_SERVICE") != nullptr;
+ 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 (argc > callbackIndex || sStagedUpdate || sReplaceRequest) {
+ if (!sUsingService &&
+ (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
NS_tchar updateLockFilePath[MAXPATHLEN];
if (sStagedUpdate) {
// When staging an update, the lock file is:
@@ -2722,17 +2808,20 @@ int NS_main(int argc, NS_tchar **argv)
bool startedFromUnelevatedUpdater =
GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
- // 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 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) {
+ 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");
@@ -2759,10 +2848,102 @@ int NS_main(int argc, NS_tchar **argv)
return 1;
}
- // When staging an 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 (sStagedUpdate) {
+ // 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 an 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);
}
@@ -2770,7 +2951,32 @@ int NS_main(int argc, NS_tchar **argv)
return 0;
}
- if (updateLockFileHandle == INVALID_HANDLE_VALUE) {
+ // 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)) {
+ 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);
@@ -2796,16 +3002,33 @@ int NS_main(int argc, NS_tchar **argv)
if (argc > callbackIndex) {
LaunchCallbackApp(argv[5], argc - callbackIndex,
- argv + callbackIndex);
+ argv + callbackIndex, sUsingService);
}
CloseHandle(elevatedFileHandle);
- if (INVALID_HANDLE_VALUE == updateLockFileHandle) {
- // We ran the elevated updater.exe.
+ 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;
}
}
}
@@ -2886,7 +3109,7 @@ int NS_main(int argc, NS_tchar **argv)
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
if (argc > callbackIndex) {
LaunchCallbackApp(argv[5], argc - callbackIndex,
- argv + callbackIndex);
+ argv + callbackIndex, sUsingService);
}
return 1;
}
@@ -2939,7 +3162,8 @@ int NS_main(int argc, NS_tchar **argv)
if (argc > callbackIndex) {
LaunchCallbackApp(argv[5],
argc - callbackIndex,
- argv + callbackIndex);
+ argv + callbackIndex,
+ sUsingService);
}
return 1;
}
@@ -3019,7 +3243,8 @@ int NS_main(int argc, NS_tchar **argv)
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
LaunchCallbackApp(argv[5],
argc - callbackIndex,
- argv + callbackIndex);
+ argv + callbackIndex,
+ sUsingService);
return 1;
}
LOG(("NS_main: callback app file in use, continuing without " \
@@ -3134,6 +3359,17 @@ int NS_main(int argc, NS_tchar **argv)
if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
fprintf(stderr, "The post update process was not launched");
}
+
+ // 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) {
+ StartServiceUpdate(gInstallDirPath);
+ }
}
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
#endif /* XP_WIN */
@@ -3146,7 +3382,8 @@ int NS_main(int argc, NS_tchar **argv)
if (getenv("MOZ_PROCESS_UPDATES") == nullptr) {
LaunchCallbackApp(argv[5],
argc - callbackIndex,
- argv + callbackIndex);
+ argv + callbackIndex,
+ sUsingService);
}
}