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:
Brian R. Bondy 2012-01-04 23:19:15 -05:00
Родитель 61a9e5f11a
Коммит 48e8c66f8e
12 изменённых файлов: 800 добавлений и 918 удалений

Просмотреть файл

@ -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