diff options
Diffstat (limited to 'filesys/miniFilter/delete/delete.c')
| -rw-r--r-- | filesys/miniFilter/delete/delete.c | 3253 |
1 files changed, 3253 insertions, 0 deletions
diff --git a/filesys/miniFilter/delete/delete.c b/filesys/miniFilter/delete/delete.c new file mode 100644 index 00000000..ec515c81 --- /dev/null +++ b/filesys/miniFilter/delete/delete.c @@ -0,0 +1,3253 @@ +/*++ + +Copyright (c) 1999 - 2002 Microsoft Corporation + +Module Name: + + delete.c + +Abstract: + + This is the main file for the delete detection sample minifilter. + + +Environment: + + Kernel mode + + +--*/ + + +#include <fltKernel.h> +#include <dontuse.h> +#include <suppress.h> + +#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers") + + + +#define DFDBG_TRACE_ERRORS 0x00000001 +#define DFDBG_TRACE_ROUTINES 0x00000002 +#define DFDBG_TRACE_OPERATION_STATUS 0x00000004 + +#define DF_VOLUME_GUID_NAME_SIZE 48 + +#define DF_INSTANCE_CONTEXT_POOL_TAG 'nIfD' +#define DF_STREAM_CONTEXT_POOL_TAG 'xSfD' +#define DF_TRANSACTION_CONTEXT_POOL_TAG 'xTfD' +#define DF_ERESOURCE_POOL_TAG 'sRfD' +#define DF_DELETE_NOTIFY_POOL_TAG 'nDfD' +#define DF_STRING_POOL_TAG 'rSfD' + +#define DF_CONTEXT_POOL_TYPE PagedPool + +#define DF_NOTIFICATION_MASK (TRANSACTION_NOTIFY_COMMIT_FINALIZE | \ + TRANSACTION_NOTIFY_ROLLBACK) + + +////////////////////////////////////////////////////////////////////////////// +// Macros // +////////////////////////////////////////////////////////////////////////////// + +#define DF_PRINT( ... ) \ + DbgPrintEx( DPFLTR_FLTMGR_ID, DPFLTR_ERROR_LEVEL, __VA_ARGS__ ) + +#define DF_DBG_PRINT( _dbgLevel, ... ) \ + (FlagOn( gTraceFlags, (_dbgLevel) ) ? \ + DF_PRINT( __VA_ARGS__ ): \ + (0)) + +#define FlagOnAll( F, T ) \ + (FlagOn( F, T ) == T) + + +////////////////////////////////////////////////////////////////////////////// +// Main Globals // +////////////////////////////////////////////////////////////////////////////// + +PFLT_FILTER gFilterHandle; +ULONG gTraceFlags = DFDBG_TRACE_ERRORS; + + +////////////////////////////////////////////////////////////////////////////// +// ReFS Compatibility Helpers // +////////////////////////////////////////////////////////////////////////////// + +// +// This helps us deal with ReFS 128-bit file IDs and NTFS 64-bit file IDs. +// + +typedef union _DF_FILE_REFERENCE { + + struct { + ULONGLONG Value; // The 64-bit file ID lives here. + ULONGLONG UpperZeroes; // In a 64-bit file ID this will be 0. + } FileId64; + + UCHAR FileId128[16]; // The 128-bit file ID lives here. + +} DF_FILE_REFERENCE, *PDF_FILE_REFERENCE; + +#define DfSizeofFileId(FID) ( \ + ((FID).FileId64.UpperZeroes == 0ll) ? \ + sizeof((FID).FileId64.Value) : \ + sizeof((FID).FileId128) \ + ) + + +////////////////////////////////////////////////////////////////////////////// +// Types // +////////////////////////////////////////////////////////////////////////////// + +// +// This is the instance context for this minifilter, it stores the volume's +// GUID name. +// + +typedef struct _DF_INSTANCE_CONTEXT { + + // + // Volume GUID name. + // + + UNICODE_STRING VolumeGuidName; + +} DF_INSTANCE_CONTEXT, *PDF_INSTANCE_CONTEXT; + + +// +// This is the stream context for this minifilter, attached whenever a stream +// becomes a candidate for deletion. +// + +typedef struct _DF_STREAM_CONTEXT { + + // + // FLT_FILE_NAME_INFORMATION structure with the names for this stream + // and file. This is only used for printing out the opened name when + // notifying deletes. This will be the result of an opened query name + // done at the last pre-cleanup on the file/stream. + // + // Therefore, there is no requirement of maintaining the file name + // information (for the purposes we use it) in sync with the FltMgr name + // cache or the file system. This makes it okay to store it in the stream + // context. + // + + PFLT_FILE_NAME_INFORMATION NameInfo; + + // + // File ID, obtained from querying the file system for FileInternalInformation. + // If the File ID is 128 bits (as in ReFS) we get it via FileIdInformation. + // + + DF_FILE_REFERENCE FileId; + + // + // Number of SetDisp operations in flight. + // + + volatile LONG NumOps; + + // + // IsNotified == 1 means a file/stream deletion was already notified. + // + + volatile LONG IsNotified; + + // + // Whether or not we've already queried the file ID. + // + + BOOLEAN FileIdSet; + + // + // Delete Disposition for this stream. + // + + BOOLEAN SetDisp; + + // + // Delete-on-Close state for this stream. + // + + BOOLEAN DeleteOnClose; + +} DF_STREAM_CONTEXT, *PDF_STREAM_CONTEXT; + + +// +// This is the transaction context for this minifilter, attached at post- +// -cleanup when notifying a delete within a transaction. +// + +typedef struct _DF_TRANSACTION_CONTEXT { + + // + // List of DF_DELETE_NOTIFY structures representing pending delete + // notifications. + // + + LIST_ENTRY DeleteNotifyList; + + // + // ERESOURCE for synchronized access to the DeleteNotifyList. + // + // ERESOURCEs must be allocated from NonPagedPool. If an ERESOURCE was + // declared here as a direct member of a structure, instead of just a + // pointer, then the whole transaction context would need to be allocated + // out of NonPagedPool. + // + // Therefore, declaring it as a pointer and only allocating at context + // initialization time helps us save some NonPagedPool. This is + // particularly important in larger context structures. + // + + PERESOURCE Resource; + +} DF_TRANSACTION_CONTEXT, *PDF_TRANSACTION_CONTEXT; + + +// +// This structure represents pending delete notifications for files that have +// been deleted in an open transaction. +// + +typedef struct _DF_DELETE_NOTIFY { + + // + // Links to other DF_DELETE_NOTIFY structures in the list. + // + + LIST_ENTRY Links; + + // + // Pointer to the stream context for the deleted stream/file. + // + + PDF_STREAM_CONTEXT StreamContext; + + // + // TRUE for a deleted file, FALSE for a stream. + // + + BOOLEAN FileDelete; + +} DF_DELETE_NOTIFY, *PDF_DELETE_NOTIFY; + + +////////////////////////////////////////////////////////////////////////////// +// Prototypes // +////////////////////////////////////////////////////////////////////////////// + +DRIVER_INITIALIZE DriverEntry; +NTSTATUS +DriverEntry ( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ); + +NTSTATUS +DfUnload ( + _In_ FLT_FILTER_UNLOAD_FLAGS Flags + ); + +NTSTATUS +DfInstanceSetup ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_SETUP_FLAGS Flags, + _In_ DEVICE_TYPE VolumeDeviceType, + _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType + ); + +NTSTATUS +DfInstanceQueryTeardown ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags + ); + +VOID +DfInstanceTeardownStart ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags + ); + +VOID +DfInstanceTeardownComplete ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags + ); + +NTSTATUS +DfSetupInstanceContext( + _In_ PCFLT_RELATED_OBJECTS FltObjects + ); + +NTSTATUS +DfAllocateContext ( + _In_ FLT_CONTEXT_TYPE ContextType, + _Outptr_ PFLT_CONTEXT *Context + ); + +NTSTATUS +DfSetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _In_ FLT_CONTEXT_TYPE ContextType, + _In_ PFLT_CONTEXT NewContext, + _Outptr_opt_result_maybenull_ PFLT_CONTEXT *OldContext + ); + +NTSTATUS +DfGetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _In_ FLT_CONTEXT_TYPE ContextType, + _Outptr_ PFLT_CONTEXT *Context + ); + +NTSTATUS +DfGetOrSetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _Outptr_ _Pre_valid_ PFLT_CONTEXT *Context, + _In_ FLT_CONTEXT_TYPE ContextType + ); + +VOID +DfStreamContextCleanupCallback ( + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ FLT_CONTEXT_TYPE ContextType + ); + +VOID +DfTransactionContextCleanupCallback ( + _In_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ FLT_CONTEXT_TYPE ContextType + ); + +VOID +DfInstanceContextCleanupCallback ( + _In_ PDF_INSTANCE_CONTEXT InstanceContext, + _In_ FLT_CONTEXT_TYPE ContextType + ); + +NTSTATUS +DfGetFileNameInformation ( + _In_ PFLT_CALLBACK_DATA Data, + _Inout_ PDF_STREAM_CONTEXT StreamContext + ); + +NTSTATUS +DfAllocateUnicodeString ( + _Inout_ PUNICODE_STRING String + ); + +VOID +DfFreeUnicodeString ( + _Inout_ PUNICODE_STRING String + ); + +NTSTATUS +DfBuildFileIdString ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext, + _Out_ PUNICODE_STRING String + ); + +NTSTATUS +DfDetectDeleteByFileId ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext + ); + +NTSTATUS +DfIsFileDeleted ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ BOOLEAN IsTransaction + ); + +NTSTATUS +DfAddTransDeleteNotify ( + _Inout_ PDF_STREAM_CONTEXT StreamContext, + _Inout_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ BOOLEAN FileDelete + ); + +VOID +DfNotifyDelete ( + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ BOOLEAN IsFile, + _Inout_opt_ PDF_TRANSACTION_CONTEXT TransactionContext + ); + +VOID +DfNotifyDeleteOnTransactionEnd ( + _In_ PDF_DELETE_NOTIFY DeleteNotify, + _In_ BOOLEAN Commit + ); + +NTSTATUS +DfProcessDelete ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext + ); + +FLT_PREOP_CALLBACK_STATUS +DfPreCreateCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Outptr_result_maybenull_ PVOID *CompletionContext + ); + +FLT_POSTOP_CALLBACK_STATUS +DfPostCreateCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ); + +FLT_PREOP_CALLBACK_STATUS +DfPreSetInfoCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ); + +FLT_POSTOP_CALLBACK_STATUS +DfPostSetInfoCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ); + +FLT_PREOP_CALLBACK_STATUS +DfPreCleanupCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ); + +FLT_POSTOP_CALLBACK_STATUS +DfPostCleanupCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ); + +NTSTATUS +DfTransactionNotificationCallback ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ ULONG NotificationMask + ); + +NTSTATUS +DfGetVolumeGuidName ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Inout_ PUNICODE_STRING VolumeGuidName + ); + +NTSTATUS +DfGetFileId ( + _In_ PFLT_CALLBACK_DATA Data, + _Inout_ PDF_STREAM_CONTEXT StreamContext + ); + +////////////////////////////////////////////////////////////////////////////// +// Text section assignments for all routines // +////////////////////////////////////////////////////////////////////////////// + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(PAGE, DfUnload) +#pragma alloc_text(PAGE, DfInstanceSetup) +#pragma alloc_text(PAGE, DfInstanceQueryTeardown) +#pragma alloc_text(PAGE, DfInstanceTeardownStart) +#pragma alloc_text(PAGE, DfInstanceTeardownComplete) +#pragma alloc_text(PAGE, DfSetupInstanceContext) +#pragma alloc_text(PAGE, DfAllocateContext) +#pragma alloc_text(PAGE, DfSetContext) +#pragma alloc_text(PAGE, DfGetContext) +#pragma alloc_text(PAGE, DfGetOrSetContext) +#pragma alloc_text(PAGE, DfStreamContextCleanupCallback) +#pragma alloc_text(PAGE, DfTransactionContextCleanupCallback) +#pragma alloc_text(PAGE, DfInstanceContextCleanupCallback) +#pragma alloc_text(PAGE, DfGetFileNameInformation) +#pragma alloc_text(PAGE, DfAllocateUnicodeString) +#pragma alloc_text(PAGE, DfFreeUnicodeString) +#pragma alloc_text(PAGE, DfBuildFileIdString) +#pragma alloc_text(PAGE, DfDetectDeleteByFileId) +#pragma alloc_text(PAGE, DfIsFileDeleted) +#pragma alloc_text(PAGE, DfAddTransDeleteNotify) +#pragma alloc_text(PAGE, DfNotifyDelete) +#pragma alloc_text(PAGE, DfNotifyDeleteOnTransactionEnd) +#pragma alloc_text(PAGE, DfProcessDelete) +#pragma alloc_text(PAGE, DfPreCreateCallback) +#pragma alloc_text(PAGE, DfPostCreateCallback) +#pragma alloc_text(PAGE, DfPreSetInfoCallback) +#pragma alloc_text(PAGE, DfPostSetInfoCallback) +#pragma alloc_text(PAGE, DfPreCleanupCallback) +#pragma alloc_text(PAGE, DfPostCleanupCallback) +#pragma alloc_text(PAGE, DfTransactionNotificationCallback) +#pragma alloc_text(PAGE, DfGetVolumeGuidName) +#pragma alloc_text(PAGE, DfGetFileId) +#endif + + +////////////////////////////////////////////////////////////////////////////// +// Context Registration // +////////////////////////////////////////////////////////////////////////////// + +CONST FLT_CONTEXT_REGISTRATION Contexts[] = { + + { FLT_INSTANCE_CONTEXT, + 0, + DfInstanceContextCleanupCallback, + sizeof(DF_INSTANCE_CONTEXT), + DF_INSTANCE_CONTEXT_POOL_TAG, + NULL, + NULL, + NULL }, + + { FLT_STREAM_CONTEXT, + 0, + DfStreamContextCleanupCallback, + sizeof(DF_STREAM_CONTEXT), + DF_STREAM_CONTEXT_POOL_TAG, + NULL, + NULL, + NULL }, + + { FLT_TRANSACTION_CONTEXT, + 0, + DfTransactionContextCleanupCallback, + sizeof(DF_TRANSACTION_CONTEXT), + DF_TRANSACTION_CONTEXT_POOL_TAG, + NULL, + NULL, + NULL }, + + { FLT_CONTEXT_END } + +}; + + +////////////////////////////////////////////////////////////////////////////// +// Operation Registration // +////////////////////////////////////////////////////////////////////////////// + +CONST FLT_OPERATION_REGISTRATION Callbacks[] = { + + { IRP_MJ_CREATE, + 0, + DfPreCreateCallback, + DfPostCreateCallback }, + + { IRP_MJ_SET_INFORMATION, + FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO, + DfPreSetInfoCallback, + DfPostSetInfoCallback }, + + { IRP_MJ_CLEANUP, + 0, + DfPreCleanupCallback, + DfPostCleanupCallback }, + + { IRP_MJ_OPERATION_END } + +}; + + +////////////////////////////////////////////////////////////////////////////// +// Filter Registration // +////////////////////////////////////////////////////////////////////////////// + +CONST FLT_REGISTRATION FilterRegistration = { + + sizeof( FLT_REGISTRATION ), // Size + FLT_REGISTRATION_VERSION, // Version + 0, // Flags + + Contexts, // Context + Callbacks, // Operation callbacks + + DfUnload, // MiniFilterUnload + + DfInstanceSetup, // InstanceSetup + DfInstanceQueryTeardown, // InstanceQueryTeardown + DfInstanceTeardownStart, // InstanceTeardownStart + DfInstanceTeardownComplete, // InstanceTeardownComplete + NULL, // GenerateFileName + NULL, // NormalizeNameComponent + NULL, // NormalizeContextCleanup + DfTransactionNotificationCallback, // TransactionNotification + NULL // NormalizeNameComponentEx + +}; + + +////////////////////////////////////////////////////////////////////////////// +// MiniFilter initialization and unload routines // +////////////////////////////////////////////////////////////////////////////// + +NTSTATUS +DriverEntry ( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) +/*++ + +Routine Description: + + This is the initialization routine for this miniFilter driver. This + registers with FltMgr and initializes all global data structures. + +Arguments: + + DriverObject - Pointer to driver object created by the system to + represent this driver. + + RegistryPath - Unicode string identifying where the parameters for this + driver are located in the registry. + +Return Value: + + Returns STATUS_SUCCESS. + +--*/ +{ + NTSTATUS status; + + UNREFERENCED_PARAMETER( RegistryPath ); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DriverEntry: Entered\n" ); + + // + // Default to NonPagedPoolNx for non paged pool allocations where supported. + // + + ExInitializeDriverRuntime( DrvRtPoolNxOptIn ); + + // + // Register with FltMgr to tell it our callback routines + // + + status = FltRegisterFilter( DriverObject, + &FilterRegistration, + &gFilterHandle ); + + ASSERT( NT_SUCCESS( status ) ); + + if (NT_SUCCESS( status )) { + + // + // Start filtering i/o + // + + status = FltStartFiltering( gFilterHandle ); + + if (!NT_SUCCESS( status )) { + + FltUnregisterFilter( gFilterHandle ); + } + } + + return status; +} + + +NTSTATUS +DfUnload ( + _In_ FLT_FILTER_UNLOAD_FLAGS Flags + ) +/*++ + +Routine Description: + + This is the unload routine for this miniFilter driver. This is called + when the minifilter is about to be unloaded. + +Arguments: + + Flags - Indicating if this is a mandatory unload. + +Return Value: + + Returns the final status of this operation. + +--*/ +{ + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfUnload: Entered\n" ); + + FltUnregisterFilter( gFilterHandle ); + + return STATUS_SUCCESS; +} + + +////////////////////////////////////////////////////////////////////////////// +// Filter Instance Callbacks (Setup/Teardown/QueryTeardown) // +////////////////////////////////////////////////////////////////////////////// + +NTSTATUS +DfInstanceSetup ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_SETUP_FLAGS Flags, + _In_ DEVICE_TYPE VolumeDeviceType, + _In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType + ) +/*++ + +Routine Description: + + This routine is called whenever a new instance is created on a volume. This + gives us a chance to decide if we need to attach to this volume or not. + + New instances are only created and attached to a volume if it is a writable + NTFS or ReFS volume. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Flags - Flags describing the reason for this attach request. + + VolumeFilesystemType - A FLT_FSTYPE_* value indicating which file system type + the Filter Manager is offering to attach us to. + +Return Value: + + STATUS_SUCCESS - attach + STATUS_FLT_DO_NOT_ATTACH - do not attach + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN isWritable = FALSE; + + UNREFERENCED_PARAMETER( Flags ); + UNREFERENCED_PARAMETER( VolumeDeviceType ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfInstanceSetup: Entered\n" ); + + status = FltIsVolumeWritable( FltObjects->Volume, + &isWritable ); + + if (!NT_SUCCESS( status )) { + + return STATUS_FLT_DO_NOT_ATTACH; + } + + // + // Attaching to read-only volumes is pointless as you should not be able + // to delete files on such a volume. + // + + if (isWritable) { + + switch (VolumeFilesystemType) { + + case FLT_FSTYPE_NTFS: + case FLT_FSTYPE_REFS: + + status = STATUS_SUCCESS; + break; + + default: + + return STATUS_FLT_DO_NOT_ATTACH; + } + + } else { + + return STATUS_FLT_DO_NOT_ATTACH; + } + + return status; +} + + +NTSTATUS +DfInstanceQueryTeardown ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags + ) +/*++ + +Routine Description: + + This is called when an instance is being manually deleted by a + call to FltDetachVolume or FilterDetach thereby giving us a + chance to fail that detach request. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Flags - Indicating where this detach request came from. + +Return Value: + + Returns the status of this operation. + +--*/ +{ + UNREFERENCED_PARAMETER( FltObjects ); + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfInstanceQueryTeardown: Entered\n" ); + + return STATUS_SUCCESS; +} + + +VOID +DfInstanceTeardownStart ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags + ) +/*++ + +Routine Description: + + This routine is called at the start of instance teardown. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Flags - Reason why this instance is been deleted. + +Return Value: + + None. + +--*/ +{ + UNREFERENCED_PARAMETER( FltObjects ); + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfInstanceTeardownStart: Entered\n" ); +} + + +VOID +DfInstanceTeardownComplete ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ FLT_INSTANCE_TEARDOWN_FLAGS Flags + ) +/*++ + +Routine Description: + + This routine is called at the end of instance teardown. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Flags - Reason why this instance is been deleted. + +Return Value: + + None. + +--*/ +{ + UNREFERENCED_PARAMETER( FltObjects ); + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfInstanceTeardownComplete: Entered\n" ); +} + + +////////////////////////////////////////////////////////////////////////////// +// Context manipulation functions // +////////////////////////////////////////////////////////////////////////////// + +NTSTATUS +DfAllocateContext ( + _In_ FLT_CONTEXT_TYPE ContextType, + _Outptr_ PFLT_CONTEXT *Context + ) +/*++ + +Routine Description: + + This routine allocates and initializes a context of given type. + +Arguments: + + ContextType - Type of context to be allocated/initialized. + + Context - Pointer to a context pointer. + +Return Value: + + Returns a status forwarded from FltAllocateContext. + +--*/ +{ + NTSTATUS status; + PDF_TRANSACTION_CONTEXT transactionContext; + + PAGED_CODE(); + + switch (ContextType) { + + case FLT_STREAM_CONTEXT: + + status = FltAllocateContext( gFilterHandle, + FLT_STREAM_CONTEXT, + sizeof(DF_STREAM_CONTEXT), + DF_CONTEXT_POOL_TYPE, + Context ); + + if (NT_SUCCESS( status )) { + RtlZeroMemory( *Context, sizeof(DF_STREAM_CONTEXT) ); + } + + return status; + + case FLT_TRANSACTION_CONTEXT: + + status = FltAllocateContext( gFilterHandle, + FLT_TRANSACTION_CONTEXT, + sizeof(DF_TRANSACTION_CONTEXT), + DF_CONTEXT_POOL_TYPE, + Context ); + + if (NT_SUCCESS( status )) { + RtlZeroMemory( *Context, sizeof(DF_TRANSACTION_CONTEXT) ); + + transactionContext = *Context; + + InitializeListHead( &transactionContext->DeleteNotifyList ); + + transactionContext->Resource = ExAllocatePoolWithTag( NonPagedPool, + sizeof(ERESOURCE), + DF_ERESOURCE_POOL_TAG ); + + if (NULL == transactionContext->Resource) { + FltReleaseContext( transactionContext ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + ExInitializeResourceLite( transactionContext->Resource ); + } + + return status; + + case FLT_INSTANCE_CONTEXT: + + status = FltAllocateContext( gFilterHandle, + FLT_INSTANCE_CONTEXT, + sizeof(DF_INSTANCE_CONTEXT), + DF_CONTEXT_POOL_TYPE, + Context ); + + if (NT_SUCCESS( status )) { + RtlZeroMemory( *Context, sizeof(DF_INSTANCE_CONTEXT) ); + } + + return status; + + default: + + return STATUS_INVALID_PARAMETER; + } +} + + +NTSTATUS +DfSetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _In_ FLT_CONTEXT_TYPE ContextType, + _In_ PFLT_CONTEXT NewContext, + _Outptr_opt_result_maybenull_ PFLT_CONTEXT *OldContext + ) +/*++ + +Routine Description: + + This routine sets the given context to the target. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Target - Pointer to the target to which we want to attach the + context. It will actually be either a FILE_OBJECT or + a KTRANSACTION. For instance contexts, it's ignored, as + the target is the FLT_INSTANCE itself, obtained from + Data->Iopb->TargetInstance. + + ContextType - Type of context to get/allocate/attach. Also used to + disambiguate the target/context type as this minifilter + only has one type of context per target. + + NewContext - Pointer to the context the caller wants to attach. + + OldContext - Returns the context already attached to the target, if + that is the case. + +Return Value: + + Returns a status forwarded from FltSetXxxContext. + +--*/ +{ + PAGED_CODE(); + + switch (ContextType) { + + case FLT_STREAM_CONTEXT: + + return FltSetStreamContext( FltObjects->Instance, + (PFILE_OBJECT)Target, + FLT_SET_CONTEXT_KEEP_IF_EXISTS, + NewContext, + OldContext ); + + case FLT_TRANSACTION_CONTEXT: + + return FltSetTransactionContext( FltObjects->Instance, + (PKTRANSACTION)Target, + FLT_SET_CONTEXT_KEEP_IF_EXISTS, + NewContext, + OldContext ); + + case FLT_INSTANCE_CONTEXT: + + return FltSetInstanceContext( FltObjects->Instance, + FLT_SET_CONTEXT_KEEP_IF_EXISTS, + NewContext, + OldContext ); + + default: + + ASSERT( !"Unexpected context type!\n" ); + + return STATUS_INVALID_PARAMETER; + } +} + + +NTSTATUS +DfGetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _In_ FLT_CONTEXT_TYPE ContextType, + _Outptr_ PFLT_CONTEXT *Context + ) +/*++ + +Routine Description: + + This routine gets the given context from the target. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Target - Pointer to the target from which we want to obtain the + context. It will actually be either a FILE_OBJECT or + a KTRANSACTION. For instance contexts, it's ignored, as + the target is the FLT_INSTANCE itself, obtained from + Data->Iopb->TargetInstance. + + ContextType - Type of context to get. Also used to disambiguate + the target/context type as this minifilter + only has one type of context per target. + + Context - Pointer returning a pointer to the attached context. + +Return Value: + + Returns a status forwarded from FltSetXxxContext. + +--*/ +{ + PAGED_CODE(); + + switch (ContextType) { + + case FLT_STREAM_CONTEXT: + + return FltGetStreamContext( FltObjects->Instance, + (PFILE_OBJECT)Target, + Context ); + + case FLT_TRANSACTION_CONTEXT: + + return FltGetTransactionContext( FltObjects->Instance, + (PKTRANSACTION)Target, + Context ); + + case FLT_INSTANCE_CONTEXT: + + return FltGetInstanceContext( FltObjects->Instance, + Context ); + + default: + + return STATUS_INVALID_PARAMETER; + } +} + + +NTSTATUS +DfGetOrSetContext ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _When_(ContextType==FLT_INSTANCE_CONTEXT, _In_opt_) _When_(ContextType!=FLT_INSTANCE_CONTEXT, _In_) PVOID Target, + _Outptr_ _Pre_valid_ PFLT_CONTEXT *Context, + _In_ FLT_CONTEXT_TYPE ContextType + ) +/*++ + +Routine Description: + + This routine obtains a context of type ContextType that is attached to + Target. + + If a context is already attached to Target, it will be returned in + *Context. If a context is already attached, but *Context points to + another context, *Context will be released. + + If no context is attached, and *Context points to a previously allocated + context, *Context will be attached to the Target. + + Finally, if no previously allocated context is passed to this routine + (*Context is a NULL pointer), a new Context is created and then attached + to Target. + + In case of race conditions (or the presence of a previously allocated + context at *Context), the existing attached context is returned via + *Context. + + In case of a transaction context, this function will also enlist in the + transaction. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance and its associated volume. + + Target - Pointer to the target to which we want to attach the + context. It will actually be either a FILE_OBJECT or + a KTRANSACTION. It is NULL for an Instance context. + + Context - Pointer to a pointer to a context. Used both for + returning an allocated/attached context or for receiving + a context to attach to the Target. + + ContextType - Type of context to get/allocate/attach. Also used to + disambiguate the target/context type as this minifilter + only has one type of context per target. + +Return Value: + + Returns a status forwarded from Flt(((Get|Set)Xxx)|Allocate)Context or + FltEnlistInTransaction. + +--*/ +{ + NTSTATUS status; + PFLT_CONTEXT newContext; + PFLT_CONTEXT oldContext; + + PAGED_CODE(); + + ASSERT( NULL != Context ); + + newContext = *Context; + + // + // Is there already a context attached to the target? + // + + status = DfGetContext( FltObjects, + Target, + ContextType, + &oldContext ); + + if (STATUS_NOT_FOUND == status) { + + // + // There is no attached context. This means we have to either attach the + // one provided by the caller or allocate a new one and attach it. + // + + if (NULL == newContext) { + + // + // No provided context. Allocate one. + // + + status = DfAllocateContext( ContextType, &newContext ); + + if (!NT_SUCCESS( status )) { + + // + // We failed to allocate. + // + + return status; + } + } + + } else if (!NT_SUCCESS( status )) { + + // + // We failed trying to get a context from the target. + // + + return status; + + } else { + + // + // There is already a context attached to the target, so return + // that context. + // + // If a context was provided by the caller, release it if it's not + // the one attached to the target. + // + + // + // The caller is not allowed to set the same context on the target + // twice. + // + ASSERT( newContext != oldContext ); + + if (NULL != newContext) { + + FltReleaseContext( newContext ); + } + + *Context = oldContext; + return status; + } + + // + // At this point we should have a context to set on the target (newContext). + // + + status = DfSetContext( FltObjects, + Target, + ContextType, + newContext, + &oldContext ); + + if (!NT_SUCCESS( status )) { + + // + // FltSetStreamContext failed so we must release the new context. + // + + FltReleaseContext( newContext ); + + if (STATUS_FLT_CONTEXT_ALREADY_DEFINED == status) { + + // + // We're racing with some other call which managed to set the + // context before us. We will return that context instead, which + // will be in oldContext. + // + + *Context = oldContext; + return STATUS_SUCCESS; + + } else { + + // + // Failed to set the context. Return NULL. + // + + *Context = NULL; + return status; + } + } + + // + // If this is setting a transaction context, we want to enlist in the + // transaction as well. + // + + if (FLT_TRANSACTION_CONTEXT == ContextType) { + + status = FltEnlistInTransaction( FltObjects->Instance, + (PKTRANSACTION)Target, + newContext, + DF_NOTIFICATION_MASK ); + + } + + // + // Setting the context was successful so just return newContext. + // + + *Context = newContext; + return status; +} + + +////////////////////////////////////////////////////////////////////////////// +// Context Cleanup Callbacks // +////////////////////////////////////////////////////////////////////////////// + +VOID +DfStreamContextCleanupCallback ( + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ FLT_CONTEXT_TYPE ContextType + ) +/*++ + +Routine Description: + + This routine cleans up a stream context. The only cleanup necessary is + releasing the FLT_FILE_NAME_INFORMATION object of the NameInfo field. + +Arguments: + + StreamContext - Pointer to DF_STREAM_CONTEXT to be cleaned up. + + ContextType - Type of StreamContext. Must be FLT_STREAM_CONTEXT. + +--*/ +{ + UNREFERENCED_PARAMETER( ContextType ); + + PAGED_CODE(); + + ASSERT( ContextType == FLT_STREAM_CONTEXT ); + + // + // Release NameInfo if present. + // + + if (StreamContext->NameInfo != NULL) { + + FltReleaseFileNameInformation(StreamContext->NameInfo); + StreamContext->NameInfo = NULL; + } +} + + +VOID +DfTransactionContextCleanupCallback ( + _In_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ FLT_CONTEXT_TYPE ContextType + ) +/*++ + +Routine Description: + + This routine cleans up a transaction context. + This operation consists basically of walking the DeleteNotifyList and + deleting all the deletion notifications pending on behalf of this + transaction. + +Arguments: + + TransactionContext - Pointer to DF_TRANSACTION_CONTEXT to be cleaned up. + + ContextType - Type of TransactionContext. Must be FLT_TRANSACTION_CONTEXT. + +--*/ +{ + PDF_DELETE_NOTIFY deleteNotify = NULL; + + UNREFERENCED_PARAMETER( ContextType ); + + PAGED_CODE(); + + ASSERT( ContextType == FLT_TRANSACTION_CONTEXT ); + + if (NULL != TransactionContext->Resource) { + + FltAcquireResourceExclusive( TransactionContext->Resource ); + + while (!IsListEmpty( &TransactionContext->DeleteNotifyList )) { + + // + // Remove every DF_DELETE_NOTIFY, releasing their corresponding + // FLT_FILE_NAME_INFORMATION objects and freeing pool used by + // them. + // + + deleteNotify = CONTAINING_RECORD( RemoveHeadList( &TransactionContext->DeleteNotifyList ), + DF_DELETE_NOTIFY, + Links ); + + FltReleaseContext( deleteNotify->StreamContext ); + ExFreePool( deleteNotify ); + + } + + FltReleaseResource( TransactionContext->Resource ); + + // + // Delete and free the DeleteNotifyList synchronization resource. + // + + ExDeleteResourceLite( TransactionContext->Resource ); + ExFreePool( TransactionContext->Resource ); + } +} + + +VOID +DfInstanceContextCleanupCallback ( + _In_ PDF_INSTANCE_CONTEXT InstanceContext, + _In_ FLT_CONTEXT_TYPE ContextType + ) +/*++ + +Routine Description: + + This routine cleans up an instance context, which consists on freeing + pool used by the volume GUID name string. + +Arguments: + + InstanceContext - Pointer to DF_INSTANCE_CONTEXT to be cleaned up. + + ContextType - Type of InstanceContext. Must be FLT_INSTANCE_CONTEXT. + +--*/ +{ + UNREFERENCED_PARAMETER( ContextType ); + + PAGED_CODE(); + + ASSERT( ContextType == FLT_INSTANCE_CONTEXT ); + + DfFreeUnicodeString( &InstanceContext->VolumeGuidName ); +} + + +////////////////////////////////////////////////////////////////////////////// +// Miscellaneous String, File Name and File ID Functions // +////////////////////////////////////////////////////////////////////////////// + +NTSTATUS +DfGetFileNameInformation ( + _In_ PFLT_CALLBACK_DATA Data, + _Inout_ PDF_STREAM_CONTEXT StreamContext + ) +/*++ + +Routine Description: + + This routine gets and parses the file name information, obtains the File + ID and saves them in the stream context. + +Arguments: + + Data - Pointer to FLT_CALLBACK_DATA. + + StreamContext - Pointer to stream context that will receive the file + information. + +Return Value: + + Returns statuses forwarded from Flt(Get|Parse)FileNameInformation or + FltQueryInformationFile. + +--*/ +{ + NTSTATUS status; + PFLT_FILE_NAME_INFORMATION oldNameInfo; + PFLT_FILE_NAME_INFORMATION newNameInfo; + + PAGED_CODE(); + + // + // FltGetFileNameInformation - this is enough for a file name. + // + + status = FltGetFileNameInformation( Data, + (FLT_FILE_NAME_OPENED | + FLT_FILE_NAME_QUERY_DEFAULT), + &newNameInfo ); + + if (!NT_SUCCESS( status )) { + return status; + } + + // + // FltParseFileNameInformation - this fills in the other gaps, like the + // stream name, if present. + // + + status = FltParseFileNameInformation( newNameInfo ); + + if (!NT_SUCCESS( status )) { + return status; + } + + // + // Now that we have a good NameInfo, set it in the context, replacing + // the previous one. + // + + oldNameInfo = InterlockedExchangePointer( &StreamContext->NameInfo, + newNameInfo ); + + if (NULL != oldNameInfo) { + + FltReleaseFileNameInformation( oldNameInfo ); + } + + return status; +} + + +NTSTATUS +DfGetFileId ( + _In_ PFLT_CALLBACK_DATA Data, + _Inout_ PDF_STREAM_CONTEXT StreamContext + ) +/*++ + +Routine Description: + + This routine obtains the File ID and saves it in the stream context. + +Arguments: + + Data - Pointer to FLT_CALLBACK_DATA. + + StreamContext - Pointer to stream context that will receive the file + ID. + +Return Value: + + Returns statuses forwarded from FltQueryInformationFile, including + STATUS_FILE_DELETED. + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + FILE_INTERNAL_INFORMATION fileInternalInformation; + + PAGED_CODE(); + + // + // Only query the file system for the file ID for the first time. + // This is just an optimization. It doesn't need any real synchronization + // because file IDs don't change. + // + + if (!StreamContext->FileIdSet) { + + // + // Querying for FileInternalInformation gives you the file ID. + // + + status = FltQueryInformationFile( Data->Iopb->TargetInstance, + Data->Iopb->TargetFileObject, + &fileInternalInformation, + sizeof(FILE_INTERNAL_INFORMATION), + FileInternalInformation, + NULL ); + + if (NT_SUCCESS( status )) { + + // + // ReFS uses 128-bit file IDs. FileInternalInformation supports 64- + // bit file IDs. ReFS signals that a particular file ID can only + // be represented in 128 bits by returning FILE_INVALID_FILE_ID as + // the file ID. In that case we need to use FileIdInformation. + // + + if (fileInternalInformation.IndexNumber.QuadPart == FILE_INVALID_FILE_ID) { + + FILE_ID_INFORMATION fileIdInformation; + + status = FltQueryInformationFile( Data->Iopb->TargetInstance, + Data->Iopb->TargetFileObject, + &fileIdInformation, + sizeof(FILE_ID_INFORMATION), + FileIdInformation, + NULL ); + + if (NT_SUCCESS( status )) { + + // + // We don't use DfSizeofFileId() here because we are not + // measuring the size of a DF_FILE_REFERENCE. We know we have + // a 128-bit value. + // + + RtlCopyMemory( &StreamContext->FileId, + &fileIdInformation.FileId, + sizeof(StreamContext->FileId) ); + + // + // Because there's (currently) no support for 128-bit values in + // the compiler we need to ensure the setting of the ID and our + // remembering that the file ID was set occur in the right order. + // + + KeMemoryBarrier(); + + StreamContext->FileIdSet = TRUE; + } + + } else { + + StreamContext->FileId.FileId64.Value = fileInternalInformation.IndexNumber.QuadPart; + StreamContext->FileId.FileId64.UpperZeroes = 0ll; + + // + // Because there's (currently) no support for 128-bit values in + // the compiler we need to ensure the setting of the ID and our + // remembering that the file ID was set occur in the right order. + // + + KeMemoryBarrier(); + + StreamContext->FileIdSet = TRUE; + } + } + } + + return status; +} + + +NTSTATUS +DfAllocateUnicodeString ( + _Inout_ PUNICODE_STRING String + ) +/*++ + +Routine Description: + + This helper routine simply allocates a buffer for a UNICODE_STRING and + initializes its Length to zero. + + It uses whatever value is present in the MaximumLength field as the size + for the allocation. + +Arguments: + + String - Pointer to UNICODE_STRING. + +Return Value: + + STATUS_INSUFFICIENT_RESOURCES if it was not possible to allocate the + buffer from pool. + + STATUS_SUCCESS otherwise. + +--*/ +{ + PAGED_CODE(); + + ASSERT( NULL != String ); + ASSERT( 0 != String->MaximumLength ); + + String->Length = 0; + + String->Buffer = ExAllocatePoolWithTag( DF_CONTEXT_POOL_TYPE, + String->MaximumLength, + DF_STRING_POOL_TAG ); + + if (NULL == String->Buffer) { + + return STATUS_INSUFFICIENT_RESOURCES; + } + + return STATUS_SUCCESS; +} + + +VOID +DfFreeUnicodeString ( + _Inout_ PUNICODE_STRING String + ) +/*++ + +Routine Description: + + This helper routine frees the buffer of a UNICODE_STRING and resets its + Length to zero. + +Arguments: + + String - Pointer to UNICODE_STRING. + +--*/ +{ + PAGED_CODE(); + + ASSERT( NULL != String ); + ASSERT( 0 != String->MaximumLength ); + + String->Length = 0; + + if ( NULL != String->Buffer ) { + + String->MaximumLength = 0; + ExFreePool( String->Buffer ); + String->Buffer = NULL; + } +} + + +NTSTATUS +DfGetVolumeGuidName ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Inout_ PUNICODE_STRING VolumeGuidName + ) +/*++ + +Routine Description: + + This helper routine returns a volume GUID name (with an added trailing + backslash for convenience) in the VolumeGuidName string passed by the + caller. + + The volume GUID name is cached in the instance context for the instance + attached to the volume, and this function will set up an instance context + with the cached name on it if there isn't one already attached to the + instance. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + VolumeGuidName - Pointer to UNICODE_STRING, returning the volume GUID name. + +Return Value: + + Return statuses forwarded by DfAllocateUnicodeString or + FltGetVolumeGuidName. On error, caller needs to DfFreeUnicodeString on + VolumeGuidName. + +--*/ +{ + NTSTATUS status; + PUNICODE_STRING sourceGuidName; + PDF_INSTANCE_CONTEXT instanceContext = NULL; + + PAGED_CODE(); + + // + // Obtain an instance context. Target is NULL for instance context, as + // the FLT_INSTANCE can be obtained from the FltObjects. + // + + status = DfGetOrSetContext( FltObjects, + NULL, + &instanceContext, + FLT_INSTANCE_CONTEXT ); + + if (NT_SUCCESS( status )) { + + // + // sourceGuidName is the source from where we'll copy the volume + // GUID name. Hopefully the name is present in the instance context + // already (buffer is not NULL) so we'll try to use that. + // + + sourceGuidName = &instanceContext->VolumeGuidName; + + if (NULL == sourceGuidName->Buffer) { + + // + // The volume GUID name is not cached in the instance context + // yet, so we will have to query the volume for it and put it + // in the instance context, so future queries can get it directly + // from the context. + // + + UNICODE_STRING tempString; + + // + // Add sizeof(WCHAR) so it's possible to add a trailing backslash here. + // + + tempString.MaximumLength = DF_VOLUME_GUID_NAME_SIZE * + sizeof(WCHAR) + + sizeof(WCHAR); + + status = DfAllocateUnicodeString( &tempString ); + + if (!NT_SUCCESS( status )) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!%s: DfAllocateUnicodeString returned 0x%08x!\n", + __FUNCTION__, + status ); + + return status; + } + + // while there is no guid name, don't do the open by id deletion logic. + // (it's actually better to defer obtaining the volume GUID name up to + // the point when we actually need it, in the open by ID scenario.) + status = FltGetVolumeGuidName( FltObjects->Volume, + &tempString, + NULL ); + + if (!NT_SUCCESS( status )) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!%s: FltGetVolumeGuidName returned 0x%08x!\n", + __FUNCTION__, + status ); + + DfFreeUnicodeString( &tempString ); + + return status; + } + + // + // Append trailing backslash. + // + + RtlAppendUnicodeToString( &tempString, L"\\" ); + + // + // Now set the sourceGuidName to the tempString. It is okay to + // set Length and MaximumLength with no synchronization because + // those will always be the same value (size of a volume GUID + // name with an extra trailing backslash). + // + + sourceGuidName->Length = tempString.Length; + sourceGuidName->MaximumLength = tempString.MaximumLength; + + // + // Setting the buffer, however, requires some synchronization, + // because another thread might be attempting to do the same, + // and even though they're exactly the same string, they're + // different allocations (buffers) so if the other thread we're + // racing with manages to set the buffer before us, we need to + // free our temporary string buffer. + // + + InterlockedCompareExchangePointer( &sourceGuidName->Buffer, + tempString.Buffer, + NULL ); + + if (sourceGuidName->Buffer != tempString.Buffer) { + + // + // We didn't manage to set the buffer, so let's free the + // tempString buffer. + // + + DfFreeUnicodeString( &tempString ); + } + } + + // + // sourceGuidName now contains the correct GUID name, so copy that + // to the caller string. + // + + RtlCopyUnicodeString( VolumeGuidName, sourceGuidName ); + + // + // We're done with the instance context. + // + + FltReleaseContext( instanceContext ); + } + + return status; +} + + +NTSTATUS +DfBuildFileIdString ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext, + _Out_ PUNICODE_STRING String + ) +/*++ + +Routine Description: + + This helper routine builds a string used to open a file by its ID. + + It will assume the file ID is properly loaded in the stream context + (StreamContext->FileId). + +Arguments: + + Data - Pointer to FLT_CALLBACK_DATA. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + StreamContext - Pointer to the stream context. + + String - Pointer to UNICODE_STRING (output). + +Return Value: + + Return statuses forwarded by DfAllocateUnicodeString or + FltGetInstanceContext. + +--*/ +{ + NTSTATUS status; + + PAGED_CODE(); + + ASSERT( NULL != String ); + + // + // We'll compose the string with: + // 1. The volume GUID name. + // 2. A backslash + // 3. The File ID. + // + + // + // Make sure the file ID is loaded in the StreamContext. Note that if the + // file has been deleted DfGetFileId will return STATUS_FILE_DELETED. + // Since we're interested in detecting whether the file has been deleted + // that's fine; the open-by-ID will not actually take place. We have to + // ensure it is loaded before building the string length below since we + // may get either a 64-bit or 128-bit file ID back. + // + + status = DfGetFileId( Data, + StreamContext ); + + if (!NT_SUCCESS( status )) { + + return status; + } + + // + // First add the lengths of 1, 2, 3 and allocate accordingly. + // Note that ReFS understands both 64- and 128-bit file IDs when opening + // by ID, so whichever size we get back from DfSizeofFileId will work. + // + + String->MaximumLength = DF_VOLUME_GUID_NAME_SIZE * sizeof(WCHAR) + + sizeof(WCHAR) + + DfSizeofFileId( StreamContext->FileId ); + + status = DfAllocateUnicodeString( String ); + + if (!NT_SUCCESS( status )) { + + return status; + } + + // + // Now obtain the volume GUID name with a trailing backslash (1 + 2). + // + + // obtain volume GUID name here and cache it in the InstanceContext. + status = DfGetVolumeGuidName( FltObjects, + String ); + + if (!NT_SUCCESS( status )) { + + DfFreeUnicodeString( String ); + + return status; + } + + // + // Now append the file ID to the end of the string. + // + + RtlCopyMemory( Add2Ptr( String->Buffer, String->Length ), + &StreamContext->FileId, + DfSizeofFileId( StreamContext->FileId )); + + String->Length += DfSizeofFileId( StreamContext->FileId ); + + ASSERT( String->Length == String->MaximumLength ); + + return status; +} + + +NTSTATUS +DfDetectDeleteByFileId ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext + ) +/*++ + +Routine Description: + + This helper routine detects a deleted file by attempting to open it using + its file ID. + + If the file is successfully opened this routine closes the file before returning. + +Arguments: + + Data - Pointer to FLT_CALLBACK_DATA. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + StreamContext - Pointer to the stream context. + +Return Value: + + STATUS_FILE_DELETED - Returned through DfBuildFileIdString if the file has + been deleted. + + STATUS_INVALID_PARAMETER - Returned from FltCreateFileEx2 when opening by ID + a file that doesn't exist. + + STATUS_DELETE_PENDING - The file has been set to be deleted when the last handle + goes away, but there are still open handles. + + Also any other NTSTATUS returned from DfBuildFileIdString, FltCreateFileEx2, + or FltClose. + +--*/ +{ + NTSTATUS status; + UNICODE_STRING fileIdString; + HANDLE handle; + OBJECT_ATTRIBUTES objectAttributes; + IO_STATUS_BLOCK ioStatus; + IO_DRIVER_CREATE_CONTEXT driverCreateContext; + + PAGED_CODE(); + + // + // First build the file ID string. Note that this may fail with STATUS_FILE_DELETED + // and short-circuit our open-by-ID. Since we're really trying to see if + // the file is deleted, that's perfectly okay. + // + + status = DfBuildFileIdString( Data, + FltObjects, + StreamContext, + &fileIdString ); + + if (!NT_SUCCESS( status )) { + + return status; + } + + InitializeObjectAttributes( &objectAttributes, + &fileIdString, + OBJ_KERNEL_HANDLE, + NULL, + NULL ); + + // + // It is important to initialize the IO_DRIVER_CREATE_CONTEXT structure's + // TxnParameters. We'll always want to do this open on behalf of a + // transaction because opening the file by ID is the method we use to + // detect if the whole file still exists when we're in a transaction. + // + + IoInitializeDriverCreateContext( &driverCreateContext ); + driverCreateContext.TxnParameters = + IoGetTransactionParameterBlock( Data->Iopb->TargetFileObject ); + + status = FltCreateFileEx2( gFilterHandle, + Data->Iopb->TargetInstance, + &handle, + NULL, + FILE_READ_ATTRIBUTES, + &objectAttributes, + &ioStatus, + (PLARGE_INTEGER) NULL, + 0L, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN, + FILE_OPEN_REPARSE_POINT | FILE_OPEN_BY_FILE_ID, + (PVOID) NULL, + 0L, + IO_IGNORE_SHARE_ACCESS_CHECK, + &driverCreateContext ); + + if (NT_SUCCESS( status )) { + + status = FltClose( handle ); + ASSERT( NT_SUCCESS( status ) ); + } + + DfFreeUnicodeString( &fileIdString ); + + return status; +} + + +////////////////////////////////////////////////////////////////////////////// +// Deletion Verification & Processing Functions // +////////////////////////////////////////////////////////////////////////////// + +NTSTATUS +DfIsFileDeleted ( + _In_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ BOOLEAN IsTransaction + ) +/*++ + +Routine Description: + + This routine returns whether a file was deleted. It is called from + DfProcessDelete after an alternate data stream is deleted. This needs to + be done for the case when the last outstanding handle to a delete-pending + file is a handle to a delete-pending alternate data stream. When that + handle is closed, the whole file goes away, and we want to report a whole + file deletion, not just an alternate data stream deletion. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + StreamContext - Pointer to the stream context. + + IsTransaction - TRUE if in a transaction, FALSE otherwise. + +Return Value: + + STATUS_FILE_DELETED - The whole file was deleted. + Successful status - The file still exists, this was probably just a named + data stream being deleted. + Anything else - Failure in finding out if the file was deleted. + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + FILE_OBJECTID_BUFFER fileObjectIdBuf; + + FLT_FILESYSTEM_TYPE fileSystemType; + + PAGED_CODE(); + + // + // We need to know whether we're on ReFS or NTFS. + // + + status = FltGetFileSystemType( FltObjects->Instance, + &fileSystemType ); + + if (status != STATUS_SUCCESS) { + + return status; + } + + // + // FSCTL_GET_OBJECT_ID does not return STATUS_FILE_DELETED if the + // file was deleted in a transaction, and this is why we need another + // method for detecting if the file is still present: opening by ID. + // + // If we're on ReFS we also need to open by file ID because ReFS does not + // support object IDs. + // + + if (IsTransaction || + (fileSystemType == FLT_FSTYPE_REFS)) { + + status = DfDetectDeleteByFileId( Data, + FltObjects, + StreamContext ); + + switch (status) { + + case STATUS_INVALID_PARAMETER: + + // + // The file was deleted. In this case, trying to open it + // by ID returns STATUS_INVALID_PARAMETER. + // + + return STATUS_FILE_DELETED; + + case STATUS_DELETE_PENDING: + + // + // In this case, the main file still exists, but is in + // a delete pending state, so we return STATUS_SUCCESS, + // signaling it still exists and wasn't deleted by this + // operation. + // + + return STATUS_SUCCESS; + + default: + + return status; + } + + } else { + + // + // When not in a transaction, attempting to get the object ID of the + // file is a cheaper alternative compared to opening the file by ID. + // + + status = FltFsControlFile( Data->Iopb->TargetInstance, + Data->Iopb->TargetFileObject, + FSCTL_GET_OBJECT_ID, + NULL, + 0, + &fileObjectIdBuf, + sizeof(FILE_OBJECTID_BUFFER), + NULL ); + + switch (status) { + + case STATUS_OBJECTID_NOT_FOUND: + + // + // Getting back STATUS_OBJECTID_NOT_FOUND means the file + // still exists, it just doesn't have an object ID. + + return STATUS_SUCCESS; + + default: + + // + // Else we just get back STATUS_FILE_DELETED if the file + // doesn't exist anymore, or some error status, so no + // status conversion is necessary. + // + + NOTHING; + } + } + + return status; +} + + +NTSTATUS +DfAddTransDeleteNotify ( + _Inout_ PDF_STREAM_CONTEXT StreamContext, + _Inout_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ BOOLEAN FileDelete + ) +/*++ + +Routine Description: + + This routine adds a pending deletion notification (DF_DELETE_NOTIFY) + object to the transaction context DeleteNotifyList. It is called from + DfNotifyDelete when a file or stream gets deleted in a transaction. + +Arguments: + + StreamContext - Pointer to the stream context. + + TransactionContext - Pointer to the transaction context. + + FileDelete - TRUE if this is a FILE deletion, FALSE if it's a STREAM + deletion. + +Return Value: + + STATUS_SUCCESS. + +--*/ +{ + PDF_DELETE_NOTIFY deleteNotify; + + PAGED_CODE(); + + ASSERT( NULL != TransactionContext->Resource ); + + ASSERT( NULL != StreamContext ); + + deleteNotify = ExAllocatePoolWithTag( DF_CONTEXT_POOL_TYPE, + sizeof(DF_DELETE_NOTIFY), + DF_DELETE_NOTIFY_POOL_TAG ); + + if (NULL == deleteNotify) { + + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlZeroMemory( deleteNotify, sizeof(DF_DELETE_NOTIFY) ); + + FltReferenceContext( StreamContext ); + deleteNotify->StreamContext = StreamContext; + deleteNotify->FileDelete = FileDelete; + + FltAcquireResourceExclusive( TransactionContext->Resource ); + + InsertTailList( &TransactionContext->DeleteNotifyList, + &deleteNotify->Links ); + + FltReleaseResource( TransactionContext->Resource ); + + return STATUS_SUCCESS; +} + + +VOID +DfNotifyDelete ( + _In_ PDF_STREAM_CONTEXT StreamContext, + _In_ BOOLEAN IsFile, + _Inout_opt_ PDF_TRANSACTION_CONTEXT TransactionContext + ) +/*++ + +Routine Description: + + This routine does the processing after it is verified, in the post-cleanup + callback, that a file or stream were deleted. It sorts out whether it's a + file or a stream delete, whether this is in a transacted context or not, + and issues the appropriate notifications. + +Arguments: + + StreamContext - Pointer to the stream context of the deleted file/stream. + + IsFile - TRUE if deleting a file, FALSE for an alternate data stream. + + TransactionContext - The transaction context. Present if in a transaction, + NULL otherwise. + +--*/ +{ + PAGED_CODE(); + + if (InterlockedIncrement( &StreamContext->IsNotified ) <= 1) { + + if (IsFile) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfPostCleanupCallback: " + "A file \"%wZ\" (%p) has been", + &StreamContext->NameInfo->Name, + StreamContext ); + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfPostCleanupCallback: " + "An alternate data stream \"%wZ\" (%p) has been", + &StreamContext->NameInfo->Name, + StreamContext ); + } + + // + // Flag that a delete has been notified on this file/stream. + // + + if (NULL == TransactionContext) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + " deleted!\n" ); + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + " deleted in a transaction!\n" ); + + DfAddTransDeleteNotify( StreamContext, + TransactionContext, + IsFile ); + } + } +} + + +VOID +DfNotifyDeleteOnTransactionEnd ( + _In_ PDF_DELETE_NOTIFY DeleteNotify, + _In_ BOOLEAN Commit + ) +/*++ + +Routine Description: + + This routine is called by the transaction notification callback to issue + the proper notifications for a file that has been deleted in the context + of that transaction. + The file will be reported as finally deleted, if the transaction was + committed, or "saved" if the transaction was rolled back. + +Arguments: + + DeleteNotify - Pointer to the DF_DELETE_NOTIFY object that contains the + data necessary for issuing this notification. + + Commit - TRUE if the transaction was committed, FALSE if it was + rolled back. + +--*/ +{ + PAGED_CODE(); + + if (DeleteNotify->FileDelete) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfTransactionNotificationCallback: " + "A file \"%wZ\" (%p) has been", + &DeleteNotify->StreamContext->NameInfo->Name, + DeleteNotify->StreamContext ); + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfTransactionNotificationCallback: " + "An alternate data stream \"%wZ\" (%p) has been", + &DeleteNotify->StreamContext->NameInfo->Name, + DeleteNotify->StreamContext ); + } + + if (Commit) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + " deleted due to a transaction commit!\n" ); + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + " saved due to a transaction rollback!\n" ); + } +} + + +NTSTATUS +DfProcessDelete ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_STREAM_CONTEXT StreamContext + ) +/*++ + +Routine Description: + + This routine does the processing after it is verified, in the post-cleanup + callback, that a file or stream were deleted. It sorts out whether it's a + file or a stream delete, whether this is in a transacted context or not, + and issues the appropriate notifications. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + StreamContext - Pointer to the stream context of the deleted file/stream. + +Return Value: + + STATUS_SUCCESS. + +--*/ +{ + BOOLEAN isTransaction; + BOOLEAN isFileDeleted = FALSE; + NTSTATUS status; + PDF_TRANSACTION_CONTEXT transactionContext = NULL; + + PAGED_CODE(); + + // Is this in a transacted context? + isTransaction = (NULL != FltObjects->Transaction); + + if (isTransaction) { + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfProcessDelete: In a transaction!\n" ); + + status = DfGetOrSetContext( FltObjects, + FltObjects->Transaction, + &transactionContext, + FLT_TRANSACTION_CONTEXT ); + + if (!NT_SUCCESS( status )) { + + return status; + } + } + + // + // Notify deletion. If this is an Alternate Data Stream being deleted, + // check if the whole file was deleted (by calling DfIsFileDeleted) as + // this could be the last handle to a delete-pending file. + // + + status = DfIsFileDeleted( Data, + FltObjects, + StreamContext, + isTransaction ); + + if (STATUS_FILE_DELETED == status) { + + isFileDeleted = TRUE; + status = STATUS_SUCCESS; + + } else if (!NT_SUCCESS( status )) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!%s: DfIsFileDeleted returned 0x%08x!\n", + __FUNCTION__, + status ); + + goto _exit; + } + + DfNotifyDelete( StreamContext, + isFileDeleted, + transactionContext ); + +_exit: + + if (NULL != transactionContext) { + + FltReleaseContext( transactionContext ); + } + + return status; +} + + +////////////////////////////////////////////////////////////////////////////// +// MiniFilter Operation Callback Routines // +////////////////////////////////////////////////////////////////////////////// + +FLT_PREOP_CALLBACK_STATUS +DfPreCreateCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Outptr_result_maybenull_ PVOID *CompletionContext + ) +/*++ + +Routine Description: + + This routine is the pre-operation completion routine for + IRP_MJ_CREATE in this miniFilter. + + In the pre-create phase we're concerned with creates with + FILE_DELETE_ON_CLOSE set, and in those cases we want to flag + this stream as a candidate for being deleted. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The context for the completion routine for this + operation. + +Return Value: + + FLT_PREOP_SUCCESS_WITH_CALLBACK - When FILE_DELETE_ON_CLOSE is set and + a stream context is created. + + FLT_PREOP_SUCCESS_NO_CALLBACK - When FILE_DELETE_ON_CLOSE is not set + and no stream context is created. + +--*/ +{ + PDF_STREAM_CONTEXT streamContext; + NTSTATUS status; + + UNREFERENCED_PARAMETER( FltObjects ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfPreCreateCallback: Entered\n" ); + + // + // Creates are only interesting in the FILE_DELETE_ON_CLOSE scenario, + // in which we'll want to flag this file as a candidate for being + // deleted. + // + // The way we do that is allocate a stream context for this and return + // FLT_PREOP_SUCCESS_NO_CALLBACK, passing down the stream context via + // the completion context, so that the post-create callback can, in case + // of a successful create, attach this context to the stream and flag it + // as a real deletion candidate. + // + + if (FlagOn( Data->Iopb->Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) { + + status = DfAllocateContext( FLT_STREAM_CONTEXT, + &streamContext ); + + if (NT_SUCCESS( status )) { + + *CompletionContext = (PVOID)streamContext; + + return FLT_PREOP_SYNCHRONIZE; + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfPreCreateCallback: An error occurred with DfAllocateStreamContext!\n" ); + } + } + + *CompletionContext = NULL; + + return FLT_PREOP_SUCCESS_NO_CALLBACK; +} + + +FLT_POSTOP_CALLBACK_STATUS +DfPostCreateCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ) +/*++ + +Routine Description: + + This routine is the post-operation completion routine for + IRP_MJ_CREATE in this miniFilter. + + The post-create callback will only be called when this is a create with + FILE_DELETE_ON_CLOSE, meaning we have to flag it as a deletion candidate. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The context for the completion routine for this + operation. This will point to a DF_STREAM_CONTEXT allocated by + DfPreCreateCallback, which will be used for flagging this stream + as a deletion candidate. + +Return Value: + + FLT_POSTOP_FINISHED_PROCESSING - we never do any sort of asynchronous + processing here. + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + PDF_STREAM_CONTEXT streamContext = NULL; + + UNREFERENCED_PARAMETER( FltObjects ); + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + ASSERT( NULL != CompletionContext ); + + streamContext = (PDF_STREAM_CONTEXT)CompletionContext; + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfPostCreateCallback: Entered\n" ); + + // this status check handles the draining scenario. + if (NT_SUCCESS( Data->IoStatus.Status ) && + (STATUS_REPARSE != Data->IoStatus.Status)) { + + // assert we're not draining. + ASSERT( !FlagOn( Flags, FLTFL_POST_OPERATION_DRAINING ) ); + + // + // Flag the stream as a deletion candidate: try setting the stream + // context on it to the stream context allocated by DfPreCreateCallback. + // If a context is already attached to the stream, DfGetOrSetContext + // will do the right thing and set streamContext to it, freeing the + // other context. + // + + status = DfGetOrSetContext( FltObjects, + Data->Iopb->TargetFileObject, + &streamContext, + FLT_STREAM_CONTEXT ); + + if (NT_SUCCESS( status )) { + + // + // Set DeleteOnClose on the stream context: a delete-on-close stream will + // always be checked for deletion on cleanup. + // + + streamContext->DeleteOnClose = BooleanFlagOn( Data->Iopb->Parameters.Create.Options, + FILE_DELETE_ON_CLOSE ); + } + } + + // + // We will have a context in streamContext, because if allocation fails + // in DfPreCreateCallback, FLT_PREOP_SUCCESS_NO_CALLBACK is returned, so + // there is no post-create callback. + // + // If DfGetOrSetContext failed, if will have released streamContext + // already, so only release it if status is successful. + // + + if (NT_SUCCESS( status )) { + + FltReleaseContext( streamContext ); + } + + return FLT_POSTOP_FINISHED_PROCESSING; +} + + +FLT_PREOP_CALLBACK_STATUS +DfPreSetInfoCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ) +/*++ + +Routine Description: + + This routine is the pre-operation completion routine for + IRP_MJ_SET_INFORMATION in this miniFilter. + + The pre-setinfo callback is important because setting + FileDispositionInformation is another way of putting the file in a + delete-pending state. + + Since the delete disposition is a reversible condition, we have to + make sure to do the right thing when multiple operations are racing: + we won't be able to tell the the final outcome of the delete + disposition state of the stream, so everytime a race like that happens, + we assume this stream as a permanent deletion candidate, so it will be + checked for deletion in the post-cleanup callback. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The context for the completion routine for this + operation. + +Return Value: + + FLT_PREOP_SYNCHRONIZE - we never do any sort of asynchronous processing + here, and we synchronize postop. + + FLT_PREOP_SUCCESS_NO_CALLBACK - if not FileDispositionInformation or we + can't set a streamcontext. + +--*/ +{ + NTSTATUS status; + PDF_STREAM_CONTEXT streamContext = NULL; + BOOLEAN race; + + UNREFERENCED_PARAMETER( FltObjects ); + + PAGED_CODE(); + + switch (Data->Iopb->Parameters.SetFileInformation.FileInformationClass) { + + case FileDispositionInformation: + + // + // We're interested when the file delete disposition changes. + // + + status = DfGetOrSetContext( FltObjects, + Data->Iopb->TargetFileObject, + &streamContext, + FLT_STREAM_CONTEXT ); + + if (!NT_SUCCESS( status )) { + + return FLT_PREOP_SUCCESS_NO_CALLBACK; + } + + // + // Race detection logic. The NumOps field in the StreamContext + // counts the number of in-flight changes to delete disposition + // on the stream. + // + // If there's already some operations in flight, don't bother + // doing postop. Since there will be no postop, this value won't + // be decremented, staying forever 2 or more, which is one of + // the conditions for checking deletion at post-cleanup. + // + + race = (InterlockedIncrement( &streamContext->NumOps ) > 1); + + if (!race) { + + // + // This is the only operation in flight, so do a postop on + // it because the final outcome of the delete disposition + // state of the stream is deterministic. + // + + *CompletionContext = (PVOID)streamContext; + + return FLT_PREOP_SYNCHRONIZE; + + } else { + + FltReleaseContext( streamContext ); + } + + // FALL_THROUGH + + default: + + return FLT_PREOP_SUCCESS_NO_CALLBACK; + + break; + } +} + + +FLT_POSTOP_CALLBACK_STATUS +DfPostSetInfoCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ) +/*++ + +Routine Description: + + This routine is the post-operation completion routine for + IRP_MJ_SET_INFORMATION in this miniFilter. + + In this postop callback we will update the deletion disposition state + of this stream in the stream context. This callback will only be reached + when there's a single change to deletion disposition in flight for the + stream or when this was the first of many racing ops to hit the preop. + + In the latter case, the race is already detected and adequately flagged + in the other preops, so we're safe just decrementing NumOps, because the + other operations will never reach postop and NumOps won't ever be + decremented for them, guaranteeing that NumOps will stay nonzero forever, + effectively flagging the race. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The context for the completion routine for this + operation. + +Return Value: + + FLT_POSTOP_FINISHED_PROCESSING - we never do any sort of asynchronous + processing here. + +--*/ +{ + PDF_STREAM_CONTEXT streamContext; + + UNREFERENCED_PARAMETER( FltObjects ); + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + // assert on FileDispositionInformation + ASSERT( Data->Iopb->Parameters.SetFileInformation.FileInformationClass + == FileDispositionInformation ); + + // pass from pre-callback to post-callback + ASSERT( NULL != CompletionContext ); + streamContext = (PDF_STREAM_CONTEXT) CompletionContext; + + // + // Reaching a postop for FileDispositionInformation means we + // MUST have a stream context passed in the CompletionContext. + // + + if (NT_SUCCESS( Data->IoStatus.Status )) { + + // + // No synchronization is needed to set the SetDisp field, + // because in case of races, the NumOps field will be perpetually + // positive, and it being positive is already an indication this + // file is a delete candidate, so it will be checked at post- + // -cleanup regardless of the value of SetDisp. + // + + streamContext->SetDisp = ((PFILE_DISPOSITION_INFORMATION) + Data->Iopb->Parameters.SetFileInformation.InfoBuffer)->DeleteFile; + } + + // + // Now that the operation is over, decrement NumOps. + // + + InterlockedDecrement( &streamContext->NumOps ); + + FltReleaseContext( streamContext ); + + return FLT_POSTOP_FINISHED_PROCESSING; +} + + +FLT_PREOP_CALLBACK_STATUS +DfPreCleanupCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _Flt_CompletionContext_Outptr_ PVOID *CompletionContext + ) +/*++ + +Routine Description: + + This routine is the pre-operation completion routine for + IRP_MJ_CLEANUP in this miniFilter. + + In the preop callback for cleanup, we obtain the file information and + save it in the stream context, just so we have a name to use when + reporting file deletions. + + That is done for every stream with an attached stream context because + those will be deletion candidates most of the time. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The context for the completion routine for this + operation. + +Return Value: + + FLT_PREOP_SYNCHRONIZE - we never do any sort of asynchronous processing + here, and we want to synchronize the postop. + + FLT_PREOP_SUCCESS_NO_CALLBACK - when we don't manage to get a stream + context. + +--*/ +{ + PDF_STREAM_CONTEXT streamContext; + NTSTATUS status; + + UNREFERENCED_PARAMETER( FltObjects ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfPreCleanupCallback: Entered\n" ); + + status = FltGetStreamContext( Data->Iopb->TargetInstance, + Data->Iopb->TargetFileObject, + &streamContext ); + + if (NT_SUCCESS( status )) { + + // + // Only streams with stream context will be sent for deletion check + // in post-cleanup, which makes sense because they would only ever + // have one if they were flagged as candidates at some point. + // + // Gather file information here so that we have a name to report. + // The name will be accurate most of the times, and in the cases it + // won't, it serves as a good clue and the stream context pointer + // value should offer a way to disambiguate that in case of renames + // etc. + // + + status = DfGetFileNameInformation( Data, streamContext ); + + if (NT_SUCCESS( status )) { + + // pass from pre-callback to post-callback + *CompletionContext = (PVOID)streamContext; + + return FLT_PREOP_SYNCHRONIZE; + + } else { + + FltReleaseContext( streamContext ); + } + } + + return FLT_PREOP_SUCCESS_NO_CALLBACK; +} + + +FLT_POSTOP_CALLBACK_STATUS +DfPostCleanupCallback ( + _Inout_ PFLT_CALLBACK_DATA Data, + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PVOID CompletionContext, + _In_ FLT_POST_OPERATION_FLAGS Flags + ) +/*++ + +Routine Description: + + This routine is the post-operation completion routine for + IRP_MJ_CLEANUP in this miniFilter. + + Post-cleanup is the core of this minifilter. Here we check to see if + the stream or file were deleted and report that through DbgPrint. + +Arguments: + + Data - Pointer to the filter callbackData that is passed to us. + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + CompletionContext - The completion context set in the pre-operation routine. + + Flags - Denotes whether the completion is successful or is being drained. + +Return Value: + + FLT_POSTOP_FINISHED_PROCESSING - we never do any sort of asynchronous + processing here. + +--*/ +{ + FILE_STANDARD_INFORMATION fileInfo; + PDF_STREAM_CONTEXT streamContext = NULL; + NTSTATUS status; + + UNREFERENCED_PARAMETER( CompletionContext ); + + UNREFERENCED_PARAMETER( Flags ); + + PAGED_CODE(); + + DF_DBG_PRINT( DFDBG_TRACE_ROUTINES, + "delete!DfPostCleanupCallback: Entered\n" ); + + // assert we're not draining. + ASSERT( !FlagOn( Flags, FLTFL_POST_OPERATION_DRAINING ) ); + + // pass from pre-callback to post-callback + ASSERT( NULL != CompletionContext ); + streamContext = (PDF_STREAM_CONTEXT) CompletionContext; + + if (NT_SUCCESS( Data->IoStatus.Status )) { + + // + // Determine whether or not we should check for deletion. What + // flags a file as a deletion candidate is one or more of the following: + // + // 1. NumOps > 0. This means there are or were racing changes to + // the file delete disposition state, and, in that case, + // we don't know what that state is. So, let's err to the side of + // caution and check if it was deleted. + // + // 2. SetDisp. If this is TRUE and we haven't raced in setting delete + // disposition, this reflects the true delete disposition state of the + // file, meaning we must check for deletes if it is set to TRUE. + // + // 3. DeleteOnClose. If the file was ever opened with + // FILE_DELETE_ON_CLOSE, we must check to see if it was deleted. + // + // Also, if a deletion of this stream was already notified, there is no + // point notifying it again. + // + + if (((streamContext->NumOps > 0) || + (streamContext->SetDisp) || + (streamContext->DeleteOnClose)) && + (0 == streamContext->IsNotified)) { + + // + // The check for deletion is done via a query to + // FileStandardInformation. If that returns STATUS_FILE_DELETED + // it means the stream was deleted. + // + + status = FltQueryInformationFile( Data->Iopb->TargetInstance, + Data->Iopb->TargetFileObject, + &fileInfo, + sizeof(fileInfo), + FileStandardInformation, + NULL ); + + if (STATUS_FILE_DELETED == status) { + + status = DfProcessDelete( Data, + FltObjects, + streamContext ); + + if (!NT_SUCCESS( status )) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!%s: It was not possible to verify " + "deletion due to an error in DfProcessDelete (0x%08x)!\n", + __FUNCTION__, + status ); + } + } + } + } + + FltReleaseContext( streamContext ); + + return FLT_POSTOP_FINISHED_PROCESSING; +} + + +NTSTATUS +DfTransactionNotificationCallback ( + _In_ PCFLT_RELATED_OBJECTS FltObjects, + _In_ PDF_TRANSACTION_CONTEXT TransactionContext, + _In_ ULONG NotificationMask + ) +/*++ + +Routine Description: + + This routine is the transaction notification callback for this minifilter. + It is called when a transaction we're enlisted in is committed or rolled + back so that it's possible to emit notifications about files that were + deleted in that transaction. + +Arguments: + + FltObjects - Pointer to the FLT_RELATED_OBJECTS data structure containing + opaque handles to this filter, instance, its associated volume and + file object. + + TransactionContext - The transaction context, set/modified when a delete + is detected. + + NotificationMask - A mask of flags indicating the notifications received + from FltMgr. Should be either TRANSACTION_NOTIFY_COMMIT or + TRANSACTION_NOTIFY_ROLLBACK. + +Return Value: + + STATUS_SUCCESS - This operation is never pended. + +--*/ +{ + BOOLEAN commit = BooleanFlagOn( NotificationMask, TRANSACTION_NOTIFY_COMMIT_FINALIZE ); + PDF_DELETE_NOTIFY deleteNotify = NULL; + + UNREFERENCED_PARAMETER( FltObjects ); + + PAGED_CODE(); + + // + // There is no such thing as a simultaneous commit and rollback, nor + // should we get notifications for events other than a commit or a + // rollback. + // + + ASSERT( (!FlagOnAll( NotificationMask, (DF_NOTIFICATION_MASK) )) && + FlagOn( NotificationMask, (DF_NOTIFICATION_MASK) ) ); + + if (commit) { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfTransactionNotificationCallback: COMMIT!\n" ); + + } else { + + DF_DBG_PRINT( DFDBG_TRACE_ERRORS, + "delete!DfTransactionNotificationCallback: ROLLBACK!\n" ); + } + + ASSERT( NULL != TransactionContext->Resource ); + + FltAcquireResourceExclusive( TransactionContext->Resource ); + + while (!IsListEmpty( &TransactionContext->DeleteNotifyList )) { + + deleteNotify = CONTAINING_RECORD( RemoveHeadList( &TransactionContext->DeleteNotifyList ), + DF_DELETE_NOTIFY, + Links ); + + ASSERT( NULL != deleteNotify->StreamContext ); + + if (!commit) { + InterlockedDecrement( &deleteNotify->StreamContext->IsNotified ); + } + + DfNotifyDeleteOnTransactionEnd( deleteNotify, + commit ); + + // release stream context + FltReleaseContext( deleteNotify->StreamContext ); + ExFreePool( deleteNotify ); + } + + FltReleaseResource( TransactionContext->Resource ); + + return STATUS_SUCCESS; +} + + + |
