Bug 711692 - Various fixes for intermittent failures. r=rstrong.

This commit is contained in:
Brian R. Bondy 2011-12-28 21:08:37 -05:00
parent 07e9369d65
commit c97793c692
15 changed files with 784 additions and 234 deletions

View File

@ -35,9 +35,6 @@
#
# ***** END LICENSE BLOCK *****
# Required Plugins:
# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis
; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
!verbose 3
@ -170,18 +167,12 @@ Section "MaintenanceService"
CreateDirectory $INSTDIR
SetOutPath $INSTDIR
; If the service already exists, then stop it if it is running.
ServicesHelper::IsInstalled "MozillaMaintenance"
Pop $R9
${If} $R9 == 1
; Stop the maintenance service so we can overwrite any
; binaries that it uses.
ServicesHelper::Stop "MozillaMaintenance"
${EndIf}
; If we don't have maintenanceservice.exe already installed
; then keep that name. If we do use maintenanceservice_tmp.exe
; which will auto install itself when you call it with the install parameter.
; 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"

View File

@ -50,8 +50,9 @@
SERVICE_STATUS gSvcStatus = { 0 };
SERVICE_STATUS_HANDLE gSvcStatusHandle = NULL;
HANDLE ghSvcStopEvent = NULL;
BOOL gServiceStopping = FALSE;
HANDLE gWorkDoneEvent = NULL;
HANDLE gThread = NULL;
bool gServiceControlStopping = false;
// logs are pretty small ~10 lines, so 5 seems reasonable.
#define LOGS_TO_KEEP 5
@ -129,33 +130,19 @@ wmain(int argc, WCHAR **argv)
}
SERVICE_TABLE_ENTRYW DispatchTable[] = {
{ SVC_NAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ SVC_NAME, (LPSERVICE_MAIN_FUNCTIONW) SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher(DispatchTable)) {
if (!StartServiceCtrlDispatcherW(DispatchTable)) {
LOG(("StartServiceCtrlDispatcher failed (%d)\n", GetLastError()));
}
return 0;
}
/**
* Wrapper callback for the monitoring thread.
*
* @param param Unused thread callback parameter
*/
DWORD
WINAPI StartMaintenanceServiceThread(LPVOID param)
{
ThreadData *threadData = reinterpret_cast<ThreadData*>(param);
ExecuteServiceCommand(threadData->argc, threadData->argv);
delete threadData;
return 0;
}
/**
* Obtains the base path where logs should be stored
*
@ -163,7 +150,7 @@ WINAPI StartMaintenanceServiceThread(LPVOID param)
* @return TRUE if successful.
*/
BOOL
GetLogDirectoryPath(WCHAR *path)
GetLogDirectoryPath(WCHAR *path)
{
HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL,
SHGFP_TYPE_CURRENT, path);
@ -218,7 +205,7 @@ GetBackupLogPath(LPWSTR path, LPCWSTR basePath, int logNumber)
* @param numLogsToKeep The number of logs to keep
*/
void
BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
{
WCHAR oldPath[MAX_PATH + 1];
WCHAR newPath[MAX_PATH + 1];
@ -231,17 +218,51 @@ BackupOldLogs(LPCWSTR basePath, int numLogsToKeep)
continue;
}
if (!MoveFileEx(oldPath, newPath, MOVEFILE_REPLACE_EXISTING)) {
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(NULL, 0, EnsureProcessTerminatedThread,
NULL, 0, NULL);
if (thread) {
CloseHandle(thread);
}
}
/**
* Main entry point when running as a service.
*/
void WINAPI
SvcMain(DWORD dwArgc, LPWSTR *lpszArgv)
void WINAPI
SvcMain(DWORD argc, LPWSTR *argv)
{
// Setup logging, and backup the old logs
WCHAR updatePath[MAX_PATH + 1];
@ -258,51 +279,45 @@ SvcMain(DWORD dwArgc, LPWSTR *lpszArgv)
gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
if (!gSvcStatusHandle) {
LOG(("RegisterServiceCtrlHandler failed (%d)\n", GetLastError()));
return;
ExecuteServiceCommand(argc, argv);
LogFinish();
exit(1);
}
// These SERVICE_STATUS members remain as set here
// 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);
// Perform service-specific initialization and work.
SvcInit(dwArgc, lpszArgv);
}
/**
* Service initialization.
*/
void
SvcInit(DWORD argc, LPWSTR *argv)
{
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ghSvcStopEvent) {
// This event will be used to tell the SvcCtrlHandler when the work is
// done for when a stop comamnd is manually issued.
gWorkDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!gWorkDoneEvent) {
ReportSvcStatus(SERVICE_STOPPED, 1, 0);
StartTerminationThread();
return;
}
ThreadData *threadData = new ThreadData();
threadData->argc = argc;
threadData->argv = argv;
DWORD threadID;
HANDLE thread = CreateThread(NULL, 0, StartMaintenanceServiceThread,
threadData, 0, &threadID);
// Report running status when initialization is complete.
// 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);
// Perform work until service stops.
for(;;) {
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
// 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);
return;
StartTerminationThread();
}
}
@ -320,15 +335,16 @@ ReportSvcStatus(DWORD currentState,
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = currentState;
gSvcStatus.dwWin32ExitCode = exitCode;
gSvcStatus.dwWaitHint = waitHint;
if (SERVICE_START_PENDING == currentState) {
if (SERVICE_START_PENDING == currentState ||
SERVICE_STOP_PENDING == currentState) {
gSvcStatus.dwControlsAccepted = 0;
} else {
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_SHUTDOWN;
}
if ((SERVICE_RUNNING == currentState) ||
@ -342,6 +358,23 @@ ReportSvcStatus(DWORD currentState,
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 = NULL;
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.
@ -349,18 +382,34 @@ ReportSvcStatus(DWORD currentState,
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_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
LogFinish();
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(NULL, 0, StopServiceAndWaitForCommandThread,
NULL, 0, NULL);
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(NULL);
}
}
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
default:
break;
}
}

View File

@ -41,7 +41,3 @@ void WINAPI SvcCtrlHandler(DWORD dwCtrl);
void ReportSvcStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint);
struct ThreadData {
LPWSTR *argv;
DWORD argc;
};

