/*++ 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 Revision History: --*/ #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; // // 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) // // ULONG // LongAlign ( // IN ULONG Pointer // ); // #define LongAlign(P) ( \ ((((ULONG)(P)) + 3) & 0xfffffffc) \ ) // // 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)((ULONG)(OFFSET) - (ULONG)(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); \ } \ } // // 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 ); VOID FsRtlNotifySetCancelRoutine ( IN PIRP NotifyIrp, IN BOOLEAN NotifyComplete ); 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 Irp ); 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) FsRtlAllocatePool( 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 = FsRtlAllocatePool( 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 ); } // // 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 ); // // 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 ); try_return( NOTHING ); } // // Add the Irp to the tail of the notify queue. // NotifyIrp->IoStatus.Information = (ULONG) Notify; IoMarkIrpPending( NotifyIrp ); InsertTailList( &Notify->NotifyIrps, &NotifyIrp->Tail.Overlay.ListEntry ); // // Call the routine to set the cancel routine. // FsRtlNotifySetCancelRoutine( NotifyIrp, FALSE ); try_exit: NOTHING; } finally { // // Release the mutex. // ReleaseNotifySync( NotifySync ); // // If there is still a subject context then release it and deallocate // the structure. // if (SubjectContext != 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; UCHAR ComponentCount; ULONG SizeOfEntry; ULONG CurrentOffset; ULONG NextEntryOffset; PAGED_CODE(); DebugTrace( +1, Dbg, "FsRtlNotifyFullReportChange: Entered\n", 0 ); // // If this is a change to the root directory then return immediately. // if (TargetNameOffset == 0) { 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 ); // // 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 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 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 = 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 = FsRtlAllocatePool( PagedPool, AllocationLength ); Notify->ThisBufferLength = AllocationLength; NotifyInfo = Notify->Buffer; } except( (GetExceptionCode() == STATUS_QUOTA_EXCEEDED) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // // 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 ); if (Notify->AllocatedBuffer != NULL) { PsReturnPoolQuota( Notify->OwningProcess, PagedPool, Notify->ThisBufferLength ); ExFreePool( Notify->AllocatedBuffer ); } 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 ) /*++ 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. Return Value: None. --*/ { PIO_STACK_LOCATION IrpSp; PAGED_CODE(); DebugTrace( +1, Dbg, "FsRtlIsNotifyCompleteIrp: Entered\n", 0 ); // // Clear the Iosb in the Irp. // FsRtlNotifySetCancelRoutine( NotifyIrp, TRUE ); // // 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 // VOID FsRtlNotifySetCancelRoutine ( IN PIRP NotifyIrp, IN BOOLEAN NotifyComplete ) /*++ Routine Description: This is a separate routine because it cannot be paged. Arguments: NotifyIrp - Set the cancel routine in this Irp. NotifyComplete - Set to TRUE if we are in the process of completing an Irp normally. If so then we simply need to clear the necessary Irp fields while holding the spinlock. Return Value: None. --*/ { // // 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 (NotifyComplete) { IoSetCancelRoutine( NotifyIrp, NULL ); NotifyIrp->IoStatus.Information = 0; IoReleaseCancelSpinLock( NotifyIrp->CancelIrql ); // // 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; } // // 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 ); } 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 ); // // We grab the cancel spinlock and clear the // cancel routine. // IoAcquireCancelSpinLock( &Irp->CancelIrql ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( Irp->CancelIrql ); RemoveHeadList( &Notify->NotifyIrps ); // // Call our completion routine to complete the request. // FsRtlNotifyCompleteIrp( Irp, Notify, DataLength, Status ); // // 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 Irp ) /*++ 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. Irp - This is the Irp to cancel. Return Value: None. --*/ { PLIST_ENTRY Links; PIRP ThisIrp; PNOTIFY_CHANGE Notify; UNREFERENCED_PARAMETER( DeviceObject ); DebugTrace( +1, Dbg, "FsRtlCancelNotify: Entered\n", 0 ); DebugTrace( 0, Dbg, "Irp -> %08lx\n", Irp ); Notify = (PNOTIFY_CHANGE) Irp->IoStatus.Information; // // Void the cancel routine and release the cancel spinlock. // IoSetCancelRoutine( Irp, NULL ); Irp->IoStatus.Information = 0; IoReleaseCancelSpinLock( Irp->CancelIrql ); FsRtlEnterFileSystem(); // // Grab the mutex for this structure. // AcquireNotifySync( Notify->NotifySync ); // // Use a try finally to faciltate cleanup. // try { Links = Notify->NotifyIrps.Flink; // // Look at all the Irps for this structure. // while (Links != &Notify->NotifyIrps) { ThisIrp = CONTAINING_RECORD( Links, IRP, Tail.Overlay.ListEntry ); // // Check if this Irp has been cancelled. // if (ThisIrp->Cancel) { // // Now we need to remove this waiter and call the // completion routine. But we must not mess up our link // iteration so we need to back up link one step and // then the next iteration will go to our current flink. // Links = Links->Blink; // // Remove the Irp from the queue. // RemoveEntryList( Links->Flink ); 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 = FsRtlAllocatePool( 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( (GetExceptionCode() == STATUS_QUOTA_EXCEEDED) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { // // 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; } } // // If this is not the Irp we were passed then we need // to carefully clear the cancel information. Otherwise // a real cancel on this Irp may come in and we might // look at stale values in the .Information field. // if (ThisIrp != Irp) { FsRtlNotifySetCancelRoutine( ThisIrp, TRUE ); } // // Complete the Irp with status cancelled. // FsRtlCompleteRequest( ThisIrp, STATUS_CANCELLED ); } Links = Links->Flink; } } finally { // // No matter how we exit, we release the mutex. // ReleaseNotifySync( Notify->NotifySync ); 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; }