2931 lines
80 KiB
C
2931 lines
80 KiB
C
/*++
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
Notify.c
|
|
|
|
Abstract:
|
|
The Notify package provides support to filesystems which implement
|
|
NotifyChangeDirectory. This package will manage a queue of notify
|
|
blocks which are attached to some filesystem structure (i.e. Vcb
|
|
in Fat, HPFS). The filesystems will allocate a fast mutex to be used
|
|
by this package to synchronize access to the notify queue.
|
|
|
|
The following routines are provided by this package:
|
|
|
|
o FsRtlNotifyInitializeSync - Create and initializes the synchronization object.
|
|
o FsrtlNotifyUninitializeSync - Deallocates the synchronization object.
|
|
o FsRtlNotifyChangeDirectory - This routine is called whenever the
|
|
filesystems receive a NotifyChangeDirectoryFile call. This
|
|
routine allocates any neccessary structures and places the
|
|
Irp in the NotifyQueue (or possibly completes or cancels it immediately).
|
|
o FsRtlNotifyFullChangeDirectory - This routine is called whenever the
|
|
filesystems receive a NotifyChangeDirectoryFile call. This differs
|
|
from the FsRtlNotifyChangeDirectory in that it expects to return the notify information in the user's buffer.
|
|
o FsRtlNotifyReportChange - This routine is called by the filesystems whenever they perform some operation that could
|
|
cause the completion of a notify operation. This routine will
|
|
walk through the notify queue to see if any Irps are affected by the indicated operation.
|
|
o FsRtlNotifyFullReportChange - This routine is called by the filesystems whenever they perform some operation that could
|
|
cause the completion of a notify operation. This routine differs
|
|
from the FsRtlNotifyReportChange call in that it returns more
|
|
detailed information in the caller's buffer if present.
|
|
o FsRtlNotifyCleanup - This routine is called to remove any
|
|
references to a particular FsContext structure from the notify
|
|
queue. If the matching FsContext structure is found in the queue, then all associated Irps are completed.
|
|
|
|
Author:
|
|
Brian Andrew [BrianAn] 9-19-1991
|
|
--*/
|
|
|
|
#include "FsRtlP.h"
|
|
|
|
|
|
// Trace level for the module
|
|
|
|
|
|
#define Dbg (0x04000000)
|
|
|
|
|
|
// This is the synchronization object for the notify package. The caller
|
|
// given a pointer to this structure.
|
|
|
|
|
|
typedef struct _REAL_NOTIFY_SYNC {
|
|
FAST_MUTEX FastMutex;
|
|
ERESOURCE_THREAD OwningThread;
|
|
ULONG OwnerCount;
|
|
} REAL_NOTIFY_SYNC, *PREAL_NOTIFY_SYNC;
|
|
|
|
|
|
// A list of the following structures is used to store the NotifyChange
|
|
// requests. They are linked to a filesystem-defined list head.
|
|
|
|
|
|
typedef struct _NOTIFY_CHANGE {
|
|
|
|
|
|
// Fast Mutex. This fast mutex is used to access the list containing this
|
|
// structure.
|
|
|
|
|
|
PREAL_NOTIFY_SYNC NotifySync;
|
|
|
|
|
|
// FsContext. This value is given by the filesystems to uniquely
|
|
// identify this structure. The identification is on a
|
|
// per-user file object basis. The expected value is the Ccb address
|
|
// for this user file object.
|
|
|
|
|
|
PVOID FsContext;
|
|
|
|
|
|
// StreamID. This value matches the FsContext field in the file object for
|
|
// the directory being watched. This is used to identify the directory stream
|
|
// when the directory is being deleted.
|
|
|
|
|
|
PVOID StreamID;
|
|
|
|
|
|
// TraverseAccessCallback. This is the filesystem-supplied routine used
|
|
// to call back into the filesystem to check whether the caller has traverse
|
|
// access when watching a sub-directory. Only applies when watching a
|
|
// sub-directory.
|
|
|
|
|
|
PCHECK_FOR_TRAVERSE_ACCESS TraverseCallback;
|
|
|
|
|
|
// SubjectContext. If the caller specifies a traverse callback routine
|
|
// we will need to pass the Security Context from the thread which
|
|
// originated this call. The notify package will free this structure
|
|
// on tearing down the notify package. We don't expect to need this
|
|
// structure often.
|
|
|
|
|
|
PSECURITY_SUBJECT_CONTEXT SubjectContext;
|
|
|
|
|
|
// Full Directory Name. The following string is the full directory
|
|
// name of the directory being watched. It is used during watch tree
|
|
// operations to check whether this directory is an ancestor of
|
|
// the modified file. The string could be in ANSI or UNICODE form.
|
|
|
|
|
|
PSTRING FullDirectoryName;
|
|
|
|
|
|
// Notify List. The following field links the notify structures for
|
|
// a particular volume.
|
|
|
|
|
|
LIST_ENTRY NotifyList;
|
|
|
|
|
|
// Notify Irps. The following field links the Irps associated with
|
|
|
|
|
|
|
|
LIST_ENTRY NotifyIrps;
|
|
|
|
|
|
// Flags. State of the notify for this volume.
|
|
|
|
|
|
USHORT Flags;
|
|
|
|
|
|
// Character size. Larger size indicates unicode characters.
|
|
// unicode names.
|
|
|
|
|
|
UCHAR CharacterSize;
|
|
|
|
|
|
// Completion Filter. This field is used to mask the modification
|
|
// actions to determine whether to complete the notify irp.
|
|
|
|
|
|
ULONG CompletionFilter;
|
|
|
|
|
|
// The following values are used to manage a buffer if there is no current
|
|
// Irp to complete. The fields have the following meaning:
|
|
|
|
// AllocatedBuffer - Buffer we need to allocate
|
|
// Buffer - Buffer to store data in
|
|
// BufferLength - Length of original user buffer
|
|
// ThisBufferLength - Length of the buffer we are using
|
|
// DataLength - Current length of the data in the buffer
|
|
// LastEntry - Offset of previous entry in the buffer
|
|
|
|
|
|
PVOID AllocatedBuffer;
|
|
PVOID Buffer;
|
|
ULONG BufferLength;
|
|
ULONG ThisBufferLength;
|
|
ULONG DataLength;
|
|
ULONG LastEntry;
|
|
|
|
|
|
// Reference count which keeps the notify structure around. Such references include
|
|
|
|
// - Lifetime reference. Count set to one initially and removed on cleanup
|
|
// - Cancel reference. Reference the notify struct when storing the cancel routine
|
|
// in the Irp. The routine which actually clears the routine will decrement
|
|
// this value.
|
|
|
|
|
|
ULONG ReferenceCount;
|
|
|
|
|
|
// This is the process on whose behalf the structure was allocated. We
|
|
// charge any quota to this process.
|
|
|
|
|
|
PEPROCESS OwningProcess;
|
|
|
|
} NOTIFY_CHANGE, *PNOTIFY_CHANGE;
|
|
|
|
#define NOTIFY_WATCH_TREE (0x0001)
|
|
#define NOTIFY_IMMEDIATE_NOTIFY (0x0002)
|
|
#define NOTIFY_CLEANUP_CALLED (0x0004)
|
|
#define NOTIFY_DEFER_NOTIFY (0x0008)
|
|
#define NOTIFY_DIR_IS_ROOT (0x0010)
|
|
#define NOTIFY_STREAM_IS_DELETED (0x0020)
|
|
|
|
|
|
// CAST
|
|
// Add2Ptr (
|
|
// IN PVOID Pointer,
|
|
// IN ULONG Increment
|
|
// IN (CAST)
|
|
// );
|
|
|
|
// ULONG
|
|
// PtrOffset (
|
|
// IN PVOID BasePtr,
|
|
// IN PVOID OffsetPtr
|
|
// );
|
|
|
|
|
|
#define Add2Ptr(PTR,INC,CAST) ((CAST)((PUCHAR)(PTR) + (INC)))
|
|
|
|
#define PtrOffset(BASE,OFFSET) ((ULONG)((PCHAR)(OFFSET) - (PCHAR)(BASE)))
|
|
|
|
|
|
// VOID
|
|
// SetFlag (
|
|
// IN ULONG Flags,
|
|
// IN ULONG SingleFlag
|
|
// );
|
|
|
|
// VOID
|
|
// ClearFlag (
|
|
// IN ULONG Flags,
|
|
// IN ULONG SingleFlag
|
|
// );
|
|
|
|
|
|
#define SetFlag(F,SF) { \
|
|
(F) |= (SF); \
|
|
}
|
|
|
|
#define ClearFlag(F,SF) { \
|
|
(F) &= ~(SF); \
|
|
}
|
|
|
|
|
|
// VOID
|
|
// AcquireNotifySync (
|
|
// IN PREAL_NOTIFY_SYNC NotifySync
|
|
// );
|
|
|
|
// VOID
|
|
// ReleaseNotifySync (
|
|
// IN PREAL_NOTIFY_SYNC NotifySync
|
|
// );
|
|
|
|
|
|
#define AcquireNotifySync(NS) { \
|
|
ERESOURCE_THREAD _CurrentThread; \
|
|
_CurrentThread = (ERESOURCE_THREAD) PsGetCurrentThread(); \
|
|
if (_CurrentThread != ((PREAL_NOTIFY_SYNC) (NS))->OwningThread) { \
|
|
ExAcquireFastMutexUnsafe( &((PREAL_NOTIFY_SYNC) (NS))->FastMutex ); \
|
|
((PREAL_NOTIFY_SYNC) (NS))->OwningThread = _CurrentThread; \
|
|
} \
|
|
((PREAL_NOTIFY_SYNC) (NS))->OwnerCount += 1; \
|
|
}
|
|
|
|
#define ReleaseNotifySync(NS) { \
|
|
((PREAL_NOTIFY_SYNC) (NS))->OwnerCount -= 1; \
|
|
if (((PREAL_NOTIFY_SYNC) (NS))->OwnerCount == 0) { \
|
|
((PREAL_NOTIFY_SYNC) (NS))->OwningThread = (ERESOURCE_THREAD) 0; \
|
|
ExReleaseFastMutexUnsafe(&((PREAL_NOTIFY_SYNC) (NS))->FastMutex); \
|
|
} \
|
|
}
|
|
|
|
|
|
// Define a tag for general pool allocations from this module
|
|
|
|
|
|
#undef MODULE_POOL_TAG
|
|
#define MODULE_POOL_TAG ('NrSF')
|
|
|
|
|
|
|
|
// Local support routines
|
|
|
|
|
|
PNOTIFY_CHANGE
|
|
FsRtlIsNotifyOnList(
|
|
IN PLIST_ENTRY NotifyListHead,
|
|
IN PVOID FsContext
|
|
);
|
|
|
|
VOID
|
|
FsRtlNotifyCompleteIrp(
|
|
IN PIRP NotifyIrp,
|
|
IN PNOTIFY_CHANGE Notify,
|
|
IN ULONG DataLength,
|
|
IN NTSTATUS Status,
|
|
IN ULONG CheckCancel
|
|
);
|
|
|
|
BOOLEAN
|
|
FsRtlNotifySetCancelRoutine(
|
|
IN PIRP NotifyIrp,
|
|
IN PNOTIFY_CHANGE Notify OPTIONAL
|
|
);
|
|
|
|
BOOLEAN
|
|
FsRtlNotifyUpdateBuffer(
|
|
IN PFILE_NOTIFY_INFORMATION NotifyInfo,
|
|
IN ULONG FileAction,
|
|
IN PSTRING ParentName,
|
|
IN PSTRING TargetName,
|
|
IN PSTRING StreamName OPTIONAL,
|
|
IN BOOLEAN UnicodeName,
|
|
IN ULONG SizeOfEntry
|
|
);
|
|
|
|
VOID
|
|
FsRtlNotifyCompleteIrpList(
|
|
IN PNOTIFY_CHANGE Notify,
|
|
IN NTSTATUS Status
|
|
);
|
|
|
|
VOID
|
|
FsRtlCancelNotify(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP ThisIrp
|
|
);
|
|
|
|
VOID
|
|
FsRtlCheckNotifyForDelete(
|
|
IN PLIST_ENTRY NotifyListHead,
|
|
IN PVOID FsContext
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, FsRtlNotifyInitializeSync)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyUninitializeSync)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyFullChangeDirectory)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyFullReportChange)
|
|
#pragma alloc_text(PAGE, FsRtlIsNotifyOnList)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyChangeDirectory)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyCleanup)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyCompleteIrp)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyReportChange)
|
|
#pragma alloc_text(PAGE, FsRtlNotifyUpdateBuffer)
|
|
#pragma alloc_text(PAGE, FsRtlCheckNotifyForDelete)
|
|
#endif
|
|
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FsRtlNotifyInitializeSync(
|
|
IN PNOTIFY_SYNC *NotifySync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to allocate and initialize the synchronization object
|
|
for this notify list.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the address to store the structure we allocate.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PREAL_NOTIFY_SYNC RealSync;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyInitializeSync: Entered\n", 0);
|
|
|
|
|
|
// Clear the pointer and then attempt to allocate a non-paged
|
|
// structure.
|
|
|
|
|
|
*NotifySync = NULL;
|
|
|
|
RealSync = (PREAL_NOTIFY_SYNC)FsRtlpAllocatePool(NonPagedPool,
|
|
sizeof(REAL_NOTIFY_SYNC));
|
|
|
|
|
|
// Initialize the structure.
|
|
|
|
|
|
ExInitializeFastMutex(&RealSync->FastMutex);
|
|
RealSync->OwningThread = (ERESOURCE_THREAD)0;
|
|
RealSync->OwnerCount = 0;
|
|
|
|
*NotifySync = (PNOTIFY_SYNC)RealSync;
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyInitializeSync: Exit\n", 0);
|
|
return;
|
|
}
|
|
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FsRtlNotifyUninitializeSync(
|
|
IN PNOTIFY_SYNC *NotifySync
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to uninitialize the synchronization object
|
|
for this notify list.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the address containing the pointer to our synchronization
|
|
object.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyUninitializeSync: Entered\n", 0);
|
|
|
|
|
|
// Free the structure if present and clear the pointer.
|
|
|
|
|
|
if (*NotifySync != NULL) {
|
|
|
|
ExFreePool(*NotifySync);
|
|
*NotifySync = NULL;
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyUninitializeSync: Exit\n", 0);
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyChangeDirectory(
|
|
IN PNOTIFY_SYNC NotifySync,
|
|
IN PVOID FsContext,
|
|
IN PSTRING FullDirectoryName,
|
|
IN PLIST_ENTRY NotifyList,
|
|
IN BOOLEAN WatchTree,
|
|
IN ULONG CompletionFilter,
|
|
IN PIRP NotifyIrp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a file system which has received a NotifyChange
|
|
request. This routine checks if there is already a notify structure and
|
|
inserts one if not present. With a notify structure in hand, we check
|
|
whether we already have a pending notify and report it if so. If there
|
|
is no pending notify, we check if this Irp has already been cancelled and
|
|
completes it if so. Otherwise we add this to the list of Irps waiting
|
|
for notification.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the controlling fast mutex for this notify list.
|
|
It is stored here so that it can be found for an Irp which is being
|
|
cancelled.
|
|
|
|
FsContext - This is supplied by the file system so that this notify
|
|
structure can be uniquely identified.
|
|
|
|
FullDirectoryName - Points to the full name for the directory associated
|
|
with this notify structure.
|
|
|
|
NotifyList - This is the start of the notify list to add this
|
|
structure to.
|
|
|
|
WatchTree - This indicates whether all subdirectories for this directory
|
|
should be watched, or just the directory itself.
|
|
|
|
CompletionFilter - This provides the mask to determine which operations
|
|
will trigger the notify operations.
|
|
|
|
NotifyIrp - This is the Irp to complete on notify change.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyChangeDirectory: Entered\n", 0);
|
|
|
|
|
|
// We will simply call the full notify routine to do the real work.
|
|
|
|
|
|
FsRtlNotifyFullChangeDirectory(NotifySync,
|
|
NotifyList,
|
|
FsContext,
|
|
FullDirectoryName,
|
|
WatchTree,
|
|
TRUE,
|
|
CompletionFilter,
|
|
NotifyIrp,
|
|
NULL,
|
|
NULL);
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyChangeDirectory: Exit\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyFullChangeDirectory(
|
|
IN PNOTIFY_SYNC NotifySync,
|
|
IN PLIST_ENTRY NotifyList,
|
|
IN PVOID FsContext,
|
|
IN PSTRING FullDirectoryName,
|
|
IN BOOLEAN WatchTree,
|
|
IN BOOLEAN IgnoreBuffer,
|
|
IN ULONG CompletionFilter,
|
|
IN PIRP NotifyIrp,
|
|
IN PCHECK_FOR_TRAVERSE_ACCESS TraverseCallback OPTIONAL,
|
|
IN PSECURITY_SUBJECT_CONTEXT SubjectContext OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a file system which has received a NotifyChange
|
|
request. This routine checks if there is already a notify structure and
|
|
inserts one if not present. With a notify structure in hand, we check
|
|
whether we already have a pending notify and report it if so. If there
|
|
is no pending notify, we check if this Irp has already been cancelled and
|
|
completes it if so. Otherwise we add this to the list of Irps waiting
|
|
for notification.
|
|
|
|
This is the version of this routine which understands about the user's
|
|
buffer and will fill it in on a reported change.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the controlling fast mutex for this notify list.
|
|
It is stored here so that it can be found for an Irp which is being
|
|
cancelled.
|
|
|
|
NotifyList - This is the start of the notify list to add this
|
|
structure to.
|
|
|
|
FsContext - This is supplied by the file system so that this notify
|
|
structure can be uniquely identified. If the NotifyIrp is not specified
|
|
then this is used to identify the stream and it will match the FsContext
|
|
field in the file object of a stream being deleted.
|
|
|
|
FullDirectoryName - Points to the full name for the directory associated
|
|
with this notify structure. Ignored if the NotifyIrp is not specified.
|
|
|
|
WatchTree - This indicates whether all subdirectories for this directory
|
|
should be watched, or just the directory itself. Ignored if the
|
|
NotifyIrp is not specified.
|
|
|
|
IgnoreBuffer - Indicates whether we will always ignore any user buffer
|
|
and force the directory to be reenumerated. This will speed up the
|
|
operation. Ignored if the NotifyIrp is not specified.
|
|
|
|
CompletionFilter - This provides the mask to determine which operations
|
|
will trigger the notify operations. Ignored if the NotifyIrp is not
|
|
specified.
|
|
|
|
NotifyIrp - This is the Irp to complete on notify change. If this irp is
|
|
not specified it means that the stream represented by this file object
|
|
is being deleted.
|
|
|
|
TraverseCallback - If specified we must call this routine when a change
|
|
has occurred in a subdirectory being watched in a tree. This will
|
|
let the filesystem check if the watcher has traverse access to that
|
|
directory. Ignored if the NotifyIrp is not specified.
|
|
|
|
SubjectContext - If there is a traverse callback routine then we will
|
|
pass this subject context as a parameter to the call. We will release
|
|
the context and free the structure when done with it. Ignored if the
|
|
NotifyIrp is not specified.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNOTIFY_CHANGE Notify;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyFullChangeDirectory: Entered\n", 0);
|
|
|
|
|
|
// Acquire exclusive access to the list by acquiring the mutex.
|
|
|
|
|
|
AcquireNotifySync(NotifySync);
|
|
|
|
|
|
// Use a try-finally to facilitate cleanup.
|
|
|
|
|
|
try {
|
|
|
|
|
|
// If there is no Irp then find all of the pending Irps whose file objects
|
|
// refer to the same stream and complete them with STATUS_DELETE_PENDING.
|
|
|
|
|
|
if (NotifyIrp == NULL) {
|
|
|
|
FsRtlCheckNotifyForDelete(NotifyList, FsContext);
|
|
try_return(NOTHING);
|
|
}
|
|
|
|
|
|
// Get the current Stack location
|
|
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(NotifyIrp);
|
|
|
|
|
|
// Clear the Iosb in the Irp.
|
|
|
|
|
|
NotifyIrp->IoStatus.Status = STATUS_SUCCESS;
|
|
NotifyIrp->IoStatus.Information = 0;
|
|
|
|
|
|
// If the file object has already gone through cleanup, then complete
|
|
// the request immediately.
|
|
|
|
|
|
if (FlagOn(IrpSp->FileObject->Flags, FO_CLEANUP_COMPLETE)) {
|
|
|
|
|
|
// Always mark this Irp as pending returned.
|
|
|
|
|
|
IoMarkIrpPending(NotifyIrp);
|
|
|
|
FsRtlCompleteRequest(NotifyIrp, STATUS_NOTIFY_CLEANUP);
|
|
try_return(NOTHING);
|
|
}
|
|
|
|
|
|
// If the notify structure is not already in the list, add it
|
|
// now.
|
|
|
|
|
|
Notify = FsRtlIsNotifyOnList(NotifyList, FsContext);
|
|
|
|
if (Notify == NULL) {
|
|
|
|
|
|
// Allocate and initialize the structure.
|
|
|
|
|
|
Notify = FsRtlpAllocatePool(PagedPool, sizeof(NOTIFY_CHANGE));
|
|
RtlZeroMemory(Notify, sizeof(NOTIFY_CHANGE));
|
|
|
|
Notify->NotifySync = (PREAL_NOTIFY_SYNC)NotifySync;
|
|
Notify->FsContext = FsContext;
|
|
Notify->StreamID = IrpSp->FileObject->FsContext;
|
|
|
|
Notify->TraverseCallback = TraverseCallback;
|
|
Notify->SubjectContext = SubjectContext;
|
|
SubjectContext = NULL;
|
|
|
|
Notify->FullDirectoryName = FullDirectoryName;
|
|
|
|
InitializeListHead(&Notify->NotifyIrps);
|
|
|
|
if (WatchTree) {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_WATCH_TREE);
|
|
}
|
|
|
|
if (FullDirectoryName == NULL) {
|
|
|
|
|
|
// In the view index we aren't using this buffer to hold a
|
|
// unicode string.
|
|
|
|
|
|
Notify->CharacterSize = sizeof(CHAR);
|
|
|
|
}
|
|
else {
|
|
|
|
|
|
// We look at the directory name to decide if we have a unicode
|
|
// name.
|
|
|
|
|
|
if (FullDirectoryName->Length >= 2
|
|
&& FullDirectoryName->Buffer[1] == '\0') {
|
|
|
|
Notify->CharacterSize = sizeof(WCHAR);
|
|
|
|
}
|
|
else {
|
|
|
|
Notify->CharacterSize = sizeof(CHAR);
|
|
}
|
|
|
|
if (FullDirectoryName->Length == Notify->CharacterSize) {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_DIR_IS_ROOT);
|
|
}
|
|
}
|
|
|
|
Notify->CompletionFilter = CompletionFilter;
|
|
|
|
|
|
// If we are to return data to the user then look for the length
|
|
// of the original buffer in the IrpSp.
|
|
|
|
|
|
if (!IgnoreBuffer) {
|
|
|
|
Notify->BufferLength = IrpSp->Parameters.NotifyDirectory.Length;
|
|
}
|
|
|
|
Notify->OwningProcess = THREAD_TO_PROCESS(NotifyIrp->Tail.Overlay.Thread);
|
|
InsertTailList(NotifyList, &Notify->NotifyList);
|
|
|
|
Notify->ReferenceCount = 1;
|
|
|
|
|
|
// If we have already been called with cleanup then complete
|
|
// the request immediately.
|
|
|
|
|
|
}
|
|
else if (FlagOn(Notify->Flags, NOTIFY_CLEANUP_CALLED)) {
|
|
|
|
|
|
// Always mark this Irp as pending returned.
|
|
|
|
|
|
IoMarkIrpPending(NotifyIrp);
|
|
|
|
FsRtlCompleteRequest(NotifyIrp, STATUS_NOTIFY_CLEANUP);
|
|
try_return(NOTHING);
|
|
|
|
|
|
// If this file has been deleted then complete with STATUS_DELETE_PENDING.
|
|
|
|
|
|
}
|
|
else if (FlagOn(Notify->Flags, NOTIFY_STREAM_IS_DELETED)) {
|
|
|
|
|
|
// Always mark this Irp as pending returned.
|
|
|
|
|
|
IoMarkIrpPending(NotifyIrp);
|
|
|
|
FsRtlCompleteRequest(NotifyIrp, STATUS_DELETE_PENDING);
|
|
try_return(NOTHING);
|
|
|
|
|
|
// If the notify pending flag is set or there is data in an internal buffer
|
|
// we complete this Irp immediately and exit.
|
|
|
|
|
|
}
|
|
else if (FlagOn(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY)
|
|
&& !FlagOn(Notify->Flags, NOTIFY_DEFER_NOTIFY)) {
|
|
|
|
DebugTrace(0, Dbg, "Notify has been pending\n", 0);
|
|
|
|
|
|
// Clear the flag in our notify structure before completing the
|
|
// Irp. This will prevent a caller who reposts in his completion
|
|
// routine from looping in the completion routine.
|
|
|
|
|
|
ClearFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
|
|
|
|
// Always mark this Irp as pending returned.
|
|
|
|
|
|
IoMarkIrpPending(NotifyIrp);
|
|
|
|
FsRtlCompleteRequest(NotifyIrp, STATUS_NOTIFY_ENUM_DIR);
|
|
try_return(NOTHING);
|
|
|
|
}
|
|
else if (Notify->DataLength != 0
|
|
&& !FlagOn(Notify->Flags, NOTIFY_DEFER_NOTIFY)) {
|
|
|
|
ULONG ThisDataLength = Notify->DataLength;
|
|
|
|
|
|
// Now set our buffer pointers back to indicate an empty buffer.
|
|
|
|
|
|
Notify->DataLength = 0;
|
|
Notify->LastEntry = 0;
|
|
|
|
FsRtlNotifyCompleteIrp(NotifyIrp,
|
|
Notify,
|
|
ThisDataLength,
|
|
STATUS_SUCCESS,
|
|
FALSE);
|
|
|
|
try_return(NOTHING);
|
|
}
|
|
|
|
|
|
// Add the Irp to the tail of the notify queue.
|
|
|
|
|
|
NotifyIrp->IoStatus.Information = (ULONG_PTR)Notify;
|
|
IoMarkIrpPending(NotifyIrp);
|
|
InsertTailList(&Notify->NotifyIrps, &NotifyIrp->Tail.Overlay.ListEntry);
|
|
|
|
|
|
// Increment the reference count to indicate that Irp might go through cancel.
|
|
|
|
|
|
InterlockedIncrement(&Notify->ReferenceCount);
|
|
|
|
|
|
// Call the routine to set the cancel routine.
|
|
|
|
|
|
FsRtlNotifySetCancelRoutine(NotifyIrp, NULL);
|
|
|
|
try_exit: NOTHING;
|
|
}
|
|
finally{
|
|
|
|
|
|
// Release the mutex.
|
|
|
|
|
|
ReleaseNotifySync(NotifySync);
|
|
|
|
|
|
// If there is still a subject context then release it and deallocate
|
|
// the structure. Remember that if FullDirectoryName is null, it means
|
|
// this is a view index, not a directory index, and the SubjectContext
|
|
// is really a piece of file system context information.
|
|
|
|
|
|
if ((SubjectContext != NULL) &&
|
|
(Notify->FullDirectoryName != NULL)) {
|
|
|
|
SeReleaseSubjectContext(SubjectContext);
|
|
ExFreePool(SubjectContext);
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyFullChangeDirectory: Exit\n", 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyReportChange(
|
|
IN PNOTIFY_SYNC NotifySync,
|
|
IN PLIST_ENTRY NotifyList,
|
|
IN PSTRING FullTargetName,
|
|
IN PSTRING TargetName,
|
|
IN ULONG FilterMatch
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a file system when a file has been modified in
|
|
such a way that it will cause a notify change Irp to complete. We walk
|
|
through all the notify structures looking for those structures which
|
|
would be associated with an ancestor directory of the target file name.
|
|
|
|
We look for all the notify structures which have a filter match and
|
|
then check that the directory name in the notify structure is a
|
|
proper prefix of the full target name.
|
|
|
|
If we find a notify structure which matches the above conditions, we
|
|
complete all the Irps for the notify structure. If the structure has
|
|
no Irps, we mark the notify pending field.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the controlling fast mutex for this notify list.
|
|
It is stored here so that it can be found for an Irp which is being
|
|
cancelled.
|
|
|
|
NotifyList - This is the start of the notify list to add this
|
|
structure to.
|
|
|
|
FullTargetName - This is the full name of the file which has been
|
|
changed.
|
|
|
|
TargetName - This is the final component of the modified file.
|
|
|
|
FilterMatch - This flag field is compared with the completion filter
|
|
in the notify structure. If any of the corresponding
|
|
bits in the completion filter are set, then a notify
|
|
condition exists.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyReportChange: Entered\n", 0);
|
|
|
|
|
|
// Call the full notify routine to do the actual work.
|
|
|
|
|
|
FsRtlNotifyFullReportChange(NotifySync,
|
|
NotifyList,
|
|
FullTargetName,
|
|
(USHORT)(FullTargetName->Length - TargetName->Length),
|
|
NULL,
|
|
NULL,
|
|
FilterMatch,
|
|
0,
|
|
NULL);
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyReportChange: Exit\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyFullReportChange(
|
|
IN PNOTIFY_SYNC NotifySync,
|
|
IN PLIST_ENTRY NotifyList,
|
|
IN PSTRING FullTargetName,
|
|
IN USHORT TargetNameOffset,
|
|
IN PSTRING StreamName OPTIONAL,
|
|
IN PSTRING NormalizedParentName OPTIONAL,
|
|
IN ULONG FilterMatch,
|
|
IN ULONG Action,
|
|
IN PVOID TargetContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by a file system when a file has been modified in
|
|
such a way that it will cause a notify change Irp to complete. We walk
|
|
through all the notify structures looking for those structures which
|
|
would be associated with an ancestor directory of the target file name.
|
|
|
|
We look for all the notify structures which have a filter match and
|
|
then check that the directory name in the notify structure is a
|
|
proper prefix of the full target name.
|
|
|
|
If we find a notify structure which matches the above conditions, we
|
|
complete all the Irps for the notify structure. If the structure has
|
|
no Irps, we mark the notify pending field.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the controlling fast mutex for this notify list.
|
|
It is stored here so that it can be found for an Irp which is being
|
|
cancelled.
|
|
|
|
NotifyList - This is the start of the notify list to add this
|
|
structure to.
|
|
|
|
FullTargetName - This is the full name of the file from the root of the volume.
|
|
|
|
TargetNameOffset - This is the offset in the full name of the final component
|
|
of the name.
|
|
|
|
StreamName - If present then this is the stream name to store with
|
|
the filename.
|
|
|
|
NormalizedParentName - If present this is the same path as the parent name
|
|
but the DOS-ONLY names have been replaced with the associated long name.
|
|
|
|
FilterMatch - This flag field is compared with the completion filter
|
|
in the notify structure. If any of the corresponding bits in the
|
|
completion filter are set, then a notify condition exists.
|
|
|
|
Action - This is the action code to store in the user's buffer if
|
|
present.
|
|
|
|
TargetContext - This is one of the context pointers to pass to the file
|
|
system if performing a traverse check in the case of a tree being
|
|
watched.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY NotifyLinks;
|
|
|
|
STRING NormalizedParent;
|
|
STRING ParentName;
|
|
STRING TargetName;
|
|
|
|
PNOTIFY_CHANGE Notify;
|
|
STRING TargetParent;
|
|
PIRP NotifyIrp;
|
|
|
|
BOOLEAN NotifyIsParent;
|
|
BOOLEAN ViewIndex = FALSE;
|
|
UCHAR ComponentCount;
|
|
ULONG SizeOfEntry;
|
|
ULONG CurrentOffset;
|
|
ULONG NextEntryOffset;
|
|
ULONG ExceptionCode;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyFullReportChange: Entered\n", 0);
|
|
|
|
|
|
// If this is a change to the root directory then return immediately.
|
|
|
|
|
|
if ((TargetNameOffset == 0) && (FullTargetName != NULL)) {
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyFullReportChange: Exit\n", 0);
|
|
return;
|
|
}
|
|
|
|
ParentName.Buffer = NULL;
|
|
TargetName.Buffer = NULL;
|
|
|
|
|
|
// Acquire exclusive access to the list by acquiring the mutex.
|
|
|
|
|
|
AcquireNotifySync(NotifySync);
|
|
|
|
|
|
// Use a try-finally to facilitate cleanup.
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Walk through all the notify blocks.
|
|
|
|
|
|
for (NotifyLinks = NotifyList->Flink;
|
|
NotifyLinks != NotifyList;
|
|
NotifyLinks = NotifyLinks->Flink) {
|
|
|
|
|
|
// Obtain the Notify structure from the list entry.
|
|
|
|
|
|
Notify = CONTAINING_RECORD(NotifyLinks, NOTIFY_CHANGE, NotifyList);
|
|
|
|
|
|
// The rules for deciding whether this notification applies are
|
|
// different for view indices versus file name indices (directories).
|
|
|
|
|
|
if (FullTargetName == NULL) {
|
|
|
|
ASSERTMSG("Directory notify handle in view index notify list!", Notify->FullDirectoryName == NULL);
|
|
|
|
|
|
// Make sure this is the Fcb being watched.
|
|
|
|
|
|
if (TargetContext != Notify->SubjectContext) {
|
|
|
|
continue;
|
|
}
|
|
|
|
TargetParent.Buffer = NULL;
|
|
TargetParent.Length = 0;
|
|
|
|
ViewIndex = TRUE;
|
|
|
|
|
|
// Handle the directory case.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
ASSERTMSG("View index notify handle in directory notify list!", Notify->FullDirectoryName != NULL);
|
|
|
|
|
|
// If the length of the name in the notify block is currently zero then
|
|
// someone is doing a rename and we can skip this block.
|
|
|
|
|
|
if (Notify->FullDirectoryName->Length == 0) {
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
// If this filter match is not part of the completion filter then continue.
|
|
|
|
|
|
if (!(FilterMatch & Notify->CompletionFilter)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
// If there is no normalized name then set its value from the full
|
|
// file name.
|
|
|
|
|
|
if (!ARGUMENT_PRESENT(NormalizedParentName)) {
|
|
NormalizedParent.Buffer = FullTargetName->Buffer;
|
|
NormalizedParent.Length = TargetNameOffset;
|
|
|
|
if (NormalizedParent.Length != Notify->CharacterSize) {
|
|
|
|
NormalizedParent.Length -= Notify->CharacterSize;
|
|
}
|
|
|
|
NormalizedParent.MaximumLength = NormalizedParent.Length;
|
|
|
|
NormalizedParentName = &NormalizedParent;
|
|
}
|
|
|
|
|
|
// If the length of the directory being watched is longer than the
|
|
// parent of the modified file then it can't be an ancestor of the
|
|
// modified file.
|
|
|
|
|
|
if (Notify->FullDirectoryName->Length > NormalizedParentName->Length) {
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
// If the lengths match exactly then this can only be the parent of
|
|
// the modified file.
|
|
|
|
|
|
if (NormalizedParentName->Length == Notify->FullDirectoryName->Length) {
|
|
|
|
NotifyIsParent = TRUE;
|
|
|
|
|
|
// If we are not watching the subtree of this directory then continue.
|
|
|
|
|
|
}
|
|
else if (!FlagOn(Notify->Flags, NOTIFY_WATCH_TREE)) {
|
|
|
|
continue;
|
|
|
|
|
|
// The watched directory can only be an ancestor of the modified
|
|
// file. Make sure that there is legal pathname separator immediately
|
|
// after the end of the watched directory name within the normalized name.
|
|
// If the watched directory is the root then we know this condition is TRUE.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
if (!FlagOn(Notify->Flags, NOTIFY_DIR_IS_ROOT)) {
|
|
|
|
|
|
// Check for the character size.
|
|
|
|
|
|
if (Notify->CharacterSize == sizeof(CHAR)) {
|
|
|
|
if (*(Add2Ptr(NormalizedParentName->Buffer,
|
|
Notify->FullDirectoryName->Length,
|
|
PCHAR)) != '\\') {
|
|
|
|
continue;
|
|
}
|
|
|
|
}
|
|
else if (*(Add2Ptr(NormalizedParentName->Buffer,
|
|
Notify->FullDirectoryName->Length,
|
|
PWCHAR)) != L'\\') {
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
NotifyIsParent = FALSE;
|
|
}
|
|
|
|
|
|
// We now have a correct match of the name lengths. Now verify that the
|
|
// characters match exactly.
|
|
|
|
|
|
if (!RtlEqualMemory(Notify->FullDirectoryName->Buffer,
|
|
NormalizedParentName->Buffer,
|
|
Notify->FullDirectoryName->Length)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
// The characters are correct. Now check in the case of a non-parent
|
|
// notify that we have traverse callback.
|
|
|
|
|
|
if (!NotifyIsParent &&
|
|
Notify->TraverseCallback != NULL &&
|
|
!Notify->TraverseCallback(Notify->FsContext,
|
|
TargetContext,
|
|
Notify->SubjectContext)) {
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
// If this entry is going into a buffer then check that
|
|
// it will fit.
|
|
|
|
|
|
if (!FlagOn(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY)
|
|
&& Notify->BufferLength != 0) {
|
|
|
|
ULONG AllocationLength;
|
|
|
|
AllocationLength = 0;
|
|
NotifyIrp = NULL;
|
|
|
|
|
|
// If we don't already have a buffer then check to see
|
|
// if we have any Irps in the list and use the buffer
|
|
// length in the Irp.
|
|
|
|
|
|
if (Notify->ThisBufferLength == 0) {
|
|
|
|
|
|
// If there is an entry in the list then get the length.
|
|
|
|
|
|
if (!IsListEmpty(&Notify->NotifyIrps)) {
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
NotifyIrp = CONTAINING_RECORD(Notify->NotifyIrps.Flink,
|
|
IRP,
|
|
Tail.Overlay.ListEntry);
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(NotifyIrp);
|
|
|
|
AllocationLength = IrpSp->Parameters.NotifyDirectory.Length;
|
|
|
|
|
|
// Otherwise use the caller's last buffer size.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
AllocationLength = Notify->BufferLength;
|
|
}
|
|
|
|
|
|
// Otherwise use the length of the current buffer.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
AllocationLength = Notify->ThisBufferLength;
|
|
}
|
|
|
|
|
|
// Build the strings for the relative name. This includes
|
|
// the strings for the parent name, file name and stream
|
|
// name.
|
|
|
|
|
|
if (!NotifyIsParent) {
|
|
|
|
|
|
// We need to find the string for the ancestor of this
|
|
// file from the watched directory. If the normalized parent
|
|
// name is the same as the parent name then we can use
|
|
// the tail of the parent directly. Otherwise we need to
|
|
// count the matching name components and capture the
|
|
// final components.
|
|
|
|
|
|
if (!ViewIndex) {
|
|
|
|
|
|
// If the watched directory is the root then we just use the full
|
|
// parent name.
|
|
|
|
|
|
if (FlagOn(Notify->Flags, NOTIFY_DIR_IS_ROOT) ||
|
|
NormalizedParentName->Buffer != FullTargetName->Buffer) {
|
|
|
|
|
|
// If we don't have a string for the parent then construct
|
|
// it now.
|
|
|
|
|
|
if (ParentName.Buffer == NULL) {
|
|
|
|
ParentName.Buffer = FullTargetName->Buffer;
|
|
ParentName.Length = TargetNameOffset;
|
|
|
|
if (ParentName.Length != Notify->CharacterSize) {
|
|
|
|
ParentName.Length -= Notify->CharacterSize;
|
|
}
|
|
|
|
ParentName.MaximumLength = ParentName.Length;
|
|
}
|
|
|
|
|
|
// Count through the components of the parent until we have
|
|
// swallowed the same number of name components as in the
|
|
// watched directory name. We have the unicode version and
|
|
// the Ansi version to watch for.
|
|
|
|
|
|
ComponentCount = 0;
|
|
CurrentOffset = 0;
|
|
|
|
|
|
// If this is the root then there is no more to do.
|
|
|
|
|
|
if (FlagOn(Notify->Flags, NOTIFY_DIR_IS_ROOT)) {
|
|
|
|
NOTHING;
|
|
|
|
}
|
|
else {
|
|
|
|
ULONG ParentComponentCount;
|
|
ULONG ParentOffset;
|
|
|
|
ParentComponentCount = 1;
|
|
ParentOffset = 0;
|
|
|
|
if (Notify->CharacterSize == sizeof(CHAR)) {
|
|
|
|
|
|
// Find the number of components in the parent. We
|
|
// have to do this for each one because this name and
|
|
// the number of components could have changed.
|
|
|
|
|
|
while (ParentOffset < Notify->FullDirectoryName->Length) {
|
|
|
|
if (*((PCHAR)Notify->FullDirectoryName->Buffer + ParentOffset) == '\\') {
|
|
|
|
ParentComponentCount += 1;
|
|
}
|
|
|
|
ParentOffset += 1;
|
|
}
|
|
|
|
while (TRUE) {
|
|
|
|
if (*((PCHAR)ParentName.Buffer + CurrentOffset) == '\\') {
|
|
|
|
ComponentCount += 1;
|
|
|
|
if (ComponentCount == ParentComponentCount) {
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
CurrentOffset += 1;
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
|
|
// Find the number of components in the parent. We
|
|
// have to do this for each one because this name and
|
|
// the number of components could have changed.
|
|
|
|
|
|
while (ParentOffset < Notify->FullDirectoryName->Length / sizeof(WCHAR)) {
|
|
|
|
if (*((PWCHAR)Notify->FullDirectoryName->Buffer + ParentOffset) == '\\') {
|
|
|
|
ParentComponentCount += 1;
|
|
}
|
|
|
|
ParentOffset += 1;
|
|
}
|
|
|
|
while (TRUE) {
|
|
|
|
if (*((PWCHAR)ParentName.Buffer + CurrentOffset) == L'\\') {
|
|
|
|
ComponentCount += 1;
|
|
|
|
if (ComponentCount == ParentComponentCount) {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
CurrentOffset += 1;
|
|
}
|
|
|
|
|
|
// Convert characters to bytes.
|
|
|
|
|
|
CurrentOffset *= Notify->CharacterSize;
|
|
}
|
|
}
|
|
|
|
|
|
// We now know the offset into the parent name of the separator
|
|
// immediately preceding the relative parent name. Construct the
|
|
// target parent name for the buffer.
|
|
|
|
|
|
CurrentOffset += Notify->CharacterSize;
|
|
|
|
TargetParent.Buffer = Add2Ptr(ParentName.Buffer,
|
|
CurrentOffset,
|
|
PCHAR);
|
|
TargetParent.MaximumLength =
|
|
TargetParent.Length = ParentName.Length - (USHORT)CurrentOffset;
|
|
|
|
|
|
// If the normalized is the same as the parent name use the portion
|
|
// after the match with the watched directory.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
TargetParent.Buffer = Add2Ptr(NormalizedParentName->Buffer,
|
|
(Notify->FullDirectoryName->Length +
|
|
Notify->CharacterSize),
|
|
PCHAR);
|
|
|
|
TargetParent.MaximumLength =
|
|
TargetParent.Length = NormalizedParentName->Length -
|
|
Notify->FullDirectoryName->Length -
|
|
Notify->CharacterSize;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
|
|
// The length of the target parent is zero.
|
|
|
|
|
|
TargetParent.Length = 0;
|
|
}
|
|
|
|
|
|
// Compute how much buffer space this report will take.
|
|
|
|
|
|
SizeOfEntry = FIELD_OFFSET(FILE_NOTIFY_INFORMATION, FileName);
|
|
|
|
if (ViewIndex) {
|
|
|
|
|
|
// In the view index case, the information to copy to the
|
|
// buffer comes to us in the stream name, and that is all
|
|
// the room we need to worry about having.
|
|
|
|
|
|
ASSERT(ARGUMENT_PRESENT(StreamName));
|
|
|
|
SizeOfEntry += StreamName->Length;
|
|
|
|
}
|
|
else {
|
|
|
|
|
|
// If there is a parent to report, find the size and include a separator
|
|
// character.
|
|
|
|
|
|
if (!NotifyIsParent) {
|
|
|
|
if (Notify->CharacterSize == sizeof(CHAR)) {
|
|
|
|
SizeOfEntry += RtlOemStringToCountedUnicodeSize(&TargetParent);
|
|
|
|
}
|
|
else {
|
|
|
|
SizeOfEntry += TargetParent.Length;
|
|
}
|
|
|
|
|
|
// Include the separator. This is always a unicode character.
|
|
|
|
|
|
SizeOfEntry += sizeof(WCHAR);
|
|
}
|
|
|
|
|
|
// If we don't have the string for the target then construct it now.
|
|
|
|
|
|
if (TargetName.Buffer == NULL) {
|
|
|
|
TargetName.Buffer = Add2Ptr(FullTargetName->Buffer, TargetNameOffset, PCHAR);
|
|
TargetName.MaximumLength =
|
|
TargetName.Length = FullTargetName->Length - TargetNameOffset;
|
|
}
|
|
|
|
if (Notify->CharacterSize == sizeof(CHAR)) {
|
|
|
|
SizeOfEntry += RtlOemStringToCountedUnicodeSize(&TargetName);
|
|
|
|
}
|
|
else {
|
|
|
|
SizeOfEntry += TargetName.Length;
|
|
}
|
|
|
|
|
|
// If there is a stream name then add the bytes needed
|
|
// for that.
|
|
|
|
|
|
if (ARGUMENT_PRESENT(StreamName)) {
|
|
|
|
|
|
// Add the space needed for the ':' separator.
|
|
|
|
|
|
if (Notify->CharacterSize == sizeof(WCHAR)) {
|
|
|
|
SizeOfEntry += (StreamName->Length + sizeof(WCHAR));
|
|
|
|
}
|
|
else {
|
|
|
|
SizeOfEntry += (RtlOemStringToCountedUnicodeSize(StreamName)
|
|
+ sizeof(CHAR));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Remember if this report would overflow the buffer.
|
|
|
|
|
|
NextEntryOffset = (ULONG)LongAlign(Notify->DataLength);
|
|
|
|
if (SizeOfEntry <= AllocationLength
|
|
&& (NextEntryOffset + SizeOfEntry) <= AllocationLength) {
|
|
|
|
PFILE_NOTIFY_INFORMATION NotifyInfo = NULL;
|
|
|
|
|
|
// If there is already a notify buffer, we append this
|
|
// data to it.
|
|
|
|
|
|
if (Notify->Buffer != NULL) {
|
|
|
|
NotifyInfo = Add2Ptr(Notify->Buffer,
|
|
Notify->LastEntry,
|
|
PFILE_NOTIFY_INFORMATION);
|
|
|
|
NotifyInfo->NextEntryOffset = NextEntryOffset - Notify->LastEntry;
|
|
|
|
Notify->LastEntry = NextEntryOffset;
|
|
|
|
NotifyInfo = Add2Ptr(Notify->Buffer,
|
|
Notify->LastEntry,
|
|
PFILE_NOTIFY_INFORMATION);
|
|
|
|
|
|
// If there is an Irp list we check whether we will need
|
|
// to allocate a new buffer.
|
|
|
|
|
|
}
|
|
else if (NotifyIrp != NULL) {
|
|
|
|
if (NotifyIrp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
Notify->Buffer =
|
|
NotifyInfo = NotifyIrp->AssociatedIrp.SystemBuffer;
|
|
|
|
Notify->ThisBufferLength = AllocationLength;
|
|
|
|
}
|
|
else if (NotifyIrp->MdlAddress != NULL) {
|
|
|
|
Notify->Buffer =
|
|
NotifyInfo = MmGetSystemAddressForMdl(NotifyIrp->MdlAddress);
|
|
|
|
Notify->ThisBufferLength = AllocationLength;
|
|
}
|
|
}
|
|
|
|
|
|
// If we need to allocate a buffer, we will charge quota
|
|
// to the original process and allocate paged pool.
|
|
|
|
|
|
if (Notify->Buffer == NULL) {
|
|
|
|
BOOLEAN ChargedQuota = FALSE;
|
|
|
|
try {
|
|
|
|
PsChargePoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
AllocationLength);
|
|
|
|
ChargedQuota = TRUE;
|
|
|
|
Notify->AllocatedBuffer =
|
|
Notify->Buffer = FsRtlpAllocatePool(PagedPool,
|
|
AllocationLength);
|
|
|
|
Notify->ThisBufferLength = AllocationLength;
|
|
|
|
NotifyInfo = Notify->Buffer;
|
|
|
|
} except((ExceptionCode = GetExceptionCode(), FsRtlIsNtstatusExpected(ExceptionCode))
|
|
? EXCEPTION_EXECUTE_HANDLER
|
|
: EXCEPTION_CONTINUE_SEARCH) {
|
|
|
|
|
|
ASSERT((ExceptionCode == STATUS_INSUFFICIENT_RESOURCES) ||
|
|
(ExceptionCode == STATUS_QUOTA_EXCEEDED));
|
|
|
|
|
|
// Return quota if we allocated the buffer.
|
|
|
|
|
|
if (ChargedQuota) {
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
AllocationLength);
|
|
|
|
}
|
|
|
|
|
|
// Forget any current buffer and resort to immediate
|
|
// notify.
|
|
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
}
|
|
}
|
|
|
|
|
|
// If we have a buffer then fill in the results
|
|
// from this operation. Otherwise we remember
|
|
// to simply alert the caller.
|
|
|
|
|
|
if (NotifyInfo != NULL) {
|
|
|
|
|
|
// Update the buffer with the current data.
|
|
|
|
|
|
if (FsRtlNotifyUpdateBuffer(NotifyInfo,
|
|
Action,
|
|
&TargetParent,
|
|
&TargetName,
|
|
StreamName,
|
|
(BOOLEAN)(Notify->CharacterSize == sizeof(WCHAR)),
|
|
SizeOfEntry)) {
|
|
|
|
|
|
// Update the buffer data length.
|
|
|
|
|
|
Notify->DataLength = NextEntryOffset + SizeOfEntry;
|
|
|
|
|
|
// We couldn't copy the data into the buffer. Just
|
|
// notify without any additional information.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
}
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
}
|
|
|
|
|
|
// If we have a buffer but can't use it then clear all of the
|
|
// buffer related fields. Also deallocate any buffer allocated
|
|
// by us.
|
|
|
|
|
|
if (FlagOn(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY)
|
|
&& Notify->Buffer != NULL) {
|
|
|
|
if (Notify->AllocatedBuffer != NULL) {
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
Notify->ThisBufferLength);
|
|
|
|
ExFreePool(Notify->AllocatedBuffer);
|
|
}
|
|
|
|
Notify->AllocatedBuffer = Notify->Buffer = NULL;
|
|
|
|
Notify->ThisBufferLength = Notify->DataLength = Notify->LastEntry = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Complete the next entry on the list if we aren't holding
|
|
// up notification.
|
|
|
|
|
|
if (Action == FILE_ACTION_RENAMED_OLD_NAME) {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_DEFER_NOTIFY);
|
|
|
|
}
|
|
else {
|
|
|
|
ClearFlag(Notify->Flags, NOTIFY_DEFER_NOTIFY);
|
|
|
|
if (!IsListEmpty(&Notify->NotifyIrps)) {
|
|
|
|
FsRtlNotifyCompleteIrpList(Notify, STATUS_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
finally{
|
|
|
|
ReleaseNotifySync(NotifySync);
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyFullReportChange: Exit\n", 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyCleanup(
|
|
IN PNOTIFY_SYNC NotifySync,
|
|
IN PLIST_ENTRY NotifyList,
|
|
IN PVOID FsContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called for a cleanup of a user directory handle. We
|
|
walk through our notify structures looking for a matching context field.
|
|
We complete all the pending notify Irps for this Notify structure, remove
|
|
the notify structure and deallocate it.
|
|
|
|
Arguments:
|
|
|
|
NotifySync - This is the controlling fast mutex for this notify list.
|
|
It is stored here so that it can be found for an Irp which is being
|
|
cancelled.
|
|
|
|
NotifyList - This is the start of the notify list to add this
|
|
structure to.
|
|
|
|
FsContext - This is a unique value assigned by the file system to
|
|
identify a particular notify structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNOTIFY_CHANGE Notify;
|
|
PSECURITY_SUBJECT_CONTEXT SubjectContext = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyCleanup: Entered\n", 0);
|
|
DebugTrace(0, Dbg, "Mutex -> %08lx\n", Mutex);
|
|
DebugTrace(0, Dbg, "Notify List -> %08lx\n", NotifyList);
|
|
DebugTrace(0, Dbg, "FsContext -> %08lx\n", FsContext);
|
|
|
|
|
|
// Acquire exclusive access to the list by acquiring the mutex.
|
|
|
|
|
|
AcquireNotifySync(NotifySync);
|
|
|
|
|
|
// Use a try-finally to facilitate cleanup.
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Search for a match on the list.
|
|
|
|
|
|
Notify = FsRtlIsNotifyOnList(NotifyList, FsContext);
|
|
|
|
|
|
// If found, then complete all the Irps with STATUS_NOTIFY_CLEANUP
|
|
|
|
|
|
if (Notify != NULL) {
|
|
|
|
|
|
// Set the flag to indicate that we have been called with cleanup.
|
|
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_CLEANUP_CALLED);
|
|
|
|
if (!IsListEmpty(&Notify->NotifyIrps)) {
|
|
|
|
FsRtlNotifyCompleteIrpList(Notify, STATUS_NOTIFY_CLEANUP);
|
|
}
|
|
|
|
RemoveEntryList(&Notify->NotifyList);
|
|
|
|
InterlockedDecrement(&Notify->ReferenceCount);
|
|
|
|
if (Notify->ReferenceCount == 0) {
|
|
|
|
if (Notify->AllocatedBuffer != NULL) {
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
Notify->ThisBufferLength);
|
|
|
|
ExFreePool(Notify->AllocatedBuffer);
|
|
}
|
|
|
|
if (Notify->FullDirectoryName != NULL) {
|
|
|
|
SubjectContext = Notify->SubjectContext;
|
|
}
|
|
|
|
ExFreePool(Notify);
|
|
}
|
|
}
|
|
|
|
}
|
|
finally{
|
|
|
|
ReleaseNotifySync(NotifySync);
|
|
|
|
if (SubjectContext != NULL) {
|
|
|
|
SeReleaseSubjectContext(SubjectContext);
|
|
ExFreePool(SubjectContext);
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyCleanup: Exit\n", 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
PNOTIFY_CHANGE
|
|
FsRtlIsNotifyOnList(
|
|
IN PLIST_ENTRY NotifyListHead,
|
|
IN PVOID FsContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to walk through a notify list searching for
|
|
a member associated with the FsContext value.
|
|
|
|
Arguments:
|
|
|
|
NotifyListHead - This is the start of the notify list.
|
|
|
|
FsContext - This is supplied by the file system so that this notify
|
|
structure can be uniquely identified.
|
|
|
|
Return Value:
|
|
|
|
PNOTIFY_CHANGE - A pointer to the matching structure is returned. NULL
|
|
is returned if the structure isn't present.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY Link;
|
|
|
|
PNOTIFY_CHANGE ThisNotify;
|
|
PNOTIFY_CHANGE Notify;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlIsNotifyOnList: Entered\n", 0);
|
|
|
|
|
|
// Assume we won't have a match.
|
|
|
|
|
|
Notify = NULL;
|
|
|
|
|
|
// Walk through all the entries on the list looking for a match.
|
|
|
|
|
|
for (Link = NotifyListHead->Flink;
|
|
Link != NotifyListHead;
|
|
Link = Link->Flink) {
|
|
|
|
|
|
// Obtain the notify structure from the link.
|
|
|
|
|
|
ThisNotify = CONTAINING_RECORD(Link, NOTIFY_CHANGE, NotifyList);
|
|
|
|
|
|
// If the context field matches, remember this structure and
|
|
// exit.
|
|
|
|
|
|
if (ThisNotify->FsContext == FsContext) {
|
|
|
|
Notify = ThisNotify;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DebugTrace(0, Dbg, "Notify Structure -> %08lx\n", Notify);
|
|
DebugTrace(-1, Dbg, "FsRtlIsNotifyOnList: Exit\n", 0);
|
|
|
|
return Notify;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyCompleteIrp(
|
|
IN PIRP NotifyIrp,
|
|
IN PNOTIFY_CHANGE Notify,
|
|
IN ULONG DataLength,
|
|
IN NTSTATUS Status,
|
|
IN ULONG CheckCancel
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to complete an Irp with the data in the NOTIFY_CHANGE
|
|
structure.
|
|
|
|
Arguments:
|
|
|
|
NotifyIrp - Irp to complete.
|
|
|
|
Notify - Notify structure which contains the data.
|
|
|
|
DataLength - This is the length of the data in the buffer in the notify
|
|
structure. A value of zero indicates that we should complete the
|
|
request with STATUS_NOTIFY_ENUM_DIR.
|
|
|
|
Status - Indicates the status to complete the Irp with.
|
|
|
|
CheckCancel - Indicates if we should only complete the irp if we clear the cancel routine
|
|
ourselves.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlIsNotifyCompleteIrp: Entered\n", 0);
|
|
|
|
|
|
// Attempt to clear the cancel routine. If this routine owns the cancel
|
|
// routine then it can complete the irp. Otherwise there is a cancel underway
|
|
// on this.
|
|
|
|
|
|
if (FsRtlNotifySetCancelRoutine(NotifyIrp, Notify) || !CheckCancel) {
|
|
|
|
|
|
// We only process the buffer if the status is STATUS_SUCCESS.
|
|
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
|
|
// Get the current Stack location
|
|
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(NotifyIrp);
|
|
|
|
|
|
// If the data won't fit in the user's buffer or there was already a
|
|
// buffer overflow then return the alternate status code. If the data
|
|
// was already stored in the Irp buffer then we know that we won't
|
|
// take this path. Otherwise we wouldn't be cleaning up the Irp
|
|
// correctly.
|
|
|
|
|
|
if (DataLength == 0
|
|
|| IrpSp->Parameters.NotifyDirectory.Length < DataLength) {
|
|
|
|
Status = STATUS_NOTIFY_ENUM_DIR;
|
|
|
|
|
|
// We have to carefully return the buffer to the user and handle all
|
|
// of the different buffer cases. If there is no allocated buffer
|
|
// in the notify structure it means that we have already used the
|
|
// caller's buffer.
|
|
|
|
// 1 - If the system allocated an associated system buffer we
|
|
// can simply fill that in.
|
|
|
|
// 2 - If there is an Mdl then we get a system address for the Mdl
|
|
// and copy the data into it.
|
|
|
|
// 3 - If there is only a user's buffer and pending has not been
|
|
// returned, we can fill the user's buffer in directly.
|
|
|
|
// 4 - If there is only a user's buffer and pending has been returned
|
|
// then we are not in the user's address space. We dress up
|
|
// the Irp with our system buffer and let the Io system
|
|
// copy the data in.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
if (Notify->AllocatedBuffer != NULL) {
|
|
|
|
|
|
// Protect the copy with a try-except and ignore the buffer
|
|
// if we have some error in copying it to the buffer.
|
|
|
|
|
|
try {
|
|
|
|
if (NotifyIrp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
RtlCopyMemory(NotifyIrp->AssociatedIrp.SystemBuffer,
|
|
Notify->AllocatedBuffer,
|
|
DataLength);
|
|
|
|
}
|
|
else if (NotifyIrp->MdlAddress != NULL) {
|
|
|
|
RtlCopyMemory(MmGetSystemAddressForMdl(NotifyIrp->MdlAddress),
|
|
Notify->AllocatedBuffer,
|
|
DataLength);
|
|
|
|
}
|
|
else if (!FlagOn(IrpSp->Control, SL_PENDING_RETURNED)) {
|
|
|
|
RtlCopyMemory(NotifyIrp->UserBuffer,
|
|
Notify->AllocatedBuffer,
|
|
DataLength);
|
|
|
|
}
|
|
else {
|
|
|
|
NotifyIrp->Flags |= (IRP_BUFFERED_IO | IRP_INPUT_OPERATION | IRP_DEALLOCATE_BUFFER);
|
|
NotifyIrp->AssociatedIrp.SystemBuffer = Notify->AllocatedBuffer;
|
|
|
|
}
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
Status = STATUS_NOTIFY_ENUM_DIR;
|
|
DataLength = 0;
|
|
}
|
|
|
|
|
|
// Return the quota and deallocate the buffer if we didn't pass it
|
|
// back via the irp.
|
|
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess, PagedPool, Notify->ThisBufferLength);
|
|
|
|
if (Notify->AllocatedBuffer != NotifyIrp->AssociatedIrp.SystemBuffer
|
|
&& Notify->AllocatedBuffer != NULL) {
|
|
|
|
ExFreePool(Notify->AllocatedBuffer);
|
|
}
|
|
|
|
Notify->AllocatedBuffer = NULL;
|
|
Notify->ThisBufferLength = 0;
|
|
}
|
|
|
|
|
|
// Update the data length in the Irp.
|
|
|
|
|
|
NotifyIrp->IoStatus.Information = DataLength;
|
|
|
|
|
|
// Show that there is no buffer in the notify package
|
|
// anymore.
|
|
|
|
|
|
Notify->Buffer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Make sure the Irp is marked as pending returned.
|
|
|
|
|
|
IoMarkIrpPending(NotifyIrp);
|
|
|
|
|
|
// Now complete the request.
|
|
|
|
|
|
FsRtlCompleteRequest(NotifyIrp, Status);
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlIsNotifyCompleteIrp: Exit\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
BOOLEAN
|
|
FsRtlNotifySetCancelRoutine(
|
|
IN PIRP NotifyIrp,
|
|
IN PNOTIFY_CHANGE Notify OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a separate routine because it cannot be paged.
|
|
|
|
Arguments:
|
|
|
|
NotifyIrp - Set the cancel routine in this Irp.
|
|
|
|
Notify - If NULL then we are setting the cancel routine. If not-NULL then we
|
|
are clearing the cancel routine. If the cancel routine is not-null then
|
|
we need to decrement the reference count on this Notify structure
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - Only meaningfull if Notify is specified. It indicates if this
|
|
routine cleared the cancel routine. FALSE indicates that the cancel
|
|
routine is processing the Irp.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN ClearedCancel = FALSE;
|
|
PDRIVER_CANCEL CurrentCancel;
|
|
|
|
|
|
// Grab the cancel spinlock and set our cancel routine in the Irp.
|
|
|
|
|
|
IoAcquireCancelSpinLock(&NotifyIrp->CancelIrql);
|
|
|
|
|
|
// If we are completing an Irp then clear the cancel routine and
|
|
// the information field.
|
|
|
|
|
|
if (ARGUMENT_PRESENT(Notify)) {
|
|
|
|
CurrentCancel = IoSetCancelRoutine(NotifyIrp, NULL);
|
|
NotifyIrp->IoStatus.Information = 0;
|
|
|
|
IoReleaseCancelSpinLock(NotifyIrp->CancelIrql);
|
|
|
|
|
|
// If the current cancel routine is non-NULL then decrement the reference count
|
|
// in the Notify.
|
|
|
|
|
|
if (CurrentCancel != NULL) {
|
|
|
|
InterlockedDecrement(&Notify->ReferenceCount);
|
|
ClearedCancel = TRUE;
|
|
}
|
|
|
|
|
|
// If the cancel flag is set, we complete the Irp with cancelled
|
|
// status and exit.
|
|
|
|
|
|
}
|
|
else if (NotifyIrp->Cancel) {
|
|
|
|
DebugTrace(0, Dbg, "Irp has been cancelled\n", 0);
|
|
|
|
FsRtlCancelNotify(NULL, NotifyIrp);
|
|
|
|
}
|
|
else {
|
|
|
|
|
|
// Set our cancel routine in the Irp.
|
|
|
|
|
|
IoSetCancelRoutine(NotifyIrp, FsRtlCancelNotify);
|
|
|
|
IoReleaseCancelSpinLock(NotifyIrp->CancelIrql);
|
|
}
|
|
|
|
return ClearedCancel;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
BOOLEAN
|
|
FsRtlNotifyUpdateBuffer(
|
|
IN PFILE_NOTIFY_INFORMATION NotifyInfo,
|
|
IN ULONG FileAction,
|
|
IN PSTRING ParentName,
|
|
IN PSTRING TargetName,
|
|
IN PSTRING StreamName OPTIONAL,
|
|
IN BOOLEAN UnicodeName,
|
|
IN ULONG SizeOfEntry
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to fill in a FILE_NOTIFY_INFORMATION structure for a
|
|
notify change event. The main work is in converting an OEM string to Unicode.
|
|
|
|
Arguments:
|
|
|
|
NotifyInfo - Information structure to complete.
|
|
|
|
FileAction - Action which triggered the notification event.
|
|
|
|
ParentName - Relative path to the parent of the changed file from the
|
|
directory being watched. The length for this will be zero if the modified
|
|
file is in the watched directory.
|
|
|
|
TargetName - This is the name of the modified file.
|
|
|
|
StreamName - If present there is a stream name to append to the filename.
|
|
|
|
UnicodeName - Indicates if the above name is Unicode or Oem.
|
|
|
|
SizeOfEntry - Indicates the number of bytes to be used in the buffer.
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if we were able to update the buffer, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN CopiedToBuffer;
|
|
ULONG BufferOffset = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyUpdateBuffer: Entered\n", 0);
|
|
|
|
|
|
// Protect the entire call with a try-except. If we had an error
|
|
// we will assume that we have a bad buffer and we won't return
|
|
// the data in the buffer.
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Update the common fields in the notify information.
|
|
|
|
|
|
NotifyInfo->NextEntryOffset = 0;
|
|
NotifyInfo->Action = FileAction;
|
|
|
|
NotifyInfo->FileNameLength = SizeOfEntry - FIELD_OFFSET(FILE_NOTIFY_INFORMATION, FileName);
|
|
|
|
|
|
// If we have a unicode name, then copy the data directly into the output buffer.
|
|
|
|
|
|
if (UnicodeName) {
|
|
|
|
if (ParentName->Length != 0) {
|
|
|
|
RtlCopyMemory(NotifyInfo->FileName,
|
|
ParentName->Buffer,
|
|
ParentName->Length);
|
|
|
|
*(Add2Ptr(NotifyInfo->FileName, ParentName->Length, PWCHAR)) = L'\\';
|
|
BufferOffset = ParentName->Length + sizeof(WCHAR);
|
|
}
|
|
|
|
RtlCopyMemory(Add2Ptr(NotifyInfo->FileName,
|
|
BufferOffset,
|
|
PVOID),
|
|
TargetName->Buffer,
|
|
TargetName->Length);
|
|
|
|
if (ARGUMENT_PRESENT(StreamName)) {
|
|
|
|
BufferOffset += TargetName->Length;
|
|
|
|
*(Add2Ptr(NotifyInfo->FileName, BufferOffset, PWCHAR)) = L':';
|
|
|
|
RtlCopyMemory(Add2Ptr(NotifyInfo->FileName,
|
|
BufferOffset + sizeof(WCHAR),
|
|
PVOID),
|
|
StreamName->Buffer,
|
|
StreamName->Length);
|
|
}
|
|
|
|
|
|
// For a non-unicode name, use the conversion routines.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
ULONG BufferLength;
|
|
|
|
if (ParentName->Length != 0) {
|
|
|
|
RtlOemToUnicodeN(NotifyInfo->FileName,
|
|
NotifyInfo->FileNameLength,
|
|
&BufferLength,
|
|
ParentName->Buffer,
|
|
ParentName->Length);
|
|
|
|
*(Add2Ptr(NotifyInfo->FileName, BufferLength, PWCHAR)) = L'\\';
|
|
|
|
BufferOffset = BufferLength + sizeof(WCHAR);
|
|
}
|
|
|
|
|
|
// For view indices, we do not have a parent name.
|
|
|
|
|
|
if (ParentName->Length == 0) {
|
|
|
|
ASSERT(ARGUMENT_PRESENT(StreamName));
|
|
|
|
RtlCopyMemory(Add2Ptr(NotifyInfo->FileName,
|
|
BufferOffset,
|
|
PCHAR),
|
|
StreamName->Buffer,
|
|
StreamName->Length);
|
|
|
|
}
|
|
else {
|
|
|
|
RtlOemToUnicodeN(Add2Ptr(NotifyInfo->FileName,
|
|
BufferOffset,
|
|
PWCHAR),
|
|
NotifyInfo->FileNameLength,
|
|
&BufferLength,
|
|
TargetName->Buffer,
|
|
TargetName->Length);
|
|
|
|
if (ARGUMENT_PRESENT(StreamName)) {
|
|
|
|
BufferOffset += BufferLength;
|
|
|
|
*(Add2Ptr(NotifyInfo->FileName, BufferOffset, PWCHAR)) = L':';
|
|
|
|
RtlOemToUnicodeN(Add2Ptr(NotifyInfo->FileName,
|
|
BufferOffset + sizeof(WCHAR),
|
|
PWCHAR),
|
|
NotifyInfo->FileNameLength,
|
|
&BufferLength,
|
|
StreamName->Buffer,
|
|
StreamName->Length);
|
|
}
|
|
}
|
|
}
|
|
|
|
CopiedToBuffer = TRUE;
|
|
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
CopiedToBuffer = FALSE;
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyUpdateBuffer: Exit\n", 0);
|
|
|
|
return CopiedToBuffer;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
VOID
|
|
FsRtlNotifyCompleteIrpList(
|
|
IN OUT PNOTIFY_CHANGE Notify,
|
|
IN NTSTATUS Status
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks through the Irps for a particular notify structure
|
|
and completes the Irps with the indicated status. If the status is
|
|
STATUS_SUCCESS then we are completing an Irp because of a notification event.
|
|
In that case we look at the notify structure to decide if we can return the
|
|
data to the user.
|
|
|
|
Arguments:
|
|
|
|
Notify - This is the notify change structure.
|
|
|
|
Status - Indicates the status used to complete the request. If this status
|
|
is STATUS_SUCCESS then we only want to complete one Irp. Otherwise we
|
|
want complete all the Irps in the list.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PIRP Irp;
|
|
ULONG DataLength;
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlNotifyCompleteIrpList: Entered\n", 0);
|
|
|
|
DataLength = Notify->DataLength;
|
|
|
|
|
|
// Clear the fields to indicate that there is no more data to return.
|
|
|
|
|
|
ClearFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
Notify->DataLength = 0;
|
|
Notify->LastEntry = 0;
|
|
|
|
|
|
// Walk through all the Irps in the list. We are never called unless
|
|
// there is at least one irp.
|
|
|
|
|
|
do {
|
|
|
|
Irp = CONTAINING_RECORD(Notify->NotifyIrps.Flink, IRP, Tail.Overlay.ListEntry);
|
|
|
|
RemoveHeadList(&Notify->NotifyIrps);
|
|
|
|
|
|
// Call our completion routine to complete the request.
|
|
|
|
|
|
FsRtlNotifyCompleteIrp(Irp,
|
|
Notify,
|
|
DataLength,
|
|
Status,
|
|
TRUE);
|
|
|
|
|
|
// If we were only to complete one Irp then break now.
|
|
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
break;
|
|
}
|
|
|
|
} while (!IsListEmpty(&Notify->NotifyIrps));
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlNotifyCompleteIrpList: Exit\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
VOID
|
|
FsRtlCancelNotify(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP ThisIrp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is for an Irp which is being cancelled. We Null the cancel
|
|
routine and then walk through the Irps for this notify structure and
|
|
complete all cancelled Irps. It is possible there is pending notify
|
|
stored in the buffer for this Irp. In this case we want to copy the
|
|
data to a system buffer if possible.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Ignored.
|
|
|
|
ThisIrp - This is the Irp to cancel.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSECURITY_SUBJECT_CONTEXT SubjectContext = NULL;
|
|
|
|
PNOTIFY_CHANGE Notify;
|
|
PNOTIFY_SYNC NotifySync;
|
|
LONG ExceptionCode;
|
|
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
DebugTrace(+1, Dbg, "FsRtlCancelNotify: Entered\n", 0);
|
|
DebugTrace(0, Dbg, "Irp -> %08lx\n", Irp);
|
|
|
|
|
|
// Capture the notify structure.
|
|
|
|
|
|
Notify = (PNOTIFY_CHANGE)ThisIrp->IoStatus.Information;
|
|
|
|
|
|
// Void the cancel routine and release the cancel spinlock.
|
|
|
|
|
|
IoSetCancelRoutine(ThisIrp, NULL);
|
|
ThisIrp->IoStatus.Information = 0;
|
|
IoReleaseCancelSpinLock(ThisIrp->CancelIrql);
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
|
|
// Grab the mutex for this structure.
|
|
|
|
|
|
NotifySync = Notify->NotifySync;
|
|
AcquireNotifySync(NotifySync);
|
|
|
|
|
|
// Use a try finally to faciltate cleanup.
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Remove the Irp from the queue.
|
|
|
|
|
|
RemoveEntryList(&ThisIrp->Tail.Overlay.ListEntry);
|
|
|
|
IoMarkIrpPending(ThisIrp);
|
|
|
|
|
|
// We now have the Irp. Check to see if there is data stored
|
|
// in the buffer for this Irp.
|
|
|
|
|
|
if (Notify->Buffer != NULL
|
|
&& Notify->AllocatedBuffer == NULL
|
|
|
|
&& ((ThisIrp->MdlAddress != NULL
|
|
&& MmGetSystemAddressForMdl(ThisIrp->MdlAddress) == Notify->Buffer)
|
|
|
|
|| (Notify->Buffer == ThisIrp->AssociatedIrp.SystemBuffer))) {
|
|
|
|
PIRP NextIrp;
|
|
PVOID NewBuffer;
|
|
ULONG NewBufferLength;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
|
|
// Initialize the above values.
|
|
|
|
|
|
NewBuffer = NULL;
|
|
NewBufferLength = 0;
|
|
|
|
|
|
// Remember the next Irp on the list. Find the length of any
|
|
// buffer it might have. Also keep a pointer to the buffer
|
|
// if present.
|
|
|
|
|
|
if (!IsListEmpty(&Notify->NotifyIrps)) {
|
|
|
|
NextIrp = CONTAINING_RECORD(Notify->NotifyIrps.Flink,
|
|
IRP,
|
|
Tail.Overlay.ListEntry);
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(NextIrp);
|
|
|
|
|
|
// If the buffer here is large enough to hold the data we
|
|
// can use that buffer.
|
|
|
|
|
|
if (IrpSp->Parameters.NotifyDirectory.Length >= Notify->DataLength) {
|
|
|
|
|
|
// If there is a system buffer or Mdl then get a new
|
|
// buffer there.
|
|
|
|
|
|
if (NextIrp->AssociatedIrp.SystemBuffer != NULL) {
|
|
|
|
NewBuffer = NextIrp->AssociatedIrp.SystemBuffer;
|
|
|
|
}
|
|
else if (NextIrp->MdlAddress != NULL) {
|
|
|
|
NewBuffer = MmGetSystemAddressForMdl(NextIrp->MdlAddress);
|
|
}
|
|
|
|
NewBufferLength = IrpSp->Parameters.NotifyDirectory.Length;
|
|
|
|
if (NewBufferLength > Notify->BufferLength) {
|
|
|
|
NewBufferLength = Notify->BufferLength;
|
|
}
|
|
}
|
|
|
|
|
|
// Otherwise check if the user's original buffer is larger than
|
|
// the current buffer.
|
|
|
|
|
|
}
|
|
else if (Notify->BufferLength >= Notify->DataLength) {
|
|
|
|
NewBufferLength = Notify->BufferLength;
|
|
}
|
|
|
|
|
|
// If we have a new buffer length then we either have a new
|
|
// buffer or need to allocate one. We will do this under
|
|
// the protection of a try-except in order to continue in the
|
|
// event of a failure.
|
|
|
|
|
|
if (NewBufferLength != 0) {
|
|
|
|
BOOLEAN ChargedQuota;
|
|
|
|
try {
|
|
|
|
ChargedQuota = FALSE;
|
|
|
|
if (NewBuffer == NULL) {
|
|
|
|
PsChargePoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
NewBufferLength);
|
|
|
|
ChargedQuota = TRUE;
|
|
|
|
|
|
// If we didn't get an error then attempt to
|
|
// allocate the pool. If there is an error
|
|
// don't forget to release the quota.
|
|
|
|
|
|
NewBuffer = FsRtlpAllocatePool(PagedPool,
|
|
NewBufferLength);
|
|
|
|
Notify->AllocatedBuffer = NewBuffer;
|
|
}
|
|
|
|
|
|
// Now copy the data over to the new buffer.
|
|
|
|
|
|
RtlCopyMemory(NewBuffer,
|
|
Notify->Buffer,
|
|
Notify->DataLength);
|
|
|
|
|
|
// It is possible that the buffer size changed.
|
|
|
|
|
|
Notify->ThisBufferLength = NewBufferLength;
|
|
Notify->Buffer = NewBuffer;
|
|
|
|
} except(FsRtlIsNtstatusExpected(ExceptionCode = GetExceptionCode()) ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH) {
|
|
|
|
ASSERT((ExceptionCode == STATUS_INSUFFICIENT_RESOURCES) ||
|
|
(ExceptionCode == STATUS_QUOTA_EXCEEDED));
|
|
|
|
|
|
// Return quota if we allocated the buffer.
|
|
|
|
|
|
if (ChargedQuota) {
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
NewBufferLength);
|
|
}
|
|
|
|
|
|
// Forget any current buffer and resort to immediate
|
|
// notify.
|
|
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
}
|
|
|
|
|
|
// Otherwise set the immediate notify flag.
|
|
|
|
|
|
}
|
|
else {
|
|
|
|
SetFlag(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY);
|
|
}
|
|
|
|
|
|
// If the immediate notify flag is set then clear the other
|
|
// values in the notify structures.
|
|
|
|
|
|
if (FlagOn(Notify->Flags, NOTIFY_IMMEDIATE_NOTIFY)) {
|
|
|
|
|
|
// Forget any current buffer and resort to immediate
|
|
// notify.
|
|
|
|
|
|
Notify->AllocatedBuffer = Notify->Buffer = NULL;
|
|
|
|
Notify->ThisBufferLength =
|
|
Notify->DataLength = Notify->LastEntry = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Complete the Irp with status cancelled.
|
|
|
|
|
|
FsRtlCompleteRequest(ThisIrp, STATUS_CANCELLED);
|
|
|
|
|
|
// Decrement the count of Irps that might go through the cancel path.
|
|
|
|
|
|
InterlockedDecrement(&Notify->ReferenceCount);
|
|
|
|
if (Notify->ReferenceCount == 0) {
|
|
|
|
if (Notify->AllocatedBuffer != NULL) {
|
|
|
|
PsReturnPoolQuota(Notify->OwningProcess,
|
|
PagedPool,
|
|
Notify->ThisBufferLength);
|
|
|
|
ExFreePool(Notify->AllocatedBuffer);
|
|
}
|
|
|
|
if (Notify->FullDirectoryName != NULL) {
|
|
|
|
SubjectContext = Notify->SubjectContext;
|
|
}
|
|
|
|
ExFreePool(Notify);
|
|
Notify = NULL;
|
|
}
|
|
|
|
}
|
|
finally{
|
|
|
|
|
|
// No matter how we exit, we release the mutex.
|
|
|
|
|
|
ReleaseNotifySync(NotifySync);
|
|
|
|
if (SubjectContext != NULL) {
|
|
|
|
SeReleaseSubjectContext(SubjectContext);
|
|
ExFreePool(SubjectContext);
|
|
}
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
DebugTrace(-1, Dbg, "FsRtlCancelNotify: Exit\n", 0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
// Local support routine
|
|
|
|
|
|
VOID
|
|
FsRtlCheckNotifyForDelete(
|
|
IN PLIST_ENTRY NotifyListHead,
|
|
IN PVOID StreamID
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
This routine is called when a stream is being marked for delete. We will
|
|
walk through the notify structures looking for an Irp for the same stream.
|
|
We will complete these Irps with STATUS_DELETE_PENDING.
|
|
Arguments:
|
|
NotifyListHead - This is the start of the notify list.
|
|
StreamID - This is the Context ID used to identify the stream.
|
|
Return Value:
|
|
None.
|
|
--*/
|
|
{
|
|
PLIST_ENTRY Link;
|
|
|
|
PNOTIFY_CHANGE ThisNotify;
|
|
|
|
PAGED_CODE();
|
|
|
|
// Walk through all the entries on the list looking for a match.
|
|
for (Link = NotifyListHead->Flink; Link != NotifyListHead; Link = Link->Flink) {
|
|
// Obtain the notify structure from the link.
|
|
ThisNotify = CONTAINING_RECORD(Link, NOTIFY_CHANGE, NotifyList);
|
|
|
|
// If the context field matches, then complete any waiting Irps.
|
|
if (ThisNotify->StreamID == StreamID) {
|
|
// Start by marking the notify structure as file deleted.
|
|
SetFlag(ThisNotify->Flags, NOTIFY_STREAM_IS_DELETED);
|
|
|
|
// Now complete all of the Irps on this list.
|
|
if (!IsListEmpty(&ThisNotify->NotifyIrps)) {
|
|
FsRtlNotifyCompleteIrpList(ThisNotify, STATUS_DELETE_PENDING);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|