2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/fsrtl/notify.c
5 * PURPOSE: Change Notifications and Sync for File System Drivers
6 * PROGRAMMERS: Pierre Schweitzer
9 /* INCLUDES ******************************************************************/
15 /* PRIVATE FUNCTIONS *********************************************************/
18 FsRtlNotifyCompleteIrpList(IN PNOTIFY_CHANGE NotifyChange
,
22 FsRtlNotifySetCancelRoutine(IN PIRP Irp
,
23 IN PNOTIFY_CHANGE NotifyChange OPTIONAL
);
27 FsRtlCancelNotify(IN PDEVICE_OBJECT DeviceObject
,
30 IoReleaseCancelSpinLock(Irp
->CancelIrql
);
38 FsRtlCheckNotifyForDelete(IN PLIST_ENTRY NotifyList
,
41 PLIST_ENTRY NextEntry
;
42 PNOTIFY_CHANGE NotifyChange
;
44 if (!IsListEmpty(NotifyList
))
46 /* Browse the notifications list to find the matching entry */
47 for (NextEntry
= NotifyList
->Flink
;
48 NextEntry
!= NotifyList
;
49 NextEntry
= NextEntry
->Flink
)
51 NotifyChange
= CONTAINING_RECORD(NextEntry
, NOTIFY_CHANGE
, NotifyList
);
52 /* If the current record matches with the given context, it's the good one */
53 if (NotifyChange
->FsContext
== FsContext
&& !IsListEmpty(&(NotifyChange
->NotifyIrps
)))
55 FsRtlNotifyCompleteIrpList(NotifyChange
, STATUS_DELETE_PENDING
);
62 FsRtlIsNotifyOnList(IN PLIST_ENTRY NotifyList
,
65 PLIST_ENTRY NextEntry
;
66 PNOTIFY_CHANGE NotifyChange
;
68 if (!IsListEmpty(NotifyList
))
70 /* Browse the notifications list to find the matching entry */
71 for (NextEntry
= NotifyList
->Flink
;
72 NextEntry
!= NotifyList
;
73 NextEntry
= NextEntry
->Flink
)
75 NotifyChange
= CONTAINING_RECORD(NextEntry
, NOTIFY_CHANGE
, NotifyList
);
76 /* If the current record matches with the given context, it's the good one */
77 if (NotifyChange
->FsContext
== FsContext
)
88 FsRtlNotifyAcquireFastMutex(IN PREAL_NOTIFY_SYNC RealNotifySync
)
90 ULONG_PTR CurrentThread
= (ULONG_PTR
)KeGetCurrentThread();
92 /* Only acquire fast mutex if it's not already acquired by the current thread */
93 if (RealNotifySync
->OwningThread
!= CurrentThread
)
95 ExAcquireFastMutexUnsafe(&(RealNotifySync
->FastMutex
));
96 RealNotifySync
->OwningThread
= CurrentThread
;
98 /* Whatever the case, keep trace of the attempt to acquire fast mutex */
99 RealNotifySync
->OwnerCount
++;
106 FsRtlNotifyCompleteIrp(IN PIRP Irp
,
107 IN PNOTIFY_CHANGE NotifyChange
,
110 IN BOOLEAN SkipCompletion
)
113 PIO_STACK_LOCATION Stack
;
117 /* Check if we need to complete */
118 if (!FsRtlNotifySetCancelRoutine(Irp
, NotifyChange
) && SkipCompletion
)
123 /* No succes => no data to return just complete */
124 if (Status
!= STATUS_SUCCESS
)
129 /* Ensure there's something to return */
130 Stack
= IoGetCurrentIrpStackLocation(Irp
);
131 if (!DataLength
|| Stack
->Parameters
.NotifyDirectory
.Length
< DataLength
)
133 Status
= STATUS_NOTIFY_ENUM_DIR
;
137 /* Ensture there's a buffer where to find data */
138 if (!NotifyChange
->AllocatedBuffer
)
140 Irp
->IoStatus
.Information
= DataLength
;
141 NotifyChange
->Buffer
= NULL
;
145 /* Now, browse all the way to return data
146 * and find the one that will work. We will
147 * return data whatever happens
149 if (Irp
->AssociatedIrp
.SystemBuffer
)
151 Buffer
= Irp
->AssociatedIrp
.SystemBuffer
;
152 goto CopyAndComplete
;
157 Buffer
= MmGetSystemAddressForMdl(Irp
->MdlAddress
);
158 goto CopyAndComplete
;
161 if (!(Stack
->Control
& SL_PENDING_RETURNED
))
163 Buffer
= Irp
->UserBuffer
;
164 goto CopyAndComplete
;
167 Irp
->Flags
|= (IRP_BUFFERED_IO
| IRP_DEALLOCATE_BUFFER
| IRP_SYNCHRONOUS_PAGING_IO
);
168 Irp
->AssociatedIrp
.SystemBuffer
= NotifyChange
->AllocatedBuffer
;
169 /* Nothing to copy */
170 goto ReleaseAndComplete
;
175 RtlCopyMemory(Buffer
, NotifyChange
->AllocatedBuffer
, DataLength
);
177 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
184 PsReturnProcessPagedPoolQuota(NotifyChange
->OwningProcess
, NotifyChange
->ThisBufferLength
);
186 /* Release buffer UNLESS it's used */
187 if (NotifyChange
->AllocatedBuffer
!= Irp
->AssociatedIrp
.SystemBuffer
&&
188 NotifyChange
->AllocatedBuffer
)
190 ExFreePoolWithTag(NotifyChange
->AllocatedBuffer
, 0);
193 /* Prepare for return */
194 NotifyChange
->AllocatedBuffer
= 0;
195 NotifyChange
->ThisBufferLength
= 0;
196 Irp
->IoStatus
.Information
= DataLength
;
197 NotifyChange
->Buffer
= NULL
;
199 /* Finally complete */
201 IoMarkIrpPending(Irp
);
202 Irp
->IoStatus
.Status
= Status
;
203 IofCompleteRequest(Irp
, EVENT_INCREMENT
);
210 FsRtlNotifyCompleteIrpList(IN PNOTIFY_CHANGE NotifyChange
,
215 PLIST_ENTRY NextEntry
;
217 DataLength
= NotifyChange
->DataLength
;
219 NotifyChange
->Flags
&= (INVALIDATE_BUFFERS
| WATCH_TREE
);
220 NotifyChange
->DataLength
= 0;
221 NotifyChange
->LastEntry
= 0;
223 while (!IsListEmpty(&(NotifyChange
->NotifyIrps
)))
225 /* We take the first entry */
226 NextEntry
= RemoveHeadList(&(NotifyChange
->NotifyIrps
));
227 Irp
= CONTAINING_RECORD(NextEntry
, IRP
, Tail
.Overlay
.ListEntry
);
229 FsRtlNotifyCompleteIrp(Irp
, NotifyChange
, DataLength
, Status
, TRUE
);
230 /* If we're notifying success, just notify first one */
231 if (Status
== STATUS_SUCCESS
)
238 FsRtlNotifyReleaseFastMutex(IN PREAL_NOTIFY_SYNC RealNotifySync
)
240 RealNotifySync
->OwnerCount
--;
241 /* Release the fast mutex only if no other instance needs it */
242 if (!RealNotifySync
->OwnerCount
)
244 ExReleaseFastMutexUnsafe(&(RealNotifySync
->FastMutex
));
245 RealNotifySync
->OwningThread
= (ULONG_PTR
)0;
253 FsRtlNotifySetCancelRoutine(IN PIRP Irp
,
254 IN PNOTIFY_CHANGE NotifyChange OPTIONAL
)
256 PDRIVER_CANCEL CancelRoutine
;
258 /* Acquire cancel lock */
259 IoAcquireCancelSpinLock(&Irp
->CancelIrql
);
261 /* If NotifyChange was given */
264 /* First get cancel routine */
265 CancelRoutine
= IoSetCancelRoutine(Irp
, NULL
);
266 Irp
->IoStatus
.Information
= 0;
267 /* Release cancel lock */
268 IoReleaseCancelSpinLock(Irp
->CancelIrql
);
269 /* If there was a cancel routine */
272 /* Decrease reference count */
273 InterlockedDecrement((PLONG
)&NotifyChange
->ReferenceCount
);
274 /* Notify that we removed cancel routine */
280 /* If IRP is cancel, call FsRtl cancel routine */
283 FsRtlCancelNotify(NULL
, Irp
);
287 /* Otherwise, define FsRtl cancel routine as IRP cancel routine */
288 IoSetCancelRoutine(Irp
, FsRtlCancelNotify
);
290 IoReleaseCancelSpinLock(Irp
->CancelIrql
);
294 /* Return that we didn't removed cancel routine */
298 /* PUBLIC FUNCTIONS **********************************************************/
301 * @name FsRtlNotifyChangeDirectory
304 * Lets FSD know if changes occures in the specified directory.
305 * Directory will be reenumerated.
308 * Synchronization object pointer
311 * Used to identify the notify structure
313 * @param FullDirectoryName
314 * String (A or W) containing the full directory name
317 * Notify list pointer (to head)
320 * True to notify changes in subdirectories too
322 * @param CompletionFilter
323 * Used to define types of changes to notify
326 * IRP pointer to complete notify operation. It can be null
330 * @remarks This function only redirects to FsRtlNotifyFilterChangeDirectory.
335 FsRtlNotifyChangeDirectory(IN PNOTIFY_SYNC NotifySync
,
337 IN PSTRING FullDirectoryName
,
338 IN PLIST_ENTRY NotifyList
,
339 IN BOOLEAN WatchTree
,
340 IN ULONG CompletionFilter
,
343 FsRtlNotifyFilterChangeDirectory(NotifySync
,
357 * @name FsRtlNotifyCleanup
360 * Called by FSD when all handles to FileObject (identified by FsContext) are closed
363 * Synchronization object pointer
366 * Notify list pointer (to head)
369 * Used to identify the notify structure
378 FsRtlNotifyCleanup(IN PNOTIFY_SYNC NotifySync
,
379 IN PLIST_ENTRY NotifyList
,
382 PNOTIFY_CHANGE NotifyChange
;
383 PREAL_NOTIFY_SYNC RealNotifySync
;
384 PSECURITY_SUBJECT_CONTEXT SubjectContext
= NULL
;
386 /* Get real structure hidden behind the opaque pointer */
387 RealNotifySync
= (PREAL_NOTIFY_SYNC
)NotifySync
;
389 /* Acquire the fast mutex */
390 FsRtlNotifyAcquireFastMutex(RealNotifySync
);
394 /* Find if there's a matching notification with the FsContext */
395 NotifyChange
= FsRtlIsNotifyOnList(NotifyList
, FsContext
);
398 /* Mark it as to know that cleanup is in process */
399 NotifyChange
->Flags
|= CLEANUP_IN_PROCESS
;
401 /* If there are pending IRPs, complete them using the STATUS_NOTIFY_CLEANUP status */
402 if (!IsListEmpty(&NotifyChange
->NotifyIrps
))
404 FsRtlNotifyCompleteIrpList(NotifyChange
, STATUS_NOTIFY_CLEANUP
);
407 /* Decrease reference number and if 0 is reached, it's time to do complete cleanup */
408 if (!InterlockedDecrement((PLONG
)&(NotifyChange
->ReferenceCount
)))
410 /* Remove it from the notifications list */
411 RemoveEntryList(&NotifyChange
->NotifyList
);
413 /* In case there was an allocated buffer, free it */
414 if (NotifyChange
->AllocatedBuffer
)
416 PsReturnProcessPagedPoolQuota(NotifyChange
->OwningProcess
, NotifyChange
->ThisBufferLength
);
417 ExFreePool(NotifyChange
->AllocatedBuffer
);
420 /* In case there the string was set, get the captured subject security context */
421 if (NotifyChange
->FullDirectoryName
)
423 SubjectContext
= NotifyChange
->SubjectContext
;
426 /* Finally, free the notification, as it's not needed anymore */
427 ExFreePool(NotifyChange
);
433 /* Release fast mutex */
434 FsRtlNotifyReleaseFastMutex(RealNotifySync
);
436 /* If the subject security context was captured, release and free it */
439 SeReleaseSubjectContext(SubjectContext
);
440 ExFreePool(SubjectContext
);
447 * @name FsRtlNotifyFilterChangeDirectory
461 * @param FullDirectoryName
467 * @param IgnoreBuffer
470 * @param CompletionFilter
476 * @param TraverseCallback
479 * @param SubjectContext
482 * @param FilterCallback
492 FsRtlNotifyFilterChangeDirectory(IN PNOTIFY_SYNC NotifySync
,
493 IN PLIST_ENTRY NotifyList
,
495 IN PSTRING FullDirectoryName
,
496 IN BOOLEAN WatchTree
,
497 IN BOOLEAN IgnoreBuffer
,
498 IN ULONG CompletionFilter
,
500 IN PCHECK_FOR_TRAVERSE_ACCESS TraverseCallback OPTIONAL
,
501 IN PSECURITY_SUBJECT_CONTEXT SubjectContext OPTIONAL
,
502 IN PFILTER_REPORT_CHANGE FilterCallback OPTIONAL
)
504 KeBugCheck(FILE_SYSTEM
);
508 * @name FsRtlNotifyFilterReportChange
519 * @param FullTargetName
522 * @param TargetNameOffset
528 * @param NormalizedParentName
537 * @param TargetContext
540 * @param FilterContext
550 FsRtlNotifyFilterReportChange(IN PNOTIFY_SYNC NotifySync
,
551 IN PLIST_ENTRY NotifyList
,
552 IN PSTRING FullTargetName
,
553 IN USHORT TargetNameOffset
,
554 IN PSTRING StreamName OPTIONAL
,
555 IN PSTRING NormalizedParentName OPTIONAL
,
556 IN ULONG FilterMatch
,
558 IN PVOID TargetContext
,
559 IN PVOID FilterContext
)
561 KeBugCheck(FILE_SYSTEM
);
565 * @name FsRtlNotifyFullChangeDirectory
568 * Lets FSD know if changes occures in the specified directory.
571 * Synchronization object pointer
574 * Notify list pointer (to head)
577 * Used to identify the notify structure
579 * @param FullDirectoryName
580 * String (A or W) containing the full directory name
583 * True to notify changes in subdirectories too
585 * @param IgnoreBuffer
586 * True to reenumerate directory. It's ignored it NotifyIrp is null
588 * @param CompletionFilter
589 * Used to define types of changes to notify
592 * IRP pointer to complete notify operation. It can be null
594 * @param TraverseCallback
595 * Pointer to a callback function. It's called each time a change is
596 * done in a subdirectory of the main directory. It's ignored it NotifyIrp
599 * @param SubjectContext
600 * Pointer to pass to SubjectContext member of TraverseCallback.
601 * It's freed after use. It's ignored it NotifyIrp is null
605 * @remarks This function only redirects to FsRtlNotifyFilterChangeDirectory.
610 FsRtlNotifyFullChangeDirectory(IN PNOTIFY_SYNC NotifySync
,
611 IN PLIST_ENTRY NotifyList
,
613 IN PSTRING FullDirectoryName
,
614 IN BOOLEAN WatchTree
,
615 IN BOOLEAN IgnoreBuffer
,
616 IN ULONG CompletionFilter
,
618 IN PCHECK_FOR_TRAVERSE_ACCESS TraverseCallback OPTIONAL
,
619 IN PSECURITY_SUBJECT_CONTEXT SubjectContext OPTIONAL
)
621 FsRtlNotifyFilterChangeDirectory(NotifySync
,
635 * @name FsRtlNotifyFullReportChange
638 * Complets the pending notify IRPs.
641 * Synchronization object pointer
644 * Notify list pointer (to head)
646 * @param FullTargetName
647 * String (A or W) containing the full directory name that changed
649 * @param TargetNameOffset
650 * Offset, in FullTargetName, of the final component that is in the changed directory
653 * String (A or W) containing a stream name
655 * @param NormalizedParentName
656 * String (A or W) containing the full directory name that changed with long names
659 * Flags that will be compared to the completion filter
662 * Action code to store in user's buffer
664 * @param TargetContext
665 * Pointer to a callback function. It's called each time a change is
666 * done in a subdirectory of the main directory.
670 * @remarks This function only redirects to FsRtlNotifyFilterReportChange.
675 FsRtlNotifyFullReportChange(IN PNOTIFY_SYNC NotifySync
,
676 IN PLIST_ENTRY NotifyList
,
677 IN PSTRING FullTargetName
,
678 IN USHORT TargetNameOffset
,
679 IN PSTRING StreamName OPTIONAL
,
680 IN PSTRING NormalizedParentName OPTIONAL
,
681 IN ULONG FilterMatch
,
683 IN PVOID TargetContext
)
685 FsRtlNotifyFilterReportChange(NotifySync
,
690 NormalizedParentName
,
698 * @name FsRtlNotifyInitializeSync
701 * Allocates the internal structure associated with notifications.
704 * Opaque pointer. It will receive the address of the allocated internal structure.
708 * @remarks This function raise an exception in case of a failure.
713 FsRtlNotifyInitializeSync(IN PNOTIFY_SYNC
*NotifySync
)
715 PREAL_NOTIFY_SYNC RealNotifySync
;
719 RealNotifySync
= ExAllocatePoolWithTag(NonPagedPool
| POOL_RAISE_IF_ALLOCATION_FAILURE
,
720 sizeof(REAL_NOTIFY_SYNC
), 'FSNS');
721 ExInitializeFastMutex(&(RealNotifySync
->FastMutex
));
722 RealNotifySync
->OwningThread
= 0;
723 RealNotifySync
->OwnerCount
= 0;
725 *NotifySync
= RealNotifySync
;
729 * @name FsRtlNotifyReportChange
732 * Complets the pending notify IRPs.
735 * Synchronization object pointer
738 * Notify list pointer (to head)
740 * @param FullTargetName
741 * String (A or W) containing the full directory name that changed
743 * @param FileNamePartLength
744 * Length of the final component that is in the changed directory
747 * Flags that will be compared to the completion filter
751 * @remarks This function only redirects to FsRtlNotifyFilterReportChange.
756 FsRtlNotifyReportChange(IN PNOTIFY_SYNC NotifySync
,
757 IN PLIST_ENTRY NotifyList
,
758 IN PSTRING FullTargetName
,
759 IN PUSHORT FileNamePartLength
,
760 IN ULONG FilterMatch
)
762 FsRtlNotifyFilterReportChange(NotifySync
,
765 FullTargetName
->Length
- *FileNamePartLength
,
775 * @name FsRtlNotifyUninitializeSync
778 * Uninitialize a NOTIFY_SYNC object
781 * Address of a pointer to a PNOTIFY_SYNC object previously
782 * initialized by FsRtlNotifyInitializeSync()
791 FsRtlNotifyUninitializeSync(IN PNOTIFY_SYNC
*NotifySync
)
795 ExFreePoolWithTag(*NotifySync
, 'FSNS');