toolkit/mozapps/update/common/updatehelper.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #ifdef MOZ_METRO
michael@0 6 // Needed for COM calls to launch Metro applications
michael@0 7 #undef WINVER
michael@0 8 #undef _WIN32_WINNT
michael@0 9 #define WINVER 0x602
michael@0 10 #define _WIN32_WINNT 0x602
michael@0 11 #include <objbase.h>
michael@0 12 #include <shobjidl.h>
michael@0 13 #pragma comment(lib, "ole32.lib")
michael@0 14 #endif
michael@0 15
michael@0 16 #include <windows.h>
michael@0 17
michael@0 18 // Needed for CreateToolhelp32Snapshot
michael@0 19 #include <tlhelp32.h>
michael@0 20 #ifndef ONLY_SERVICE_LAUNCHING
michael@0 21
michael@0 22 #include <stdio.h>
michael@0 23 #include "shlobj.h"
michael@0 24 #include "updatehelper.h"
michael@0 25 #include "uachelper.h"
michael@0 26 #include "pathhash.h"
michael@0 27 #include "mozilla/Scoped.h"
michael@0 28
michael@0 29 // Needed for PathAppendW
michael@0 30 #include <shlwapi.h>
michael@0 31
michael@0 32 WCHAR* MakeCommandLine(int argc, WCHAR **argv);
michael@0 33 BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
michael@0 34
michael@0 35 /**
michael@0 36 * Obtains the path of a file in the same directory as the specified file.
michael@0 37 *
michael@0 38 * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
michael@0 39 * @param siblingFIlePath The path of another file in the same directory
michael@0 40 * @param newFileName The filename of another file in the same directory
michael@0 41 * @return TRUE if successful
michael@0 42 */
michael@0 43 BOOL
michael@0 44 PathGetSiblingFilePath(LPWSTR destinationBuffer,
michael@0 45 LPCWSTR siblingFilePath,
michael@0 46 LPCWSTR newFileName)
michael@0 47 {
michael@0 48 if (wcslen(siblingFilePath) >= MAX_PATH) {
michael@0 49 return FALSE;
michael@0 50 }
michael@0 51
michael@0 52 wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH);
michael@0 53 if (!PathRemoveFileSpecW(destinationBuffer)) {
michael@0 54 return FALSE;
michael@0 55 }
michael@0 56
michael@0 57 if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
michael@0 58 return FALSE;
michael@0 59 }
michael@0 60
michael@0 61 return PathAppendSafe(destinationBuffer, newFileName);
michael@0 62 }
michael@0 63
michael@0 64 /**
michael@0 65 * Launch the post update application as the specified user (helper.exe).
michael@0 66 * It takes in the path of the callback application to calculate the path
michael@0 67 * of helper.exe. For service updates this is called from both the system
michael@0 68 * account and the current user account.
michael@0 69 *
michael@0 70 * @param installationDir The path to the callback application binary.
michael@0 71 * @param updateInfoDir The directory where update info is stored.
michael@0 72 * @param forceSync If true even if the ini file specifies async, the
michael@0 73 * process will wait for termination of PostUpdate.
michael@0 74 * @param userToken The user token to run as, if nullptr the current
michael@0 75 * user will be used.
michael@0 76 * @return TRUE if there was no error starting the process.
michael@0 77 */
michael@0 78 BOOL
michael@0 79 LaunchWinPostProcess(const WCHAR *installationDir,
michael@0 80 const WCHAR *updateInfoDir,
michael@0 81 bool forceSync,
michael@0 82 HANDLE userToken)
michael@0 83 {
michael@0 84 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
michael@0 85 wcsncpy(workingDirectory, installationDir, MAX_PATH);
michael@0 86
michael@0 87 // Launch helper.exe to perform post processing (e.g. registry and log file
michael@0 88 // modifications) for the update.
michael@0 89 WCHAR inifile[MAX_PATH + 1] = { L'\0' };
michael@0 90 wcsncpy(inifile, installationDir, MAX_PATH);
michael@0 91 if (!PathAppendSafe(inifile, L"updater.ini")) {
michael@0 92 return FALSE;
michael@0 93 }
michael@0 94
michael@0 95 WCHAR exefile[MAX_PATH + 1];
michael@0 96 WCHAR exearg[MAX_PATH + 1];
michael@0 97 WCHAR exeasync[10];
michael@0 98 bool async = true;
michael@0 99 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
michael@0 100 exefile, MAX_PATH + 1, inifile)) {
michael@0 101 return FALSE;
michael@0 102 }
michael@0 103
michael@0 104 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
michael@0 105 MAX_PATH + 1, inifile)) {
michael@0 106 return FALSE;
michael@0 107 }
michael@0 108
michael@0 109 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
michael@0 110 exeasync,
michael@0 111 sizeof(exeasync)/sizeof(exeasync[0]),
michael@0 112 inifile)) {
michael@0 113 return FALSE;
michael@0 114 }
michael@0 115
michael@0 116 WCHAR exefullpath[MAX_PATH + 1] = { L'\0' };
michael@0 117 wcsncpy(exefullpath, installationDir, MAX_PATH);
michael@0 118 if (!PathAppendSafe(exefullpath, exefile)) {
michael@0 119 return false;
michael@0 120 }
michael@0 121
michael@0 122 WCHAR dlogFile[MAX_PATH + 1];
michael@0 123 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
michael@0 124 return FALSE;
michael@0 125 }
michael@0 126
michael@0 127 WCHAR slogFile[MAX_PATH + 1] = { L'\0' };
michael@0 128 wcsncpy(slogFile, updateInfoDir, MAX_PATH);
michael@0 129 if (!PathAppendSafe(slogFile, L"update.log")) {
michael@0 130 return FALSE;
michael@0 131 }
michael@0 132
michael@0 133 WCHAR dummyArg[14] = { L'\0' };
michael@0 134 wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
michael@0 135
michael@0 136 size_t len = wcslen(exearg) + wcslen(dummyArg);
michael@0 137 WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
michael@0 138 if (!cmdline) {
michael@0 139 return FALSE;
michael@0 140 }
michael@0 141
michael@0 142 wcsncpy(cmdline, dummyArg, len);
michael@0 143 wcscat(cmdline, exearg);
michael@0 144
michael@0 145 if (forceSync ||
michael@0 146 !_wcsnicmp(exeasync, L"false", 6) ||
michael@0 147 !_wcsnicmp(exeasync, L"0", 2)) {
michael@0 148 async = false;
michael@0 149 }
michael@0 150
michael@0 151 // We want to launch the post update helper app to update the Windows
michael@0 152 // registry even if there is a failure with removing the uninstall.update
michael@0 153 // file or copying the update.log file.
michael@0 154 CopyFileW(slogFile, dlogFile, false);
michael@0 155
michael@0 156 STARTUPINFOW si = {sizeof(si), 0};
michael@0 157 si.lpDesktop = L"";
michael@0 158 PROCESS_INFORMATION pi = {0};
michael@0 159
michael@0 160 bool ok;
michael@0 161 if (userToken) {
michael@0 162 ok = CreateProcessAsUserW(userToken,
michael@0 163 exefullpath,
michael@0 164 cmdline,
michael@0 165 nullptr, // no special security attributes
michael@0 166 nullptr, // no special thread attributes
michael@0 167 false, // don't inherit filehandles
michael@0 168 0, // No special process creation flags
michael@0 169 nullptr, // inherit my environment
michael@0 170 workingDirectory,
michael@0 171 &si,
michael@0 172 &pi);
michael@0 173 } else {
michael@0 174 ok = CreateProcessW(exefullpath,
michael@0 175 cmdline,
michael@0 176 nullptr, // no special security attributes
michael@0 177 nullptr, // no special thread attributes
michael@0 178 false, // don't inherit filehandles
michael@0 179 0, // No special process creation flags
michael@0 180 nullptr, // inherit my environment
michael@0 181 workingDirectory,
michael@0 182 &si,
michael@0 183 &pi);
michael@0 184 }
michael@0 185 free(cmdline);
michael@0 186 if (ok) {
michael@0 187 if (!async)
michael@0 188 WaitForSingleObject(pi.hProcess, INFINITE);
michael@0 189 CloseHandle(pi.hProcess);
michael@0 190 CloseHandle(pi.hThread);
michael@0 191 }
michael@0 192 return ok;
michael@0 193 }
michael@0 194
michael@0 195 /**
michael@0 196 * Starts the upgrade process for update of the service if it is
michael@0 197 * already installed.
michael@0 198 *
michael@0 199 * @param installDir the installation directory where
michael@0 200 * maintenanceservice_installer.exe is located.
michael@0 201 * @return TRUE if successful
michael@0 202 */
michael@0 203 BOOL
michael@0 204 StartServiceUpdate(LPCWSTR installDir)
michael@0 205 {
michael@0 206 // Get a handle to the local computer SCM database
michael@0 207 SC_HANDLE manager = OpenSCManager(nullptr, nullptr,
michael@0 208 SC_MANAGER_ALL_ACCESS);
michael@0 209 if (!manager) {
michael@0 210 return FALSE;
michael@0 211 }
michael@0 212
michael@0 213 // Open the service
michael@0 214 SC_HANDLE svc = OpenServiceW(manager, SVC_NAME,
michael@0 215 SERVICE_ALL_ACCESS);
michael@0 216 if (!svc) {
michael@0 217 CloseServiceHandle(manager);
michael@0 218 return FALSE;
michael@0 219 }
michael@0 220
michael@0 221 // If we reach here, then the service is installed, so
michael@0 222 // proceed with upgrading it.
michael@0 223
michael@0 224 CloseServiceHandle(manager);
michael@0 225
michael@0 226 // The service exists and we opened it, get the config bytes needed
michael@0 227 DWORD bytesNeeded;
michael@0 228 if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
michael@0 229 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
michael@0 230 CloseServiceHandle(svc);
michael@0 231 return FALSE;
michael@0 232 }
michael@0 233
michael@0 234 // Get the service config information, in particular we want the binary
michael@0 235 // path of the service.
michael@0 236 mozilla::ScopedDeleteArray<char> serviceConfigBuffer(new char[bytesNeeded]);
michael@0 237 if (!QueryServiceConfigW(svc,
michael@0 238 reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
michael@0 239 bytesNeeded, &bytesNeeded)) {
michael@0 240 CloseServiceHandle(svc);
michael@0 241 return FALSE;
michael@0 242 }
michael@0 243
michael@0 244 CloseServiceHandle(svc);
michael@0 245
michael@0 246 QUERY_SERVICE_CONFIGW &serviceConfig =
michael@0 247 *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
michael@0 248
michael@0 249 PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
michael@0 250
michael@0 251 // Obtain the temp path of the maintenance service binary
michael@0 252 WCHAR tmpService[MAX_PATH + 1] = { L'\0' };
michael@0 253 if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName,
michael@0 254 L"maintenanceservice_tmp.exe")) {
michael@0 255 return FALSE;
michael@0 256 }
michael@0 257
michael@0 258 // Get the new maintenance service path from the install dir
michael@0 259 WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' };
michael@0 260 wcsncpy(newMaintServicePath, installDir, MAX_PATH);
michael@0 261 PathAppendSafe(newMaintServicePath,
michael@0 262 L"maintenanceservice.exe");
michael@0 263
michael@0 264 // Copy the temp file in alongside the maintenace service.
michael@0 265 // This is a requirement for maintenance service upgrades.
michael@0 266 if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) {
michael@0 267 return FALSE;
michael@0 268 }
michael@0 269
michael@0 270 // Start the upgrade comparison process
michael@0 271 STARTUPINFOW si = {0};
michael@0 272 si.cb = sizeof(STARTUPINFOW);
michael@0 273 // No particular desktop because no UI
michael@0 274 si.lpDesktop = L"";
michael@0 275 PROCESS_INFORMATION pi = {0};
michael@0 276 WCHAR cmdLine[64] = { '\0' };
michael@0 277 wcsncpy(cmdLine, L"dummyparam.exe upgrade",
michael@0 278 sizeof(cmdLine) / sizeof(cmdLine[0]) - 1);
michael@0 279 BOOL svcUpdateProcessStarted = CreateProcessW(tmpService,
michael@0 280 cmdLine,
michael@0 281 nullptr, nullptr, FALSE,
michael@0 282 0,
michael@0 283 nullptr, installDir, &si, &pi);
michael@0 284 if (svcUpdateProcessStarted) {
michael@0 285 CloseHandle(pi.hProcess);
michael@0 286 CloseHandle(pi.hThread);
michael@0 287 }
michael@0 288 return svcUpdateProcessStarted;
michael@0 289 }
michael@0 290
michael@0 291 #endif
michael@0 292
michael@0 293 /**
michael@0 294 * Executes a maintenance service command
michael@0 295 *
michael@0 296 * @param argc The total number of arguments in argv
michael@0 297 * @param argv An array of null terminated strings to pass to the service,
michael@0 298 * @return ERROR_SUCCESS if the service command was started.
michael@0 299 * Less than 16000, a windows system error code from StartServiceW
michael@0 300 * More than 20000, 20000 + the last state of the service constant if
michael@0 301 * the last state is something other than stopped.
michael@0 302 * 17001 if the SCM could not be opened
michael@0 303 * 17002 if the service could not be opened
michael@0 304 */
michael@0 305 DWORD
michael@0 306 StartServiceCommand(int argc, LPCWSTR* argv)
michael@0 307 {
michael@0 308 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
michael@0 309 if (lastState != SERVICE_STOPPED) {
michael@0 310 return 20000 + lastState;
michael@0 311 }
michael@0 312
michael@0 313 // Get a handle to the SCM database.
michael@0 314 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
michael@0 315 SC_MANAGER_CONNECT |
michael@0 316 SC_MANAGER_ENUMERATE_SERVICE);
michael@0 317 if (!serviceManager) {
michael@0 318 return 17001;
michael@0 319 }
michael@0 320
michael@0 321 // Get a handle to the service.
michael@0 322 SC_HANDLE service = OpenServiceW(serviceManager,
michael@0 323 SVC_NAME,
michael@0 324 SERVICE_START);
michael@0 325 if (!service) {
michael@0 326 CloseServiceHandle(serviceManager);
michael@0 327 return 17002;
michael@0 328 }
michael@0 329
michael@0 330 // Wait at most 5 seconds trying to start the service in case of errors
michael@0 331 // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
michael@0 332 const DWORD maxWaitMS = 5000;
michael@0 333 DWORD currentWaitMS = 0;
michael@0 334 DWORD lastError = ERROR_SUCCESS;
michael@0 335 while (currentWaitMS < maxWaitMS) {
michael@0 336 BOOL result = StartServiceW(service, argc, argv);
michael@0 337 if (result) {
michael@0 338 lastError = ERROR_SUCCESS;
michael@0 339 break;
michael@0 340 } else {
michael@0 341 lastError = GetLastError();
michael@0 342 }
michael@0 343 Sleep(100);
michael@0 344 currentWaitMS += 100;
michael@0 345 }
michael@0 346 CloseServiceHandle(service);
michael@0 347 CloseServiceHandle(serviceManager);
michael@0 348 return lastError;
michael@0 349 }
michael@0 350
michael@0 351 #ifndef ONLY_SERVICE_LAUNCHING
michael@0 352
michael@0 353 /**
michael@0 354 * Launch a service initiated action for a software update with the
michael@0 355 * specified arguments.
michael@0 356 *
michael@0 357 * @param exePath The path of the executable to run
michael@0 358 * @param argc The total number of arguments in argv
michael@0 359 * @param argv An array of null terminated strings to pass to the exePath,
michael@0 360 * argv[0] must be the path to the updater.exe
michael@0 361 * @return ERROR_SUCCESS if successful
michael@0 362 */
michael@0 363 DWORD
michael@0 364 LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv)
michael@0 365 {
michael@0 366 // The service command is the same as the updater.exe command line except
michael@0 367 // it has 2 extra args: 1) The Path to udpater.exe, and 2) the command
michael@0 368 // being executed which is "software-update"
michael@0 369 LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2];
michael@0 370 updaterServiceArgv[0] = L"MozillaMaintenance";
michael@0 371 updaterServiceArgv[1] = L"software-update";
michael@0 372
michael@0 373 for (int i = 0; i < argc; ++i) {
michael@0 374 updaterServiceArgv[i + 2] = argv[i];
michael@0 375 }
michael@0 376
michael@0 377 // Execute the service command by starting the service with
michael@0 378 // the passed in arguments.
michael@0 379 DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
michael@0 380 delete[] updaterServiceArgv;
michael@0 381 return ret;
michael@0 382 }
michael@0 383
michael@0 384 /**
michael@0 385 * Joins a base directory path with a filename.
michael@0 386 *
michael@0 387 * @param base The base directory path of size MAX_PATH + 1
michael@0 388 * @param extra The filename to append
michael@0 389 * @return TRUE if the file name was successful appended to base
michael@0 390 */
michael@0 391 BOOL
michael@0 392 PathAppendSafe(LPWSTR base, LPCWSTR extra)
michael@0 393 {
michael@0 394 if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
michael@0 395 return FALSE;
michael@0 396 }
michael@0 397
michael@0 398 return PathAppendW(base, extra);
michael@0 399 }
michael@0 400
michael@0 401 /**
michael@0 402 * Sets update.status to pending so that the next startup will not use
michael@0 403 * the service and instead will attempt an update the with a UAC prompt.
michael@0 404 *
michael@0 405 * @param updateDirPath The path of the update directory
michael@0 406 * @return TRUE if successful
michael@0 407 */
michael@0 408 BOOL
michael@0 409 WriteStatusPending(LPCWSTR updateDirPath)
michael@0 410 {
michael@0 411 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
michael@0 412 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
michael@0 413 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
michael@0 414 return FALSE;
michael@0 415 }
michael@0 416
michael@0 417 const char pending[] = "pending";
michael@0 418 HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
michael@0 419 nullptr, CREATE_ALWAYS, 0, nullptr);
michael@0 420 if (statusFile == INVALID_HANDLE_VALUE) {
michael@0 421 return FALSE;
michael@0 422 }
michael@0 423
michael@0 424 DWORD wrote;
michael@0 425 BOOL ok = WriteFile(statusFile, pending,
michael@0 426 sizeof(pending) - 1, &wrote, nullptr);
michael@0 427 CloseHandle(statusFile);
michael@0 428 return ok && (wrote == sizeof(pending) - 1);
michael@0 429 }
michael@0 430
michael@0 431 /**
michael@0 432 * Sets update.status to a specific failure code
michael@0 433 *
michael@0 434 * @param updateDirPath The path of the update directory
michael@0 435 * @return TRUE if successful
michael@0 436 */
michael@0 437 BOOL
michael@0 438 WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
michael@0 439 {
michael@0 440 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
michael@0 441 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
michael@0 442 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
michael@0 443 return FALSE;
michael@0 444 }
michael@0 445
michael@0 446 HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
michael@0 447 nullptr, CREATE_ALWAYS, 0, nullptr);
michael@0 448 if (statusFile == INVALID_HANDLE_VALUE) {
michael@0 449 return FALSE;
michael@0 450 }
michael@0 451 char failure[32];
michael@0 452 sprintf(failure, "failed: %d", errorCode);
michael@0 453
michael@0 454 DWORD toWrite = strlen(failure);
michael@0 455 DWORD wrote;
michael@0 456 BOOL ok = WriteFile(statusFile, failure,
michael@0 457 toWrite, &wrote, nullptr);
michael@0 458 CloseHandle(statusFile);
michael@0 459 return ok && wrote == toWrite;
michael@0 460 }
michael@0 461
michael@0 462 #endif
michael@0 463
michael@0 464 /**
michael@0 465 * Waits for a service to enter a stopped state.
michael@0 466 * This function does not stop the service, it just blocks until the service
michael@0 467 * is stopped.
michael@0 468 *
michael@0 469 * @param serviceName The service to wait for.
michael@0 470 * @param maxWaitSeconds The maximum number of seconds to wait
michael@0 471 * @return state of the service after a timeout or when stopped.
michael@0 472 * A value of 255 is returned for an error. Typical values are:
michael@0 473 * SERVICE_STOPPED 0x00000001
michael@0 474 * SERVICE_START_PENDING 0x00000002
michael@0 475 * SERVICE_STOP_PENDING 0x00000003
michael@0 476 * SERVICE_RUNNING 0x00000004
michael@0 477 * SERVICE_CONTINUE_PENDING 0x00000005
michael@0 478 * SERVICE_PAUSE_PENDING 0x00000006
michael@0 479 * SERVICE_PAUSED 0x00000007
michael@0 480 * last status not set 0x000000CF
michael@0 481 * Could no query status 0x000000DF
michael@0 482 * Could not open service, access denied 0x000000EB
michael@0 483 * Could not open service, invalid handle 0x000000EC
michael@0 484 * Could not open service, invalid name 0x000000ED
michael@0 485 * Could not open service, does not exist 0x000000EE
michael@0 486 * Could not open service, other error 0x000000EF
michael@0 487 * Could not open SCM, access denied 0x000000FD
michael@0 488 * Could not open SCM, database does not exist 0x000000FE;
michael@0 489 * Could not open SCM, other error 0x000000FF;
michael@0 490 * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
michael@0 491 * in case Windows comes out with other service stats higher than 7, they
michael@0 492 * would likely call it 8 and above. JS code that uses this in TestAUSHelper
michael@0 493 * only handles values up to 255 so that's why we don't use GetLastError
michael@0 494 * directly.
michael@0 495 */
michael@0 496 DWORD
michael@0 497 WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
michael@0 498 {
michael@0 499 // 0x000000CF is defined above to be not set
michael@0 500 DWORD lastServiceState = 0x000000CF;
michael@0 501
michael@0 502 // Get a handle to the SCM database.
michael@0 503 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
michael@0 504 SC_MANAGER_CONNECT |
michael@0 505 SC_MANAGER_ENUMERATE_SERVICE);
michael@0 506 if (!serviceManager) {
michael@0 507 DWORD lastError = GetLastError();
michael@0 508 switch(lastError) {
michael@0 509 case ERROR_ACCESS_DENIED:
michael@0 510 return 0x000000FD;
michael@0 511 case ERROR_DATABASE_DOES_NOT_EXIST:
michael@0 512 return 0x000000FE;
michael@0 513 default:
michael@0 514 return 0x000000FF;
michael@0 515 }
michael@0 516 }
michael@0 517
michael@0 518 // Get a handle to the service.
michael@0 519 SC_HANDLE service = OpenServiceW(serviceManager,
michael@0 520 serviceName,
michael@0 521 SERVICE_QUERY_STATUS);
michael@0 522 if (!service) {
michael@0 523 DWORD lastError = GetLastError();
michael@0 524 CloseServiceHandle(serviceManager);
michael@0 525 switch(lastError) {
michael@0 526 case ERROR_ACCESS_DENIED:
michael@0 527 return 0x000000EB;
michael@0 528 case ERROR_INVALID_HANDLE:
michael@0 529 return 0x000000EC;
michael@0 530 case ERROR_INVALID_NAME:
michael@0 531 return 0x000000ED;
michael@0 532 case ERROR_SERVICE_DOES_NOT_EXIST:
michael@0 533 return 0x000000EE;
michael@0 534 default:
michael@0 535 return 0x000000EF;
michael@0 536 }
michael@0 537 }
michael@0 538
michael@0 539 DWORD currentWaitMS = 0;
michael@0 540 SERVICE_STATUS_PROCESS ssp;
michael@0 541 ssp.dwCurrentState = lastServiceState;
michael@0 542 while (currentWaitMS < maxWaitSeconds * 1000) {
michael@0 543 DWORD bytesNeeded;
michael@0 544 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
michael@0 545 sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
michael@0 546 DWORD lastError = GetLastError();
michael@0 547 switch (lastError) {
michael@0 548 case ERROR_INVALID_HANDLE:
michael@0 549 ssp.dwCurrentState = 0x000000D9;
michael@0 550 break;
michael@0 551 case ERROR_ACCESS_DENIED:
michael@0 552 ssp.dwCurrentState = 0x000000DA;
michael@0 553 break;
michael@0 554 case ERROR_INSUFFICIENT_BUFFER:
michael@0 555 ssp.dwCurrentState = 0x000000DB;
michael@0 556 break;
michael@0 557 case ERROR_INVALID_PARAMETER:
michael@0 558 ssp.dwCurrentState = 0x000000DC;
michael@0 559 break;
michael@0 560 case ERROR_INVALID_LEVEL:
michael@0 561 ssp.dwCurrentState = 0x000000DD;
michael@0 562 break;
michael@0 563 case ERROR_SHUTDOWN_IN_PROGRESS:
michael@0 564 ssp.dwCurrentState = 0x000000DE;
michael@0 565 break;
michael@0 566 // These 3 errors can occur when the service is not yet stopped but
michael@0 567 // it is stopping.
michael@0 568 case ERROR_INVALID_SERVICE_CONTROL:
michael@0 569 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
michael@0 570 case ERROR_SERVICE_NOT_ACTIVE:
michael@0 571 currentWaitMS += 50;
michael@0 572 Sleep(50);
michael@0 573 continue;
michael@0 574 default:
michael@0 575 ssp.dwCurrentState = 0x000000DF;
michael@0 576 }
michael@0 577
michael@0 578 // We couldn't query the status so just break out
michael@0 579 break;
michael@0 580 }
michael@0 581
michael@0 582 // The service is already in use.
michael@0 583 if (ssp.dwCurrentState == SERVICE_STOPPED) {
michael@0 584 break;
michael@0 585 }
michael@0 586 currentWaitMS += 50;
michael@0 587 Sleep(50);
michael@0 588 }
michael@0 589
michael@0 590 lastServiceState = ssp.dwCurrentState;
michael@0 591 CloseServiceHandle(service);
michael@0 592 CloseServiceHandle(serviceManager);
michael@0 593 return lastServiceState;
michael@0 594 }
michael@0 595
michael@0 596 #ifndef ONLY_SERVICE_LAUNCHING
michael@0 597
michael@0 598 /**
michael@0 599 * Determines if there is at least one process running for the specified
michael@0 600 * application. A match will be found across any session for any user.
michael@0 601 *
michael@0 602 * @param process The process to check for existance
michael@0 603 * @return ERROR_NOT_FOUND if the process was not found
michael@0 604 * ERROR_SUCCESS if the process was found and there were no errors
michael@0 605 * Other Win32 system error code for other errors
michael@0 606 **/
michael@0 607 DWORD
michael@0 608 IsProcessRunning(LPCWSTR filename)
michael@0 609 {
michael@0 610 // Take a snapshot of all processes in the system.
michael@0 611 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
michael@0 612 if (INVALID_HANDLE_VALUE == snapshot) {
michael@0 613 return GetLastError();
michael@0 614 }
michael@0 615
michael@0 616 PROCESSENTRY32W processEntry;
michael@0 617 processEntry.dwSize = sizeof(PROCESSENTRY32W);
michael@0 618 if (!Process32FirstW(snapshot, &processEntry)) {
michael@0 619 DWORD lastError = GetLastError();
michael@0 620 CloseHandle(snapshot);
michael@0 621 return lastError;
michael@0 622 }
michael@0 623
michael@0 624 do {
michael@0 625 if (wcsicmp(filename, processEntry.szExeFile) == 0) {
michael@0 626 CloseHandle(snapshot);
michael@0 627 return ERROR_SUCCESS;
michael@0 628 }
michael@0 629 } while (Process32NextW(snapshot, &processEntry));
michael@0 630 CloseHandle(snapshot);
michael@0 631 return ERROR_NOT_FOUND;
michael@0 632 }
michael@0 633
michael@0 634 /**
michael@0 635 * Waits for the specified applicaiton to exit.
michael@0 636 *
michael@0 637 * @param filename The application to wait for.
michael@0 638 * @param maxSeconds The maximum amount of seconds to wait for all
michael@0 639 * instances of the application to exit.
michael@0 640 * @return ERROR_SUCCESS if no instances of the application exist
michael@0 641 * WAIT_TIMEOUT if the process is still running after maxSeconds.
michael@0 642 * Any other Win32 system error code.
michael@0 643 */
michael@0 644 DWORD
michael@0 645 WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
michael@0 646 {
michael@0 647 DWORD applicationRunningError = WAIT_TIMEOUT;
michael@0 648 for(DWORD i = 0; i < maxSeconds; i++) {
michael@0 649 DWORD applicationRunningError = IsProcessRunning(filename);
michael@0 650 if (ERROR_NOT_FOUND == applicationRunningError) {
michael@0 651 return ERROR_SUCCESS;
michael@0 652 }
michael@0 653 Sleep(1000);
michael@0 654 }
michael@0 655
michael@0 656 if (ERROR_SUCCESS == applicationRunningError) {
michael@0 657 return WAIT_TIMEOUT;
michael@0 658 }
michael@0 659
michael@0 660 return applicationRunningError;
michael@0 661 }
michael@0 662
michael@0 663 /**
michael@0 664 * Determines if the fallback key exists or not
michael@0 665 *
michael@0 666 * @return TRUE if the fallback key exists and there was no error checking
michael@0 667 */
michael@0 668 BOOL
michael@0 669 DoesFallbackKeyExist()
michael@0 670 {
michael@0 671 HKEY testOnlyFallbackKey;
michael@0 672 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
michael@0 673 TEST_ONLY_FALLBACK_KEY_PATH, 0,
michael@0 674 KEY_READ | KEY_WOW64_64KEY,
michael@0 675 &testOnlyFallbackKey) != ERROR_SUCCESS) {
michael@0 676 return FALSE;
michael@0 677 }
michael@0 678
michael@0 679 RegCloseKey(testOnlyFallbackKey);
michael@0 680 return TRUE;
michael@0 681 }
michael@0 682
michael@0 683 #endif
michael@0 684
michael@0 685 /**
michael@0 686 * Determines if the file system for the specified file handle is local
michael@0 687 * @param file path to check the filesystem type for, must be at most MAX_PATH
michael@0 688 * @param isLocal out parameter which will hold TRUE if the drive is local
michael@0 689 * @return TRUE if the call succeeded
michael@0 690 */
michael@0 691 BOOL
michael@0 692 IsLocalFile(LPCWSTR file, BOOL &isLocal)
michael@0 693 {
michael@0 694 WCHAR rootPath[MAX_PATH + 1] = { L'\0' };
michael@0 695 if (wcslen(file) > MAX_PATH) {
michael@0 696 return FALSE;
michael@0 697 }
michael@0 698
michael@0 699 wcsncpy(rootPath, file, MAX_PATH);
michael@0 700 PathStripToRootW(rootPath);
michael@0 701 isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
michael@0 702 return TRUE;
michael@0 703 }
michael@0 704
michael@0 705
michael@0 706 /**
michael@0 707 * Determines the DWORD value of a registry key value
michael@0 708 *
michael@0 709 * @param key The base key to where the value name exists
michael@0 710 * @param valueName The name of the value
michael@0 711 * @param retValue Out parameter which will hold the value
michael@0 712 * @return TRUE on success
michael@0 713 */
michael@0 714 static BOOL
michael@0 715 GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue)
michael@0 716 {
michael@0 717 DWORD regDWORDValueSize = sizeof(DWORD);
michael@0 718 LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr,
michael@0 719 reinterpret_cast<LPBYTE>(&retValue),
michael@0 720 &regDWORDValueSize);
michael@0 721 return ERROR_SUCCESS == retCode;
michael@0 722 }
michael@0 723
michael@0 724 /**
michael@0 725 * Determines if the the system's elevation type allows
michael@0 726 * unprmopted elevation.
michael@0 727 *
michael@0 728 * @param isUnpromptedElevation Out parameter which specifies if unprompted
michael@0 729 * elevation is allowed.
michael@0 730 * @return TRUE if the user can actually elevate and the value was obtained
michael@0 731 * successfully.
michael@0 732 */
michael@0 733 BOOL
michael@0 734 IsUnpromptedElevation(BOOL &isUnpromptedElevation)
michael@0 735 {
michael@0 736 if (!UACHelper::CanUserElevate()) {
michael@0 737 return FALSE;
michael@0 738 }
michael@0 739
michael@0 740 LPCWSTR UACBaseRegKey =
michael@0 741 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
michael@0 742 HKEY baseKey;
michael@0 743 LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
michael@0 744 UACBaseRegKey, 0,
michael@0 745 KEY_READ, &baseKey);
michael@0 746 if (retCode != ERROR_SUCCESS) {
michael@0 747 return FALSE;
michael@0 748 }
michael@0 749
michael@0 750 DWORD consent, secureDesktop;
michael@0 751 BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin",
michael@0 752 consent);
michael@0 753 success = success &&
michael@0 754 GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
michael@0 755 isUnpromptedElevation = !consent && !secureDesktop;
michael@0 756
michael@0 757 RegCloseKey(baseKey);
michael@0 758 return success;
michael@0 759 }
michael@0 760
michael@0 761 #ifdef MOZ_METRO
michael@0 762 /*
michael@0 763 * Retrieve the app model id of the firefox metro browser.
michael@0 764 *
michael@0 765 * @aPathBuffer Buffer to fill
michael@0 766 * @aCharLength Length of buffer to fill in characters
michael@0 767 */
michael@0 768 bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer, long aCharLength)
michael@0 769 {
michael@0 770 if (!aIDBuffer || aCharLength <= 0)
michael@0 771 return false;
michael@0 772
michael@0 773 memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength));
michael@0 774 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
michael@0 775
michael@0 776 HKEY key;
michael@0 777 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
michael@0 778 0, KEY_READ, &key) != ERROR_SUCCESS) {
michael@0 779 return false;
michael@0 780 }
michael@0 781 DWORD len = aCharLength * sizeof(WCHAR);
michael@0 782 memset(aIDBuffer, 0, len);
michael@0 783 if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
michael@0 784 (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
michael@0 785 RegCloseKey(key);
michael@0 786 return false;
michael@0 787 }
michael@0 788 RegCloseKey(key);
michael@0 789 return true;
michael@0 790 }
michael@0 791
michael@0 792 HRESULT
michael@0 793 LaunchDefaultMetroBrowser()
michael@0 794 {
michael@0 795 CoInitialize(nullptr);
michael@0 796 HRESULT hr = E_FAIL;
michael@0 797 // The interface that allows us to activate the browser
michael@0 798 IApplicationActivationManager *activateMgr;
michael@0 799 if (FAILED(hr = CoCreateInstance(CLSID_ApplicationActivationManager,
michael@0 800 nullptr, CLSCTX_LOCAL_SERVER,
michael@0 801 IID_IApplicationActivationManager,
michael@0 802 (void**)&activateMgr))) {
michael@0 803 CoUninitialize();
michael@0 804 return hr;
michael@0 805 }
michael@0 806
michael@0 807 // Activation is based on the browser's registered app model id
michael@0 808 WCHAR appModelID[256];
michael@0 809 if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) {
michael@0 810 activateMgr->Release();
michael@0 811 CoUninitialize();
michael@0 812 return hr;
michael@0 813 }
michael@0 814
michael@0 815 // Hand off focus rights to the out-of-process activation server. Without
michael@0 816 // this the metro interface won't launch.
michael@0 817 CoAllowSetForegroundWindow(activateMgr, nullptr);
michael@0 818
michael@0 819 // Launch default browser in Metro
michael@0 820 DWORD processID;
michael@0 821 hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI,
michael@0 822 &processID);
michael@0 823 activateMgr->Release();
michael@0 824 CoUninitialize();
michael@0 825 return hr;
michael@0 826 }
michael@0 827 #endif

mercurial