View File

@ -189,12 +189,13 @@ SvcInstall(SvcInstallAction action)
return FALSE;
}
if (!DeleteFile(serviceConfig.lpBinaryPathName)) {
LOG(("Could not delete old service binary file. (%d)\n", GetLastError()));
if (!DeleteFileW(serviceConfig.lpBinaryPathName)) {
LOG(("Could not delete old service binary file %ls. (%d)\n",
serviceConfig.lpBinaryPathName, GetLastError()));
return FALSE;
}
if (!CopyFile(newServiceBinaryPath,
if (!CopyFileW(newServiceBinaryPath,
serviceConfig.lpBinaryPathName, FALSE)) {
LOG(("Could not overwrite old service binary file. "
"This should never happen, but if it does the next upgrade will fix"
@ -207,7 +208,7 @@ SvcInstall(SvcInstallAction action)
// 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.
MoveFileEx(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
MoveFileExW(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
// Setup the new module path
wcsncpy(newServiceBinaryPath, serviceConfig.lpBinaryPathName, MAX_PATH);
@ -216,7 +217,7 @@ SvcInstall(SvcInstallAction action)
// 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.
MoveFileEx(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
MoveFileExW(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
return TRUE; // nothing to do, we already have a newer service installed
}
@ -274,10 +275,24 @@ StopService()
return FALSE;
}
// Stop logging before stopping the service.
LogFinish();
LOG(("Sending stop request...\n"));
SERVICE_STATUS status;
if (!ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
LOG(("Error sending stop request: %d\n", GetLastError()));
}
return WaitForServiceStop(SVC_NAME, 60);
schSCManager.reset();
schService.reset();
LOG(("Waiting for service stop...\n"));
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\n", lastState));
return lastState == SERVICE_STOPPED;
}
/**

View File

@ -57,8 +57,6 @@
#include "uachelper.h"
#include "updatehelper.h"
extern HANDLE ghSvcStopEvent;
// 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.
@ -75,6 +73,45 @@ const int SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
const int SERVICE_UPDATER_SIGN_ERROR = 16002;
const int SERVICE_UPDATER_COMPARE_ERROR = 16003;
const int SERVICE_UPDATER_IDENTITY_ERROR = 16004;
const int SERVICE_STILL_APPLYING_ON_SUCCESS = 16005;
const int SERVICE_STILL_APPLYING_ON_FAILURE = 16006;
/*
* 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];
wcscpy(updateStatusFilePath, updateDirPath);
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
return FALSE;
}
nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ,
FILE_SHARE_READ |
FILE_SHARE_WRITE |
FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, 0, NULL));
char buf[32] = { 0 };
DWORD read;
if (!ReadFile(statusFile, buf, sizeof(buf), &read, NULL)) {
return FALSE;
}
const char kApplying[] = "applying";
isApplying = strncmp(buf, kApplying,
sizeof(kApplying) - 1) == 0;
return TRUE;
}
/**
* Runs an update process as the service using the SYSTEM account.
@ -124,25 +161,22 @@ StartUpdateProcess(int argc,
// because of background updates.
if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") &&
PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) {
selfHandlePostUpdate = MoveFileEx(updaterINI, updaterINITemp,
MOVEFILE_REPLACE_EXISTING);
selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp,
MOVEFILE_REPLACE_EXISTING);
}
// 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
// for more info.
WCHAR envVarString[32];
wsprintf(envVarString, L"MOZ_USING_SERVICE=1");
_wputenv(envVarString);
// Empty value on _wputenv is how you remove an env variable in Windows
// 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\n", cmdLine));
processStarted = CreateProcessW(argv[0], cmdLine,
NULL, NULL, FALSE,
CREATE_DEFAULT_ERROR_MODE,
NULL,
NULL, &si, &pi);
_wputenv(L"MOZ_USING_SERVICE=");
// Empty value on putenv is how you remove an env variable in Windows
putenv(const_cast<char*>("MOZ_USING_SERVICE="));
BOOL updateWasSuccessful = FALSE;
if (processStarted) {
@ -166,6 +200,31 @@ StartUpdateProcess(int argc,
}
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.\n"));
if (!WriteStatusFailure(argv[1],
SERVICE_STILL_APPLYING_ON_SUCCESS)) {
LOG(("Could not write update.status still applying on"
" success error.\n"));
}
// Since we still had applying we know updater.exe didn't do its
// job correctly.
updateWasSuccessful = FALSE;
} else {
LOG(("update.status is still applying and update was not successful.\n"));
if (!WriteStatusFailure(argv[1],
SERVICE_STILL_APPLYING_ON_FAILURE)) {
LOG(("Could not write update.status still applying on"
" success error.\n"));
}
}
}
} else {
DWORD lastError = GetLastError();
LOG(("Could not create process as current user, "
@ -177,7 +236,7 @@ StartUpdateProcess(int argc,
// We use it ourselves, and also we want it back in case we had any type
// of error so that the normal update process can use it.
if (selfHandlePostUpdate) {
MoveFileEx(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING);
MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING);
// Only run the PostUpdate if the update was successful
if (updateWasSuccessful && argc > 2) {
@ -211,7 +270,7 @@ StartUpdateProcess(int argc,
* @return TRUE if the update was successful.
*/
BOOL
ProessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
{
BOOL result = TRUE;
if (argc < 3) {
@ -362,11 +421,10 @@ ProessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
* @return FALSE if there was an error executing the service command.
*/
BOOL
ExecuteServiceCommand(int argc, LPWSTR *argv)
ExecuteServiceCommand(int argc, LPWSTR *argv)
{
if (argc < 3) {
LOG(("Not enough command line arguments to execute a service command\n"));
SetEvent(ghSvcStopEvent);
return FALSE;
}
@ -384,7 +442,7 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
BOOL result = FALSE;
if (!lstrcmpi(argv[2], L"software-update")) {
result = ProessSoftwareUpdateCommand(argc - 3, argv + 3);
result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
// We might not reach here if the service install succeeded
// because the service self updates itself and the service
// installer will stop the service.
@ -396,6 +454,5 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
LOG(("service command %ls complete with result: %ls.\n",
argv[1], (result ? L"Success" : L"Failure")));
SetEvent(ghSvcStopEvent);
return TRUE;
}

View File

@ -90,7 +90,7 @@ LPCTSTR UACHelper::PrivsToDisable[] = {
* @return TRUE if the OS is vista or later.
*/
BOOL
UACHelper::IsVistaOrLater()
UACHelper::IsVistaOrLater()
{
// Check if we are running Vista or later.
OSVERSIONINFO osInfo;
@ -127,7 +127,7 @@ UACHelper::OpenUserToken(DWORD sessionID)
* Caller should close the handle.
*/
HANDLE
UACHelper::OpenLinkedToken(HANDLE token)
UACHelper::OpenLinkedToken(HANDLE token)
{
// Magic below...
// UAC creates 2 tokens. One is the restricted token which we have.

View File

@ -42,10 +42,11 @@
// Needed for PathAppendW
#include <shlwapi.h>
// Needed for CreateToolhelp32Snapshot
#include <tlhelp32.h>
#pragma comment(lib, "shlwapi.lib")
WCHAR*
MakeCommandLine(int argc, WCHAR **argv);
WCHAR* MakeCommandLine(int argc, WCHAR **argv);
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
/**
@ -258,66 +259,71 @@ StartServiceUpdate(int argc, LPWSTR *argv)
BOOL svcUpdateProcessStarted = CreateProcessW(maintserviceInstallerPath,
cmdLine,
NULL, NULL, FALSE,
CREATE_DEFAULT_ERROR_MODE |
CREATE_UNICODE_ENVIRONMENT,
0,
NULL, argv[2], &si, &pi);
if (svcUpdateProcessStarted) {
// Wait on the process to finish updating to avoid problems with
// tests that are running. maintenanceservice_installer.exe
// will execute very fast anyway.
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
return svcUpdateProcessStarted;
}
/**
* Executes a maintenance service command
*
* @param argc The total number of arguments in argv
* @param argv An array of null terminated strings to pass to the service,
* @return TRUE if the service command was started.
* @return ERROR_SUCCESS if the service command was started.
* Less than 16000, a windows system error code from StartServiceW
* More than 20000, 20000 + the last state of the service constant if
* the last state is something other than stopped.
* 17001 if the SCM could not be opened
* 17002 if the service could not be opened
*/
BOOL
StartServiceCommand(int argc, LPCWSTR* argv)
DWORD
StartServiceCommand(int argc, LPCWSTR* argv)
{
DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
if (lastState != SERVICE_STOPPED) {
return 20000 + lastState;
}
// Get a handle to the SCM database.
SC_HANDLE serviceManager = OpenSCManager(NULL, NULL,
SC_MANAGER_CONNECT |
SC_MANAGER_ENUMERATE_SERVICE);
if (!serviceManager) {
return FALSE;
return 17001;
}
// Get a handle to the service.
SC_HANDLE service = OpenServiceW(serviceManager,
SVC_NAME,
SERVICE_QUERY_STATUS | SERVICE_START);
SERVICE_START);
if (!service) {
CloseServiceHandle(serviceManager);
return FALSE;
return 17002;
}
// Make sure the service is not stopped.
SERVICE_STATUS_PROCESS ssp;
DWORD bytesNeeded;
if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
CloseServiceHandle(service);
CloseServiceHandle(serviceManager);
return FALSE;
// Wait at most 5 seconds trying to start the service in case of errors
// like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
const DWORD maxWaitMS = 5000;
DWORD currentWaitMS = 0;
DWORD lastError = ERROR_SUCCESS;
while (currentWaitMS < maxWaitMS) {
BOOL result = StartServiceW(service, argc, argv);
if (result) {
lastError = ERROR_SUCCESS;
break;
} else {
lastError = GetLastError();
}
Sleep(100);
currentWaitMS += 100;
}
// The service is already in use.
if (ssp.dwCurrentState != SERVICE_STOPPED) {
CloseServiceHandle(service);
CloseServiceHandle(serviceManager);
return FALSE;
}
return StartServiceW(service, argc, argv);
CloseServiceHandle(service);
CloseServiceHandle(serviceManager);
return lastError;
}
/**
@ -328,16 +334,16 @@ StartServiceCommand(int argc, LPCWSTR* argv)
* @param argc The total number of arguments in argv
* @param argv An array of null terminated strings to pass to the exePath,
* argv[0] must be the path to the updater.exe
* @return TRUE if successful
* @return ERROR_SUCCESS if successful
*/
BOOL
DWORD
LaunchServiceSoftwareUpdateCommand(DWORD argc, LPCWSTR* argv)
{
// The service command is the same as the updater.exe command line except
// it has 2 extra args: 1) The Path to udpater.exe, and 2) the command
// being executed which is "software-update"
LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2];
updaterServiceArgv[0] = L"maintenanceservice.exe";
updaterServiceArgv[0] = L"MozillaMaintenance";
updaterServiceArgv[1] = L"software-update";
for (int i = 0; i < argc; ++i) {
@ -346,9 +352,9 @@ LaunchServiceSoftwareUpdateCommand(DWORD argc, LPCWSTR* argv)
// Execute the service command by starting the service with
// the passed in arguments.
BOOL result = StartServiceCommand(argc + 2, updaterServiceArgv);
DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
delete[] updaterServiceArgv;
return result;
return ret;
}
/**
@ -405,7 +411,7 @@ WriteStatusPending(LPCWSTR updateDirPath)
* @return TRUE if successful
*/
BOOL
WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
{
WCHAR updateStatusFilePath[MAX_PATH + 1];
wcscpy(updateStatusFilePath, updateDirPath);
@ -434,20 +440,53 @@ WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
* This function does not stop the service, it just blocks until the service
* is stopped.
*
* @param serviceName The service to wait for.
* @param maxWaitSeconds The maximum number of seconds to wait
* @return TRUE if the service was stopped after waiting at most maxWaitSeconds
* FALSE on an error or when the service was not stopped
* @param serviceName The service to wait for.
* @param maxWaitSeconds The maximum number of seconds to wait
* @return state of the service after a timeout or when stopped.
* A value of 255 is returned for an error. Typical values are:
* SERVICE_STOPPED 0x00000001
* SERVICE_START_PENDING 0x00000002
* SERVICE_STOP_PENDING 0x00000003
* SERVICE_RUNNING 0x00000004
* SERVICE_CONTINUE_PENDING 0x00000005
* SERVICE_PAUSE_PENDING 0x00000006
* SERVICE_PAUSED 0x00000007
* last status not set 0x000000CF
* Could no query status 0x000000DF
* Could not open service, access denied 0x000000EB
* Could not open service, invalid handle 0x000000EC
* Could not open service, invalid name 0x000000ED
* Could not open service, does not exist 0x000000EE
* Could not open service, other error 0x000000EF
* Could not open SCM, access denied 0x000000FD
* Could not open SCM, database does not exist 0x000000FE;
* Could not open SCM, other error 0x000000FF;
* Note: The strange choice of error codes above SERVICE_PAUSED are chosen
* in case Windows comes out with other service stats higher than 7, they
* would likely call it 8 and above. JS code that uses this in TestAUSHelper
* only handles values up to 255 so that's why we don't use GetLastError
* directly.
*/
BOOL
WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
DWORD
WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
{
// 0x000000CF is defined above to be not set
DWORD lastServiceState = 0x000000CF;
// Get a handle to the SCM database.
SC_HANDLE serviceManager = OpenSCManager(NULL, NULL,
SC_MANAGER_CONNECT |
SC_MANAGER_ENUMERATE_SERVICE);
if (!serviceManager) {
return FALSE;
DWORD lastError = GetLastError();
switch(lastError) {
case ERROR_ACCESS_DENIED:
return 0x000000FD;
case ERROR_DATABASE_DOES_NOT_EXIST:
return 0x000000FE;
default:
return 0x000000FF;
}
}
// Get a handle to the service.
@ -455,31 +494,140 @@ WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
serviceName,
SERVICE_QUERY_STATUS);
if (!service) {
DWORD lastError = GetLastError();
CloseServiceHandle(serviceManager);
return FALSE;
switch(lastError) {
case ERROR_ACCESS_DENIED:
return 0x000000EB;
case ERROR_INVALID_HANDLE:
return 0x000000EC;
case ERROR_INVALID_NAME:
return 0x000000ED;
case ERROR_SERVICE_DOES_NOT_EXIST:
return 0x000000EE;
default:
return 0x000000EF;
}
}
BOOL gotStop = FALSE;
DWORD currentWaitMS = 0;
SERVICE_STATUS_PROCESS ssp;
ssp.dwCurrentState = lastServiceState;
while (currentWaitMS < maxWaitSeconds * 1000) {
// Make sure the service is not stopped.
SERVICE_STATUS_PROCESS ssp;
DWORD bytesNeeded;
if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
DWORD lastError = GetLastError();
switch (lastError) {
case ERROR_INVALID_HANDLE:
ssp.dwCurrentState = 0x000000D9;
break;
case ERROR_ACCESS_DENIED:
ssp.dwCurrentState = 0x000000DA;
break;
case ERROR_INSUFFICIENT_BUFFER:
ssp.dwCurrentState = 0x000000DB;
break;
case ERROR_INVALID_PARAMETER:
ssp.dwCurrentState = 0x000000DC;
break;
case ERROR_INVALID_LEVEL:
ssp.dwCurrentState = 0x000000DD;
break;
case ERROR_SHUTDOWN_IN_PROGRESS:
ssp.dwCurrentState = 0x000000DE;
break;
// These 3 errors can occur when the service is not yet stopped but
// it is stopping.
case ERROR_INVALID_SERVICE_CONTROL:
case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
case ERROR_SERVICE_NOT_ACTIVE:
currentWaitMS += 50;
Sleep(50);
continue;
default:
ssp.dwCurrentState = 0x000000DF;
}
// We couldn't query the status so just break out
break;
}
// The service is already in use.
if (ssp.dwCurrentState == SERVICE_STOPPED) {
gotStop = TRUE;
break;
}
currentWaitMS += 50;
Sleep(50);
}
lastServiceState = ssp.dwCurrentState;
CloseServiceHandle(service);
CloseServiceHandle(serviceManager);
return gotStop;
return lastServiceState;
}
/**
* Determines if there is at least one process running for the specified
* application. A match will be found across any session for any user.
*
* @param process The process to check for existance
* @return ERROR_NOT_FOUND if the process was not found
* @ ERROR_SUCCESS if the process was found and there were no errors
* @ Other Win32 system error code for other errors
**/
DWORD
IsProcessRunning(LPCWSTR filename)
{
// Take a snapshot of all processes in the system.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == snapshot) {
return GetLastError();
}
PROCESSENTRY32W processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(snapshot, &processEntry)) {
DWORD lastError = GetLastError();
CloseHandle(snapshot);
return lastError;
}
do {
if (wcsicmp(filename, processEntry.szExeFile) == 0) {
CloseHandle(snapshot);
return ERROR_SUCCESS;
}
} while (Process32NextW(snapshot, &processEntry));
CloseHandle(snapshot);
return ERROR_NOT_FOUND;
}
/**
* Waits for the specified applicaiton to exit.
*
* @param filename The application to wait for.
* @param maxSeconds The maximum amount of seconds to wait for all
* instances of the application to exit.
* @return ERROR_SUCCESS if no instances of the application exist
* WAIT_TIMEOUT if the process is still running after maxSeconds.
* Any other Win32 system error code.
*/
DWORD
WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
{
DWORD applicationRunningError = WAIT_TIMEOUT;
for(DWORD i = 0; i < maxSeconds; i++) {
DWORD applicationRunningError = IsProcessRunning(filename);
if (ERROR_NOT_FOUND == applicationRunningError) {
return ERROR_SUCCESS;
}
Sleep(1000);
}
if (ERROR_SUCCESS == applicationRunningError) {
return WAIT_TIMEOUT;
}
return applicationRunningError;
}

View File

@ -41,8 +41,10 @@ BOOL LaunchWinPostProcess(const WCHAR *installationDir,
HANDLE userToken);
BOOL StartServiceUpdate(int argc, LPWSTR *argv);
BOOL GetUpdateDirectoryPath(LPWSTR path);
BOOL LaunchServiceSoftwareUpdateCommand(DWORD argc, LPCWSTR *argv);
DWORD LaunchServiceSoftwareUpdateCommand(DWORD argc, LPCWSTR *argv);
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
BOOL WriteStatusPending(LPCWSTR updateDirPath);
BOOL WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds);
DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds);
#define SVC_NAME L"MozillaMaintenance"

View File

@ -148,6 +148,8 @@ const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
const SERVICE_UPDATER_SIGN_ERROR = 16002;
const SERVICE_UPDATER_COMPARE_ERROR = 16003;
const SERVICE_UPDATER_IDENTITY_ERROR = 16004;
const SERVICE_STILL_APPLYING_ON_SUCCESS = 16005;
const SERVICE_STILL_APPLYING_ON_FAILURE = 16006;
const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
@ -1439,7 +1441,9 @@ UpdateService.prototype = {
update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR) {
update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE) {
var failCount = getPref("getIntPref",
PREF_APP_UPDATE_SERVICE_ERRORS, 0);
var maxFail = getPref("getIntPref",

View File

@ -7,6 +7,7 @@
#pragma comment(lib, "crypt32.lib")
# include <windows.h>
# include <wintrust.h>
# include <tlhelp32.h>
# include <softpub.h>
# include <direct.h>
# include <io.h>
@ -157,19 +158,53 @@ VerifyCertificateTrustForFile(LPCWSTR filePath)
* This function does not stop the service, it just blocks until the service
* is stopped.
*
* @param serviceName The service to wait for.
* @param maxWaitSeconds The maximum number of seconds to wait
* @return true if the service was stopped after waiting at most maxWaitSeconds
* false on an error or when the service was not stopped
* @param serviceName The service to wait for.
* @param maxWaitSeconds The maximum number of seconds to wait
* @return state of the service after a timeout or when stopped.
* A value of 255 is returned for an error. Typical values are:
* SERVICE_STOPPED 0x00000001
* SERVICE_START_PENDING 0x00000002
* SERVICE_STOP_PENDING 0x00000003
* SERVICE_RUNNING 0x00000004
* SERVICE_CONTINUE_PENDING 0x00000005
* SERVICE_PAUSE_PENDING 0x00000006
* SERVICE_PAUSED 0x00000007
* last status not set 0x000000CF
* Could no query status 0x000000DF
* Could not open service, access denied 0x000000EB
* Could not open service, invalid handle 0x000000EC
* Could not open service, invalid name 0x000000ED
* Could not open service, does not exist 0x000000EE
* Could not open service, other error 0x000000EF
* Could not open SCM, access denied 0x000000FD
* Could not open SCM, database does not exist 0x000000FE;
* Could not open SCM, other error 0x000000FF;
* Note: The strange choice of error codes above SERVICE_PAUSED are chosen
* in case Windows comes out with other service stats higher than 7, they
* would likely call it 8 and above. JS code that uses this in TestAUSHelper
* only handles values up to 255 so that's why we don't use GetLastError
* directly.
*/
bool WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
DWORD
WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
{
// 0x000000CF is defined above to be not set
DWORD lastServiceState = 0x000000CF;
// Get a handle to the SCM database.
SC_HANDLE serviceManager = OpenSCManager(NULL, NULL,
SC_MANAGER_CONNECT |
SC_MANAGER_ENUMERATE_SERVICE);
if (!serviceManager) {
return false;
DWORD lastError = GetLastError();
switch(lastError) {
case ERROR_ACCESS_DENIED:
return 0x000000FD;
case ERROR_DATABASE_DOES_NOT_EXIST:
return 0x000000FE;
default:
return 0x000000FF;
}
}
// Get a handle to the service.
@ -177,33 +212,152 @@ bool WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
serviceName,
SERVICE_QUERY_STATUS);
if (!service) {
DWORD lastError = GetLastError();
CloseServiceHandle(serviceManager);
return false;
switch(lastError) {
case ERROR_ACCESS_DENIED:
return 0x000000EB;
case ERROR_INVALID_HANDLE:
return 0x000000EC;
case ERROR_INVALID_NAME:
return 0x000000ED;
// If the service does not exist, keep trying in case it does exist soon.
// I think there might be an issue with the TALOS machines and some of
// the machines having an old maintenanceservice.exe that used to
// uninstall when upgrading. Those should already be upgraded but this
// is safer.
case ERROR_SERVICE_DOES_NOT_EXIST:
if (maxWaitSeconds == 0) {
return 0x000000EE;
} else {
Sleep(1000);
return WaitForServiceStop(serviceName, maxWaitSeconds - 1);
}
default:
return 0x000000EF;
}
}
bool gotStop = false;
DWORD currentWaitMS = 0;
SERVICE_STATUS_PROCESS ssp;
ssp.dwCurrentState = lastServiceState;
while (currentWaitMS < maxWaitSeconds * 1000) {
// Make sure the service is not stopped.
SERVICE_STATUS_PROCESS ssp;
DWORD bytesNeeded;
if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
DWORD lastError = GetLastError();
switch (lastError) {
case ERROR_INVALID_HANDLE:
ssp.dwCurrentState = 0x000000D9;
break;
case ERROR_ACCESS_DENIED:
ssp.dwCurrentState = 0x000000DA;
break;
case ERROR_INSUFFICIENT_BUFFER:
ssp.dwCurrentState = 0x000000DB;
break;
case ERROR_INVALID_PARAMETER:
ssp.dwCurrentState = 0x000000DC;
break;
case ERROR_INVALID_LEVEL:
ssp.dwCurrentState = 0x000000DD;
break;
case ERROR_SHUTDOWN_IN_PROGRESS:
ssp.dwCurrentState = 0x000000DE;
break;
// These 3 errors can occur when the service is not yet stopped but
// it is stopping.
case ERROR_INVALID_SERVICE_CONTROL:
case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
case ERROR_SERVICE_NOT_ACTIVE:
currentWaitMS += 50;
Sleep(50);
continue;
default:
ssp.dwCurrentState = 0x000000DF;
}
// We couldn't query the status so just break out
break;
}
// The service is already in use.
if (ssp.dwCurrentState == SERVICE_STOPPED) {
gotStop = true;
break;
}
currentWaitMS += 50;
Sleep(50);
}
lastServiceState = ssp.dwCurrentState;
CloseServiceHandle(service);
CloseServiceHandle(serviceManager);
return gotStop;
return lastServiceState;
}
/**
* Determines if there is at least one process running for the specified
* application. A match will be found across any session for any user.
*
* @param process The process to check for existance
* @return ERROR_NOT_FOUND if the process was not found
* @ ERROR_SUCCESS if the process was found and there were no errors
* @ Other Win32 system error code for other errors
**/
DWORD
IsProcessRunning(LPCWSTR filename)
{
// Take a snapshot of all processes in the system.
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == snapshot) {
return GetLastError();
}
PROCESSENTRY32W processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(snapshot, &processEntry)) {
DWORD lastError = GetLastError();
CloseHandle(snapshot);
return lastError;
}
do {
if (wcsicmp(filename, processEntry.szExeFile) == 0) {
CloseHandle(snapshot);
return ERROR_SUCCESS;
}
} while (Process32NextW(snapshot, &processEntry));
CloseHandle(snapshot);
return ERROR_NOT_FOUND;
}
/**
* Waits for the specified applicaiton to exit.
*
* @param filename The application to wait for.
* @param maxSeconds The maximum amount of seconds to wait for all
* instances of the application to exit.
* @return ERROR_SUCCESS if no instances of the application exist
* WAIT_TIMEOUT if the process is still running after maxSeconds.
* Any other Win32 system error code.
*/
DWORD
WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
{
DWORD applicationRunningError = WAIT_TIMEOUT;
for(DWORD i = 0; i < maxSeconds; i++) {
DWORD applicationRunningError = IsProcessRunning(filename);
if (ERROR_NOT_FOUND == applicationRunningError) {
return ERROR_SUCCESS;
}
Sleep(1000);
}
if (ERROR_SUCCESS == applicationRunningError) {
return WAIT_TIMEOUT;
}
return applicationRunningError;
}
@ -258,10 +412,29 @@ int NS_main(int argc, NS_tchar **argv)
#ifdef XP_WIN
const int maxWaitSeconds = NS_ttoi(argv[3]);
LPCWSTR serviceName = argv[2];
if (WaitForServiceStop(serviceName, maxWaitSeconds)) {
DWORD serviceState = WaitForServiceStop(serviceName, maxWaitSeconds);
if (SERVICE_STOPPED == serviceState) {
return 0;
} else {
return serviceState;
}
#else
// Not implemented on non-Windows platforms
return 1;
#endif
}
if (!NS_tstrcmp(argv[1], NS_T("wait-for-application-exit"))) {
#ifdef XP_WIN
const int maxWaitSeconds = NS_ttoi(argv[3]);
LPCWSTR application = argv[2];
DWORD ret = WaitForProcessExit(application, maxWaitSeconds);
if (ERROR_SUCCESS == ret) {
return 0;
} else if (WAIT_TIMEOUT == ret) {
return 1;
} else {
return 2;
}
#else
// Not implemented on non-Windows platforms

View File

@ -462,7 +462,12 @@ let gServiceLaunchedCallbackArgs = null;
*
* @return true if the test should run and false if it shouldn't.
*/
function shouldRunServiceTest() {
function shouldRunServiceTest(aFirstTest) {
// In case the machine is running an old maintenance service or if it
// is not installed, and permissions exist to install it. Then install
// the newer bin that we have.
attemptServiceInstall();
const REG_PATH = "SOFTWARE\\Mozilla\\MaintenanceService\\" +
"3932ecacee736d366d6436db0f55bce4";
@ -490,17 +495,38 @@ function shouldRunServiceTest() {
updaterBinPath = '"' + updaterBinPath + '"';
}
// Check to make sure the service is installed
let helperBin = do_get_file(HELPER_BIN_FILE);
let args = ["check-signature", updaterBinPath];
let args = ["wait-for-service-stop", "MozillaMaintenance", "10"];
let process = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
process.init(helperBin);
logTestInfo("Checking if the service exists on this machine.");
process.run(true, args, args.length);
if (process.exitValue == 0xEE) {
logTestInfo("this test can only run when the service is installed.");
return false;
} else {
logTestInfo("Service exists, return value: " + process.exitValue);
}
// If this is the first test in the series, then there is no reason the
// service should be anything but stopped, so be strict here and throw
// an error.
if (aFirstTest && process.exitValue != 0) {
do_throw("First test, check for service stopped state returned error " +
process.exitValue);
}
// Make sure the binaries are signed
args = ["check-signature", updaterBinPath];
process = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
process.init(helperBin);
process.run(true, args, args.length);
if (process.exitValue == 0) {
return true;
}
logTestInfo("this test can only run on builds with signed binaries. " +
HELPER_BIN_FILE + " returned " + process.exitValue)
return false;
@ -526,6 +552,34 @@ function copyBinToApplyToDir(filename) {
}
}
/**
* Attempts to upgrade the maintenance service if permissions are allowed.
* This is useful for XP where we have permission to upgrade in case an
* older service installer exists. Also if the user manually installed into
* a unprivileged location.
*/
function attemptServiceInstall() {
var version = AUS_Cc["@mozilla.org/system-info;1"]
.getService(AUS_Ci.nsIPropertyBag2)
.getProperty("version");
var isVistaOrHigher = (parseFloat(version) >= 6.0);
if (isVistaOrHigher) {
return;
}
let binDir = getGREDir();
let installerFile = binDir.clone();
installerFile.append(MAINTENANCE_SERVICE_INSTALLER_BIN_FILE);
if (!installerFile.exists()) {
do_throw(MAINTENANCE_SERVICE_INSTALLER_BIN_FILE + " not found.");
}
let installerProcess = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
installerProcess.init(installerFile);
logTestInfo("Starting installer process...");
installerProcess.run(true, [], 0);
}
/**
* Helper function for updater tests for launching the updater using the
* maintenance service to apply a mar file.
@ -554,26 +608,60 @@ function runUpdateUsingService(aInitialStatus, aExpectedStatus,
file.append("maintenanceservice.log");
return readFile(file);
}
function waitServiceApps() {
// maintenanceservice_installer.exe is started async during updates.
waitForApplicationStop("maintenanceservice_installer.exe");
// maintenanceservice_tmp.exe is started async from the service installer.
waitForApplicationStop("maintenanceservice_tmp.exe");
// In case the SCM thinks the service is stopped, but process still exists.
waitForApplicationStop("maintenanceservice.exe");
}
function waitForServiceStop(aFailTest) {
waitServiceApps();
logTestInfo("Waiting for service to stop if necessary...");
// Use the helper bin to ensure the service is stopped. If not
// stopped then wait for the service to be stopped (at most 20 seconds)
// stopped then wait for the service to be stopped (at most 120 seconds)
let helperBin = do_get_file(HELPER_BIN_FILE);
let helperBinArgs = ["wait-for-service-stop",
"MozillaMaintenance",
"20"];
"120"];
let helperBinProcess = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
helperBinProcess.init(helperBin);
logTestInfo("Stopping service...");
helperBinProcess.run(true, helperBinArgs, helperBinArgs.length);
if (helperBinProcess.exitValue == 0xEE) {
do_throw("The service does not exist on this machine. Return value: " +
helperBinProcess.exitValue);
} else if (helperBinProcess.exitValue != 0) {
if (aFailTest) {
do_throw("maintenance service did not stop, last state: " +
helperBinProcess.exitValue + ". Forcing test failure.");
} else {
logTestInfo("maintenance service did not stop, last state: " +
helperBinProcess.exitValue + ". May cause failures.");
}
} else {
logTestInfo("Service stopped.");
}
waitServiceApps();
}
function waitForApplicationStop(application) {
logTestInfo("Waiting for " + application + " to stop if " +
"necessary...");
// Use the helper bin to ensure the application is stopped.
// If not, then wait for it to be stopped (at most 120 seconds)
let helperBin = do_get_file(HELPER_BIN_FILE);
let helperBinArgs = ["wait-for-application-exit",
application,
"120"];
let helperBinProcess = AUS_Cc["@mozilla.org/process/util;1"].
createInstance(AUS_Ci.nsIProcess);
helperBinProcess.init(helperBin);
helperBinProcess.run(true, helperBinArgs, helperBinArgs.length);
if (helperBinProcess.exitValue != 0) {
if (aFailTest) {
do_throw("maintenance service did not stop, forcing test failure.");
} else {
logTestInfo("maintenance service did not stop, may cause failures.");
}
} else {
logTestInfo("Service stopped.");
do_throw(application + " did not stop, last state: " +
helperBinProcess.exitValue + ". Forcing test failure.");
}
}
@ -587,19 +675,6 @@ function runUpdateUsingService(aInitialStatus, aExpectedStatus,
do_register_cleanup(function serviceCleanup() {
resetEnvironment();
// Remove the copy of the application executable used for the test on
// Windows if it exists.
let appBinCopy = getCurrentProcessDir();
appBinCopy.append(TEST_ID + FILE_WIN_TEST_EXE);
if (appBinCopy.exists()) {
try {
appBinCopy.remove(false);
}
catch (e) {
logTestInfo("unable to remove file during cleanup. Exception: " + e);
}
}
// This will delete the app console log file if it exists.
try {
getAppConsoleLogPath();
@ -672,7 +747,7 @@ function runUpdateUsingService(aInitialStatus, aExpectedStatus,
copyBinToApplyToDir(MAINTENANCE_SERVICE_INSTALLER_BIN_FILE);
// Firefox does not wait for the service command to finish, but
// we still launch the process sync to avoid intemittent failures with
// we still launch the process sync to avoid intermittent failures with
// the log file not being written out yet.
// We will rely on watching the update.status file and waiting for the service
// to stop to know the service command is done.
@ -696,14 +771,15 @@ function runUpdateUsingService(aInitialStatus, aExpectedStatus,
" status, got " + status + " for now...");
return;
}
// Make sure all of the logs are written out.
waitForServiceStop(false);
do_check_eq(status, aExpectedStatus);
timer.cancel();
timer = null;
// Make sure all of the logs are written out.
waitForServiceStop(false);
if (aCheckSvcLog) {
checkServiceLogs(svcOriginalLog);
}
@ -1206,8 +1282,30 @@ function checkCallbackServiceLog() {
"and the expected command line arguments passed to it");
do_check_eq(logContents, expectedLogContents);
// Use a timeout to give any files that were in use additional time to close.
do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
(function removeCallbackCopy() {
// Remove the copy of the application executable used for the test on
// Windows if it exists.
let appBinCopy = getCurrentProcessDir();
appBinCopy.append(TEST_ID + FILE_WIN_TEST_EXE);
if (appBinCopy.exists()) {
try {
appBinCopy.remove(false);
// Use a timeout to give any files that were in use additional
// time to close. Same as updater.exe without service tests.
do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
}
catch (e) {
logTestInfo("unable to remove file during cleanup. Exception: " + e);
do_timeout(TEST_HELPER_TIMEOUT, removeCallbackCopy);
}
} else {
do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
}
})();
}
/**

View File

@ -21,7 +21,7 @@ const TEST_FILES = [
];
function run_test() {
if (!shouldRunServiceTest()) {
if (!shouldRunServiceTest(true)) {
return;
}

View File

@ -1605,8 +1605,8 @@ int NS_main(int argc, NS_tchar **argv)
bool useService = false;
bool testOnlyFallbackKeyExists = false;
bool noServiceFallback = _wgetenv(L"MOZ_NO_SERVICE_FALLBACK") != NULL;
_wputenv(L"MOZ_NO_SERVICE_FALLBACK=");
bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != NULL;
putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
// We never want the service to be used unless we build with
// the maintenance service.
@ -1680,9 +1680,10 @@ int NS_main(int argc, NS_tchar **argv)
// argument prior to callbackIndex is the working directory.
const int callbackIndex = 5;
bool usingService = false;
#if defined(XP_WIN)
bool usingService = _wgetenv(L"MOZ_USING_SERVICE") != NULL;
_wputenv(L"MOZ_USING_SERVICE=");
usingService = getenv("MOZ_USING_SERVICE") != NULL;
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.
@ -1783,16 +1784,19 @@ int NS_main(int argc, NS_tchar **argv)
if (useService) {
// If the update couldn't be started, then set useService to false so
// we do the update the old way.
useService = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
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) {
if (!WaitForServiceStop(SVC_NAME, 600)) {
DWORD lastState = WaitForServiceStop(SVC_NAME, 600);
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;
}
}
@ -1903,8 +1907,12 @@ int NS_main(int argc, NS_tchar **argv)
HANDLE callbackFile = INVALID_HANDLE_VALUE;
if (argc > callbackIndex) {
// If the callback executable is specified it must exist for a successful
// update.
// update. It is important we null out the whole buffer here because later
// we make the assumption that the callback application is inside the
// apply-to dir. If we don't have a fully null'ed out buffer it can lead
// to stack corruption which causes crashes and other problems.
NS_tchar callbackLongPath[MAXPATHLEN];
ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
if (!GetLongPathNameW(argv[callbackIndex], callbackLongPath,
sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) {
LOG(("NS_main: unable to find callback file: " LOG_S "\n", argv[callbackIndex]));

View File

@ -3136,6 +3136,25 @@ XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
if (NS_FAILED(rv))
updRoot = dirProvider.GetAppDir();
// If the MOZ_PROCESS_UPDATES environment variable already exists, then
// we are being called from the callback application.
if (EnvHasValue("MOZ_PROCESS_UPDATES")) {
// If the caller has asked us to log our arguments, do so. This is used
// to make sure that the maintenance service successfully launches the
// callback application.
const char *logFile = nsnull;
if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) {
FILE* logFP = fopen(logFile, "wb");
if (logFP) {
for (i = 1; i < gRestartArgc; ++i) {
fprintf(logFP, "%s\n", gRestartArgv[i]);
}
fclose(logFP);
}
}
return 0;
}
// Support for processing an update and exiting. The MOZ_PROCESS_UPDATES
// environment variable will be part of the updater's environment and the
// application that is relaunched by the updater. When the application is
@ -3152,20 +3171,6 @@ XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
appData.version);
if (EnvHasValue("MOZ_PROCESS_UPDATES")) {
SaveToEnv("MOZ_PROCESS_UPDATES=");
// If the caller has asked us to log our arguments, do so. This is used
// to make sure that the maintenance service successfully launches the
// callback application.
const char *logFile = nsnull;
if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) {
FILE* logFP = fopen(logFile, "wb");
if (logFP) {
for (i = 1; i < gRestartArgc; ++i) {
fprintf(logFP, "%s\n", gRestartArgv[i]);
}
fclose(logFP);
}
}
return 0;
}
#endif

View File

@ -265,13 +265,17 @@ WinLaunchChild(const PRUnichar *exePath,
si.lpDesktop = L"winsta0\\Default";
PROCESS_INFORMATION pi = {0};
DWORD creationFlags = 0;
#ifdef DEBUG
creationFlags |= CREATE_NEW_CONSOLE;
#endif
if (userToken == NULL) {
ok = CreateProcessW(exePath,
cl,
NULL, // no special security attributes
NULL, // no special thread attributes
FALSE, // don't inherit filehandles
0, // No special process creation flags
creationFlags,
NULL, // inherit my environment
NULL, // use my current directory
&si,