зеркало из https://github.com/mozilla/gecko-dev.git
Bug 708854 - No longer use session ID for work item files. r=rstrong.
--HG-- rename : toolkit/mozapps/update/common/launchwinprocess.cpp => toolkit/mozapps/update/common/updatehelper.cpp rename : toolkit/mozapps/update/common/launchwinprocess.h => toolkit/mozapps/update/common/updatehelper.h
This commit is contained in:
Родитель
61a9e5f11a
Коммит
48e8c66f8e
|
@ -53,8 +53,7 @@ CPPSRCS = \
|
|||
$(NULL)
|
||||
|
||||
# For debugging purposes only
|
||||
#DEFINES += -DDISABLE_SERVICE_AUTHENTICODE_CHECK \
|
||||
# -DDISABLE_CALLBACK_AUTHENTICODE_CHECK
|
||||
#DEFINES += -DDISABLE_UPDATER_AUTHENTICODE_CHECK
|
||||
|
||||
PROGRAM = maintenanceservice$(BIN_SUFFIX)
|
||||
DIST_PROGRAM = maintenanceservice$(BIN_SUFFIX)
|
||||
|
|
|
@ -39,5 +39,3 @@
|
|||
#include "updatelogging.h"
|
||||
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
|
||||
#define SERVICE_EVENT_NAME L"Global\\moz-5b780de9-065b-4341-a04f-ddd94b3723e5"
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
#include "servicebase.h"
|
||||
#include "registrycertificates.h"
|
||||
#include "uachelper.h"
|
||||
#include "launchwinprocess.h"
|
||||
#include "updatehelper.h"
|
||||
|
||||
extern BOOL gServiceStopping;
|
||||
|
||||
|
@ -63,8 +63,6 @@ extern BOOL gServiceStopping;
|
|||
static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
|
||||
PRUnichar* MakeCommandLine(int argc, PRUnichar **argv);
|
||||
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
|
||||
BOOL WriteStatusPending(LPCWSTR updateDirPath);
|
||||
BOOL StartCallbackApp(int argcTmp, LPWSTR *argvTmp, DWORD callbackSessionID);
|
||||
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName);
|
||||
|
||||
|
@ -73,23 +71,14 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
|
|||
const int SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000;
|
||||
const int SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
|
||||
const int SERVICE_UPDATER_SIGN_ERROR = 16002;
|
||||
const int SERVICE_CALLBACK_SIGN_ERROR = 16003;
|
||||
|
||||
/**
|
||||
* Runs an update process in the specified sessionID as an elevated process.
|
||||
* Runs an update process as the service using the SYSTEM account.
|
||||
*
|
||||
* @param updaterPath The path to the update process to start.
|
||||
* @param workingDir The working directory to execute the update process
|
||||
* @param cmdLine in. The command line parameters to pass to the update
|
||||
* process. If they specify a callback application, it
|
||||
* will be executed with an associated unelevated token
|
||||
* for the sessionID.
|
||||
* @param processStarted Returns TRUE if the process was started.
|
||||
* @param callbackSessionID
|
||||
* If 0 and Windows Vista, the callback application will
|
||||
* not be run. If non zero the callback application will
|
||||
* be injected into the user's session as a non-elevated
|
||||
* process.
|
||||
* @param updaterPath The path to the update process to start.
|
||||
* @param workingDir The working directory to execute the update process in
|
||||
* @param cmdLine The command line parameters to pass 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
|
||||
|
@ -97,28 +86,19 @@ StartUpdateProcess(LPCWSTR updaterPath,
|
|||
LPCWSTR workingDir,
|
||||
int argcTmp,
|
||||
LPWSTR *argvTmp,
|
||||
BOOL &processStarted,
|
||||
DWORD callbackSessionID = 0)
|
||||
BOOL &processStarted)
|
||||
{
|
||||
DWORD myProcessID = GetCurrentProcessId();
|
||||
DWORD mySessionID = 0;
|
||||
ProcessIdToSessionId(myProcessID, &mySessionID);
|
||||
|
||||
STARTUPINFO si = {0};
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
si.lpDesktop = L"winsta0\\Default";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
LOG(("Starting process in an elevated session. Service "
|
||||
"session ID: %d; Requested callback session ID: %d\n",
|
||||
mySessionID, callbackSessionID));
|
||||
LOG(("Starting update process as the service in session 0."));
|
||||
|
||||
// The updater command line is of the form:
|
||||
// updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
|
||||
// So update callback-dir is the 4th, callback-path is the 5th and its args
|
||||
// are the 6th index. So that we can execute the callback out of line we
|
||||
// won't call updater.exe with those callback args and we will manage the
|
||||
// callback ourselves.
|
||||
// are the 6th index.
|
||||
LPWSTR cmdLine = MakeCommandLine(argcTmp, argvTmp);
|
||||
|
||||
// If we're about to start the update process from session 0,
|
||||
|
@ -149,10 +129,12 @@ StartUpdateProcess(LPCWSTR updaterPath,
|
|||
MOVEFILE_REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
// Create an environment block for the process we're about to start using
|
||||
// the user's token.
|
||||
// Create an environment block for the updater.exe process we're about to
|
||||
// start. Indicate that MOZ_USING_SERVICE is set 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.
|
||||
WCHAR envVarString[32];
|
||||
wsprintf(envVarString, L"MOZ_SESSION_ID=%d", callbackSessionID);
|
||||
wsprintf(envVarString, L"MOZ_USING_SERVICE=1");
|
||||
_wputenv(envVarString);
|
||||
LPVOID environmentBlock = NULL;
|
||||
if (!CreateEnvironmentBlock(&environmentBlock, NULL, TRUE)) {
|
||||
|
@ -160,7 +142,7 @@ StartUpdateProcess(LPCWSTR updaterPath,
|
|||
environmentBlock = NULL;
|
||||
}
|
||||
// Empty value on _wputenv is how you remove an env variable in Windows
|
||||
_wputenv(L"MOZ_SESSION_ID=");
|
||||
_wputenv(L"MOZ_USING_SERVICE=");
|
||||
processStarted = CreateProcessW(updaterPath, cmdLine,
|
||||
NULL, NULL, FALSE,
|
||||
CREATE_DEFAULT_ERROR_MODE |
|
||||
|
@ -210,15 +192,16 @@ StartUpdateProcess(LPCWSTR updaterPath,
|
|||
if (updateWasSuccessful && argcTmp > 5) {
|
||||
LPCWSTR callbackApplication = argvTmp[5];
|
||||
LPCWSTR updateInfoDir = argvTmp[1];
|
||||
// Launch the PostUpdate process with SYSTEM in session 0. We force sync
|
||||
// because we run this twice and we want to make sure the uninstaller
|
||||
// keys get added to HKLM before the ones try to get added to HKCU. If
|
||||
// we did it async we'd have a race condition that would sometimes lead
|
||||
// to 2 uninstall icons.
|
||||
|
||||
// Launch the PostProcess with admin access in session 0. This is
|
||||
// actually launching the post update process but it takes in the
|
||||
// callback app path to figure out where to apply to.
|
||||
// The PostUpdate process with user only access will be done inside
|
||||
// the unelevated updater.exe after the update process is complete
|
||||
// from the service. We don't know here which session to start
|
||||
// the user PostUpdate process from.
|
||||
LOG(("Launching post update process as the service in session 0."));
|
||||
LaunchWinPostProcess(callbackApplication, updateInfoDir, true, NULL);
|
||||
nsAutoHandle userToken(UACHelper::OpenUserToken(callbackSessionID));
|
||||
LaunchWinPostProcess(callbackApplication,
|
||||
updateInfoDir, false, userToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,10 +239,11 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
}
|
||||
|
||||
// Indicate that the service is busy and shouldn't be used by anyone else
|
||||
// by creating a named event. Programs should check if this event exists
|
||||
// before writing a work item out.
|
||||
nsAutoHandle serviceRunning(CreateEventW(NULL, TRUE,
|
||||
FALSE, SERVICE_EVENT_NAME));
|
||||
// by opening or creating a named event. Programs should check if this
|
||||
// event exists before writing a work item out. It should already be
|
||||
// created by updater.exe so CreateEventW will lead to an open named event.
|
||||
nsAutoHandle serviceRunningEvent(CreateEventW(NULL, TRUE,
|
||||
FALSE, SERVICE_EVENT_NAME));
|
||||
|
||||
LOG(("Processing new command meta file: %ls\n", notifyInfo.FileName));
|
||||
WCHAR fullMetaUpdateFilePath[MAX_PATH + 1];
|
||||
|
@ -268,7 +252,7 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
// We only support file paths in monitoring directories that are MAX_PATH
|
||||
// chars or less.
|
||||
if (!PathAppendSafe(fullMetaUpdateFilePath, notifyInfo.FileName)) {
|
||||
// Cannot process update, skipfileSize it.
|
||||
SetEvent(serviceRunningEvent);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -282,11 +266,12 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
0, NULL));
|
||||
if (metaUpdateFile == INVALID_HANDLE_VALUE) {
|
||||
LOG(("Could not open command meta file: %ls\n", notifyInfo.FileName));
|
||||
SetEvent(serviceRunningEvent);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
DWORD fileSize = GetFileSize(metaUpdateFile, NULL);
|
||||
DWORD sessionID = 0, commandID = 0;
|
||||
DWORD commandID = 0;
|
||||
// The file should be in wide characters so if it's of odd size it's
|
||||
// an invalid file.
|
||||
const int kSanityCheckFileSize = 1024 * 64;
|
||||
|
@ -297,6 +282,7 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
LOG(("Could not obtain file size or an improper file size was encountered "
|
||||
"for command meta file: %ls\n",
|
||||
notifyInfo.FileName));
|
||||
SetEvent(serviceRunningEvent);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -307,12 +293,6 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
sizeof(DWORD), &commandIDCount, NULL);
|
||||
fileSize -= sizeof(DWORD);
|
||||
|
||||
// The next 4 bytes are for the process ID
|
||||
DWORD sessionIDCount;
|
||||
result |= ReadFile(metaUpdateFile, &sessionID,
|
||||
sizeof(DWORD), &sessionIDCount, NULL);
|
||||
fileSize -= sizeof(DWORD);
|
||||
|
||||
// The next MAX_PATH wchar's are for the app to start
|
||||
WCHAR updaterPath[MAX_PATH + 1];
|
||||
ZeroMemory(updaterPath, sizeof(updaterPath));
|
||||
|
@ -346,39 +326,26 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
if (!result ||
|
||||
commandID != 1 ||
|
||||
commandIDCount != sizeof(DWORD) ||
|
||||
sessionIDCount != sizeof(DWORD) ||
|
||||
updaterPathCount != MAX_PATH * sizeof(WCHAR) ||
|
||||
workingDirectoryCount != MAX_PATH * sizeof(WCHAR) ||
|
||||
fileSize != 0) {
|
||||
LOG(("Could not read command data for command meta file: %ls\n",
|
||||
notifyInfo.FileName));
|
||||
SetEvent(serviceRunningEvent);
|
||||
return TRUE;
|
||||
}
|
||||
cmdlineBuffer[cmdLineBufferRead] = '\0';
|
||||
cmdlineBuffer[cmdLineBufferRead + 1] = '\0';
|
||||
WCHAR *cmdlineBufferWide = reinterpret_cast<WCHAR*>(cmdlineBuffer.get());
|
||||
LOG(("An update command was detected and is being processed for command meta "
|
||||
"file: %ls\n",
|
||||
notifyInfo.FileName));
|
||||
"file: %ls\n", notifyInfo.FileName));
|
||||
|
||||
int argcTmp = 0;
|
||||
LPWSTR* argvTmp = CommandLineToArgvW(cmdlineBufferWide, &argcTmp);
|
||||
|
||||
// Check for callback application sign problems
|
||||
BOOL callbackSignProblem = FALSE;
|
||||
#ifndef DISABLE_CALLBACK_AUTHENTICODE_CHECK
|
||||
// If we have less than 6 params, then there is no callback to check, so
|
||||
// we have no callback sign problem.
|
||||
if (argcTmp > 5) {
|
||||
LPWSTR callbackApplication = argvTmp[5];
|
||||
callbackSignProblem =
|
||||
!DoesBinaryMatchAllowedCertificates(argvTmp[2], callbackApplication);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check for updater.exe sign problems
|
||||
// Check for updater.exe sign problems
|
||||
BOOL updaterSignProblem = FALSE;
|
||||
#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
|
||||
#ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
|
||||
if (argcTmp > 2) {
|
||||
updaterSignProblem = !DoesBinaryMatchAllowedCertificates(argvTmp[2],
|
||||
updaterPath);
|
||||
|
@ -387,62 +354,42 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
|
||||
// In order to proceed with the update we need at least 3 command line
|
||||
// parameters and no sign problems.
|
||||
if (argcTmp > 2 && !updaterSignProblem && !callbackSignProblem) {
|
||||
if (argcTmp > 2 && !updaterSignProblem) {
|
||||
BOOL updateProcessWasStarted = FALSE;
|
||||
if (StartUpdateProcess(updaterPath, workingDirectory,
|
||||
argcTmp, argvTmp,
|
||||
updateProcessWasStarted,
|
||||
sessionID)) {
|
||||
updateProcessWasStarted)) {
|
||||
LOG(("updater.exe was launched and run successfully!\n"));
|
||||
StartServiceUpdate(argcTmp, argvTmp);
|
||||
} else {
|
||||
LOG(("Error running process in session %d. "
|
||||
"Updating update.status. Last error: %d\n",
|
||||
sessionID, GetLastError()));
|
||||
LOG(("Error running update process. Updating update.status"
|
||||
" Last error: %d\n", GetLastError()));
|
||||
|
||||
// If the update process was started, then updater.exe is responsible for
|
||||
// setting the failure code and running the callback. 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.
|
||||
// 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(argvTmp[1],
|
||||
SERVICE_UPDATER_COULD_NOT_BE_STARTED)) {
|
||||
LOG(("Could not write update.status service update failure."
|
||||
"Last error: %d\n", GetLastError()));
|
||||
}
|
||||
|
||||
// We only hit this condition when there is no callback sign error
|
||||
// so we don't need to check it.
|
||||
StartCallbackApp(argcTmp, argvTmp, sessionID);
|
||||
}
|
||||
}
|
||||
} else if (argcTmp <= 2) {
|
||||
LOG(("Not enough command line parameters specified. "
|
||||
"Updating update.status.\n"));
|
||||
|
||||
// We can't start the callback in this case because there
|
||||
// are not enough command line parameters. 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.
|
||||
// We can only update update.status if argvTmp[1] exists. argvTmp[1] is
|
||||
// the directory where the update.status file exists.
|
||||
if (argcTmp != 2 ||
|
||||
!WriteStatusFailure(argvTmp[1],
|
||||
SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
|
||||
LOG(("Could not write update.status service update failure."
|
||||
"Last error: %d\n", GetLastError()));
|
||||
}
|
||||
} else if (callbackSignProblem) {
|
||||
LOG(("Will not run updater nor callback due to callback sign error "
|
||||
"in session %d. Updating update.status. Last error: %d\n",
|
||||
sessionID, GetLastError()));
|
||||
|
||||
// When there is a certificate check error on the callback application, we
|
||||
// want to write out the error.
|
||||
if (!WriteStatusFailure(argvTmp[1],
|
||||
SERVICE_CALLBACK_SIGN_ERROR)) {
|
||||
LOG(("Could not write pending state to update.status. (%d)\n",
|
||||
GetLastError()));
|
||||
}
|
||||
} else {
|
||||
LOG(("Could not start process due to certificate check error on "
|
||||
"updater.exe. Updating update.status. Last error: %d\n", GetLastError()));
|
||||
|
@ -454,13 +401,9 @@ ProcessWorkItem(LPCWSTR monitoringBasePath,
|
|||
LOG(("Could not write pending state to update.status. (%d)\n",
|
||||
GetLastError()));
|
||||
}
|
||||
|
||||
// On certificate check errors on updater.exe, updater.exe won't run at all
|
||||
// so we need to manually start the callback application ourselves.
|
||||
// This condition will only be hit when the callback app has no sign errors
|
||||
StartCallbackApp(argcTmp, argvTmp, sessionID);
|
||||
}
|
||||
LocalFree(argvTmp);
|
||||
SetEvent(serviceRunningEvent);
|
||||
|
||||
// We processed a work item, whether or not it was successful.
|
||||
return TRUE;
|
||||
|
@ -544,104 +487,3 @@ StartDirectoryChangeMonitor()
|
|||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the callback application from the updater.exe command line arguments.
|
||||
*
|
||||
* @param argcTmp The argc value normally sent to updater.exe
|
||||
* @param argvTmp The argv value normally sent to updater.exe
|
||||
* @param callbackSessionID The session ID to start the callback with
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
StartCallbackApp(int argcTmp, LPWSTR *argvTmp, DWORD callbackSessionID)
|
||||
{
|
||||
if (0 == callbackSessionID && UACHelper::IsVistaOrLater()) {
|
||||
LOG(("Attempted to run callback application in session 0, not allowed.\n"));
|
||||
LOG(("Session 0 is only for services on Vista and up.\n"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (argcTmp < 5) {
|
||||
LOG(("No callback application specified.\n"));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
LPWSTR callbackArgs = NULL;
|
||||
LPWSTR callbackDirectory = argvTmp[4];
|
||||
LPWSTR callbackApplication = argvTmp[5];
|
||||
if (argcTmp > 6) {
|
||||
// Make room for all of the ending args minus the first 6
|
||||
// + 1 for the app itself
|
||||
const size_t callbackCommandLineLen = argcTmp - 5;
|
||||
WCHAR **params = new WCHAR*[callbackCommandLineLen];
|
||||
params[0] = callbackApplication;
|
||||
for (size_t i = 1; i < callbackCommandLineLen; ++i) {
|
||||
params[i] = argvTmp[5 + i];
|
||||
}
|
||||
callbackArgs = MakeCommandLine(callbackCommandLineLen, params);
|
||||
delete[] params;
|
||||
}
|
||||
|
||||
if (!callbackApplication) {
|
||||
LOG(("Callback application is NULL.\n"));
|
||||
if (callbackArgs) {
|
||||
free(callbackArgs);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
nsAutoHandle unelevatedToken(UACHelper::OpenUserToken(callbackSessionID));
|
||||
if (!unelevatedToken) {
|
||||
LOG(("Could not obtain unelevated token for callback app.\n"));
|
||||
if (callbackArgs) {
|
||||
free(callbackArgs);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Create an environment block for the process we're about to start using
|
||||
// the user's token.
|
||||
LPVOID environmentBlock = NULL;
|
||||
if (!CreateEnvironmentBlock(&environmentBlock, unelevatedToken, TRUE)) {
|
||||
LOG(("Could not create an environment block, setting it to NULL.\n"));
|
||||
environmentBlock = NULL;
|
||||
}
|
||||
|
||||
STARTUPINFOW si = {0};
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
si.lpDesktop = L"winsta0\\Default";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
if (CreateProcessAsUserW(unelevatedToken,
|
||||
callbackApplication,
|
||||
callbackArgs,
|
||||
NULL, NULL, FALSE,
|
||||
CREATE_DEFAULT_ERROR_MODE |
|
||||
#ifdef DEBUG
|
||||
CREATE_NEW_CONSOLE |
|
||||
#endif
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
environmentBlock,
|
||||
callbackDirectory,
|
||||
&si, &pi)) {
|
||||
LOG(("Callback app was run successfully.\n"));
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
if (environmentBlock) {
|
||||
DestroyEnvironmentBlock(environmentBlock);
|
||||
}
|
||||
if (callbackArgs) {
|
||||
free(callbackArgs);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
LOG(("Could not run callback app, last error: %d", GetLastError()));
|
||||
if (environmentBlock) {
|
||||
DestroyEnvironmentBlock(environmentBlock);
|
||||
}
|
||||
if (callbackArgs) {
|
||||
free(callbackArgs);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
@ -59,11 +59,11 @@ EXPORTS = updatelogging.h \
|
|||
$(NULL)
|
||||
|
||||
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
|
||||
CPPSRCS += launchwinprocess.cpp \
|
||||
CPPSRCS += updatehelper.cpp \
|
||||
uachelper.cpp \
|
||||
$(NULL)
|
||||
|
||||
EXPORTS = launchwinprocess.h \
|
||||
EXPORTS = updatehelper.h \
|
||||
uachelper.h \
|
||||
$(NULL)
|
||||
endif
|
||||
|
|
|
@ -1,259 +0,0 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is common code between maintenanceservice and updater
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Brian R. Bondy <netzen@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
|
||||
|
||||
/**
|
||||
* Obtains the path of a file in the same directory as the specified file.
|
||||
*
|
||||
* @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
|
||||
* @param siblingFIlePath The path of another file in the same directory
|
||||
* @param newFileName The filename of another file in the same directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
PathGetSiblingFilePath(LPWSTR destinationBuffer,
|
||||
LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName)
|
||||
{
|
||||
if (wcslen(siblingFilePath) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcscpy(destinationBuffer, siblingFilePath);
|
||||
if (!PathRemoveFileSpecW(destinationBuffer)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return PathAppendSafe(destinationBuffer, newFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the post update application as the specified user (helper.exe).
|
||||
* It takes in the path of the callback application to calculate the path
|
||||
* of helper.exe. For service updates this is called from both the system
|
||||
* account and the current user account.
|
||||
*
|
||||
* @param appExe The path to the callback application binary.
|
||||
* @param updateInfoDir The directory where update info is stored.
|
||||
* @param forceSync If true even if the ini file specifies async, the
|
||||
* process will wait for termination of PostUpdate.
|
||||
* @param userToken The user token to run as, if NULL the current user
|
||||
* will be used.
|
||||
*/
|
||||
BOOL
|
||||
LaunchWinPostProcess(const WCHAR *appExe,
|
||||
const WCHAR *updateInfoDir,
|
||||
bool forceSync,
|
||||
HANDLE userToken)
|
||||
{
|
||||
WCHAR workingDirectory[MAX_PATH + 1];
|
||||
wcscpy(workingDirectory, appExe);
|
||||
if (!PathRemoveFileSpecW(workingDirectory)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Launch helper.exe to perform post processing (e.g. registry and log file
|
||||
// modifications) for the update.
|
||||
WCHAR inifile[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(inifile, appExe, L"updater.ini")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR exefile[MAX_PATH + 1];
|
||||
WCHAR exearg[MAX_PATH + 1];
|
||||
WCHAR exeasync[10];
|
||||
bool async = true;
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", NULL, exefile,
|
||||
MAX_PATH + 1, inifile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", NULL, exearg,
|
||||
MAX_PATH + 1, inifile))
|
||||
return FALSE;
|
||||
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
|
||||
exeasync,
|
||||
sizeof(exeasync)/sizeof(exeasync[0]), inifile))
|
||||
return FALSE;
|
||||
|
||||
WCHAR exefullpath[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(exefullpath, appExe, exefile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR dlogFile[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR slogFile[MAX_PATH + 1];
|
||||
wcscpy(slogFile, updateInfoDir);
|
||||
if (!PathAppendSafe(slogFile, L"update.log")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR dummyArg[14];
|
||||
wcscpy(dummyArg, L"argv0ignored ");
|
||||
|
||||
size_t len = wcslen(exearg) + wcslen(dummyArg);
|
||||
WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
|
||||
if (!cmdline) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcscpy(cmdline, dummyArg);
|
||||
wcscat(cmdline, exearg);
|
||||
|
||||
if (forceSync ||
|
||||
!_wcsnicmp(exeasync, L"false", 6) ||
|
||||
!_wcsnicmp(exeasync, L"0", 2)) {
|
||||
async = false;
|
||||
}
|
||||
|
||||
// We want to launch the post update helper app to update the Windows
|
||||
// registry even if there is a failure with removing the uninstall.update
|
||||
// file or copying the update.log file.
|
||||
CopyFileW(slogFile, dlogFile, false);
|
||||
|
||||
STARTUPINFOW si = {sizeof(si), 0};
|
||||
si.lpDesktop = L"";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
bool ok;
|
||||
if (userToken) {
|
||||
ok = CreateProcessAsUserW(userToken,
|
||||
exefullpath,
|
||||
cmdline,
|
||||
NULL, // no special security attributes
|
||||
NULL, // no special thread attributes
|
||||
false, // don't inherit filehandles
|
||||
0, // No special process creation flags
|
||||
NULL, // inherit my environment
|
||||
workingDirectory,
|
||||
&si,
|
||||
&pi);
|
||||
} else {
|
||||
ok = CreateProcessW(exefullpath,
|
||||
cmdline,
|
||||
NULL, // no special security attributes
|
||||
NULL, // no special thread attributes
|
||||
false, // don't inherit filehandles
|
||||
0, // No special process creation flags
|
||||
NULL, // inherit my environment
|
||||
workingDirectory,
|
||||
&si,
|
||||
&pi);
|
||||
}
|
||||
free(cmdline);
|
||||
if (ok) {
|
||||
if (!async)
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the upgrade process for update of the service if it is
|
||||
* already installed.
|
||||
*
|
||||
* @param argc The argc value normally sent to updater.exe
|
||||
* @param argv The argv value normally sent to updater.exe
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
StartServiceUpdate(int argc, LPWSTR *argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a handle to the local computer SCM database
|
||||
SC_HANDLE manager = OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (!manager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the service
|
||||
SC_HANDLE svc = OpenServiceW(manager, L"MozillaMaintenance",
|
||||
SERVICE_ALL_ACCESS);
|
||||
if (!svc) {
|
||||
CloseServiceHandle(manager);
|
||||
return FALSE;
|
||||
}
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(manager);
|
||||
|
||||
// If we reach here, then the service is installed, so
|
||||
// proceed with upgrading it.
|
||||
|
||||
STARTUPINFOW si = {0};
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
// No particular desktop because no UI
|
||||
si.lpDesktop = L"";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
WCHAR maintserviceInstallerPath[MAX_PATH + 1];
|
||||
wcscpy(maintserviceInstallerPath, argv[2]);
|
||||
PathAppendSafe(maintserviceInstallerPath,
|
||||
L"maintenanceservice_installer.exe");
|
||||
WCHAR cmdLine[64];
|
||||
wcscpy(cmdLine, L"dummyparam.exe /Upgrade");
|
||||
BOOL svcUpdateProcessStarted = CreateProcessW(maintserviceInstallerPath,
|
||||
cmdLine,
|
||||
NULL, NULL, FALSE,
|
||||
CREATE_DEFAULT_ERROR_MODE |
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
NULL, argv[2], &si, &pi);
|
||||
if (svcUpdateProcessStarted) {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return svcUpdateProcessStarted;
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is common code between maintenanceservice and updater
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Brian R. Bondy <netzen@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include "shlobj.h"
|
||||
|
||||
// Needed for PathAppendW
|
||||
#include <shlwapi.h>
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
|
||||
WCHAR*
|
||||
MakeCommandLine(int argc, WCHAR **argv);
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
|
||||
/**
|
||||
* Obtains the directory path to store work item files.
|
||||
*
|
||||
* @param path The buffer of size MAX_PATH to store the update directory to
|
||||
* @return TRUE if the path was obtained successfully.
|
||||
*/
|
||||
BOOL
|
||||
GetUpdateDirectoryPath(LPWSTR path)
|
||||
{
|
||||
HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL,
|
||||
SHGFP_TYPE_CURRENT, path);
|
||||
if (FAILED(hr)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!PathAppendSafe(path, L"Mozilla")) {
|
||||
return FALSE;
|
||||
}
|
||||
// The directory should already be created from the installer, but
|
||||
// just to be safe in case someone deletes.
|
||||
CreateDirectoryW(path, NULL);
|
||||
|
||||
if (!PathAppendSafe(path, L"updates")) {
|
||||
return FALSE;
|
||||
}
|
||||
CreateDirectoryW(path, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the path of a file in the same directory as the specified file.
|
||||
*
|
||||
* @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
|
||||
* @param siblingFIlePath The path of another file in the same directory
|
||||
* @param newFileName The filename of another file in the same directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
PathGetSiblingFilePath(LPWSTR destinationBuffer,
|
||||
LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName)
|
||||
{
|
||||
if (wcslen(siblingFilePath) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcscpy(destinationBuffer, siblingFilePath);
|
||||
if (!PathRemoveFileSpecW(destinationBuffer)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return PathAppendSafe(destinationBuffer, newFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the post update application as the specified user (helper.exe).
|
||||
* It takes in the path of the callback application to calculate the path
|
||||
* of helper.exe. For service updates this is called from both the system
|
||||
* account and the current user account.
|
||||
*
|
||||
* @param appExe The path to the callback application binary.
|
||||
* @param updateInfoDir The directory where update info is stored.
|
||||
* @param forceSync If true even if the ini file specifies async, the
|
||||
* process will wait for termination of PostUpdate.
|
||||
* @param userToken The user token to run as, if NULL the current user
|
||||
* will be used.
|
||||
* @return TRUE if there was no error starting the process.
|
||||
*/
|
||||
BOOL
|
||||
LaunchWinPostProcess(const WCHAR *appExe,
|
||||
const WCHAR *updateInfoDir,
|
||||
bool forceSync,
|
||||
HANDLE userToken)
|
||||
{
|
||||
WCHAR workingDirectory[MAX_PATH + 1];
|
||||
wcscpy(workingDirectory, appExe);
|
||||
if (!PathRemoveFileSpecW(workingDirectory)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Launch helper.exe to perform post processing (e.g. registry and log file
|
||||
// modifications) for the update.
|
||||
WCHAR inifile[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(inifile, appExe, L"updater.ini")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR exefile[MAX_PATH + 1];
|
||||
WCHAR exearg[MAX_PATH + 1];
|
||||
WCHAR exeasync[10];
|
||||
bool async = true;
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", NULL, exefile,
|
||||
MAX_PATH + 1, inifile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", NULL, exearg,
|
||||
MAX_PATH + 1, inifile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
|
||||
exeasync,
|
||||
sizeof(exeasync)/sizeof(exeasync[0]), inifile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR exefullpath[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(exefullpath, appExe, exefile)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR dlogFile[MAX_PATH + 1];
|
||||
if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR slogFile[MAX_PATH + 1];
|
||||
wcscpy(slogFile, updateInfoDir);
|
||||
if (!PathAppendSafe(slogFile, L"update.log")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR dummyArg[14];
|
||||
wcscpy(dummyArg, L"argv0ignored ");
|
||||
|
||||
size_t len = wcslen(exearg) + wcslen(dummyArg);
|
||||
WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
|
||||
if (!cmdline) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wcscpy(cmdline, dummyArg);
|
||||
wcscat(cmdline, exearg);
|
||||
|
||||
if (forceSync ||
|
||||
!_wcsnicmp(exeasync, L"false", 6) ||
|
||||
!_wcsnicmp(exeasync, L"0", 2)) {
|
||||
async = false;
|
||||
}
|
||||
|
||||
// We want to launch the post update helper app to update the Windows
|
||||
// registry even if there is a failure with removing the uninstall.update
|
||||
// file or copying the update.log file.
|
||||
CopyFileW(slogFile, dlogFile, false);
|
||||
|
||||
STARTUPINFOW si = {sizeof(si), 0};
|
||||
si.lpDesktop = L"";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
bool ok;
|
||||
if (userToken) {
|
||||
ok = CreateProcessAsUserW(userToken,
|
||||
exefullpath,
|
||||
cmdline,
|
||||
NULL, // no special security attributes
|
||||
NULL, // no special thread attributes
|
||||
false, // don't inherit filehandles
|
||||
0, // No special process creation flags
|
||||
NULL, // inherit my environment
|
||||
workingDirectory,
|
||||
&si,
|
||||
&pi);
|
||||
} else {
|
||||
ok = CreateProcessW(exefullpath,
|
||||
cmdline,
|
||||
NULL, // no special security attributes
|
||||
NULL, // no special thread attributes
|
||||
false, // don't inherit filehandles
|
||||
0, // No special process creation flags
|
||||
NULL, // inherit my environment
|
||||
workingDirectory,
|
||||
&si,
|
||||
&pi);
|
||||
}
|
||||
free(cmdline);
|
||||
if (ok) {
|
||||
if (!async)
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the upgrade process for update of the service if it is
|
||||
* already installed.
|
||||
*
|
||||
* @param argc The argc value normally sent to updater.exe
|
||||
* @param argv The argv value normally sent to updater.exe
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
StartServiceUpdate(int argc, LPWSTR *argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a handle to the local computer SCM database
|
||||
SC_HANDLE manager = OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (!manager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Open the service
|
||||
SC_HANDLE svc = OpenServiceW(manager, L"MozillaMaintenance",
|
||||
SERVICE_ALL_ACCESS);
|
||||
if (!svc) {
|
||||
CloseServiceHandle(manager);
|
||||
return FALSE;
|
||||
}
|
||||
CloseServiceHandle(svc);
|
||||
CloseServiceHandle(manager);
|
||||
|
||||
// If we reach here, then the service is installed, so
|
||||
// proceed with upgrading it.
|
||||
|
||||
STARTUPINFOW si = {0};
|
||||
si.cb = sizeof(STARTUPINFOW);
|
||||
// No particular desktop because no UI
|
||||
si.lpDesktop = L"";
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
WCHAR maintserviceInstallerPath[MAX_PATH + 1];
|
||||
wcscpy(maintserviceInstallerPath, argv[2]);
|
||||
PathAppendSafe(maintserviceInstallerPath,
|
||||
L"maintenanceservice_installer.exe");
|
||||
WCHAR cmdLine[64];
|
||||
wcscpy(cmdLine, L"dummyparam.exe /Upgrade");
|
||||
BOOL svcUpdateProcessStarted = CreateProcessW(maintserviceInstallerPath,
|
||||
cmdLine,
|
||||
NULL, NULL, FALSE,
|
||||
CREATE_DEFAULT_ERROR_MODE |
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
NULL, argv[2], &si, &pi);
|
||||
if (svcUpdateProcessStarted) {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return svcUpdateProcessStarted;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the maintenance service is running or not.
|
||||
*
|
||||
* @return TRUE if the maintenance service is running.
|
||||
*/
|
||||
BOOL
|
||||
EnsureWindowsServiceRunning()
|
||||
{
|
||||
// Get a handle to the SCM database.
|
||||
SC_HANDLE serviceManager = OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_CONNECT |
|
||||
SC_MANAGER_ENUMERATE_SERVICE);
|
||||
if (!serviceManager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a handle to the service.
|
||||
SC_HANDLE service = OpenServiceW(serviceManager,
|
||||
L"MozillaMaintenance",
|
||||
SERVICE_QUERY_STATUS | SERVICE_START);
|
||||
if (!service) {
|
||||
CloseServiceHandle(serviceManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState == SERVICE_STOPPED) {
|
||||
if (!StartService(service, 0, NULL)) {
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(serviceManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Make sure we can get into a started state without waiting too long.
|
||||
// This usually starts instantly but the extra code is just in case it
|
||||
// takes longer.
|
||||
DWORD totalWaitTime = 0;
|
||||
static const int maxWaitTime = 1000 * 5; // Never wait more than 5 seconds
|
||||
while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
|
||||
sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
|
||||
if (ssp.dwCurrentState == SERVICE_RUNNING) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState == SERVICE_START_PENDING &&
|
||||
totalWaitTime > maxWaitTime) {
|
||||
// We will probably eventually start, but we can't wait any longer.
|
||||
break;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState != SERVICE_START_PENDING) {
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(serviceManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Sleep(ssp.dwWaitHint);
|
||||
// Increment by at least 10 milliseconds to ensure we always make
|
||||
// progress towards maxWaitTime in case dwWaitHint is 0.
|
||||
totalWaitTime += (ssp.dwWaitHint + 10);
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(serviceManager);
|
||||
return ssp.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a service initiated action with the specified arguments.
|
||||
*
|
||||
* @param exePath The path of the executable to run
|
||||
* @param argc The total number of arguments in argv
|
||||
* @param argv An array of null terminated strings to pass to the exePath,
|
||||
* argv[0] is ignored
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WinLaunchServiceCommand(LPCWSTR exePath, int argc, LPWSTR* argv)
|
||||
{
|
||||
// Ensure the service is running, if not we should try to start it, if it is
|
||||
// not in a running state we cannot execute a service command.
|
||||
if (!EnsureWindowsServiceRunning()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR updateData[MAX_PATH + 1];
|
||||
if (!GetUpdateDirectoryPath(updateData)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a unique filename
|
||||
WCHAR tempFilePath[MAX_PATH + 1];
|
||||
const int USE_SYSTEM_TIME = 0;
|
||||
if (!GetTempFileNameW(updateData, L"moz", USE_SYSTEM_TIME, tempFilePath)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const int FILE_SHARE_NONE = 0;
|
||||
HANDLE updateMetaFile = CreateFileW(tempFilePath, GENERIC_WRITE,
|
||||
FILE_SHARE_NONE, NULL, CREATE_ALWAYS,
|
||||
0, NULL);
|
||||
if (updateMetaFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Write out the command ID.
|
||||
// Command ID 1 is for an update work item file, which is the only supported
|
||||
// command at this time.
|
||||
DWORD commandID = 1, commandIDWrote;
|
||||
BOOL result = WriteFile(updateMetaFile, &commandID,
|
||||
sizeof(DWORD),
|
||||
&commandIDWrote, NULL);
|
||||
|
||||
// Write out the command line arguments that are passed to updater.exe
|
||||
WCHAR *commandLineBuffer = MakeCommandLine(argc, argv);
|
||||
if (!commandLineBuffer) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WCHAR appBuffer[MAX_PATH + 1];
|
||||
ZeroMemory(appBuffer, sizeof(appBuffer));
|
||||
wcscpy(appBuffer, exePath);
|
||||
DWORD appBufferWrote;
|
||||
result |= WriteFile(updateMetaFile, appBuffer,
|
||||
MAX_PATH * sizeof(WCHAR),
|
||||
&appBufferWrote, NULL);
|
||||
|
||||
WCHAR workingDirectory[MAX_PATH + 1];
|
||||
ZeroMemory(workingDirectory, sizeof(appBuffer));
|
||||
GetCurrentDirectoryW(sizeof(workingDirectory) / sizeof(workingDirectory[0]),
|
||||
workingDirectory);
|
||||
DWORD workingDirectoryWrote;
|
||||
result |= WriteFile(updateMetaFile, workingDirectory,
|
||||
MAX_PATH * sizeof(WCHAR),
|
||||
&workingDirectoryWrote, NULL);
|
||||
|
||||
DWORD commandLineLength = wcslen(commandLineBuffer) * sizeof(WCHAR);
|
||||
DWORD commandLineWrote;
|
||||
result |= WriteFile(updateMetaFile, commandLineBuffer,
|
||||
commandLineLength,
|
||||
&commandLineWrote, NULL);
|
||||
free(commandLineBuffer);
|
||||
if (!result ||
|
||||
commandIDWrote != sizeof(DWORD) ||
|
||||
appBufferWrote != MAX_PATH * sizeof(WCHAR) ||
|
||||
workingDirectoryWrote != MAX_PATH * sizeof(WCHAR) ||
|
||||
commandLineWrote != commandLineLength) {
|
||||
CloseHandle(updateMetaFile);
|
||||
DeleteFileW(tempFilePath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Note we construct the 'service work' meta object with a .tmp extension,
|
||||
// When we want the service to start processing it we simply rename it to
|
||||
// have a .mz extension. This ensures that the service will never try to
|
||||
// process a partial update work meta file.
|
||||
CloseHandle(updateMetaFile);
|
||||
WCHAR completedMetaFilePath[MAX_PATH + 1];
|
||||
wcscpy(completedMetaFilePath, tempFilePath);
|
||||
|
||||
// Change the file extension of the temp file path from .tmp to .mz
|
||||
LPWSTR extensionPart =
|
||||
&(completedMetaFilePath[wcslen(completedMetaFilePath) - 3]);
|
||||
wcscpy(extensionPart, L"mz");
|
||||
return MoveFileExW(tempFilePath, completedMetaFilePath,
|
||||
MOVEFILE_REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a base directory path with a filename.
|
||||
*
|
||||
* @param base The base directory path of size MAX_PATH + 1
|
||||
* @param extra The filename to append
|
||||
* @return TRUE if the file name was successful appended to base
|
||||
*/
|
||||
BOOL
|
||||
PathAppendSafe(LPWSTR base, LPCWSTR extra)
|
||||
{
|
||||
if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return PathAppendW(base, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets update.status to pending so that the next startup will not use
|
||||
* the service and instead will attempt an update the with a UAC prompt.
|
||||
*
|
||||
* @param updateDirPath The path of the update directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WriteStatusPending(LPCWSTR updateDirPath)
|
||||
{
|
||||
WCHAR updateStatusFilePath[MAX_PATH + 1];
|
||||
wcscpy(updateStatusFilePath, updateDirPath);
|
||||
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const char pending[] = "pending";
|
||||
HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
|
||||
NULL, CREATE_ALWAYS, 0, NULL);
|
||||
if (statusFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD wrote;
|
||||
BOOL ok = WriteFile(statusFile, pending,
|
||||
sizeof(pending) - 1, &wrote, NULL);
|
||||
CloseHandle(statusFile);
|
||||
return ok && (wrote == sizeof(pending) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets update.status to a specific failure code
|
||||
*
|
||||
* @param updateDirPath The path of the update directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
|
||||
{
|
||||
WCHAR updateStatusFilePath[MAX_PATH + 1];
|
||||
wcscpy(updateStatusFilePath, updateDirPath);
|
||||
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
|
||||
NULL, CREATE_ALWAYS, 0, NULL);
|
||||
if (statusFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
char failure[32];
|
||||
sprintf(failure, "failed: %d", errorCode);
|
||||
|
||||
DWORD toWrite = strlen(failure);
|
||||
DWORD wrote;
|
||||
BOOL ok = WriteFile(statusFile, failure,
|
||||
toWrite, &wrote, NULL);
|
||||
CloseHandle(statusFile);
|
||||
return ok && wrote == toWrite;
|
||||
}
|
|
@ -40,3 +40,8 @@ BOOL LaunchWinPostProcess(const WCHAR *appExe,
|
|||
bool forceSync,
|
||||
HANDLE userToken);
|
||||
BOOL StartServiceUpdate(int argc, LPWSTR *argv);
|
||||
BOOL GetUpdateDirectoryPath(LPWSTR path);
|
||||
BOOL WinLaunchServiceCommand(LPCWSTR exePath, int argc, WCHAR **argv);
|
||||
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
|
||||
BOOL WriteStatusPending(LPCWSTR updateDirPath);
|
||||
#define SERVICE_EVENT_NAME L"Global\\moz-5b780de9-065b-4341-a04f-ddd94b3723e5"
|
|
@ -146,7 +146,6 @@ const ELEVATION_CANCELED = 9;
|
|||
const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 16000;
|
||||
const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 16001;
|
||||
const SERVICE_UPDATER_SIGN_ERROR = 16002;
|
||||
const SERVICE_CALLBACK_SIGN_ERROR = 16003;
|
||||
|
||||
const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
|
||||
const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
|
||||
|
@ -1436,8 +1435,7 @@ UpdateService.prototype = {
|
|||
|
||||
if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
|
||||
update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
|
||||
update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
|
||||
update.errorCode == SERVICE_CALLBACK_SIGN_ERROR) {
|
||||
update.errorCode == SERVICE_UPDATER_SIGN_ERROR) {
|
||||
var failCount = getPref("getIntPref",
|
||||
PREF_APP_UPDATE_SERVICE_ERRORS, 0);
|
||||
var maxFail = getPref("getIntPref",
|
||||
|
|
|
@ -119,7 +119,7 @@ void LaunchMacPostProcess(const char* aAppExe);
|
|||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include "launchwinprocess.h"
|
||||
#include "updatehelper.h"
|
||||
|
||||
// Closes the handle if valid and if the updater is elevated returns with the
|
||||
// return code specified. This prevents multiple launches of the callback
|
||||
|
@ -1402,25 +1402,6 @@ LaunchCallbackApp(const NS_tchar *workingDir, int argc, NS_tchar **argv)
|
|||
execv(argv[0], argv);
|
||||
#elif defined(XP_MACOSX)
|
||||
LaunchChild(argc, argv);
|
||||
#elif defined(MOZ_MAINTENANCE_SERVICE)
|
||||
// If updater.exe is run as session ID 0 and we have a MOZ_SESSION_ID
|
||||
// set, then get the unelevated token and use that to start the callback
|
||||
// application. Getting tokens will only work if the process is running
|
||||
// as the system account.
|
||||
DWORD myProcessID = GetCurrentProcessId();
|
||||
DWORD mySessionID = 0;
|
||||
ProcessIdToSessionId(myProcessID, &mySessionID);
|
||||
nsAutoHandle unelevatedToken(NULL);
|
||||
if (mySessionID == 0) {
|
||||
WCHAR *sessionIDStr = _wgetenv(L"MOZ_SESSION_ID");
|
||||
if (sessionIDStr) {
|
||||
// Remove the env var now that we have its value.
|
||||
int callbackSessionID = _wtoi(sessionIDStr);
|
||||
_wputenv(L"MOZ_SESSION_ID=");
|
||||
unelevatedToken.own(UACHelper::OpenUserToken(callbackSessionID));
|
||||
}
|
||||
}
|
||||
WinLaunchChild(argv[0], argc, argv, unelevatedToken);
|
||||
#elif defined(XP_WIN)
|
||||
WinLaunchChild(argv[0], argc, argv, NULL);
|
||||
#else
|
||||
|
@ -1453,6 +1434,88 @@ WriteStatusFile(int status)
|
|||
fwrite(text, strlen(text), 1, file);
|
||||
}
|
||||
|
||||
static bool
|
||||
WriteStatusApplying()
|
||||
{
|
||||
NS_tchar filename[MAXPATHLEN];
|
||||
NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
|
||||
NS_T("%s/update.status"), gSourcePath);
|
||||
|
||||
AutoFile file = NS_tfopen(filename, NS_T("wb+"));
|
||||
if (file == NULL)
|
||||
return false;
|
||||
|
||||
static const char kApplying[] = "Applying\n";
|
||||
if (fwrite(kApplying, strlen(kApplying), 1, file) != 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
IsUpdateStatusPending(bool &isPendingService)
|
||||
{
|
||||
bool isPending = false;
|
||||
isPendingService = false;
|
||||
NS_tchar filename[MAXPATHLEN];
|
||||
NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
|
||||
NS_T("%s/update.status"), gSourcePath);
|
||||
|
||||
AutoFile file = NS_tfopen(filename, NS_T("rb"));
|
||||
if (file == NULL)
|
||||
return false;
|
||||
|
||||
char buf[32] = { 0 };
|
||||
fread(buf, sizeof(buf), 1, file);
|
||||
|
||||
const char kPending[] = "pending";
|
||||
const char kPendingService[] = "pending-service";
|
||||
isPending = strncmp(buf, kPending,
|
||||
sizeof(kPending) - 1) == 0;
|
||||
|
||||
isPendingService = strncmp(buf, kPendingService,
|
||||
sizeof(kPendingService) - 1) == 0;
|
||||
return isPending;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the update.status file and sets isSuccess to true if
|
||||
* the status is set to succeeded.
|
||||
*
|
||||
* @param isSucceeded Out parameter for specifying if the status
|
||||
* is set to succeeded or not.
|
||||
* @return true if the information was retrieved and it is succeeded.
|
||||
*/
|
||||
static bool
|
||||
IsUpdateStatusSucceeded(bool &isSucceeded)
|
||||
{
|
||||
isSucceeded = false;
|
||||
NS_tchar filename[MAXPATHLEN];
|
||||
NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
|
||||
NS_T("%s/update.status"), gSourcePath);
|
||||
|
||||
AutoFile file = NS_tfopen(filename, NS_T("rb"));
|
||||
if (file == NULL)
|
||||
return false;
|
||||
|
||||
char buf[32] = { 0 };
|
||||
fread(buf, sizeof(buf), 1, file);
|
||||
|
||||
const char kSucceeded[] = "succeeded";
|
||||
isSucceeded = strncmp(buf, kSucceeded,
|
||||
sizeof(kSucceeded) - 1) == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
UpdateThreadFunc(void *param)
|
||||
{
|
||||
|
@ -1524,6 +1587,23 @@ int NS_main(int argc, NS_tchar **argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// The directory containing the update information.
|
||||
gSourcePath = argv[1];
|
||||
|
||||
#ifdef XP_WIN
|
||||
bool useService = false;
|
||||
// We never want the service to be used unless we build with
|
||||
// the maintenance service.
|
||||
#ifdef MOZ_MAINTENANCE_SERVICE
|
||||
IsUpdateStatusPending(useService);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (!WriteStatusApplying()) {
|
||||
LOG(("failed setting status to 'applying'\n"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
// Remove everything except close window from the context menu
|
||||
{
|
||||
|
@ -1567,9 +1647,6 @@ int NS_main(int argc, NS_tchar **argv)
|
|||
#endif
|
||||
}
|
||||
|
||||
// The directory containing the update information.
|
||||
gSourcePath = argv[1];
|
||||
|
||||
// The callback is the remaining arguments starting at callbackIndex.
|
||||
// The argument specified by callbackIndex is the callback executable and the
|
||||
// argument prior to callbackIndex is the working directory.
|
||||
|
@ -1634,26 +1711,80 @@ int NS_main(int argc, NS_tchar **argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
SHELLEXECUTEINFO sinfo;
|
||||
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
|
||||
sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sinfo.fMask = SEE_MASK_FLAG_NO_UI |
|
||||
SEE_MASK_FLAG_DDEWAIT |
|
||||
SEE_MASK_NOCLOSEPROCESS;
|
||||
sinfo.hwnd = NULL;
|
||||
sinfo.lpFile = argv[0];
|
||||
sinfo.lpParameters = cmdLine;
|
||||
sinfo.lpVerb = L"runas";
|
||||
sinfo.nShow = SW_SHOWNORMAL;
|
||||
HANDLE serviceInUseEvent = NULL;
|
||||
if (useService) {
|
||||
// Make sure the service isn't already busy processing another work item.
|
||||
// This event will also be used by the service who will signal it when
|
||||
// it is done with the udpate.
|
||||
serviceInUseEvent = CreateEventW(NULL, TRUE,
|
||||
FALSE, SERVICE_EVENT_NAME);
|
||||
|
||||
bool result = ShellExecuteEx(&sinfo);
|
||||
free(cmdLine);
|
||||
// Only use the service if we know the event can be created and
|
||||
// doesn't already exist.
|
||||
if (!serviceInUseEvent) {
|
||||
useService = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
WaitForSingleObject(sinfo.hProcess, INFINITE);
|
||||
CloseHandle(sinfo.hProcess);
|
||||
} else {
|
||||
WriteStatusFile(ELEVATION_CANCELED);
|
||||
// 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.
|
||||
useService = WinLaunchServiceCommand(argv[0], argc, argv);
|
||||
|
||||
// The command was launched, so we should wait for the work to be done.
|
||||
// The service will set the event we wait on when it is done.
|
||||
if (useService) {
|
||||
WaitForSingleObject(serviceInUseEvent, INFINITE);
|
||||
CloseHandle(serviceInUseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (useService) {
|
||||
bool updateStatusSucceeded = false;
|
||||
if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
|
||||
updateStatusSucceeded) {
|
||||
LaunchWinPostProcess(argv[callbackIndex], gSourcePath, false, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 used to without the service.
|
||||
if (!useService) {
|
||||
SHELLEXECUTEINFO sinfo;
|
||||
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
|
||||
sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sinfo.fMask = SEE_MASK_FLAG_NO_UI |
|
||||
SEE_MASK_FLAG_DDEWAIT |
|
||||
SEE_MASK_NOCLOSEPROCESS;
|
||||
sinfo.hwnd = NULL;
|
||||
sinfo.lpFile = argv[0];
|
||||
sinfo.lpParameters = cmdLine;
|
||||
sinfo.lpVerb = L"runas";
|
||||
sinfo.nShow = SW_SHOWNORMAL;
|
||||
|
||||
bool result = ShellExecuteEx(&sinfo);
|
||||
free(cmdLine);
|
||||
|
||||
if (result) {
|
||||
WaitForSingleObject(sinfo.hProcess, INFINITE);
|
||||
CloseHandle(sinfo.hProcess);
|
||||
} else {
|
||||
WriteStatusFile(ELEVATION_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
if (argc > callbackIndex) {
|
||||
|
@ -1835,9 +1966,9 @@ int NS_main(int argc, NS_tchar **argv)
|
|||
// 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_SESSION_ID will not exist.
|
||||
WCHAR *sessionIDStr = _wgetenv(L"MOZ_SESSION_ID");
|
||||
if (!sessionIDStr) {
|
||||
// through the service, then MOZ_USING_SERVICE will not exist.
|
||||
WCHAR *usingService = _wgetenv(L"MOZ_USING_SERVICE");
|
||||
if (!usingService) {
|
||||
if (!LaunchWinPostProcess(argv[2], gSourcePath, false, NULL)) {
|
||||
LOG(("NS_main: The post update process could not be launched.\n"));
|
||||
}
|
||||
|
|
|
@ -139,10 +139,6 @@ WriteConsoleLog();
|
|||
BOOL
|
||||
WinLaunchChild(const PRUnichar *exePath, int argc,
|
||||
char **argv, HANDLE userToken = NULL);
|
||||
BOOL
|
||||
WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv);
|
||||
BOOL
|
||||
WriteStatusPending(LPCWSTR updateDirPath);
|
||||
#endif
|
||||
|
||||
#define NS_NATIVEAPPSUPPORT_CONTRACTID "@mozilla.org/toolkit/native-app-support;1"
|
||||
|
|
|
@ -191,7 +191,7 @@ GetStatusFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
|
|||
}
|
||||
|
||||
static bool
|
||||
IsPending(nsILocalFile *statusFile, bool &isPendingService)
|
||||
IsPending(nsILocalFile *statusFile)
|
||||
{
|
||||
PRFileDesc *fd = nsnull;
|
||||
nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
|
||||
|
@ -207,30 +207,7 @@ IsPending(nsILocalFile *statusFile, bool &isPendingService)
|
|||
|
||||
const char kPending[] = "pending";
|
||||
bool isPending = (strncmp(buf, kPending, sizeof(kPending) - 1) == 0);
|
||||
|
||||
const char kPendingService[] = "pending-service";
|
||||
isPendingService = (strncmp(buf, kPendingService,
|
||||
sizeof(kPendingService) - 1) == 0);
|
||||
|
||||
return isPending || isPendingService;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetStatusApplying(nsILocalFile *statusFile)
|
||||
{
|
||||
PRFileDesc *fd = nsnull;
|
||||
nsresult rv = statusFile->OpenNSPRFileDesc(PR_WRONLY |
|
||||
PR_TRUNCATE |
|
||||
PR_CREATE_FILE,
|
||||
0660, &fd);
|
||||
if (NS_FAILED(rv))
|
||||
return false;
|
||||
|
||||
static const char kApplying[] = "Applying\n";
|
||||
PR_Write(fd, kApplying, sizeof(kApplying) - 1);
|
||||
PR_Close(fd);
|
||||
|
||||
return true;
|
||||
return isPending;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -344,7 +321,7 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
|
|||
|
||||
static void
|
||||
ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
|
||||
nsIFile *appDir, int appArgc, char **appArgv, bool isPendingService)
|
||||
nsIFile *appDir, int appArgc, char **appArgv)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
|
@ -447,10 +424,11 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
|
|||
if (NS_FAILED(rv))
|
||||
return;
|
||||
|
||||
if (!SetStatusApplying(statusFile)) {
|
||||
LOG(("failed setting status to 'applying'\n"));
|
||||
return;
|
||||
}
|
||||
// We used to write out "Applying" to the update.status file here.
|
||||
// Instead we do this from within the updater application now.
|
||||
// This is so that we don't overwrite the status of pending-service
|
||||
// in the Windows case. This change was made for all platforms so
|
||||
// that it stays consistent across all OS.
|
||||
|
||||
// Construct the PID argument for this process. If we are using execv, then
|
||||
// we pass "0" which is then ignored by the updater.
|
||||
|
@ -491,38 +469,9 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
|
|||
execv(updaterPath.get(), argv);
|
||||
#elif defined(XP_WIN)
|
||||
|
||||
#ifndef MOZ_MAINTENANCE_SERVICE
|
||||
// We never want the service to be used unless we have Firefox
|
||||
isPendingService = false;
|
||||
#endif
|
||||
|
||||
if (isPendingService) {
|
||||
// Make sure the service isn't already busy processing another work item.
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
HANDLE serviceRunningEvent =
|
||||
OpenEvent(EVENT_ALL_ACCESS,
|
||||
FALSE,
|
||||
L"Global\\moz-5b780de9-065b-4341-a04f-ddd94b3723e5");
|
||||
// Only use the service if we know the event exists.
|
||||
// If we have a non NULL handle, or if ERROR_ACCESS_DENIED is returned,
|
||||
// then the event exists.
|
||||
isPendingService = !serviceRunningEvent &&
|
||||
GetLastError() != ERROR_ACCESS_DENIED;
|
||||
if (serviceRunningEvent) {
|
||||
CloseHandle(serviceRunningEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// Launch the update operation using the service if the status file said so.
|
||||
// We also set the status to pending to ensure we never attempt to use the
|
||||
// service more than once in a row for a single update.
|
||||
if (!isPendingService ||
|
||||
!WriteStatusPending(NS_ConvertUTF8toUTF16(updateDirPath).get()) ||
|
||||
!WinLaunchServiceCommand(updaterPathW.get(), argc, argv)) {
|
||||
// Launch the update using updater.exe
|
||||
if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
|
||||
return;
|
||||
}
|
||||
// Launch the update using updater.exe
|
||||
if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are going to process an update so we should exit now
|
||||
|
@ -587,9 +536,8 @@ ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
|
|||
}
|
||||
|
||||
nsCOMPtr<nsILocalFile> statusFile;
|
||||
bool isPendingService;
|
||||
if (GetStatusFile(updatesDir, statusFile) &&
|
||||
IsPending(statusFile, isPendingService)) {
|
||||
IsPending(statusFile)) {
|
||||
nsCOMPtr<nsILocalFile> versionFile;
|
||||
nsCOMPtr<nsILocalFile> channelChangeFile;
|
||||
// Remove the update if the update application version file doesn't exist
|
||||
|
@ -601,7 +549,7 @@ ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
|
|||
updatesDir->Remove(true);
|
||||
} else {
|
||||
ApplyUpdate(greDir, updatesDir, statusFile, appDir,
|
||||
argc, argv, isPendingService);
|
||||
argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,36 +47,13 @@
|
|||
#endif
|
||||
|
||||
#include "nsUTF8Utils.h"
|
||||
#include "nsWindowsHelpers.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shlobj.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
#include <rpc.h>
|
||||
|
||||
// Needed for CreateEnvironmentBlock
|
||||
#include <userenv.h>
|
||||
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
#pragma comment(lib, "rpcrt4.lib")
|
||||
#pragma comment(lib, "userenv.lib")
|
||||
|
||||
#ifndef ERROR_ELEVATION_REQUIRED
|
||||
#define ERROR_ELEVATION_REQUIRED 740L
|
||||
#endif
|
||||
|
||||
BOOL (WINAPI *pCreateProcessWithTokenW)(HANDLE,
|
||||
DWORD,
|
||||
LPCWSTR,
|
||||
LPWSTR,
|
||||
DWORD,
|
||||
LPVOID,
|
||||
LPCWSTR,
|
||||
LPSTARTUPINFOW,
|
||||
LPPROCESS_INFORMATION);
|
||||
|
||||
BOOL (WINAPI *pIsUserAnAdmin)(VOID);
|
||||
|
||||
/**
|
||||
* Get the length that the string will take and takes into account the
|
||||
* additional length if the string needs to be quoted and if characters need to
|
||||
|
@ -236,312 +213,6 @@ FreeAllocStrings(int argc, PRUnichar **argv)
|
|||
delete [] argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the maintenance service is running or not.
|
||||
*
|
||||
* @return TRUE if the maintenance service is running.
|
||||
*/
|
||||
BOOL
|
||||
EnsureWindowsServiceRunning() {
|
||||
// Get a handle to the SCM database.
|
||||
nsAutoServiceHandle serviceManager(OpenSCManager(NULL, NULL,
|
||||
SC_MANAGER_CONNECT |
|
||||
SC_MANAGER_ENUMERATE_SERVICE));
|
||||
if (!serviceManager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a handle to the service.
|
||||
nsAutoServiceHandle service(OpenServiceW(serviceManager,
|
||||
L"MozillaMaintenance",
|
||||
SERVICE_QUERY_STATUS | SERVICE_START));
|
||||
if (!service) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState == SERVICE_STOPPED) {
|
||||
if (!StartService(service, 0, NULL)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Make sure we can get into a started state without waiting too long.
|
||||
// This usually starts instantly but the extra code is just in case it
|
||||
// takes longer.
|
||||
DWORD totalWaitTime = 0;
|
||||
static const int maxWaitTime = 1000 * 5; // Never wait more than 5 seconds
|
||||
while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
|
||||
sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
|
||||
if (ssp.dwCurrentState == SERVICE_RUNNING) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState == SERVICE_START_PENDING &&
|
||||
totalWaitTime > maxWaitTime) {
|
||||
// We will probably eventually start, but we can't wait any longer.
|
||||
break;
|
||||
}
|
||||
|
||||
if (ssp.dwCurrentState != SERVICE_START_PENDING) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Sleep(ssp.dwWaitHint);
|
||||
// Increment by at least 10 milliseconds to ensure we always make
|
||||
// progress towards maxWaitTime in case dwWaitHint is 0.
|
||||
totalWaitTime += (ssp.dwWaitHint + 10);
|
||||
}
|
||||
}
|
||||
|
||||
return ssp.dwCurrentState == SERVICE_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a base directory path with a filename.
|
||||
*
|
||||
* @param base The base directory path of size MAX_PATH + 1
|
||||
* @param extra The filename to append
|
||||
* @return TRUE if the file name was successful appended to base
|
||||
*/
|
||||
BOOL
|
||||
PathAppendSafe(LPWSTR base, LPCWSTR extra)
|
||||
{
|
||||
if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return PathAppendW(base, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the directory path to store work item files.
|
||||
*
|
||||
* @return TRUE if the path was obtained successfully.
|
||||
*/
|
||||
BOOL
|
||||
GetUpdateDirectoryPath(PRUnichar *path)
|
||||
{
|
||||
HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL,
|
||||
SHGFP_TYPE_CURRENT, path);
|
||||
if (FAILED(hr)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!PathAppendSafe(path, L"Mozilla")) {
|
||||
return FALSE;
|
||||
}
|
||||
// The directory should already be created from the installer, but
|
||||
// just to be safe in case someone deletes.
|
||||
CreateDirectoryW(path, NULL);
|
||||
|
||||
if (!PathAppendSafe(path, L"updates")) {
|
||||
return FALSE;
|
||||
}
|
||||
CreateDirectoryW(path, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a service initiated action with the specified arguments.
|
||||
*
|
||||
* @param exePath The path of the executable to run
|
||||
* @param argc The total number of arguments in argv
|
||||
* @param argv An array of null terminated strings to pass to the exePath,
|
||||
* argv[0] is ignored
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WinLaunchServiceCommand(const PRUnichar *exePath, int argc, PRUnichar **argv)
|
||||
{
|
||||
// Ensure the service is running, if not we should try to start it, if it is
|
||||
// not in a running state we cannot execute a service command.
|
||||
if (!EnsureWindowsServiceRunning()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PRUnichar updateData[MAX_PATH + 1];
|
||||
if (!GetUpdateDirectoryPath(updateData)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get a unique filename
|
||||
PRUnichar tempFilePath[MAX_PATH + 1];
|
||||
const int USE_SYSTEM_TIME = 0;
|
||||
if (!GetTempFileNameW(updateData, L"moz", USE_SYSTEM_TIME, tempFilePath)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const int FILE_SHARE_NONE = 0;
|
||||
nsAutoHandle updateMetaFile(CreateFileW(tempFilePath, GENERIC_WRITE,
|
||||
FILE_SHARE_NONE, NULL, CREATE_ALWAYS,
|
||||
0, NULL));
|
||||
if (updateMetaFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Write out the command ID.
|
||||
// Command ID 1 is for an update work item file, which is the only supported
|
||||
// command at this time.
|
||||
DWORD commandID = 1, commandIDWrote;
|
||||
BOOL result = WriteFile(updateMetaFile, &commandID,
|
||||
sizeof(DWORD),
|
||||
&commandIDWrote, NULL);
|
||||
|
||||
// Write out the command line arguments that are passed to updater.exe
|
||||
PRUnichar *commandLineBuffer = MakeCommandLine(argc, argv);
|
||||
DWORD sessionID, sessionIDWrote;
|
||||
ProcessIdToSessionId(GetCurrentProcessId(), &sessionID);
|
||||
result |= WriteFile(updateMetaFile, &sessionID,
|
||||
sizeof(DWORD),
|
||||
&sessionIDWrote, NULL);
|
||||
|
||||
PRUnichar appBuffer[MAX_PATH + 1];
|
||||
ZeroMemory(appBuffer, sizeof(appBuffer));
|
||||
wcscpy(appBuffer, exePath);
|
||||
DWORD appBufferWrote;
|
||||
result |= WriteFile(updateMetaFile, appBuffer,
|
||||
MAX_PATH * sizeof(PRUnichar),
|
||||
&appBufferWrote, NULL);
|
||||
|
||||
PRUnichar workingDirectory[MAX_PATH + 1];
|
||||
ZeroMemory(workingDirectory, sizeof(appBuffer));
|
||||
GetCurrentDirectoryW(sizeof(workingDirectory) / sizeof(workingDirectory[0]),
|
||||
workingDirectory);
|
||||
DWORD workingDirectoryWrote;
|
||||
result |= WriteFile(updateMetaFile, workingDirectory,
|
||||
MAX_PATH * sizeof(PRUnichar),
|
||||
&workingDirectoryWrote, NULL);
|
||||
|
||||
DWORD commandLineLength = wcslen(commandLineBuffer) * sizeof(PRUnichar);
|
||||
DWORD commandLineWrote;
|
||||
result |= WriteFile(updateMetaFile, commandLineBuffer,
|
||||
commandLineLength,
|
||||
&commandLineWrote, NULL);
|
||||
free(commandLineBuffer);
|
||||
if (!result ||
|
||||
sessionIDWrote != sizeof(DWORD) ||
|
||||
commandIDWrote != sizeof(DWORD) ||
|
||||
appBufferWrote != MAX_PATH * sizeof(PRUnichar) ||
|
||||
workingDirectoryWrote != MAX_PATH * sizeof(PRUnichar) ||
|
||||
commandLineWrote != commandLineLength) {
|
||||
updateMetaFile.reset();
|
||||
DeleteFileW(tempFilePath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Note we construct the 'service work' meta object with a .tmp extension,
|
||||
// When we want the service to start processing it we simply rename it to
|
||||
// have a .mz extension. This ensures that the service will never try to
|
||||
// process a partial update work meta file.
|
||||
updateMetaFile.reset();
|
||||
PRUnichar completedMetaFilePath[MAX_PATH + 1];
|
||||
wcscpy(completedMetaFilePath, tempFilePath);
|
||||
|
||||
// Change the file extension of the temp file path from .tmp to .mz
|
||||
LPWSTR extensionPart =
|
||||
&(completedMetaFilePath[wcslen(completedMetaFilePath) - 3]);
|
||||
wcscpy(extensionPart, L"mz");
|
||||
return MoveFileExW(tempFilePath, completedMetaFilePath,
|
||||
MOVEFILE_REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets update.status to pending so that the next startup will not use
|
||||
* the service and instead will attempt an update the with a UAC prompt.
|
||||
*
|
||||
* @param updateDirPath The path of the update directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WriteStatusPending(LPCWSTR updateDirPath)
|
||||
{
|
||||
PRUnichar updateStatusFilePath[MAX_PATH + 1];
|
||||
wcscpy(updateStatusFilePath, updateDirPath);
|
||||
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const char pending[] = "pending";
|
||||
nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
|
||||
NULL, CREATE_ALWAYS, 0, NULL));
|
||||
if (statusFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD wrote;
|
||||
BOOL ok = WriteFile(statusFile, pending,
|
||||
sizeof(pending) - 1, &wrote, NULL);
|
||||
return ok && (wrote == sizeof(pending) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets update.status to a specific failure code
|
||||
*
|
||||
* @param updateDirPath The path of the update directory
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
|
||||
{
|
||||
PRUnichar updateStatusFilePath[MAX_PATH + 1];
|
||||
wcscpy(updateStatusFilePath, updateDirPath);
|
||||
if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
|
||||
NULL, CREATE_ALWAYS, 0, NULL));
|
||||
if (statusFile == INVALID_HANDLE_VALUE) {
|
||||
return FALSE;
|
||||
}
|
||||
char failure[32];
|
||||
sprintf(failure, "failed: %d", errorCode);
|
||||
|
||||
DWORD toWrite = strlen(failure);
|
||||
DWORD wrote;
|
||||
BOOL ok = WriteFile(statusFile, failure,
|
||||
toWrite, &wrote, NULL);
|
||||
return ok && wrote == toWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a service initiated action with the specified arguments.
|
||||
*
|
||||
* @param exePath The path of the executable to run
|
||||
* @param argc The total number of arguments in argv
|
||||
* @param argv An array of null terminated strings to pass to the exePath,
|
||||
* argv[0] is ignored
|
||||
* @return TRUE if successful
|
||||
*/
|
||||
BOOL
|
||||
WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv)
|
||||
{
|
||||
PRUnichar** argvConverted = new PRUnichar*[argc];
|
||||
if (!argvConverted)
|
||||
return FALSE;
|
||||
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
argvConverted[i] = AllocConvertUTF8toUTF16(argv[i]);
|
||||
if (!argvConverted[i]) {
|
||||
FreeAllocStrings(i, argvConverted);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL ok = WinLaunchServiceCommand(exePath, argc, argvConverted);
|
||||
FreeAllocStrings(argc, argvConverted);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Launch a child process with the specified arguments.
|
||||
* @note argv[0] is ignored
|
||||
|
|
Загрузка…
Ссылка в новой задаче