+ PIRP Irp;
+ PVOID OutputBuffer;
+ USHORT FullPosition;
+ PLIST_ENTRY NextEntry;
+ PIO_STACK_LOCATION Stack;
+ PNOTIFY_CHANGE NotifyChange;
+ PREAL_NOTIFY_SYNC RealNotifySync;
+ PFILE_NOTIFY_INFORMATION FileNotifyInfo;
+ BOOLEAN IsStream, IsParent, PoolQuotaCharged;
+ STRING TargetDirectory, TargetName, ParentName, IntNormalizedParentName;
+ ULONG NumberOfBytes, TargetNumberOfParts, FullNumberOfParts, LastPartOffset, ParentNameOffset, ParentNameLength;
+ ULONG DataLength, TargetNameLength, AlignedDataLength;
+
+ TargetDirectory.Length = 0;
+ TargetDirectory.MaximumLength = 0;
+ TargetDirectory.Buffer = NULL;
+ TargetName.Length = 0;
+ TargetName.MaximumLength = 0;
+ TargetName.Buffer = NULL;
+ ParentName.Length = 0;
+ ParentName.MaximumLength = 0;
+ ParentName.Buffer = NULL;
+ IsStream = FALSE;
+
+ PAGED_CODE();
+
+ DPRINT("FsRtlNotifyFilterReportChange(%p, %p, %p, %u, %p, %p, %p, %x, %x, %p, %p)\n",
+ NotifySync, NotifyList, FullTargetName, TargetNameOffset, StreamName, NormalizedParentName,
+ FilterMatch, Action, TargetContext, FilterContext);
+
+ /* We need offset in name */
+ if (!TargetNameOffset && FullTargetName)
+ {
+ return;
+ }
+
+ /* Get real structure hidden behind the opaque pointer */
+ RealNotifySync = (PREAL_NOTIFY_SYNC)NotifySync;
+ /* Acquire lock - will be released in finally block */
+ FsRtlNotifyAcquireFastMutex(RealNotifySync);
+ _SEH2_TRY
+ {
+ /* Browse all the registered notifications we have */
+ for (NextEntry = NotifyList->Flink; NextEntry != NotifyList;
+ NextEntry = NextEntry->Flink)
+ {
+ /* Try to find an entry matching our change */
+ NotifyChange = CONTAINING_RECORD(NextEntry, NOTIFY_CHANGE, NotifyList);
+ if (FullTargetName != NULL)
+ {
+ ASSERT(NotifyChange->FullDirectoryName != NULL);
+ if (!NotifyChange->FullDirectoryName->Length)
+ {
+ continue;
+ }
+
+ if (!(FilterMatch & NotifyChange->CompletionFilter))
+ {
+ continue;
+ }
+
+ /* If no normalized name provided, construct it from full target name */
+ if (NormalizedParentName == NULL)
+ {
+ IntNormalizedParentName.Buffer = FullTargetName->Buffer;
+ if (TargetNameOffset != NotifyChange->CharacterSize)
+ {
+ IntNormalizedParentName.MaximumLength =
+ IntNormalizedParentName.Length = TargetNameOffset - NotifyChange->CharacterSize;
+ }
+ else
+ {
+ IntNormalizedParentName.MaximumLength =
+ IntNormalizedParentName.Length = TargetNameOffset;
+ }
+ NormalizedParentName = &IntNormalizedParentName;
+ }
+
+ /* heh? Watched directory bigger than changed file? */
+ if (NormalizedParentName->Length < NotifyChange->FullDirectoryName->Length)
+ {
+ continue;
+ }
+
+ /* Same len => parent */
+ if (NormalizedParentName->Length == NotifyChange->FullDirectoryName->Length)
+ {
+ IsParent = TRUE;
+ }
+ /* If not, then, we have to be watching the tree, otherwise we don't have to report such changes */
+ else if (!(NotifyChange->Flags & WATCH_TREE))
+ {
+ continue;
+ }
+ /* And finally, we've to check we're properly \-terminated */
+ else
+ {
+ if (!(NotifyChange->Flags & WATCH_ROOT))
+ {
+ if (NotifyChange->CharacterSize == sizeof(CHAR))
+ {
+ if (((PSTR)NormalizedParentName->Buffer)[NotifyChange->FullDirectoryName->Length] != '\\')
+ {
+ continue;
+ }
+ }
+ else
+ {
+ if (((PWSTR)NormalizedParentName->Buffer)[NotifyChange->FullDirectoryName->Length / sizeof (WCHAR)] != L'\\')
+ {
+ continue;
+ }
+ }
+ }
+
+ IsParent = FALSE;
+ }
+
+ /* If len matches, then check that both name are equal */
+ if (!RtlEqualMemory(NormalizedParentName->Buffer, NotifyChange->FullDirectoryName->Buffer,
+ NotifyChange->FullDirectoryName->Length))
+ {
+ continue;
+ }
+
+ /* Call traverse callback (only if we have to traverse ;-)) */
+ if (!IsParent
+ && NotifyChange->TraverseCallback != NULL
+ && !NotifyChange->TraverseCallback(NotifyChange->FsContext,
+ TargetContext,
+ NotifyChange->SubjectContext))
+ {
+ continue;
+ }
+
+ /* And then, filter callback if provided */
+ if (NotifyChange->FilterCallback != NULL
+ && FilterContext != NULL
+ && !NotifyChange->FilterCallback(NotifyChange->FsContext, FilterContext))
+ {
+ continue;
+ }
+ }
+ /* We have a stream! */
+ else
+ {
+ ASSERT(NotifyChange->FullDirectoryName == NULL);
+ if (TargetContext != NotifyChange->SubjectContext)
+ {
+ continue;
+ }
+
+ ParentName.Buffer = NULL;
+ ParentName.Length = 0;
+ IsStream = TRUE;
+ IsParent = FALSE;
+ }
+
+ /* If we don't have to notify immediately, prepare for output */
+ if (!(NotifyChange->Flags & NOTIFY_IMMEDIATELY))
+ {
+ /* If we have something to output... */
+ if (NotifyChange->BufferLength)
+ {
+ /* Get size of the output */
+ NumberOfBytes = 0;
+ Irp = NULL;
+ if (!NotifyChange->ThisBufferLength)
+ {
+ if (IsListEmpty(&NotifyChange->NotifyIrps))
+ {
+ NumberOfBytes = NotifyChange->BufferLength;
+ }
+ else
+ {
+ Irp = CONTAINING_RECORD(NotifyChange->NotifyIrps.Flink, IRP, Tail.Overlay.ListEntry);
+ Stack = IoGetCurrentIrpStackLocation(Irp);
+ NumberOfBytes = Stack->Parameters.NotifyDirectory.Length;
+ }
+ }
+ else
+ {
+ NumberOfBytes = NotifyChange->ThisBufferLength;
+ }
+
+ /* If we're matching parent, we don't care about parent (redundant) */
+ if (IsParent)
+ {
+ ParentName.Length = 0;
+ }
+ else
+ {
+ /* If we don't deal with streams, some more work is required */
+ if (!IsStream)
+ {
+ if (NotifyChange->Flags & WATCH_ROOT ||
+ (NormalizedParentName->Buffer != FullTargetName->Buffer))
+ {
+ /* Construct TargetDirectory if we don't have it yet */
+ if (TargetDirectory.Buffer == NULL)
+ {
+ TargetDirectory.Buffer = FullTargetName->Buffer;
+ TargetDirectory.Length = TargetNameOffset;
+ if (TargetNameOffset != NotifyChange->CharacterSize)
+ {
+ TargetDirectory.Length = TargetNameOffset - NotifyChange->CharacterSize;
+ }
+ TargetDirectory.MaximumLength = TargetDirectory.Length;
+ }
+ /* Now, we start looking for matching parts (unless we watch root) */
+ TargetNumberOfParts = 0;
+ if (!(NotifyChange->Flags & WATCH_ROOT))
+ {
+ FullNumberOfParts = 1;
+ if (NotifyChange->CharacterSize == sizeof(CHAR))
+ {
+ FsRtlNotifyGetLastPartOffset(NotifyChange->FullDirectoryName->Length,
+ TargetDirectory.Length, PSTR, '\\');
+ }
+ else
+ {
+ FsRtlNotifyGetLastPartOffset(NotifyChange->FullDirectoryName->Length / sizeof(WCHAR),
+ TargetDirectory.Length / sizeof(WCHAR), PWSTR, L'\\');
+ LastPartOffset *= NotifyChange->CharacterSize;
+ }
+ }
+
+ /* Then, we can construct proper parent name */
+ ParentNameOffset = NotifyChange->CharacterSize + LastPartOffset;
+ ParentName.Buffer = &TargetDirectory.Buffer[ParentNameOffset];
+ ParentNameLength = TargetDirectory.Length;
+ }
+ else
+ {
+ /* Construct parent name even for streams */
+ ParentName.Buffer = &NormalizedParentName->Buffer[NotifyChange->FullDirectoryName->Length] + NotifyChange->CharacterSize;
+ ParentNameLength = NormalizedParentName->Length - NotifyChange->FullDirectoryName->Length;
+ ParentNameOffset = NotifyChange->CharacterSize;
+ }
+ ParentNameLength -= ParentNameOffset;
+ ParentName.Length = ParentNameLength;
+ ParentName.MaximumLength = ParentNameLength;
+ }
+ }
+
+ /* Start to count amount of data to write, we've first the structure itself */
+ DataLength = FIELD_OFFSET(FILE_NOTIFY_INFORMATION, FileName);
+
+ /* If stream, we'll just append stream name */
+ if (IsStream)
+ {
+ ASSERT(StreamName != NULL);
+ DataLength += StreamName->Length;
+ }
+ else
+ {
+ /* If not parent, we've to append parent name */
+ if (!IsParent)
+ {
+ if (NotifyChange->CharacterSize == sizeof(CHAR))
+ {
+ DataLength += RtlOemStringToCountedUnicodeSize(&ParentName);
+ }
+ else
+ {
+ DataLength += ParentName.Length;
+ }
+ DataLength += sizeof(WCHAR);
+ }
+
+ /* Look for target name & construct it, if required */
+ if (TargetName.Buffer)
+ {
+ TargetNameLength = TargetName.Length;
+ }
+ else
+ {
+ TargetName.Buffer = &FullTargetName->Buffer[TargetNameOffset];
+ TargetNameLength = FullTargetName->Length - TargetNameOffset;
+ TargetName.Length = TargetNameLength;
+ TargetName.MaximumLength = TargetNameLength;
+ }
+
+ /* Then, we will append it as well */
+ if (NotifyChange->CharacterSize == sizeof(CHAR))
+ {
+ DataLength += RtlOemStringToCountedUnicodeSize(&TargetName);
+ }
+ else
+ {
+ DataLength += TargetName.Length;
+ }
+
+ /* If we also had a stream name, then we can append it as well */
+ if (StreamName != NULL)
+ {
+ if (NotifyChange->CharacterSize == sizeof(WCHAR))
+ {
+ DataLength += StreamName->Length + sizeof(WCHAR);
+ }
+ else
+ {
+ DataLength = DataLength + RtlOemStringToCountedUnicodeSize(&TargetName) + sizeof(CHAR);
+ }
+ }
+ }
+
+ /* Get the position where we can put our data (aligned!) */
+ AlignedDataLength = ROUND_UP(NotifyChange->DataLength, sizeof(ULONG));
+ /* If it's higher than buffer length, then, bail out without outputing */
+ if (DataLength > NumberOfBytes || AlignedDataLength + DataLength > NumberOfBytes)
+ {
+ NotifyChange->Flags |= NOTIFY_IMMEDIATELY;
+ }
+ else
+ {
+ OutputBuffer = 0;
+ FileNotifyInfo = 0;
+ /* If we already had a buffer, update last entry position */
+ if (NotifyChange->Buffer != NULL)
+ {
+ FileNotifyInfo = (PVOID)((ULONG_PTR)NotifyChange->Buffer + NotifyChange->LastEntry);
+ FileNotifyInfo->NextEntryOffset = AlignedDataLength - NotifyChange->LastEntry;
+ NotifyChange->LastEntry = AlignedDataLength;
+ /* And get our output buffer */
+ OutputBuffer = (PVOID)((ULONG_PTR)NotifyChange->Buffer + AlignedDataLength);
+ }
+ /* If we hadn't buffer, try to find one */
+ else if (Irp != NULL)
+ {
+ if (Irp->AssociatedIrp.SystemBuffer != NULL)
+ {
+ OutputBuffer = Irp->AssociatedIrp.SystemBuffer;
+ }
+ else if (Irp->MdlAddress != NULL)
+ {
+ OutputBuffer = MmGetSystemAddressForMdl(Irp->MdlAddress);
+ }
+
+ NotifyChange->Buffer = OutputBuffer;
+ NotifyChange->ThisBufferLength = NumberOfBytes;
+ }
+
+ /* If we couldn't find one, then allocate one */
+ if (NotifyChange->Buffer == NULL)
+ {
+ PoolQuotaCharged = FALSE;
+ _SEH2_TRY
+ {
+ PsChargePoolQuota(NotifyChange->OwningProcess, PagedPool, NumberOfBytes);
+ PoolQuotaCharged = TRUE;
+ OutputBuffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
+ NumberOfBytes, 'NrSF');
+ NotifyChange->Buffer = OutputBuffer;
+ NotifyChange->AllocatedBuffer = OutputBuffer;
+ }
+ /* If something went wrong during allocation, notify immediately instead of outputing */
+ _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+ {
+ if (PoolQuotaCharged)
+ {
+ PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NumberOfBytes);
+ }
+ NotifyChange->Flags |= NOTIFY_IMMEDIATELY;
+ }
+ _SEH2_END;
+ }
+
+ /* Finally, if we have a buffer, fill it in! */
+ if (OutputBuffer != NULL)
+ {
+ if (FsRtlNotifyUpdateBuffer((FILE_NOTIFY_INFORMATION *)OutputBuffer,
+ Action, &ParentName, &TargetName,
+ StreamName, NotifyChange->CharacterSize == sizeof(WCHAR),
+ DataLength))
+ {
+ NotifyChange->DataLength = DataLength + AlignedDataLength;
+ }
+ /* If it failed, notify immediately */
+ else
+ {
+ NotifyChange->Flags |= NOTIFY_IMMEDIATELY;
+ }
+ }
+ }
+
+ /* If we have to notify right now (something went wrong?) */
+ if (NotifyChange->Flags & NOTIFY_IMMEDIATELY)
+ {
+ /* Ensure that all our buffers are NULL */
+ if (NotifyChange->Buffer != NULL)
+ {
+ if (NotifyChange->AllocatedBuffer != NULL)
+ {
+ PsReturnProcessPagedPoolQuota(NotifyChange->OwningProcess, NotifyChange->ThisBufferLength);
+ ExFreePoolWithTag(NotifyChange->AllocatedBuffer, 'NrSF');
+ }
+
+ NotifyChange->Buffer = NULL;
+ NotifyChange->AllocatedBuffer = NULL;
+ NotifyChange->LastEntry = 0;
+ NotifyChange->DataLength = 0;
+ NotifyChange->ThisBufferLength = 0;
+ }
+ }
+ }
+ }
+
+ /* If asking for old name in case of a rename, notify later on,
+ * so that we can wait for new name.
+ * http://msdn.microsoft.com/en-us/library/dn392331.aspx
+ */
+ if (Action == FILE_ACTION_RENAMED_OLD_NAME)
+ {
+ NotifyChange->Flags |= NOTIFY_LATER;
+ }
+ else
+ {
+ NotifyChange->Flags &= ~NOTIFY_LATER;
+ if (!IsListEmpty(&NotifyChange->NotifyIrps))
+ {
+ FsRtlNotifyCompleteIrpList(NotifyChange, STATUS_SUCCESS);
+ }
+ }
+ }
+ }
+ _SEH2_FINALLY
+ {
+ FsRtlNotifyReleaseFastMutex(RealNotifySync);
+ }
+ _SEH2_END;