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"
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
{
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
);
36 * @return true if the handle is a valid Restart Ranager handle.
38 inline bool ok() { return mError
== ERROR_SUCCESS
; }
41 * @return the Restart Manager handle to pass to other Restart Manager APIs.
43 inline DWORD
handle() { return mHandle
; }
48 ProfileUnlockerWin
& mUnlocker
;
51 ProfileUnlockerWin::ProfileUnlockerWin(const nsAString
& aFileName
)
52 : mRmStartSession(nullptr),
53 mRmRegisterResources(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"));
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
;
83 reinterpret_cast<RMGETLIST
>(::GetProcAddress(module
, "RmGetList"));
85 return NS_ERROR_UNEXPECTED
;
88 reinterpret_cast<RMENDSESSION
>(::GetProcAddress(module
, "RmEndSession"));
90 return NS_ERROR_UNEXPECTED
;
93 mRestartMgrModule
.steal(module
);
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
); }
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);
117 return NS_ERROR_FAILURE
;
120 LPCWSTR resources
[] = {mFileName
.get()};
121 DWORD error
= mRmRegisterResources(session
.handle(), 1, resources
, 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
;
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
,
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
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
)) {
155 // If nothing could be unlocked then we return the error code of the last
156 // failure that was returned.
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
));
171 return NS_ERROR_FAILURE
;
174 FILETIME creationTime
, exitTime
, kernelTime
, userTime
;
175 if (!::GetProcessTimes(otherProcess
, &creationTime
, &exitTime
, &kernelTime
,
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
,
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.
201 nsCOMPtr
<nsIFile
> otherProcessImageName
;
202 if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName
, imageNameLen
),
203 getter_AddRefs(otherProcessImageName
)))) {
204 return NS_ERROR_FAILURE
;
206 nsAutoString otherProcessLeafName
;
207 if (NS_FAILED(otherProcessImageName
->GetLeafName(otherProcessLeafName
))) {
208 return NS_ERROR_FAILURE
;
211 imageNameLen
= MAX_PATH
;
212 if (!::QueryFullProcessImageNameW(::GetCurrentProcess(), 0, imageName
,
214 return NS_ERROR_FAILURE
;
216 nsCOMPtr
<nsIFile
> thisProcessImageName
;
217 if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName
, imageNameLen
),
218 getter_AddRefs(thisProcessImageName
)))) {
219 return NS_ERROR_FAILURE
;
221 nsAutoString thisProcessLeafName
;
222 if (NS_FAILED(thisProcessImageName
->GetLeafName(thisProcessLeafName
))) {
223 return NS_ERROR_FAILURE
;
226 // Make sure the image leaf names match
227 if (_wcsicmp(otherProcessLeafName
.get(), thisProcessLeafName
.get())) {
228 return NS_ERROR_NOT_AVAILABLE
;
231 // We know that another process holds the lock and that it shares the same
232 // image name as our process. Let's kill it.
233 // Subtle: TerminateProcess returning ERROR_ACCESS_DENIED is actually an
234 // indicator that the target process managed to shut down on its own. In that
235 // case we should return NS_OK since we may proceed with startup.
236 if (!::TerminateProcess(otherProcess
, 1) &&
237 ::GetLastError() != ERROR_ACCESS_DENIED
) {
238 return NS_ERROR_FAILURE
;
244 } // namespace mozilla