mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1894 lines
57 KiB
1894 lines
57 KiB
/****************************** Module Header ******************************\
|
|
* Module Name: hooks.c
|
|
*
|
|
* Copyright (c) 1985-91, Microsoft Corporation
|
|
*
|
|
* This module contains the user hook APIs and support routines.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
* 08 Feb 1992 IanJa Unicode/ANSI aware & neutral
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/*
|
|
* This table is used to determine whether a particular hook
|
|
* can be set for the system or a task, and other hook-ID specific things.
|
|
*/
|
|
#define HKF_SYSTEM 0x01
|
|
#define HKF_TASK 0x02
|
|
#define HKF_JOURNAL 0x04 // JOURNAL the mouse on set
|
|
#define HKF_NZRET 0x08 // Always return NZ hook for <=3.0 compatibility
|
|
#define HKF_INTRSND 0x10 // OK to call hookproc in context of hooking thread
|
|
|
|
CONST int ampiHookError[CWINHOOKS] = {
|
|
0, // WH_MSGFILTER (-1)
|
|
0, // WH_JOURNALRECORD 0
|
|
-1, // WH_JOURNALPLAYBACK 1
|
|
0, // WH_KEYBOARD 2
|
|
0, // WH_GETMESSAGE 3
|
|
0, // WH_CALLWNDPROC 4
|
|
0, // WH_CBT 5
|
|
0, // WH_SYSMSGFILTER 6
|
|
0, // WH_MOUSE 7
|
|
0, // WH_HARDWARE 8
|
|
0, // WH_DEBUG 9
|
|
0, // WH_SHELL 10
|
|
0, // WH_FOREGROUNDIDLE 11
|
|
0 // WH_CALLWNDPROCRET 12
|
|
};
|
|
|
|
CONST BYTE abHookFlags[CWINHOOKS] = {
|
|
HKF_SYSTEM | HKF_TASK | HKF_NZRET , // WH_MSGFILTER (-1)
|
|
HKF_SYSTEM | HKF_JOURNAL | HKF_INTRSND , // WH_JOURNALRECORD 0
|
|
HKF_SYSTEM | HKF_JOURNAL | HKF_INTRSND , // WH_JOURNALPLAYBACK 1
|
|
HKF_SYSTEM | HKF_TASK | HKF_NZRET | HKF_INTRSND , // WH_KEYBOARD 2
|
|
HKF_SYSTEM | HKF_TASK , // WH_GETMESSAGE 3
|
|
HKF_SYSTEM | HKF_TASK , // WH_CALLWNDPROC 4
|
|
HKF_SYSTEM | HKF_TASK , // WH_CBT 5
|
|
HKF_SYSTEM , // WH_SYSMSGFILTER 6
|
|
HKF_SYSTEM | HKF_TASK | HKF_INTRSND , // WH_MOUSE 7
|
|
HKF_SYSTEM | HKF_TASK , // WH_HARDWARE 8
|
|
HKF_SYSTEM | HKF_TASK , // WH_DEBUG 9
|
|
HKF_SYSTEM | HKF_TASK , // WH_SHELL 10
|
|
HKF_SYSTEM | HKF_TASK , // WH_FOREGROUNDIDLE 11
|
|
HKF_SYSTEM | HKF_TASK // WH_CALLWNDPROCRET 12
|
|
};
|
|
|
|
void UnlinkHook(PHOOK phkFree);
|
|
|
|
/***************************************************************************\
|
|
* JournalAttach
|
|
*
|
|
* This attaches/detaches threads to one input queue so input is synchronized.
|
|
* Journalling requires this.
|
|
*
|
|
* 12-10-92 ScottLu Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL JournalAttach(
|
|
PTHREADINFO pti,
|
|
BOOL fAttach)
|
|
{
|
|
PTHREADINFO ptiT;
|
|
PQ pq;
|
|
PLIST_ENTRY pHead, pEntry;
|
|
|
|
/*
|
|
* If we're attaching, calculate the pqAttach for all threads journalling.
|
|
* If we're unattaching, just call ReattachThreads() and it will calculate
|
|
* the non-journalling queues to attach to.
|
|
*/
|
|
if (fAttach) {
|
|
if ((pq = AllocQueue(pti, NULL)) == NULL)
|
|
return FALSE;
|
|
|
|
pHead = &pti->rpdesk->PtiList;
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
|
|
|
|
/*
|
|
* This is the Q to attach to for all threads that will do
|
|
* journalling.
|
|
*/
|
|
if (!(ptiT->TIF_flags & (TIF_DONTJOURNALATTACH | TIF_INCLEANUP))) {
|
|
ptiT->pqAttach = pq;
|
|
ptiT->pqAttach->cThreads++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReattachThreads(fAttach);
|
|
}
|
|
/***************************************************************************\
|
|
* InterQueueMsgCleanup
|
|
*
|
|
* Walk gpsmsList looking for inter queue messages with a hung receiver;
|
|
* if one is found and it's a message that would have been an async event or
|
|
* intra queue if not journalling, then it cleans it up.
|
|
*
|
|
* While Journalling most threads are attached to the same queue. This causes
|
|
* activation and input stuff to be synchronous; if a thread hangs or dies,
|
|
* any other thread sending a message to the hung/dead thread will be
|
|
* blocked for good.
|
|
* This is critical when the blocked thread is cssr; this can happen with
|
|
* console windows or when some one requests a hard error box, specially
|
|
* during window activation.
|
|
*
|
|
* This function must be called when all queues have been detached (unless previously attached),
|
|
* so we can take care of hung/dead receivers with pending SMSs.
|
|
*
|
|
* 03-28-96 GerardoB Created
|
|
\***************************************************************************/
|
|
void InterQueueMsgCleanup (DWORD dwTimeFromLastRead)
|
|
{
|
|
PSMS *ppsms;
|
|
PSMS psmsNext;
|
|
|
|
CheckCritIn();
|
|
|
|
/*
|
|
* Walk gpsmsList
|
|
*/
|
|
for (ppsms = &gpsmsList; *ppsms; ) {
|
|
psmsNext = (*ppsms)->psmsNext;
|
|
/*
|
|
* If this is an inter queue message
|
|
*/
|
|
if (((*ppsms)->ptiSender != NULL)
|
|
&& ((*ppsms)->ptiReceiver != NULL)
|
|
&& ((*ppsms)->ptiSender->pq != (*ppsms)->ptiReceiver->pq)) {
|
|
/*
|
|
* If the receiver has been hung for a while
|
|
*/
|
|
if (FHungApp ((*ppsms)->ptiReceiver, dwTimeFromLastRead)) {
|
|
|
|
switch ((*ppsms)->message) {
|
|
/*
|
|
* Activation messages
|
|
*/
|
|
case WM_NCACTIVATE:
|
|
case WM_ACTIVATEAPP:
|
|
case WM_ACTIVATE:
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
case WM_QUERYNEWPALETTE:
|
|
/*
|
|
* Sent to spwndFocus, which now can be in a different queue
|
|
*/
|
|
case WM_INPUTLANGCHANGE:
|
|
RIPMSG3 (RIP_WARNING, "InterQueueMsgCleanup: ptiSender:%#lx ptiReceiver:%#lx message:%#lx",
|
|
(*ppsms)->ptiSender, (*ppsms)->ptiReceiver, (*ppsms)->message);
|
|
ReceiverDied(*ppsms, ppsms);
|
|
break;
|
|
|
|
} /* switch */
|
|
|
|
} /* If hung receiver */
|
|
|
|
} /* If inter queue message */
|
|
|
|
/*
|
|
* If the message was not unlinked, go to the next one.
|
|
*/
|
|
if (*ppsms != psmsNext)
|
|
ppsms = &(*ppsms)->psmsNext;
|
|
|
|
} /* for */
|
|
}
|
|
/***************************************************************************\
|
|
* CancelJournalling
|
|
*
|
|
* Journalling is cancelled with control-escape is pressed, or when the desktop
|
|
* is switched.
|
|
*
|
|
* 01-27-93 ScottLu Created.
|
|
\***************************************************************************/
|
|
|
|
void CancelJournalling(void)
|
|
{
|
|
PTHREADINFO ptiCancelJournal;
|
|
PHOOK phook;
|
|
PHOOK phookNext;
|
|
|
|
/*
|
|
* Mouse buttons sometimes get stuck down due to hardware glitches,
|
|
* usually due to input concentrator switchboxes or faulty serial
|
|
* mouse COM ports, so clear the global button state here just in case,
|
|
* otherwise we may not be able to change focus with the mouse.
|
|
* Also do this in Alt-Tab processing.
|
|
*/
|
|
#ifdef DEBUG
|
|
if (wMouseOwnerButton)
|
|
RIPMSG1(RIP_WARNING,
|
|
"wMouseOwnerButton=%x, being cleared forcibly\n",
|
|
wMouseOwnerButton);
|
|
#endif
|
|
wMouseOwnerButton = 0;
|
|
|
|
/*
|
|
* Remove journal hooks. This'll cause threads to associate with
|
|
* different queues.
|
|
*/
|
|
phook = grpdeskRitInput->pDeskInfo->asphkStart[WH_JOURNALPLAYBACK + 1];
|
|
while (phook != NULL) {
|
|
ptiCancelJournal = phook->head.pti;
|
|
|
|
if (ptiCancelJournal != NULL) {
|
|
/*
|
|
* Let the thread that set the journal hook know this is happening.
|
|
*/
|
|
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
|
|
|
|
/*
|
|
* If there was an app waiting for a response back from the journal
|
|
* application, cancel that request so the app can continue running
|
|
* (for example, we don't want winlogon or console to wait for an
|
|
* app that may be hung!)
|
|
*/
|
|
SendMsgCleanup(ptiCancelJournal);
|
|
}
|
|
|
|
phookNext = phook->sphkNext;
|
|
_UnhookWindowsHookEx(phook); // May free phook memory
|
|
phook = phookNext;
|
|
}
|
|
|
|
phook = grpdeskRitInput->pDeskInfo->asphkStart[WH_JOURNALRECORD + 1];
|
|
while (phook != NULL) {
|
|
ptiCancelJournal = phook->head.pti;
|
|
|
|
if (ptiCancelJournal != NULL) {
|
|
/*
|
|
* Let the thread that set the journal hook know this is happening.
|
|
*/
|
|
_PostThreadMessage(ptiCancelJournal, WM_CANCELJOURNAL, 0, 0);
|
|
|
|
/*
|
|
* If there was an app waiting for a response back from the journal
|
|
* application, cancel that request so the app can continue running
|
|
* (for example, we don't want winlogon or console to wait for an
|
|
* app that may be hung!)
|
|
*/
|
|
SendMsgCleanup(ptiCancelJournal);
|
|
}
|
|
|
|
phookNext = phook->sphkNext;
|
|
_UnhookWindowsHookEx(phook); // May free phook memory
|
|
phook = phookNext;
|
|
}
|
|
|
|
|
|
/*
|
|
* Make sure journalling ssync mode didn't hose any one
|
|
*/
|
|
InterQueueMsgCleanup(CMSWAITTOKILLTIMEOUT);
|
|
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* _SetWindowsHookAW (API)
|
|
*
|
|
* This is the Win32 version of the SetWindowsHook() call. It has the
|
|
* same characteristics as far as return values, but only sets 'local'
|
|
* hooks. This is because we weren't provided a DLL we can load into
|
|
* other processes. Because of this WH_SYSMSGFILTER is no longer a
|
|
* valid hook. Apps will either need to call with WH_MSGFILTER or call
|
|
* the new API SetWindowsHookEx(). Essentially this API is obsolete and
|
|
* everyone should call SetWindowsHookEx().
|
|
*
|
|
* History:
|
|
* 10-Feb-1991 DavidPe Created.
|
|
* 30-Jan-1992 IanJa Added bAnsi parameter
|
|
\***************************************************************************/
|
|
|
|
PROC _SetWindowsHookAW(
|
|
int nFilterType,
|
|
PROC pfnFilterProc,
|
|
BOOL bAnsi)
|
|
{
|
|
PHOOK phk;
|
|
|
|
phk = _SetWindowsHookEx(NULL, NULL, PtiCurrent(),
|
|
nFilterType, pfnFilterProc, bAnsi);
|
|
|
|
/*
|
|
* If we get an error from SetWindowsHookEx() then we return
|
|
* 0xFFFFFFFF to be compatible with older version of Windows.
|
|
*/
|
|
if (phk == NULL) {
|
|
return (PROC)0xFFFFFFFF;
|
|
}
|
|
|
|
/*
|
|
* Handle the backwards compatibility return value cases for
|
|
* SetWindowsHook. If this was the first hook in the chain,
|
|
* then return NULL, else return something non-zero. HKF_NZRET
|
|
* is a special case where SetWindowsHook would always return
|
|
* something because there was a default hook installed. Some
|
|
* apps relied on a non-zero return value in those cases.
|
|
*/
|
|
if ((phk->sphkNext != NULL) || (abHookFlags[nFilterType + 1] & HKF_NZRET)) {
|
|
return (PROC)phk;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _SetWindowsHookEx
|
|
*
|
|
* SetWindowsHookEx() is the updated version of SetWindowsHook(). It allows
|
|
* applications to set hooks on specific threads or throughout the entire
|
|
* system. The function returns a hook handle to the application if
|
|
* successful and NULL if a failure occured.
|
|
*
|
|
* History:
|
|
* 28-Jan-1991 DavidPe Created.
|
|
* 15-May-1991 ScottLu Changed to work client/server.
|
|
* 30-Jan-1992 IanJa Added bAnsi parameter
|
|
\***************************************************************************/
|
|
|
|
PHOOK _SetWindowsHookEx(
|
|
HANDLE hmod,
|
|
PUNICODE_STRING pstrLib,
|
|
PTHREADINFO ptiThread,
|
|
int nFilterType,
|
|
PROC pfnFilterProc,
|
|
BOOL bAnsi)
|
|
{
|
|
ACCESS_MASK amDesired;
|
|
HHOOK hhkNew;
|
|
PHOOK phkNew;
|
|
PHOOK *pphkStart;
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
/*
|
|
* Check to see if filter type is valid.
|
|
*/
|
|
if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) {
|
|
RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check to see if filter proc is valid.
|
|
*/
|
|
if (pfnFilterProc == NULL) {
|
|
RIPERR0(ERROR_INVALID_FILTER_PROC, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
if (ptiThread == NULL) {
|
|
/*
|
|
* Is the app trying to set a global hook without a library?
|
|
* If so return an error.
|
|
*/
|
|
if (hmod == NULL) {
|
|
RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
} else {
|
|
/*
|
|
* Is the app trying to set a local hook that is global-only?
|
|
* If so return an error.
|
|
*/
|
|
if (!(abHookFlags[nFilterType + 1] & HKF_TASK)) {
|
|
RIPERR0(ERROR_GLOBAL_ONLY_HOOK, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Can't hook outside our own desktop.
|
|
*/
|
|
if (ptiThread->rpdesk != ptiCurrent->rpdesk) {
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to desktop in _SetWindowsHookEx - can't hook other desktops");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (ptiCurrent->ppi != ptiThread->ppi) {
|
|
/*
|
|
* Is the app trying to set hook in another process without a library?
|
|
* If so return an error.
|
|
*/
|
|
if (hmod == NULL) {
|
|
RIPERR0(ERROR_HOOK_NEEDS_HMOD, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Is the app hooking another user without access?
|
|
* If so return an error. Note that this check is done
|
|
* for global hooks every time the hook is called.
|
|
*/
|
|
if ((!RtlEqualLuid(&ptiThread->ppi->luidSession,
|
|
&ptiCurrent->ppi->luidSession)) &&
|
|
!(ptiThread->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK)) {
|
|
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to other user in _SetWindowsHookEx");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((ptiThread->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD)) &&
|
|
!(abHookFlags[nFilterType + 1] & HKF_INTRSND)) {
|
|
|
|
/*
|
|
* Can't hook console or GUI system thread if inter-thread
|
|
* calling isn't implemented for this hook type.
|
|
*/
|
|
RIPERR1(ERROR_HOOK_TYPE_NOT_ALLOWED,
|
|
RIP_WARNING,
|
|
"nFilterType (%ld) not allowed in _SetWindowsHookEx",
|
|
nFilterType);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if this thread has access to hook its desktop.
|
|
*/
|
|
switch( nFilterType ) {
|
|
case WH_JOURNALRECORD:
|
|
amDesired = DESKTOP_JOURNALRECORD;
|
|
break;
|
|
|
|
case WH_JOURNALPLAYBACK:
|
|
amDesired = DESKTOP_JOURNALPLAYBACK;
|
|
break;
|
|
|
|
default:
|
|
amDesired = DESKTOP_HOOKCONTROL;
|
|
break;
|
|
}
|
|
|
|
if (!RtlAreAllAccessesGranted(ptiCurrent->amdesk, amDesired)) {
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied to desktop in _SetWindowsHookEx");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (amDesired != DESKTOP_HOOKCONTROL &&
|
|
(ptiCurrent->rpdesk->rpwinstaParent->dwFlags & WSF_NOIO)) {
|
|
RIPERR0(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION,
|
|
RIP_WARNING,
|
|
"Journal hooks invalid on a desktop belonging to a non-interactive WindowStation.");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Is this a journal hook?
|
|
*/
|
|
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
|
|
/*
|
|
* Is a journal hook of this type already installed?
|
|
* If so it's an error.
|
|
*/
|
|
if (ptiCurrent->pDeskInfo->asphkStart[nFilterType + 1] != NULL) {
|
|
RIPERR0(ERROR_JOURNAL_HOOK_SET, RIP_VERBOSE, "");
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Allocate the new HOOK structure.
|
|
*/
|
|
phkNew = (PHOOK)HMAllocObject(ptiCurrent, ptiCurrent->rpdesk,
|
|
TYPE_HOOK, sizeof(HOOK));
|
|
if (phkNew == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If a DLL is required for this hook, register the library with
|
|
* the library management routines so we can assure it's loaded
|
|
* into all the processes necessary.
|
|
*/
|
|
phkNew->ihmod = -1;
|
|
if (hmod != NULL) {
|
|
|
|
#if defined(WX86)
|
|
if (Wx86CurrentTib()) {
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
try {
|
|
|
|
if (RtlImageNtHeader(hmod)->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) {
|
|
phkNew->flags |= HF_WX86KNOWNDLL;
|
|
}
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = GetExceptionCode();
|
|
|
|
RIPMSG2(RIP_WARNING,
|
|
"Wx86Hooks: hmod = %x ExCode %x\n",
|
|
hmod,
|
|
Status);
|
|
|
|
HMFreeObject((PVOID)phkNew);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
phkNew->ihmod = GetHmodTableIndex(pstrLib);
|
|
|
|
if (phkNew->ihmod == -1) {
|
|
RIPERR0(ERROR_MOD_NOT_FOUND, RIP_VERBOSE, "");
|
|
HMFreeObject((PVOID)phkNew);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add a dependency on this module - meaning, increment a count
|
|
* that simply counts the number of hooks set into this module.
|
|
*/
|
|
if (phkNew->ihmod >= 0) {
|
|
AddHmodDependency(phkNew->ihmod);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Depending on whether we're setting a global or local hook,
|
|
* get the start of the appropriate linked-list of HOOKs. Also
|
|
* set the HF_GLOBAL flag if it's a global hook.
|
|
*/
|
|
if (ptiThread != NULL) {
|
|
pphkStart = &ptiThread->asphkStart[nFilterType + 1];
|
|
|
|
/*
|
|
* Set the WHF_* in the THREADINFO so we know it's hooked.
|
|
*/
|
|
ptiThread->fsHooks |= WHF_FROM_WH(nFilterType);
|
|
|
|
/*
|
|
* Set the flags in the thread's TEB
|
|
*/
|
|
if (ptiThread->pClientInfo) {
|
|
BOOL fAttached;
|
|
|
|
/*
|
|
* If the thread being hooked is in another process, attach
|
|
* to that process.
|
|
*/
|
|
if (ptiThread->ppi != ptiCurrent->ppi) {
|
|
KeAttachProcess(&ptiThread->ppi->Process->Pcb);
|
|
fAttached = TRUE;
|
|
} else
|
|
fAttached = FALSE;
|
|
|
|
ptiThread->pClientInfo->fsHooks = ptiThread->fsHooks;
|
|
|
|
if (fAttached)
|
|
KeDetachProcess();
|
|
}
|
|
|
|
/*
|
|
* Remember which thread we're hooking.
|
|
*/
|
|
phkNew->ptiHooked = ptiThread;
|
|
|
|
} else {
|
|
pphkStart = &ptiCurrent->pDeskInfo->asphkStart[nFilterType + 1];
|
|
phkNew->flags |= HF_GLOBAL;
|
|
|
|
/*
|
|
* Set the WHF_* in the SERVERINFO so we know it's hooked.
|
|
*/
|
|
ptiCurrent->pDeskInfo->fsHooks |= WHF_FROM_WH(nFilterType);
|
|
|
|
phkNew->ptiHooked = NULL;
|
|
}
|
|
|
|
/*
|
|
* Does the hook function expect ANSI or Unicode text?
|
|
*/
|
|
if (bAnsi) {
|
|
phkNew->flags |= HF_ANSI;
|
|
}
|
|
|
|
/*
|
|
* Initialize the HOOK structure. Unreferenced parameters are assumed
|
|
* to be initialized to zero by LocalAlloc().
|
|
*/
|
|
phkNew->iHook = nFilterType;
|
|
|
|
/*
|
|
* Libraries are loaded at different linear addresses in different
|
|
* process contexts. For this reason, we need to convert the filter
|
|
* proc address into an offset while setting the hook, and then convert
|
|
* it back to a real per-process function pointer when calling a
|
|
* hook. Do this by subtracting the 'hmod' (which is a pointer to the
|
|
* linear and contiguous .exe header) from the function index.
|
|
*/
|
|
phkNew->offPfn = ((DWORD)pfnFilterProc) - ((DWORD)hmod);
|
|
|
|
#ifdef HOOKBATCH
|
|
phkNew->cEventMessages = 0;
|
|
phkNew->iCurrentEvent = 0;
|
|
phkNew->CacheTimeOut = 0;
|
|
phkNew->aEventCache = NULL;
|
|
#endif //HOOKBATCH
|
|
|
|
/*
|
|
* Link this hook into the front of the hook-list.
|
|
*/
|
|
Lock(&phkNew->sphkNext, *pphkStart);
|
|
Lock(pphkStart, phkNew);
|
|
|
|
/*
|
|
* If this is a journal hook, setup synchronized input processing
|
|
* AFTER we set the hook - so this synchronization can be cancelled
|
|
* with control-esc.
|
|
*/
|
|
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
|
|
/*
|
|
* Attach everyone to us so journal-hook processing
|
|
* will be synchronized.
|
|
*/
|
|
hhkNew = PtoHq(phkNew);
|
|
if (!JournalAttach(ptiCurrent, TRUE)) {
|
|
_UnhookWindowsHookEx(phkNew);
|
|
}
|
|
phkNew = (PHOOK)HMValidateHandleNoRip(hhkNew, TYPE_HOOK);
|
|
}
|
|
|
|
if (abHookFlags[nFilterType + 1] & HKF_JOURNAL) {
|
|
/*
|
|
* If we're changing the journal hooks, jiggle the mouse.
|
|
* This way the first event will always be a mouse move, which
|
|
* will ensure that the cursor is set properly.
|
|
*/
|
|
SetFMouseMoved();
|
|
}
|
|
|
|
/*
|
|
* Can't allow a process that has set a global hook that works
|
|
* on server-side winprocs to run at background priority!
|
|
*/
|
|
if (phkNew != NULL &&
|
|
phkNew->flags & HF_GLOBAL &&
|
|
(abHookFlags[nFilterType + 1] & HKF_INTRSND) &&
|
|
ptiCurrent->Thread && THREAD_TO_PROCESS(ptiCurrent->Thread)) {
|
|
ptiCurrent->TIF_flags |= TIF_GLOBALHOOKER;
|
|
|
|
//
|
|
// Now that bg/fg is really just a quantum issue, this is not needed !
|
|
//
|
|
//
|
|
}
|
|
|
|
/*
|
|
* Return pointer to our internal hook structure so we know
|
|
* which hook to call next in CallNextHookEx().
|
|
*/
|
|
return phkNew;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallNextHookEx
|
|
*
|
|
* In the new world DefHookProc() is a bit deceptive since SetWindowsHook()
|
|
* isn't returning the actual address of the next hook to call, but instead
|
|
* a hook handle. CallNextHookEx() is a slightly clearer picture of what's
|
|
* going on so apps don't get tempted to try and call the value we return.
|
|
*
|
|
* As a side note we don't actually use the hook handle passed in. We keep
|
|
* track of which hooks is currently being called on a thread in the Q
|
|
* structure and use that. This is because SetWindowsHook() will sometimes
|
|
* return NULL to be compatible with the way it used to work, but even though
|
|
* we may be dealing with the last 'local' hook, there may be further 'global'
|
|
* hooks we need to call. PhkNext() is smart enough to jump over to the
|
|
* 'global' hook chain if it reaches the end of the 'local' hook chain.
|
|
*
|
|
* History:
|
|
* 01-30-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
DWORD xxxCallNextHookEx(
|
|
int nCode,
|
|
DWORD wParam,
|
|
DWORD lParam)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
if (PtiCurrent()->sphkCurrent == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return xxxCallHook2(_PhkNext(PtiCurrent()->sphkCurrent), nCode, wParam, lParam, &bAnsiHook);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* CheckWHFBits
|
|
*
|
|
* This routine checks to see if any hooks for nFilterType exist, and clear
|
|
* the appropriate WHF_ in the THREADINFO and SERVERINFO.
|
|
*
|
|
* History:
|
|
* 08-17-92 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID CheckWHFBits(
|
|
PTHREADINFO pti,
|
|
int nFilterType)
|
|
{
|
|
if (pti->asphkStart[nFilterType + 1] == NULL) {
|
|
pti->fsHooks &= ~(WHF_FROM_WH(nFilterType));
|
|
|
|
/*
|
|
* Set the flags in the thread's TEB
|
|
*/
|
|
if (pti->pClientInfo) {
|
|
BOOL fAttached;
|
|
|
|
/*
|
|
* If the hooked thread is in another process, attach
|
|
* to that process.
|
|
*/
|
|
if (pti->ppi != PpiCurrent()) {
|
|
KeAttachProcess(&pti->ppi->Process->Pcb);
|
|
fAttached = TRUE;
|
|
} else
|
|
fAttached = FALSE;
|
|
|
|
pti->pClientInfo->fsHooks = pti->fsHooks;
|
|
|
|
if (fAttached)
|
|
KeDetachProcess();
|
|
}
|
|
}
|
|
|
|
if (pti->pDeskInfo->asphkStart[nFilterType + 1] == NULL) {
|
|
pti->pDeskInfo->fsHooks &= ~(WHF_FROM_WH(nFilterType));
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _UnhookWindowsHook (API)
|
|
*
|
|
* This is the old version of the Unhook API. It does the same thing as
|
|
* UnhookWindowsHookEx(), but takes a filter-type and filter-proc to
|
|
* identify which hook to unhook.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL _UnhookWindowsHook(
|
|
int nFilterType,
|
|
PROC pfnFilterProc)
|
|
{
|
|
PHOOK phk;
|
|
PTHREADINFO pti;
|
|
|
|
if ((nFilterType < WH_MIN) || (nFilterType > WH_MAX)) {
|
|
RIPERR0(ERROR_INVALID_HOOK_FILTER, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
}
|
|
|
|
pti = PtiCurrent();
|
|
|
|
for (phk = PhkFirst(pti, nFilterType); phk != NULL; phk = _PhkNext(phk)) {
|
|
|
|
/*
|
|
* Is this the hook we're looking for?
|
|
*/
|
|
if (PFNHOOK(phk) == pfnFilterProc) {
|
|
|
|
/*
|
|
* Are we on the thread that set the hook?
|
|
* If not return an error.
|
|
*/
|
|
if (GETPTI(phk) != pti) {
|
|
RIPERR0(ERROR_ACCESS_DENIED,
|
|
RIP_WARNING,
|
|
"Access denied in _UnhookWindowsHook: "
|
|
"this thread is not the same as that which set the hook");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return _UnhookWindowsHookEx( phk );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Didn't find the hook we were looking for so return FALSE.
|
|
*/
|
|
RIPERR0(ERROR_HOOK_NOT_INSTALLED, RIP_VERBOSE, "");
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _UnhookWindowsHookEx (API)
|
|
*
|
|
* Applications call this API to 'unhook' a hook. First we check if someone
|
|
* is currently calling this hook. If no one is we go ahead and free the
|
|
* HOOK structure now. If someone is then we simply clear the filter-proc
|
|
* in the HOOK structure. In xxxCallHook2() we check for this and if by
|
|
* that time no one is calling the hook in question we free it there.
|
|
*
|
|
* History:
|
|
* 01-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL _UnhookWindowsHookEx(
|
|
PHOOK phkFree)
|
|
{
|
|
PTHREADINFO pti;
|
|
|
|
pti = GETPTI(phkFree);
|
|
|
|
/*
|
|
* Clear the journaling flags in all the queues.
|
|
*/
|
|
if (abHookFlags[phkFree->iHook + 1] & HKF_JOURNAL) {
|
|
JournalAttach(pti, FALSE);
|
|
/*
|
|
* If someone got stuck because of the hook, let him go
|
|
*
|
|
* I want to get some performance numbers before checking this in.
|
|
* MSTest hooks and unhooks all the time when running a script.
|
|
* This code has never been in. 5/22/96. GerardoB
|
|
*/
|
|
// InterQueueMsgCleanup(3 * CMSWAITTOKILLTIMEOUT);
|
|
}
|
|
|
|
/*
|
|
* If no one is currently calling this hook,
|
|
* go ahead and free it now.
|
|
*/
|
|
FreeHook(phkFree);
|
|
|
|
/*
|
|
* If this thread has no more global hooks that are able to hook
|
|
* server-side window procs, we must clear it's TIF_GLOBALHOOKER bit.
|
|
*/
|
|
if (pti->TIF_flags & TIF_GLOBALHOOKER) {
|
|
int iHook;
|
|
PHOOK phk;
|
|
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
|
|
/*
|
|
* Ignore those that can't hook server-side winprocs
|
|
*/
|
|
if (!(abHookFlags[iHook + 1] & HKF_INTRSND)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Scan the global hooks
|
|
*/
|
|
for (phk = pti->pDeskInfo->asphkStart[iHook + 1];
|
|
phk != NULL; phk = _PhkNext(phk)) {
|
|
if (GETPTI(phk) == pti) {
|
|
goto StillHasGlobalHooks;
|
|
}
|
|
}
|
|
}
|
|
pti->TIF_flags &= ~TIF_GLOBALHOOKER;
|
|
|
|
}
|
|
|
|
StillHasGlobalHooks:
|
|
/*
|
|
* Success, return TRUE.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* _CallMsgFilter (API)
|
|
*
|
|
* CallMsgFilter() allows applications to call the WH_*MSGFILTER hooks.
|
|
* If there's a sysmodal window we return FALSE right away. WH_MSGFILTER
|
|
* isn't called if WH_SYSMSGFILTER returned non-zero.
|
|
*
|
|
* History:
|
|
* 01-29-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL _CallMsgFilter(
|
|
LPMSG pmsg,
|
|
int nCode)
|
|
{
|
|
PTHREADINFO pti;
|
|
|
|
pti = PtiCurrent();
|
|
|
|
/*
|
|
* First call WH_SYSMSGFILTER. If it returns non-zero, don't
|
|
* bother calling WH_MSGFILTER, just return TRUE. Otherwise
|
|
* return what WH_MSGFILTER gives us.
|
|
*/
|
|
if (IsHooked(pti, WHF_SYSMSGFILTER) && xxxCallHook(nCode, 0, (DWORD)pmsg,
|
|
WH_SYSMSGFILTER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (IsHooked(pti, WHF_MSGFILTER)) {
|
|
return (BOOL)xxxCallHook(nCode, 0, (DWORD)pmsg, WH_MSGFILTER);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallHook
|
|
*
|
|
* User code calls this function to call the first hook of a specific
|
|
* type.
|
|
*
|
|
* History:
|
|
* 01-29-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
int xxxCallHook(
|
|
int nCode,
|
|
DWORD wParam,
|
|
DWORD lParam,
|
|
int iHook)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
return xxxCallHook2(PhkFirst(PtiCurrent(), iHook), nCode, wParam, lParam, &bAnsiHook);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallHook2
|
|
*
|
|
* When you have an actual HOOK structure to call, you'd use this function.
|
|
* It will check to see if the hook hasn't already been unhooked, and if
|
|
* is it will free it and keep looking until it finds a hook it can call
|
|
* or hits the end of the list. We also make sure any needed DLLs are loaded
|
|
* here. We also check to see if the HOOK was unhooked inside the call
|
|
* after we return.
|
|
*
|
|
* Note: Hooking server-side window procedures (such as the desktop and console
|
|
* windows) can only be done by sending the hook message to the hooking app.
|
|
* (This is because we must not load the hookproc DLL into the server process).
|
|
* The hook types this can be done with are currently WH_JOURNALRECORD,
|
|
* WH_JOURNALPLAYBACK, WH_KEYBOARD and WH_MOUSE : these are all marked as
|
|
* HKF_INTRSND. In order to prevent a global hooker from locking up the whole
|
|
* system, the hook message is sent with a timeout. To ensure minimal
|
|
* performance degradation, the hooker process is set to foreground priority,
|
|
* and prevented from being set back to background priority with the
|
|
* TIF_GLOBALHOOKER bit in hooking thread's pti->flags.
|
|
* Hooking emulated DOS apps is prevented with the TIF_DOSEMULATOR bit in the
|
|
* console thread: this is because these apps typically hog the CPU so much that
|
|
* the hooking app does not respond rapidly enough to the hook messsages sent
|
|
* to it. IanJa Nov 1994.
|
|
*
|
|
* History:
|
|
* 02-07-91 DavidPe Created.
|
|
* 1994 Nov 02 IanJa Hooking desktop and console windows.
|
|
\***************************************************************************/
|
|
|
|
int xxxCallHook2(
|
|
PHOOK phkCall,
|
|
int nCode,
|
|
DWORD wParam,
|
|
DWORD lParam,
|
|
LPBOOL lpbAnsiHook)
|
|
{
|
|
UINT iHook;
|
|
PHOOK phkSave;
|
|
int nRet;
|
|
PTHREADINFO ptiCurrent;
|
|
BOOL fLoadSuccess;
|
|
TL tlphkCall;
|
|
TL tlphkSave;
|
|
BYTE bHookFlags;
|
|
|
|
if (phkCall == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
iHook = phkCall->iHook;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* If this queue is in cleanup, exit: it has no business calling back
|
|
* a hook proc. Also check if hooks are disabled for the thread.
|
|
*/
|
|
if ( ptiCurrent->TIF_flags & (TIF_INCLEANUP | TIF_DISABLEHOOKS) ||
|
|
ptiCurrent->rpdesk == NULL) {
|
|
return ampiHookError[iHook + 1];
|
|
}
|
|
|
|
/*
|
|
* Now check to see if we really want to call this hook.
|
|
* If not, keep going through the list until we either
|
|
* find an 'good' hook or hit the end of the lists.
|
|
*/
|
|
tryagain:
|
|
while (phkCall != NULL) {
|
|
*lpbAnsiHook = phkCall->flags & HF_ANSI;
|
|
bHookFlags = abHookFlags[phkCall->iHook + 1];
|
|
|
|
/*
|
|
* Some WH_SHELL hook types can be called from console
|
|
*/
|
|
if ((phkCall->iHook == WH_SHELL) && (ptiCurrent->TIF_flags & TIF_CSRSSTHREAD)) {
|
|
if ((nCode == HSHELL_LANGUAGE) || (nCode == HSHELL_WINDOWACTIVATED)) {
|
|
bHookFlags |= HKF_INTRSND;
|
|
}
|
|
}
|
|
|
|
if (!(bHookFlags & HKF_INTRSND)) {
|
|
if (
|
|
/*
|
|
* If this hook was set by a WOW app, and we're not on a
|
|
* 16-bit WOW thread, and this isn't a intersend hook,
|
|
* don't call it.
|
|
*
|
|
* For seperate WOW VDMs we don't let 16-bit hooks go to another
|
|
* VDM or process. (we could do an intersend like journal hooks but
|
|
* that might break some hooking apps that expect to share data)
|
|
*/
|
|
|
|
(GETPTI(phkCall)->TIF_flags & TIF_16BIT &&
|
|
(!(ptiCurrent->TIF_flags & TIF_16BIT) ||
|
|
ptiCurrent->ppi != GETPTI(phkCall)->ppi))
|
|
|
|
/*
|
|
* If this is a global and non-journal hook, do a security
|
|
* check on the current desktop to see if we can call here.
|
|
*/
|
|
||
|
|
(phkCall->flags & HF_GLOBAL &&
|
|
!RtlEqualLuid(&GETPTI(phkCall)->ppi->luidSession, &ptiCurrent->ppi->luidSession) &&
|
|
!(ptiCurrent->TIF_flags & TIF_ALLOWOTHERACCOUNTHOOK))
|
|
|
|
/*
|
|
* Don't allow this hook to be called for console or system threads
|
|
* by another process if it can't be done via an inter-thread call.
|
|
*/
|
|
||
|
|
(ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD) &&
|
|
GETPTI(phkCall)->ppi != ptiCurrent->ppi))
|
|
{
|
|
phkCall = _PhkNext(phkCall);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This hook is fine, go ahead and call it.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Make sure that we did find a hook to call.
|
|
*/
|
|
if (phkCall == NULL) {
|
|
return ampiHookError[iHook + 1];
|
|
}
|
|
|
|
/*
|
|
* We're calling back... make sure the hook doesn't go away while
|
|
* we're calling back. We've thread locked here: we must unlock before
|
|
* returning or enumerating the next hook in the chain.
|
|
*/
|
|
ThreadLockAlwaysWithPti(ptiCurrent, phkCall, &tlphkCall);
|
|
|
|
|
|
/*
|
|
* If the hooker is a 16bit app and we are not in a 16 bit apps context,
|
|
* then run the hook in the destination's context.
|
|
* ((GETPTI(phkCall)->TIF_flags & TIF_16BIT) &&
|
|
* ((ptiCurrent->TIF_flags & TIF_16BIT)==0)))) {
|
|
*/
|
|
|
|
/*
|
|
* If the hooker & hooked threads are not the same we nust make an
|
|
* inter-thread call if:
|
|
* (1) it is a journal hook
|
|
* OR
|
|
* (2) it is an INTRSND hook on a server thread.
|
|
* OR
|
|
* (3) it is the console and we want to update the keyboard layout icon on the tray.
|
|
*
|
|
* (otherwise just call the hookproc in a DLL linked into the hooked app.)
|
|
*/
|
|
if ((GETPTI(phkCall) != ptiCurrent) &&
|
|
((bHookFlags & HKF_JOURNAL) ||
|
|
((bHookFlags & HKF_INTRSND) &&
|
|
(ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD))))) {
|
|
|
|
/*
|
|
* Receiving thread can access this structure since the
|
|
* sender thread's stack is locked down during xxxInterSendMsgEx
|
|
*/
|
|
HOOKMSGSTRUCT hkmp;
|
|
|
|
hkmp.lParam = lParam;
|
|
hkmp.phk = phkCall;
|
|
hkmp.nCode = nCode;
|
|
|
|
/*
|
|
* Thread lock right away in case the lock frees the previous contents
|
|
*/
|
|
phkSave = ptiCurrent->sphkCurrent;
|
|
|
|
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkCall);
|
|
if (ptiCurrent->pClientInfo)
|
|
ptiCurrent->pClientInfo->phkCurrent = phkCall;
|
|
|
|
/*
|
|
* Make sure we don't get hung!
|
|
*/
|
|
if ((bHookFlags & HKF_JOURNAL) ||
|
|
!(ptiCurrent->TIF_flags & (TIF_CSRSSTHREAD | TIF_SYSTEMTHREAD))) {
|
|
nRet = xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
|
|
(DWORD)&hkmp, ptiCurrent, GETPTI(phkCall), NULL);
|
|
} else {
|
|
/*
|
|
* We are a server thread (console/desktop) and we aren't
|
|
* journalling, so we can't allow the hookproc to hang us -
|
|
* we must use a timeout.
|
|
*/
|
|
INTRSENDMSGEX ism;
|
|
ism.fuCall = ISM_TIMEOUT;
|
|
ism.fuSend = SMTO_ABORTIFHUNG | SMTO_NORMAL;
|
|
ism.uTimeout = 200; // 1/5 second!!!
|
|
ism.lpdwResult = &nRet;
|
|
|
|
/*
|
|
* Don't hook DOS apps connected to the emulator - they often
|
|
* grab too much CPU for the callback to the hookproc to
|
|
* complete in a timely fashion, causing poor response.
|
|
*/
|
|
if ((ptiCurrent->TIF_flags & TIF_DOSEMULATOR) ||
|
|
!xxxInterSendMsgEx(NULL, WM_HOOKMSG, wParam,
|
|
(DWORD)&hkmp, ptiCurrent, GETPTI(phkCall), &ism)) {
|
|
nRet = ampiHookError[iHook + 1];
|
|
}
|
|
}
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkSave);
|
|
if (ptiCurrent->pClientInfo)
|
|
ptiCurrent->pClientInfo->phkCurrent = phkSave;
|
|
|
|
ThreadUnlock(&tlphkSave);
|
|
|
|
ThreadUnlock(&tlphkCall);
|
|
return nRet;
|
|
}
|
|
|
|
#if 0 // now redundant
|
|
/*
|
|
* If we're trying to call the hook from a console thread (or server
|
|
* thread), then fail it. This is new functionality: means console apps
|
|
* don't call any hooks except for journal hooks. This is because we
|
|
* can't load the .dll in the server, and we can't talk synchronously
|
|
* with any process that may be hung or really really slow because console
|
|
* windows would hang.
|
|
*
|
|
* Allow the console to call the hook if it set the hook.
|
|
*/
|
|
if (ptiCurrent->hThreadClient == ptiCurrent->hThreadServer &&
|
|
phkCall->head.pti != ptiCurrent) {
|
|
ThreadUnlock(&tlphkCall);
|
|
return ampiHookError[iHook + 1];
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Make sure the DLL for this hook, if any, has been loaded
|
|
* for the current process.
|
|
*/
|
|
if ((phkCall->ihmod != -1) &&
|
|
(TESTHMODLOADED(ptiCurrent, phkCall->ihmod) == 0)) {
|
|
|
|
BOOL bWx86KnownDll;
|
|
|
|
/*
|
|
* Try loading the library, since it isn't loaded in this processes
|
|
* context. First lock this hook so it doesn't go away while we're
|
|
* loading this library.
|
|
*/
|
|
bWx86KnownDll = (phkCall->flags & HF_WX86KNOWNDLL) != 0;
|
|
fLoadSuccess = (xxxLoadHmodIndex(phkCall->ihmod, bWx86KnownDll) != NULL);
|
|
|
|
/*
|
|
* If the LoadLibrary() failed, skip to the next hook and try
|
|
* again.
|
|
*/
|
|
if (!fLoadSuccess) {
|
|
phkCall = _PhkNext(phkCall);
|
|
ThreadUnlock(&tlphkCall);
|
|
goto tryagain;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is WH_DEBUG installed? If we're not already calling it, do so.
|
|
*/
|
|
if (IsHooked(ptiCurrent, WHF_DEBUG) && (phkCall->iHook != WH_DEBUG)) {
|
|
DEBUGHOOKINFO debug;
|
|
|
|
debug.idThread = (DWORD)ptiCurrent->Thread->Cid.UniqueThread;
|
|
debug.idThreadInstaller = 0;
|
|
debug.code = nCode;
|
|
debug.wParam = wParam;
|
|
debug.lParam = lParam;
|
|
|
|
if (xxxCallHook(HC_ACTION, phkCall->iHook, (DWORD)&debug, WH_DEBUG)) {
|
|
|
|
/*
|
|
* If WH_DEBUG returned non-zero, skip this hook and
|
|
* try the next one.
|
|
*/
|
|
phkCall = _PhkNext(phkCall);
|
|
ThreadUnlock(&tlphkCall);
|
|
goto tryagain;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the hook is still around before we
|
|
* try and call it.
|
|
*/
|
|
if (HMIsMarkDestroy(phkCall)) {
|
|
phkCall = _PhkNext(phkCall);
|
|
ThreadUnlock(&tlphkCall);
|
|
goto tryagain;
|
|
}
|
|
|
|
/*
|
|
* Time to call the hook! Lock it first so that it doesn't go away
|
|
* while we're using it. Thread lock right away in case the lock frees
|
|
* the previous contents.
|
|
*/
|
|
phkSave = ptiCurrent->sphkCurrent;
|
|
ThreadLockWithPti(ptiCurrent, phkSave, &tlphkSave);
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkCall);
|
|
if (ptiCurrent->pClientInfo)
|
|
ptiCurrent->pClientInfo->phkCurrent = phkCall;
|
|
|
|
nRet = xxxHkCallHook(phkCall, nCode, wParam, lParam);
|
|
|
|
Lock(&ptiCurrent->sphkCurrent, phkSave);
|
|
if (ptiCurrent->pClientInfo)
|
|
ptiCurrent->pClientInfo->phkCurrent = phkSave;
|
|
|
|
ThreadUnlock(&tlphkSave);
|
|
|
|
/*
|
|
* This hook proc faulted, unhook it and find the next hook.
|
|
*/
|
|
if (phkCall->flags & HF_HOOKFAULTED) {
|
|
PHOOK phkFault = phkCall;
|
|
|
|
phkCall = _PhkNext(phkCall);
|
|
phkFault = ThreadUnlock(&tlphkCall);
|
|
if (phkFault != NULL) {
|
|
FreeHook(phkFault);
|
|
}
|
|
goto tryagain;
|
|
}
|
|
|
|
|
|
/*
|
|
* Lastly, we're done with this hook so it is ok to unlock it (it may
|
|
* get freed here!
|
|
*/
|
|
ThreadUnlock(&tlphkCall);
|
|
|
|
return nRet;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxCallMouseHook
|
|
*
|
|
* This is a helper routine that packages up a MOUSEHOOKSTRUCT and calls
|
|
* the WH_MOUSE hook.
|
|
*
|
|
* History:
|
|
* 02-09-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
BOOL xxxCallMouseHook(
|
|
UINT message,
|
|
PMOUSEHOOKSTRUCT pmhs,
|
|
BOOL fRemove)
|
|
{
|
|
BOOL bAnsiHook;
|
|
|
|
/*
|
|
* Call the mouse hook.
|
|
*/
|
|
if (xxxCallHook2(PhkFirst(PtiCurrent(), WH_MOUSE), fRemove ?
|
|
HC_ACTION : HC_NOREMOVE, (DWORD)message, (DWORD)pmhs, &bAnsiHook)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallJournalRecordHook
|
|
*
|
|
* This is a helper routine that packages up an EVENTMSG and calls
|
|
* the WH_JOURNALRECORD hook.
|
|
*
|
|
* History:
|
|
* 02-28-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
void xxxCallJournalRecordHook(
|
|
PQMSG pqmsg)
|
|
{
|
|
EVENTMSG emsg;
|
|
BOOL bAnsiHook;
|
|
|
|
/*
|
|
* Setup the EVENTMSG structure.
|
|
*/
|
|
emsg.message = pqmsg->msg.message;
|
|
emsg.time = pqmsg->msg.time;
|
|
|
|
if (RevalidateHwnd(pqmsg->msg.hwnd)) {
|
|
emsg.hwnd = pqmsg->msg.hwnd;
|
|
} else {
|
|
emsg.hwnd = NULL;
|
|
}
|
|
|
|
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
|
|
emsg.paramL = (UINT)pqmsg->msg.pt.x;
|
|
emsg.paramH = (UINT)pqmsg->msg.pt.y;
|
|
|
|
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message <= WM_KEYLAST)) {
|
|
|
|
/*
|
|
* Build up a Win 3.1 compatible journal record key
|
|
* Win 3.1 ParamL 00 00 SC VK (SC=scan code VK=virtual key)
|
|
* Also set ParamH 00 00 00 SC to be compatible with our Playback
|
|
*
|
|
* If WM_*CHAR messages ever come this way we would have a problem
|
|
* because we would lose the top byte of the Unicode character. We'd
|
|
* We'd get ParamL 00 00 SC CH (SC=scan code, CH = low byte of WCHAR)
|
|
*
|
|
*/
|
|
emsg.paramL =
|
|
MAKELONG(MAKEWORD(pqmsg->msg.wParam, HIWORD(pqmsg->msg.lParam)),0);
|
|
emsg.paramH = LOBYTE(HIWORD(pqmsg->msg.lParam));
|
|
|
|
UserAssert((emsg.message != WM_CHAR) &&
|
|
(emsg.message != WM_DEADCHAR) &&
|
|
(emsg.message != WM_SYSCHAR) &&
|
|
(emsg.message != WM_SYSDEADCHAR));
|
|
/*
|
|
* Set extended-key bit.
|
|
*/
|
|
if (pqmsg->msg.lParam & 0x01000000) {
|
|
emsg.paramH |= 0x8000;
|
|
}
|
|
|
|
} else {
|
|
RIPMSG2(RIP_WARNING,
|
|
"Bad journal record message!\n"
|
|
" message = 0x%08lx\n"
|
|
" dwQEvent = 0x%08lx",
|
|
pqmsg->msg.message,
|
|
pqmsg->dwQEvent);
|
|
}
|
|
|
|
/*
|
|
* Call the journal recording hook.
|
|
*/
|
|
xxxCallHook2(PhkFirst(PtiCurrent(), WH_JOURNALRECORD), HC_ACTION, 0,
|
|
(DWORD)&emsg, &bAnsiHook);
|
|
|
|
/*
|
|
* Write the MSG parameters back because the app may have modified it.
|
|
* AfterDark's screen saver password actually zero's out the keydown
|
|
* chars.
|
|
*
|
|
* If it was a mouse message patch up the mouse point. If it was a
|
|
* WM_KEYxxx message convert the Win 3.1 compatible journal record key
|
|
* back into a half backed WM_KEYxxx format. Only the VK and SC fields
|
|
* where initialized at this point.
|
|
*
|
|
* wParam 00 00 00 VK lParam 00 SC 00 00
|
|
*/
|
|
if ((pqmsg->msg.message >= WM_MOUSEFIRST) && (pqmsg->msg.message <= WM_MOUSELAST)) {
|
|
pqmsg->msg.pt.x = emsg.paramL;
|
|
pqmsg->msg.pt.y = emsg.paramH;
|
|
|
|
} else if ((pqmsg->msg.message >= WM_KEYFIRST) && (pqmsg->msg.message <= WM_KEYLAST)) {
|
|
(BYTE)pqmsg->msg.wParam = (BYTE)emsg.paramL;
|
|
((PBYTE)&pqmsg->msg.lParam)[2] = HIBYTE(LOWORD(emsg.paramL));
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCallJournalPlaybackHook
|
|
*
|
|
*
|
|
* History:
|
|
* 03-01-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
DWORD xxxCallJournalPlaybackHook(
|
|
PQMSG pqmsg)
|
|
{
|
|
EVENTMSG emsg;
|
|
LONG dt;
|
|
PWND pwnd;
|
|
DWORD wParam;
|
|
LONG lParam;
|
|
POINT pt;
|
|
PTHREADINFO pti;
|
|
BOOL bAnsiHook;
|
|
PHOOK phkCall;
|
|
TL tlphkCall;
|
|
|
|
TryNextEvent:
|
|
|
|
/*
|
|
* Initialized to the current time for compatibility with
|
|
* <= 3.0.
|
|
*/
|
|
emsg.time = NtGetTickCount();
|
|
pti = PtiCurrent();
|
|
pwnd = NULL;
|
|
|
|
phkCall = PhkFirst(pti, WH_JOURNALPLAYBACK);
|
|
ThreadLockWithPti(pti, phkCall, &tlphkCall);
|
|
|
|
dt = (DWORD)xxxCallHook2(phkCall, HC_GETNEXT, 0, (DWORD)&emsg, &bAnsiHook);
|
|
|
|
/*
|
|
* -1 means some error occured. Return -1 for error.
|
|
*/
|
|
if (dt == 0xFFFFFFFF) {
|
|
ThreadUnlock(&tlphkCall);
|
|
return dt;
|
|
}
|
|
|
|
/*
|
|
* Update the message id. Need this if we decide to sleep.
|
|
*/
|
|
pqmsg->msg.message = emsg.message;
|
|
|
|
if (dt > 0) {
|
|
if (pti->TIF_flags & TIF_IGNOREPLAYBACKDELAY) {
|
|
/*
|
|
* This flag tells us to ignore the requested delay (set in mnloop)
|
|
* We clear it to indicate that we did so.
|
|
*/
|
|
RIPMSG1(RIP_WARNING, "Journal Playback delay ignored (%lx)", emsg.message);
|
|
pti->TIF_flags &= ~TIF_IGNOREPLAYBACKDELAY;
|
|
dt = 0;
|
|
} else {
|
|
ThreadUnlock(&tlphkCall);
|
|
return dt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The app is ready to be asked for the next event
|
|
*/
|
|
|
|
if ((emsg.message >= WM_MOUSEFIRST) && (emsg.message <= WM_MOUSELAST)) {
|
|
|
|
pt.x = (int)emsg.paramL;
|
|
pt.y = (int)emsg.paramH;
|
|
|
|
lParam = MAKELONG(LOWORD(pt.x), LOWORD(pt.y));
|
|
wParam = 0;
|
|
|
|
/*
|
|
* If the message has changed the mouse position,
|
|
* update the cursor.
|
|
*/
|
|
if ((pt.x != ptCursor.x) || (pt.y != ptCursor.y)) {
|
|
InternalSetCursorPos(pt.x, pt.y, grpdeskRitInput);
|
|
}
|
|
|
|
} else if ((emsg.message >= WM_KEYFIRST) && (emsg.message <= WM_KEYLAST)) {
|
|
UINT wExtraStuff = 0;
|
|
|
|
if ((emsg.message == WM_KEYUP) || (emsg.message == WM_SYSKEYUP)) {
|
|
wExtraStuff |= 0x8000;
|
|
}
|
|
|
|
if ((emsg.message == WM_SYSKEYUP) || (emsg.message == WM_SYSKEYDOWN)) {
|
|
wExtraStuff |= 0x2000;
|
|
}
|
|
|
|
if (emsg.paramH & 0x8000) {
|
|
wExtraStuff |= 0x0100;
|
|
}
|
|
|
|
if (TestKeyStateDown(pti->pq, (BYTE)emsg.paramL)) {
|
|
wExtraStuff |= 0x4000;
|
|
}
|
|
|
|
/*
|
|
* There are old ANSI apps that only fill in the byte for when
|
|
* they generate journal playback so we used to strip everything
|
|
* else off. That however breaks unicode journalling; 22645
|
|
* (Yes, some apps apparently do Playback WM_*CHAR msgs!)
|
|
*
|
|
#ifdef FE_SB // xxxCallJournalPlaybackHook()
|
|
*
|
|
* LATER : DBCS handling is needed for WM_CHAR...
|
|
*
|
|
#endif // FE_SB
|
|
*
|
|
*/
|
|
if (bAnsiHook) {
|
|
wParam = emsg.paramL & 0xff;
|
|
} else {
|
|
wParam = emsg.paramL & 0xffff;
|
|
}
|
|
lParam = MAKELONG(1, (UINT)((emsg.paramH & 0xFF) | wExtraStuff));
|
|
|
|
} else if (emsg.message == WM_QUEUESYNC) {
|
|
if (emsg.paramL == 0) {
|
|
pwnd = pti->pq->spwndActive;
|
|
} else {
|
|
if ((pwnd = RevalidateHwnd((HWND)emsg.paramL)) == NULL)
|
|
pwnd = pti->pq->spwndActive;
|
|
}
|
|
|
|
} else {
|
|
/*
|
|
* This event doesn't match up with what we're looking
|
|
* for. If the hook is still valid, then skip this message
|
|
* and try the next.
|
|
*/
|
|
if (phkCall == NULL || phkCall->offPfn == 0L) {
|
|
/* Hook is nolonger valid, return -1 */
|
|
ThreadUnlock(&tlphkCall);
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
RIPMSG1(RIP_WARNING,
|
|
"Bad journal playback message=0x%08lx",
|
|
emsg.message);
|
|
|
|
xxxCallHook(HC_SKIP, 0, 0, WH_JOURNALPLAYBACK);
|
|
ThreadUnlock(&tlphkCall);
|
|
goto TryNextEvent;
|
|
}
|
|
|
|
StoreQMessage(pqmsg, pwnd, emsg.message, wParam, lParam, 0, 0);
|
|
|
|
ThreadUnlock(&tlphkCall);
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FreeHook
|
|
*
|
|
* Free hook unlinks the HOOK structure from its hook-list and removes
|
|
* any hmod dependencies on this hook. It also frees the HOOK structure.
|
|
*
|
|
* History:
|
|
* 01-31-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID FreeHook(
|
|
PHOOK phkFree)
|
|
{
|
|
/*
|
|
* Unlink it first.
|
|
*/
|
|
UnlinkHook(phkFree);
|
|
|
|
/*
|
|
* Mark it for destruction. If it the object is locked it can't
|
|
* be freed right now.
|
|
*/
|
|
if (!HMMarkObjectDestroy((PVOID)phkFree))
|
|
return;
|
|
|
|
/*
|
|
* Now remove the hmod dependency and free the
|
|
* HOOK structure.
|
|
*/
|
|
if (phkFree->ihmod >= 0) {
|
|
RemoveHmodDependency(phkFree->ihmod);
|
|
}
|
|
|
|
#ifdef HOOKBATCH
|
|
/*
|
|
* Free the cached Events
|
|
*/
|
|
if (phkFree->aEventCache) {
|
|
UserFreePool(phkFree->aEventCache);
|
|
phkFree->aEventCache = NULL;
|
|
}
|
|
#endif //HOOKBATCH
|
|
|
|
/*
|
|
* We're really going to free the hook. NULL out the next hook
|
|
* pointer now. We keep it filled even if the hook has been unlinked
|
|
* so that if a hook is being called back and the app frees the same
|
|
* hook with UnhookWindowsHook(), a call to CallNextHookProc() will
|
|
* call the next hook.
|
|
*/
|
|
Unlock(&phkFree->sphkNext);
|
|
|
|
HMFreeObject((PVOID)phkFree);
|
|
return;
|
|
}
|
|
|
|
void UnlinkHook(
|
|
PHOOK phkFree)
|
|
{
|
|
PHOOK *pphkStart, phk, phkPrev;
|
|
PTHREADINFO ptiT;
|
|
|
|
/*
|
|
* Since we have the HOOK structure, we can tell if this a global
|
|
* or local hook and start on the right list.
|
|
*/
|
|
if (phkFree->flags & HF_GLOBAL) {
|
|
pphkStart = &GETPTI(phkFree)->pDeskInfo->asphkStart[phkFree->iHook + 1];
|
|
} else {
|
|
ptiT = phkFree->ptiHooked;
|
|
if (ptiT == NULL) {
|
|
return;
|
|
} else if (ptiT == PtiCurrent()) {
|
|
/*
|
|
* If we get here during ptiHooked thread cleanup, decouple
|
|
* ptiHooked field now so that if phkFree is locked by another
|
|
* thread, we won't AV on bogus ptiHooked when phkFree is finally
|
|
* unlocked, freed and unlinked.
|
|
*/
|
|
phkFree->ptiHooked = NULL;
|
|
}
|
|
pphkStart = &(ptiT->asphkStart[phkFree->iHook + 1]);
|
|
}
|
|
|
|
for (phk = *pphkStart; phk != NULL; phk = phk->sphkNext) {
|
|
|
|
/*
|
|
* We've found it. Free the hook. Don't change phkNext during
|
|
* unhooking. This is so that if an app calls UnhookWindowsHook()
|
|
* during a hook callback, and then calls CallNextHookProc(), it
|
|
* will still really call the next hook proc.
|
|
*/
|
|
if (phk == phkFree) {
|
|
TL tlphk;
|
|
|
|
ThreadLock(phk, &tlphk);
|
|
|
|
/*
|
|
* First unlink it from its hook-list.
|
|
*/
|
|
if (phk == *pphkStart) {
|
|
Lock(pphkStart, phk->sphkNext);
|
|
} else {
|
|
Lock(&phkPrev->sphkNext, phk->sphkNext);
|
|
}
|
|
|
|
CheckWHFBits(GETPTI(phk), phk->iHook);
|
|
|
|
ThreadUnlock(&tlphk);
|
|
|
|
return;
|
|
}
|
|
|
|
phkPrev = phk;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* PhkFirst
|
|
*
|
|
* Given a filter-type PhkFirst() returns the first hook, if any, of the
|
|
* specified type.
|
|
*
|
|
* History:
|
|
* 02-10-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
PHOOK PhkFirst(
|
|
PTHREADINFO pti,
|
|
int nFilterType)
|
|
{
|
|
PHOOK phk;
|
|
|
|
/*
|
|
* If we're on the RIT don't call any hooks!
|
|
*/
|
|
if (pti == gptiRit) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Grab the first hooks off the local hook-list
|
|
* for the current queue.
|
|
*/
|
|
phk = pti->asphkStart[nFilterType + 1];
|
|
|
|
/*
|
|
* If there aren't any local hooks, try the global hooks.
|
|
*/
|
|
if (phk == NULL) {
|
|
return pti->pDeskInfo->asphkStart[nFilterType + 1];
|
|
}
|
|
|
|
UserAssert(phk != NULL);
|
|
return phk;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* PhkNext
|
|
*
|
|
* This helper routine simply does phk = phk->sphkNext with a simple check
|
|
* to jump from local hooks to the global hooks if it hits the end of the
|
|
* local hook chain.
|
|
*
|
|
* History:
|
|
* 01-30-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
PHOOK _PhkNext(
|
|
PHOOK phk)
|
|
{
|
|
/*
|
|
* Return the next HOOK structure. If we reach the end of this list,
|
|
* check to see if we're still on the 'local' hook list. If so skip
|
|
* over to the global hooks.
|
|
*/
|
|
if (phk->sphkNext != NULL) {
|
|
return phk->sphkNext;
|
|
} else if ((phk->flags & HF_GLOBAL) == 0) {
|
|
return PtiCurrent()->pDeskInfo->asphkStart[phk->iHook + 1];
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* FreeThreadsWindowHooks
|
|
*
|
|
* During 'exit-list' processing this function is called to free any hooks
|
|
* created on, or set for the current queue.
|
|
*
|
|
* History:
|
|
* 02-10-91 DavidPe Created.
|
|
\***************************************************************************/
|
|
|
|
VOID FreeThreadsWindowHooks(VOID)
|
|
{
|
|
int iHook;
|
|
PHOOK phk, phkNext;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
/*
|
|
* If there is not thread info, there are not hooks to worry about
|
|
*/
|
|
if (ptiCurrent == NULL || ptiCurrent->rpdesk == NULL)
|
|
return;
|
|
|
|
/*
|
|
* Loop through all the hook types.
|
|
*/
|
|
for (iHook = WH_MIN ; iHook <= WH_MAX ; ++iHook) {
|
|
/*
|
|
* Loop through all the hooks of this type.
|
|
*/
|
|
phk = PhkFirst(ptiCurrent, iHook);
|
|
while (phk != NULL) {
|
|
|
|
/*
|
|
* Pick up the next hook pointer before we do any
|
|
* freeing so things don't get confused.
|
|
*/
|
|
phkNext = _PhkNext(phk);
|
|
|
|
/*
|
|
* If this hook wasn't created on this thread,
|
|
* then we don't want to free it.
|
|
*/
|
|
if (GETPTI(phk) == ptiCurrent) {
|
|
FreeHook(phk);
|
|
} else {
|
|
/*
|
|
* At least unlink it so we don't end up destroying a pti
|
|
* that references it, making us unable to destroy the hook
|
|
* later. This loop loops through global hooks too, and
|
|
* we don't want to unlink global hooks since they don't
|
|
* belong to this thread.
|
|
*/
|
|
if (!(phk->flags & HF_GLOBAL)) {
|
|
UnlinkHook(phk);
|
|
}
|
|
}
|
|
|
|
phk = phkNext;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* And in case we have a hook locked in as the current hook unlock it
|
|
* so it can be freed
|
|
*/
|
|
Unlock(&ptiCurrent->sphkCurrent);
|
|
if (ptiCurrent->pClientInfo)
|
|
ptiCurrent->pClientInfo->phkCurrent = NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RegisterSystemThread: Private API
|
|
*
|
|
* Used to set various attributes pertaining to a thread.
|
|
*
|
|
* History:
|
|
* 21-Jun-1994 from Chicago Created.
|
|
\***************************************************************************/
|
|
|
|
VOID _RegisterSystemThread (DWORD dwFlags, DWORD dwReserved)
|
|
{
|
|
PTHREADINFO ptiCurrent;
|
|
|
|
UserAssert(dwReserved == 0);
|
|
|
|
if (dwReserved != 0)
|
|
return;
|
|
|
|
ptiCurrent = PtiCurrent();
|
|
|
|
if (dwFlags & RST_DONTATTACHQUEUE)
|
|
ptiCurrent->TIF_flags |= TIF_DONTATTACHQUEUE;
|
|
|
|
if (dwFlags & RST_DONTJOURNALATTACH) {
|
|
ptiCurrent->TIF_flags |= TIF_DONTJOURNALATTACH;
|
|
|
|
/*
|
|
* If we are already journaling, then this queue was already
|
|
* journal attached. We need to unattach and reattach journaling
|
|
* so that we are removed from the journal attached queues.
|
|
*/
|
|
if (FJOURNALPLAYBACK() || FJOURNALRECORD()) {
|
|
JournalAttach(ptiCurrent, FALSE);
|
|
JournalAttach(ptiCurrent, TRUE);
|
|
}
|
|
}
|
|
}
|