Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / profile / ProfileUnlockerWin.cpp
blobd7ab5e0a307f5d859a3ca9d1f04f72a125add4b0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ProfileUnlockerWin.h"
8 #include "nsCOMPtr.h"
9 #include "nsIFile.h"
10 #include "nsTArray.h"
11 #include "nsXPCOM.h"
13 namespace mozilla {
15 /**
16 * RAII class to obtain and manage a handle to a Restart Manager session.
17 * It opens a new handle upon construction and releases it upon destruction.
19 class MOZ_STACK_CLASS ScopedRestartManagerSession {
20 public:
21 explicit ScopedRestartManagerSession(ProfileUnlockerWin& aUnlocker)
22 : mError(ERROR_INVALID_HANDLE),
23 mHandle((DWORD)-1) // 0 is a valid restart manager handle
25 mUnlocker(aUnlocker) {
26 mError = mUnlocker.StartSession(mHandle);
29 ~ScopedRestartManagerSession() {
30 if (mError == ERROR_SUCCESS) {
31 mUnlocker.EndSession(mHandle);
35 /**
36 * @return true if the handle is a valid Restart Ranager handle.
38 inline bool ok() { return mError == ERROR_SUCCESS; }
40 /**
41 * @return the Restart Manager handle to pass to other Restart Manager APIs.
43 inline DWORD handle() { return mHandle; }
45 private:
46 DWORD mError;
47 DWORD mHandle;
48 ProfileUnlockerWin& mUnlocker;
51 ProfileUnlockerWin::ProfileUnlockerWin(const nsAString& aFileName)
52 : mRmStartSession(nullptr),
53 mRmRegisterResources(nullptr),
54 mRmGetList(nullptr),
55 mRmEndSession(nullptr),
56 mFileName(aFileName) {}
58 ProfileUnlockerWin::~ProfileUnlockerWin() {}
60 NS_IMPL_ISUPPORTS(ProfileUnlockerWin, nsIProfileUnlocker)
62 nsresult ProfileUnlockerWin::Init() {
63 MOZ_ASSERT(!mRestartMgrModule);
64 if (mFileName.IsEmpty()) {
65 return NS_ERROR_ILLEGAL_VALUE;
68 nsModuleHandle module(::LoadLibraryW(L"Rstrtmgr.dll"));
69 if (!module) {
70 return NS_ERROR_NOT_AVAILABLE;
72 mRmStartSession = reinterpret_cast<RMSTARTSESSION>(
73 ::GetProcAddress(module, "RmStartSession"));
74 if (!mRmStartSession) {
75 return NS_ERROR_UNEXPECTED;
77 mRmRegisterResources = reinterpret_cast<RMREGISTERRESOURCES>(
78 ::GetProcAddress(module, "RmRegisterResources"));
79 if (!mRmRegisterResources) {
80 return NS_ERROR_UNEXPECTED;
82 mRmGetList =
83 reinterpret_cast<RMGETLIST>(::GetProcAddress(module, "RmGetList"));
84 if (!mRmGetList) {
85 return NS_ERROR_UNEXPECTED;
87 mRmEndSession =
88 reinterpret_cast<RMENDSESSION>(::GetProcAddress(module, "RmEndSession"));
89 if (!mRmEndSession) {
90 return NS_ERROR_UNEXPECTED;
93 mRestartMgrModule.steal(module);
94 return NS_OK;
97 DWORD
98 ProfileUnlockerWin::StartSession(DWORD& aHandle) {
99 WCHAR sessionKey[CCH_RM_SESSION_KEY + 1] = {0};
100 return mRmStartSession(&aHandle, 0, sessionKey);
103 void ProfileUnlockerWin::EndSession(DWORD aHandle) { mRmEndSession(aHandle); }
105 NS_IMETHODIMP
106 ProfileUnlockerWin::Unlock(uint32_t aSeverity) {
107 if (!mRestartMgrModule) {
108 return NS_ERROR_NOT_INITIALIZED;
111 if (aSeverity != FORCE_QUIT) {
112 return NS_ERROR_NOT_IMPLEMENTED;
115 ScopedRestartManagerSession session(*this);
116 if (!session.ok()) {
117 return NS_ERROR_FAILURE;
120 LPCWSTR resources[] = {mFileName.get()};
121 DWORD error = mRmRegisterResources(session.handle(), 1, resources, 0, nullptr,
122 0, nullptr);
123 if (error != ERROR_SUCCESS) {
124 return NS_ERROR_FAILURE;
127 // Using a AutoTArray here because we expect the required size to be 1.
128 AutoTArray<RM_PROCESS_INFO, 1> info;
129 UINT numEntries;
130 UINT numEntriesNeeded = 1;
131 error = ERROR_MORE_DATA;
132 DWORD reason = RmRebootReasonNone;
133 while (error == ERROR_MORE_DATA) {
134 info.SetLength(numEntriesNeeded);
135 numEntries = numEntriesNeeded;
136 error = mRmGetList(session.handle(), &numEntriesNeeded, &numEntries,
137 &info[0], &reason);
139 if (error != ERROR_SUCCESS) {
140 return NS_ERROR_FAILURE;
142 if (numEntries == 0) {
143 // Nobody else is locking the file; the other process must have terminated
144 return NS_OK;
147 nsresult rv = NS_ERROR_FAILURE;
148 for (UINT i = 0; i < numEntries; ++i) {
149 rv = TryToTerminate(info[i].Process);
150 if (NS_SUCCEEDED(rv)) {
151 return NS_OK;
155 // If nothing could be unlocked then we return the error code of the last
156 // failure that was returned.
157 return rv;
160 nsresult ProfileUnlockerWin::TryToTerminate(RM_UNIQUE_PROCESS& aProcess) {
161 // Subtle: If the target process terminated before this call to OpenProcess,
162 // this call will still succeed. This is because the restart manager session
163 // internally retains a handle to the target process. The rules for Windows
164 // PIDs state that the PID of a terminated process remains valid as long as
165 // at least one handle to that process remains open, so when we reach this
166 // point the PID is still valid and the process will open successfully.
167 DWORD accessRights = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
168 nsAutoHandle otherProcess(
169 ::OpenProcess(accessRights, FALSE, aProcess.dwProcessId));
170 if (!otherProcess) {
171 return NS_ERROR_FAILURE;
174 FILETIME creationTime, exitTime, kernelTime, userTime;
175 if (!::GetProcessTimes(otherProcess, &creationTime, &exitTime, &kernelTime,
176 &userTime)) {
177 return NS_ERROR_FAILURE;
179 if (::CompareFileTime(&aProcess.ProcessStartTime, &creationTime)) {
180 return NS_ERROR_NOT_AVAILABLE;
183 WCHAR imageName[MAX_PATH];
184 DWORD imageNameLen = MAX_PATH;
185 if (!::QueryFullProcessImageNameW(otherProcess, 0, imageName,
186 &imageNameLen)) {
187 // The error codes for this function are not very descriptive. There are
188 // actually two failure cases here: Either the call failed because the
189 // process is no longer running, or it failed for some other reason. We
190 // need to know which case that is.
191 DWORD otherProcessExitCode;
192 if (!::GetExitCodeProcess(otherProcess, &otherProcessExitCode) ||
193 otherProcessExitCode == STILL_ACTIVE) {
194 // The other process is still running.
195 return NS_ERROR_FAILURE;
197 // The other process must have terminated. We should return NS_OK so that
198 // this process may proceed with startup.
199 return NS_OK;
201 nsCOMPtr<nsIFile> otherProcessImageName;
202 if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
203 false,
204 getter_AddRefs(otherProcessImageName)))) {
205 return NS_ERROR_FAILURE;
207 nsAutoString otherProcessLeafName;
208 if (NS_FAILED(otherProcessImageName->GetLeafName(otherProcessLeafName))) {
209 return NS_ERROR_FAILURE;
212 imageNameLen = MAX_PATH;
213 if (!::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, imageName,
214 &imageNameLen)) {
215 return NS_ERROR_FAILURE;
217 nsCOMPtr<nsIFile> thisProcessImageName;
218 if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
219 false, getter_AddRefs(thisProcessImageName)))) {
220 return NS_ERROR_FAILURE;
222 nsAutoString thisProcessLeafName;
223 if (NS_FAILED(thisProcessImageName->GetLeafName(thisProcessLeafName))) {
224 return NS_ERROR_FAILURE;
227 // Make sure the image leaf names match
228 if (_wcsicmp(otherProcessLeafName.get(), thisProcessLeafName.get())) {
229 return NS_ERROR_NOT_AVAILABLE;
232 // We know that another process holds the lock and that it shares the same
233 // image name as our process. Let's kill it.
234 // Subtle: TerminateProcess returning ERROR_ACCESS_DENIED is actually an
235 // indicator that the target process managed to shut down on its own. In that
236 // case we should return NS_OK since we may proceed with startup.
237 if (!::TerminateProcess(otherProcess, 1) &&
238 ::GetLastError() != ERROR_ACCESS_DENIED) {
239 return NS_ERROR_FAILURE;
242 return NS_OK;
245 } // namespace mozilla