/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software.
 * Copyright (C) 2013 Spectra Logic Corporation.
 * All rights reserved.
 *
 * Module Name:
 *
 *       notify.c
 *
 * Abstract:
 *
 *        Likewise Posix File System Driver (PVFS)
 *
 *        Directory Change Notify Package
 *
 * Authors: Gerald Carter <gcarter@likewise.com>
 * Authors: Justin Gibbs  <justing@spectralogic.com>
 */

/**
 * @file lwio/server/pvfs/notify.c
 * @brief Posix File System Driver Directory Change Notify Package
 *
 * This module implements the change notification requirements
 * specified in MS-SMB2 3.3.1.3 and MS-CIFS 3.2.4.39,3.3.5.59.4.
 *
 * Client requests enter this module via PvfsReadDirectoryChange(),
 * the handler for IRP_TYPE_READ_DIRECTORY_CHANGE IRPs.
 * The client specifies the events of interest via set bits in the
 * NotifyFilter argument and the amount of "inter-command" event
 * buffering via the MaxBufferSize argument.
 *
 * The behavior of this operation is dependent on previous operations
 * on a given FCB/CCB/NotifyFilter tuple.
 *
 * If this is the first request, a PVFS_NOTIFY_FILTER_RECORD is
 * associated with the FCB to capture future events.  The IRP is
 * queued on the PVFS_NOTIFY_FILTER_RECORD and setup for asynchronous
 * completion once a matching event occurs.  If MaxBufferSize is
 * non-zero, buffer space is allocated in the PVFS_NOTIFY_FILTER_BUFFER
 * of the PVFS_NOTIFY_FILTER_RECORD in preparation for buffering
 * "inter-command" events.
 *
 * If this is a subsequent request, and any existing IRPs are
 * pending, the additional IRP is queued in FIFO order on the
 * PVFS_NOTIFY_FILTER_RECORD and setup for asynchronous completion.
 *
 * If this is a subsequent request, no IRPs are pending, and no
 * events have been buffered, the IRP is queued on the
 * PVFS_NOTIFY_FILTER_RECORD and setup for asynchronous completion.
 * If this new IRP has a different MaxBufferSize than its predecessors,
 * the "inter-command" event buffer is resized.
 *
 * If this is a subsequent request, no IRPs are pending, and events
 * have been buffered, the IRP is completed using the buffered
 * events and the event buffer is cleared.  If this new IRP has a
 * different MaxBufferSize than it's predecessors, the "inter-command"
 * event buffer is resized.
 *
 * Change events enter the module via PvfsNotifySchduleFullReport().
 * Generators of events call this function to schedule a tree walk
 * from the file or directory where the event occurred, all the way
 * to the root, looking for directories with active change filters.
 *
 * The actual walk is performed in a separate thread when
 * PvfsNotifyProcessEvent() is invoked by the work item code.
 * Starting with the parent directory of the FCB where the event
 * occurred, PvfsProcessFcb() is invoked once for each FCB ancestor.
 *
 * Clients can ask to receive changes local to a single directory 
 * or for a complete subtree.  PvfsProcessFcb() is given both the
 * current FCB being processed and the parent directory FCB of the
 * FCB where the event occurred.  If these two arguments match, then
 * this change is local to the FCB being processed.  Otherwise it is
 * an event that should only apply to filters asking for tree wide
 * events.
 *
 * When an event matches a filter record, it is applied to any pending
 * IRP if one is available.  Otherwise it is recorded in the change
 * buffer.
 *
 * Change buffers record notification status and, if MaxBufferSize is
 * non-zero, actual event records.  This status can be:
 *
 *     STATUS_SUCCESS          0 or more events buffered. No Errors.
 *     STATUS_NOTIFY_ENUM_DIR  Events have occurred.  Buffer Overflowed.
 *
 * A change buffer configured with zero event buffer space "overflows"
 * on any filtered event and report STATUS_NOTIFY_ENUM_DIR.
 */

#include "pvfs.h"

/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyAddFilter(
    PPVFS_FCB pFcb,
    PPVFS_CCB pCcb,
    PPVFS_IRP_CONTEXT pIrpContext
    );

static
NTSTATUS
PvfsNotifyReportBufferedChanges(
    PPVFS_FCB pFcb,
    PPVFS_CCB pCcb,
    PPVFS_IRP_CONTEXT pIrpContext
    );

/**
 * @brief Handle a Read Directory Change IRP
 *
 * @param[in] pIrpContext  The context for the IRP to process.
 *
 * @retval STATUS_PENDING                 IRP pending, awaiting directory
 *                                        changes.
 * @retval STATUS_SUCCESS                 Buffered change data successfully
 *                                        returned.
 * @retval STATUS_NOTIFY_ENUM_DIR         Buffered changes were available,
 *                                        but exceeded available buffer space
 *                                        in the IRP.
 * @retval STATUS_INSUFFICIENT_RESOURCES  Memory allocation failure, or too
 *                                        many change buffers associated with
 *                                        this File.
 * @retval STATUS_NOT_A_DIRECTROY         File given as argument.  Invalid
 *                                        request.
 * @retval STATUS_INVALID_PARAMETER       Directory not open.  Invalid request.
 */
NTSTATUS
PvfsReadDirectoryChange(
    PPVFS_IRP_CONTEXT  pIrpContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PIRP pIrp = pIrpContext->pIrp;
    const IRP_ARGS_READ_DIRECTORY_CHANGE *pArgs =
        &pIrp->Args.ReadDirectoryChange;
    PPVFS_CCB pCcb = NULL;

    NOTIFY_LOG_TRACE("(%p) Enter", pIrpContext);

    /* Sanity checks */
    ntError = PvfsAcquireCCB(pIrp->FileHandle, &pCcb);
    BAIL_ON_NT_STATUS(ntError);

    if (!IsSetFlag(pCcb->Flags, PVFS_CCB_FLAG_CREATE_COMPLETE))
    {
        ntError = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntError);
    }

    if (!PVFS_IS_DIR(pCcb))
    {
        ntError = STATUS_NOT_A_DIRECTORY;
        BAIL_ON_NT_STATUS(ntError);
    }

    ntError = PvfsAccessCheckFileHandle(pCcb, FILE_LIST_DIRECTORY);
    BAIL_ON_NT_STATUS(ntError);

    /* If we have something in the buffer, return that immediately.  Else
       register a notification filter */

    LWIO_ASSERT(pCcb->pScb->pOwnerFcb);

    ntError = PvfsNotifyReportBufferedChanges(
                  pCcb->pScb->pOwnerFcb,
                  pCcb,
                  pIrpContext);
    if (ntError == STATUS_NOT_FOUND)
    {
        LWIO_ASSERT(pCcb->pScb->pOwnerFcb);

        NOTIFY_LOG_DEBUG(
            "(%p:%p:0x%x:%s:%d) Adding new filter for IRP",
            pCcb->pScb->pOwnerFcb,
            pCcb,
            pArgs->NotifyFilter,
            pArgs->WatchTree ? "true" : "false",
            pArgs->MaxBufferSize ? *pArgs->MaxBufferSize : 0);

        ntError = PvfsNotifyAddFilter(
                      pCcb->pScb->pOwnerFcb,
                      pCcb,
                      pIrpContext);
        if (ntError == STATUS_SUCCESS)
        {
            ntError = STATUS_PENDING;
            goto cleanup;
        }
    }
    BAIL_ON_NT_STATUS(ntError);

cleanup:

    if (pCcb)
    {
        PvfsReleaseCCB(pCcb);
    }

    NOTIFY_LOG_TRACE("(%p) Exit %s", pIrpContext, LwNtStatusToName(ntError));

    return ntError;

error:
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyAddIrpToFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpContext);

static
VOID
PvfsNotifyReconfigureFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpContext
    );

static
NTSTATUS
PvfsNotifyReportBufferedChanges(
    PPVFS_FCB pFcb,
    PPVFS_CCB pCcb,
    PPVFS_IRP_CONTEXT pIrpContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    const IRP_ARGS_READ_DIRECTORY_CHANGE *pArgs =
        &pIrpContext->pIrp->Args.ReadDirectoryChange;
    FILE_NOTIFY_CHANGE NotifyFilter = pArgs->NotifyFilter;
    PVOID pBuffer = pArgs->Buffer;
    ULONG Length = pArgs->Length;
    PLW_LIST_LINKS pFilterLink = NULL;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;
    BOOLEAN bWatchTree = pArgs->WatchTree;
    BOOLEAN bLocked = FALSE;

    LWIO_LOCK_MUTEX(bLocked, &pFcb->NotifyMutex);

    /* See if we have any changes to report immediately */

    for (pFilterLink = PvfsListTraverse(pFcb->pNotifyList, NULL);
         pFilterLink;
         pFilterLink = PvfsListTraverse(pFcb->pNotifyList, pFilterLink))
    {
        pFilter = LW_STRUCT_FROM_FIELD(
                      pFilterLink,
                      PVFS_NOTIFY_FILTER_RECORD,
                      NotifyList);

        /* Changes are buffered per open file handle. */

        if (pFilter->pCcb != pCcb)
        {
            continue;
        }

        /* Handle requests in FIFO order. */
        if (!LwListIsEmpty(&pFilter->pIrpRecordList) ||
            ((pFilter->Buffer.Offset == 0) &&
             (pFilter->Buffer.Status == STATUS_SUCCESS)) ||
            (pFilter->NotifyFilter != NotifyFilter))
        {
            NOTIFY_LOG_DEBUG(
                "(%p:%p:%p:0x%x:%s) Hit - %s.",
                pFcb,
                pCcb,
                pFilter,
                NotifyFilter,
                bWatchTree ? "true" : "false",
                !LwListIsEmpty(&pFilter->pIrpRecordList) ?
                "Other IRPs pending" : "Nothing to report");

            ntError = PvfsNotifyAddIrpToFilter(
                          pFilter,
                          pIrpContext);
            if (ntError == STATUS_SUCCESS)
            {
                ntError = STATUS_PENDING;
            }

            BAIL_ON_NT_STATUS(ntError);
        }

        /* We have changes....Do we have enough space?  Have we already
           overflowed the buffer? */

        if (pFilter->Buffer.Status != STATUS_SUCCESS)
        {
            NOTIFY_LOG_DEBUG(
                "(%p:%p:%p:0x%x:%s) Hit - Returning status 0x%x",
                pFcb,
                pCcb,
                pFilter,
                NotifyFilter,
                bWatchTree ? "true" : "false",
                pFilter->Buffer.Status);

            ntError = pFilter->Buffer.Status;
        }
        else if (pFilter->Buffer.Offset > Length)
        {
            NOTIFY_LOG_DEBUG(
                "(%p:%p:%p:0x%x:%s) Hit - Irp Buffer Overflow",
                pFcb,
                pCcb,
                pFilter,
                NotifyFilter,
                bWatchTree ? "true" : "false");

            ntError = STATUS_NOTIFY_ENUM_DIR;
        }
        else
        {
            NOTIFY_LOG_DEBUG(
                "(%p:%p:%p:0x%x:%s) Hit - Returning %d bytes",
                pFcb,
                pCcb,
                pFilter,
                NotifyFilter,
                bWatchTree ? "true" : "false",
                pFilter->Buffer.Offset);

            memcpy(pBuffer, pFilter->Buffer.pData, pFilter->Buffer.Offset);
            pIrpContext->pIrp->IoStatusBlock.BytesTransferred =
                pFilter->Buffer.Offset;
            ntError = STATUS_SUCCESS;
        }

        PvfsNotifyReconfigureFilter(pFilter, pIrpContext);

        BAIL_ON_NT_STATUS(ntError);
        goto cleanup;
    }

    if (pFilterLink == NULL)
    {
        ntError = STATUS_NOT_FOUND;
        BAIL_ON_NT_STATUS(ntError);
    }

cleanup:
    LWIO_UNLOCK_MUTEX(bLocked, &pFcb->NotifyMutex);

    return ntError;

error:
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsFreeNotifyChangeBuffer(
    PPVFS_NOTIFY_FILTER_BUFFER pBuffer
    )
{
    PVFS_FREE(&pBuffer->pData);

    PVFS_ZERO_MEMORY(pBuffer);
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyReconfigureFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpContext
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PIRP pIrp = pIrpContext->pIrp;
    const IRP_ARGS_READ_DIRECTORY_CHANGE *pArgs =
        &pIrp->Args.ReadDirectoryChange;
    PPVFS_NOTIFY_FILTER_BUFFER pBuffer = &pFilter->Buffer;
    ULONG Length = 0;

    pFilter->NotifyFilter = pArgs->NotifyFilter;
    pFilter->bWatchTree = pArgs->WatchTree;
    pBuffer->Status = STATUS_SUCCESS;
    pBuffer->Offset = 0;
    pBuffer->pNotify = NULL;

    if (pArgs->MaxBufferSize)
    {
        Length = *pArgs->MaxBufferSize;
    }
    
    if (pBuffer->Length != Length)
    {
        PVOID pSavedData = pBuffer->pData;

        ntError = PvfsAllocateMemory((PVOID*)&pBuffer->pData, Length, TRUE);
        if (ntError != STATUS_SUCCESS)
        {
            NOTIFY_LOG_ERROR(
                "(%p) Resize from %d to %d Failed.",
                pBuffer,
                pBuffer->Length,
                Length);

            pBuffer->pData = pSavedData;
        }
        else
        {
            NOTIFY_LOG_TRACE(
                "(%p) Resize from %d to %d Succeeded.",
                pBuffer,
                pBuffer->Length,
                Length);

            PVFS_FREE(&pSavedData);
            pBuffer->Length = Length;
            pBuffer->Offset = 0;
            pBuffer->pNotify = NULL;
        }
    }
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsFreeIrpRecord(
    PPVFS_IRP_CONTEXT_RECORD *ppIrpRecord
    );

VOID
PvfsFreeNotifyFilter(
    PPVFS_NOTIFY_FILTER_RECORD *ppFilter
    )
{
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;
    PLW_LIST_LINKS pIrpRecordLink = NULL;
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = NULL;

    if (ppFilter && *ppFilter)
    {
        pFilter = *ppFilter;

        NOTIFY_LOG_TRACE("(%p:%p)", *ppFilter);
        while (!LwListIsEmpty(&pFilter->pIrpRecordList))
        {
            pIrpRecordLink = LwListRemoveHead(&pFilter->pIrpRecordList);
            pIrpRecord = LW_STRUCT_FROM_FIELD(
                             pIrpRecordLink,
                             PVFS_IRP_CONTEXT_RECORD,
                             RecordList);
            PvfsFreeIrpRecord(&pIrpRecord);
        }

        PvfsFreeNotifyChangeBuffer(&pFilter->Buffer);

        if (pFilter->pCcb)
        {
            PvfsReleaseCCB(pFilter->pCcb);
        }

        PVFS_FREE(ppFilter);
    }
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyAllocateFilter(
    PPVFS_NOTIFY_FILTER_RECORD *ppNotifyRecord,
    PPVFS_CCB pCcb
    );

static
NTSTATUS
PvfsNotifyAddFilter(
    PPVFS_FCB pFcb,
    PPVFS_CCB pCcb,
    PPVFS_IRP_CONTEXT pIrpContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PIRP pIrp = pIrpContext->pIrp;
    const IRP_ARGS_READ_DIRECTORY_CHANGE *pArgs =
        &pIrp->Args.ReadDirectoryChange;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;
    BOOLEAN bLocked = FALSE;

    BAIL_ON_INVALID_PTR(pFcb, ntError);

    NOTIFY_LOG_TRACE(
        "(%p:%p:0x%x:%s) Enter",
        pFcb,
        pCcb,
        pArgs->NotifyFilter,
        pArgs->WatchTree ? "true" : "false");

    ntError = PvfsNotifyAllocateFilter(&pFilter, pCcb);
    BAIL_ON_NT_STATUS(ntError);

    /* PvfsNotifyAddIrpToFilter() will make this IRP cancelable.
       Hold the NotifyMutex until we are really done queuing the
       IRP to avoid racing against a cancelation. */
    LWIO_LOCK_MUTEX(bLocked, &pCcb->pScb->pOwnerFcb->NotifyMutex);
    ntError = PvfsNotifyAddIrpToFilter(
                  pFilter, 
                  pIrpContext);
    BAIL_ON_NT_STATUS(ntError);

    ntError = PvfsListAddTail(
                  pFcb->pNotifyList,
                  &pFilter->NotifyList);
    LWIO_UNLOCK_MUTEX(bLocked, &pCcb->pScb->pOwnerFcb->NotifyMutex);
    BAIL_ON_NT_STATUS(ntError);

    LWIO_ASSERT(pFilter->pCcb->pChangeFilter == NULL);
    pFilter->pCcb->pChangeFilter = pFilter;

cleanup:

    LWIO_UNLOCK_MUTEX(bLocked, &pCcb->pScb->pOwnerFcb->NotifyMutex);

    NOTIFY_LOG_TRACE(
        "(%p:%p:0x%x:%s) Exit - New filter %p",
        pFcb,
        pCcb,
        pArgs->NotifyFilter,
        pArgs->WatchTree ? "true" : "false",
        pFilter);

    return ntError;

error:
    if (pFilter)
    {
        PvfsFreeNotifyFilter(&pFilter);
    }

    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsFreeIrpRecord(
    PPVFS_IRP_CONTEXT_RECORD *ppIrpRecord
    )
{
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = *ppIrpRecord;

    if (pIrpRecord && pIrpRecord->pIrpContext)
    {
        PvfsReleaseIrpContext(&pIrpRecord->pIrpContext);
    }
    PVFS_FREE(ppIrpRecord);
}

/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyAddIrpToFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpContext)
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pIrpRecord),
                  sizeof(PVFS_IRP_CONTEXT_RECORD),
                  TRUE);
    BAIL_ON_NT_STATUS(ntError);

    pIrpRecord->pIrpContext = PvfsReferenceIrpContext(pIrpContext);

    NOTIFY_LOG_TRACE(
        "Adding IrpRecord %p, IrpContext %p, to filter %p: %s",
        pIrpRecord,
        pIrpRecord->pIrpContext,
        pFilter,
        LwListIsEmpty(&pFilter->pIrpRecordList) ? "empty" : "not-empty");

    if (LwListIsEmpty(&pFilter->pIrpRecordList))
    {
        /*
         * This IRP is active, so the filter must serve it's needs.
         */ 
        PvfsNotifyReconfigureFilter(pFilter, pIrpRecord->pIrpContext);
    }

    LwListInsertTail(
        &pFilter->pIrpRecordList,
        &pIrpRecord->RecordList);

    /* Ensure SCB is available for future completion processing. */
    if (!pIrpContext->pScb)
    {
        pIrpContext->pScb = PvfsReferenceSCB(pFilter->pCcb->pScb);
    }

    /* Allow the call to be cancelled while in the queue */
    PvfsIrpContextMakePendedCancellable(
		pIrpContext, 
		PvfsQueueCancelIrp, 
		pIrpContext, 
		PVFS_QUEUE_TYPE_NOTIFY);

cleanup:
    return ntError;

error:
    PvfsFreeIrpRecord(&pIrpRecord);
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyAllocateFilter(
    PPVFS_NOTIFY_FILTER_RECORD *ppNotifyRecord,
    PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pFilter),
                  sizeof(PVFS_NOTIFY_FILTER_RECORD),
                  TRUE);
    BAIL_ON_NT_STATUS(ntError);

    LwListInit(&pFilter->pIrpRecordList);

    pFilter->pCcb = PvfsReferenceCCB(pCcb);

    *ppNotifyRecord = pFilter;
    pFilter = NULL;

cleanup:
    return ntError;

error:
    PVFS_FREE(&pFilter);
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyProcessEvent(
    PVOID pContext
    );

static
VOID
PvfsNotifyFullReportCtxFree(
    PPVFS_NOTIFY_REPORT_RECORD *ppContext
    );

VOID
PvfsNotifyScheduleFullReport(
    PPVFS_FCB pFcb,
    FILE_NOTIFY_CHANGE Filter,
    FILE_ACTION Action,
    PCSTR pszFilename
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_NOTIFY_REPORT_RECORD pReport = NULL;

    BAIL_ON_INVALID_PTR(pFcb, ntError);

    NOTIFY_LOG_TRACE("(%p:%s:0x%x,%d)", pFcb, pszFilename, Filter, Action);

    ntError = PvfsAllocateMemory(
                  OUT_PPVOID(&pReport),
                  sizeof(PVFS_NOTIFY_REPORT_RECORD),
                  FALSE);
    BAIL_ON_NT_STATUS(ntError);

    pReport->pFcb = PvfsReferenceFCB(pFcb);
    pReport->Filter = Filter;
    pReport->Action = Action;

    ntError = LwRtlCStringDuplicate(&pReport->pszFilename, pszFilename);
    BAIL_ON_NT_STATUS(ntError);

    ntError = LwRtlQueueWorkItem(
                  gPvfsDriverState.ThreadPool,
                  PvfsNotifyProcessEvent,
                  pReport,
                  0);
    BAIL_ON_NT_STATUS(ntError);

error:
    if (!NT_SUCCESS(ntError))
    {
        if (pReport)
        {
            PvfsNotifyFullReportCtxFree(&pReport);
        }
    }

    return;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyProcessFcb(
    PPVFS_FCB pFcb,
    PPVFS_FCB pReportParentFcb,
    PPVFS_NOTIFY_REPORT_RECORD pReport
    );

static
VOID
PvfsNotifyProcessEvent(
    PVOID pContext
    )
{
    PPVFS_NOTIFY_REPORT_RECORD pReport = (PPVFS_NOTIFY_REPORT_RECORD)pContext;
    PPVFS_FCB pParentFcb = NULL;
    PPVFS_FCB pCursor = NULL;
    PPVFS_FCB pReportParentFcb = NULL;

    LWIO_ASSERT(pReport);

    NOTIFY_LOG_TRACE("(%p)", pReport->pFcb);

    /* Simply walk up the ancestry and process the notify filter
       record on top if there is a match */

    pCursor = PvfsReferenceFCB(pReport->pFcb);
    pReportParentFcb = PvfsGetParentFCB(pReport->pFcb);

    while ((pParentFcb = PvfsGetParentFCB(pCursor)) != NULL)
    {
        PvfsReleaseFCB(&pCursor);

        PvfsNotifyProcessFcb(pParentFcb, pReportParentFcb, pReport);

        pCursor = pParentFcb;
    }

    if (pCursor)
    {
        PvfsReleaseFCB(&pCursor);
    }

    if (pReportParentFcb)
    {
        PvfsReleaseFCB(&pReportParentFcb);
    }

    if (pReport)
    {
        PvfsNotifyFullReportCtxFree(&pReport);
    }

    return;
}

/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyReportIrp(
    PPVFS_FCB pFcb,
    PPVFS_IRP_CONTEXT pIrpContext,
    FILE_ACTION Action,
    PCSTR pszFilename
    );

static
NTSTATUS
PvfsNotifyReportBuffer(
    PPVFS_FCB pFcb,
    PPVFS_NOTIFY_FILTER_BUFFER pFilterBuffer,
    FILE_ACTION Action,
    PCSTR pszFilename
    );

static
VOID
PvfsNotifyProcessFcb(
    PPVFS_FCB pFcb,
    PPVFS_FCB pReportParentFcb,
    PPVFS_NOTIFY_REPORT_RECORD pReport
    )
{
    LW_LIST_LINKS IrpCompleteList;
    PLW_LIST_LINKS pFilterLink = NULL;
    PLW_LIST_LINKS pIrpRecordLink = NULL;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = NULL;
    PIRP pIrp = NULL;
    const IRP_ARGS_READ_DIRECTORY_CHANGE *pArgs = NULL;
    PULONG pMaxBufferSize = NULL;
    BOOLEAN bLocked = FALSE;
    BOOLEAN bActive = FALSE;

    NOTIFY_LOG_TRACE(
        "(%p:%p:0x%x) Enter",
        pFcb,
        pReportParentFcb,
        pReport->Filter);

    LwListInit(&IrpCompleteList);

    LWIO_LOCK_MUTEX(bLocked, &pFcb->NotifyMutex);

    for (pFilterLink = PvfsListTraverse(pFcb->pNotifyList, NULL);
         pFilterLink;
         pFilterLink = PvfsListTraverse(pFcb->pNotifyList, pFilterLink))
    {
        pFilter = LW_STRUCT_FROM_FIELD(
                      pFilterLink,
                      PVFS_NOTIFY_FILTER_RECORD,
                      NotifyList);

        /* Match the filter and depth */

        if ((pFilter->NotifyFilter & pReport->Filter) &&
            ((pFcb == pReportParentFcb) || pFilter->bWatchTree))
        {
            NOTIFY_LOG_DEBUG(
                "(%p:%p:%p:0x%x:%s) Hit",
                pFcb,
                pFilter->pCcb,
                pFilter,
                pFilter->NotifyFilter,
                pFilter->bWatchTree ? "true" : "false");

            bActive = FALSE;

            /* Fill Directly to IRP if available, otherwise buffer. */
            while (!LwListIsEmpty(&pFilter->pIrpRecordList))
            {
                pIrpRecordLink = LwListRemoveHead(&pFilter->pIrpRecordList);
                pIrpRecord = LW_STRUCT_FROM_FIELD(
                                 pIrpRecordLink,
                                 PVFS_IRP_CONTEXT_RECORD,
                                 RecordList);
                NOTIFY_LOG_TRACE(
                    "(%p:%p:%p:0x%x:%s) Processing IrpRecord %p, IrpContext %p",
                    pFcb,
                    pFilter->pCcb,
                    pFilter,
                    pFilter->NotifyFilter,
                    pFilter->bWatchTree ? "true" : "false",
                    pIrpRecord,
                    pIrpRecord->pIrpContext);

                PvfsQueueCancelIrpIfRequested(pIrpRecord->pIrpContext);

                bActive = PvfsIrpContextMarkIfNotSetFlag(
                              pIrpRecord->pIrpContext,
                              PVFS_IRP_CTX_FLAG_CANCELLED,
                              PVFS_IRP_CTX_FLAG_ACTIVE);

                if (!bActive)
                {
                    NOTIFY_LOG_TRACE(
                        "(%p:%p:%p:0x%x:%s) Inactive",
                        pFcb,
                        pFilter->pCcb,
                        pFilter,
                        pFilter->NotifyFilter,
                        pFilter->bWatchTree ? "true" : "false");
                    PvfsFreeIrpRecord(&pIrpRecord);
                    continue;
                }

                NOTIFY_LOG_TRACE(
                    "(%p:%p:%p:0x%x:%s) IrpContext %p queued for completion",
                    pFcb,
                    pFilter->pCcb,
                    pFilter,
                    pFilter->NotifyFilter,
                    pFilter->bWatchTree ? "true" : "false",
                    pIrpRecord->pIrpContext);

                LwListInsertTail(
                    &IrpCompleteList,
                    &pIrpRecord->RecordList);

                pIrpRecordLink = LwListTraverse(&pFilter->pIrpRecordList, NULL);
                if (pIrpRecordLink)
                {
                    /* Update buffering size for now active request. */
                    pIrpRecord = LW_STRUCT_FROM_FIELD(
                                     pIrpRecordLink,
                                     PVFS_IRP_CONTEXT_RECORD,
                                     RecordList);
                    NOTIFY_LOG_TRACE(
                        "(%p:%p:%p:0x%x:%s) Successor IrpRecord %p",
                        pFcb,
                        pFilter->pCcb,
                        pFilter,
                        pFilter->NotifyFilter,
                        pFilter->bWatchTree ? "true" : "false",
                        pIrpRecord);

                    LWIO_ASSERT(pIrpRecord->pIrpContext);
                    pIrp = pIrpRecord->pIrpContext->pIrp;
                    LWIO_ASSERT(pIrp);
                    pArgs = &pIrp->Args.ReadDirectoryChange;
                    pMaxBufferSize = pArgs->MaxBufferSize;

                    /* Update buffering size for now active request. */
                    PvfsNotifyReconfigureFilter(
                        pFilter,
                        pIrpRecord->pIrpContext);
                }
                break;
            }

            if (!bActive)
            {
                NOTIFY_LOG_DEBUG(
                    "(%p:%p:%p:0x%x:%s) Buffering Event",
                    pFcb,
                    pFilter->pCcb,
                    pFilter,
                    pFilter->NotifyFilter,
                    pFilter->bWatchTree ? "true" : "false");

                (void) PvfsNotifyReportBuffer(
                           pFcb,
                           &pFilter->Buffer,
                           pReport->Action,
                           pReport->pszFilename);
            }
        }
    }

    LWIO_UNLOCK_MUTEX(bLocked, &pFcb->NotifyMutex);

    while (!LwListIsEmpty(&IrpCompleteList))
    {
        pIrpRecordLink = LwListRemoveHead(&IrpCompleteList);
        pIrpRecord = LW_STRUCT_FROM_FIELD(
                         pIrpRecordLink,
                         PVFS_IRP_CONTEXT_RECORD,
                         RecordList);

        NOTIFY_LOG_DEBUG(
            "(%p) Completing Queued IRP - Context %p",
            pFcb,
            pIrpRecord->pIrpContext);

        (void) PvfsNotifyReportIrp(
                   pFcb,
                   pIrpRecord->pIrpContext,
                   pReport->Action,
                   pReport->pszFilename);

        PvfsFreeIrpRecord(&pIrpRecord);
    }

    NOTIFY_LOG_TRACE(
        "(%p:%p:0x%x) Exit",
        pFcb,
        pReportParentFcb,
        pReport->Filter);
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyFormatNotifyInfo(
    PPVFS_FCB pFcb,
    PCSTR pszPosixFilename,
    FILE_ACTION Action,
    PFILE_NOTIFY_INFORMATION pNotifyInfo,
    ULONG ulBytesAvailable,
    PULONG pulBytesConsumed
    );

static
NTSTATUS
PvfsNotifyReportIrp(
    PPVFS_FCB pFcb,
    PPVFS_IRP_CONTEXT pIrpContext,
    FILE_ACTION Action,
    PCSTR pszFilename
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PVOID pBuffer = pIrpContext->pIrp->Args.ReadDirectoryChange.Buffer;
    ULONG Length = pIrpContext->pIrp->Args.ReadDirectoryChange.Length;
    PFILE_NOTIFY_INFORMATION pNotifyInfo = NULL;

    pNotifyInfo = (PFILE_NOTIFY_INFORMATION)pBuffer;

    ntError = PvfsNotifyFormatNotifyInfo(
                  pFcb,
                  pszFilename,
                  Action,
                  pNotifyInfo,
                  Length,
                  &pIrpContext->pIrp->IoStatusBlock.BytesTransferred);
    BAIL_ON_NT_STATUS(ntError);

cleanup:
    pIrpContext->pIrp->IoStatusBlock.Status = ntError;

    NOTIFY_LOG_TRACE(
        "(%p) Status 0x%x, Transferred",
        pIrpContext,
        pIrpContext->pIrp->IoStatusBlock.Status,
        pIrpContext->pIrp->IoStatusBlock.BytesTransferred);

    PvfsCompleteIrpContext(pIrpContext);

    return ntError;

error:
    /* Force the client to enumerate on any error. */
    memset(pBuffer, 0x0, Length);
    ntError = STATUS_NOTIFY_ENUM_DIR;
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyReportBuffer(
    PPVFS_FCB pFcb,
    PPVFS_NOTIFY_FILTER_BUFFER pFilterBuffer,
    FILE_ACTION Action,
    PCSTR pszFilename
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PVOID pBuffer = pFilterBuffer->pData + pFilterBuffer->Offset;
    ULONG Length = pFilterBuffer->Length - pFilterBuffer->Offset;
    PFILE_NOTIFY_INFORMATION pNotifyInfo = NULL;
    ULONG BytesConsumed = 0;

    /* Don't bother if we have already overflowed the buffer */

    ntError = pFilterBuffer->Status;
    BAIL_ON_NT_STATUS(ntError);

    pNotifyInfo = (PFILE_NOTIFY_INFORMATION)pBuffer;

    ntError = PvfsNotifyFormatNotifyInfo(
                  pFcb,
                  pszFilename,
                  Action,
                  pNotifyInfo,
                  Length,
                  &BytesConsumed);
    BAIL_ON_NT_STATUS(ntError);

    if (pFilterBuffer->pNotify)
    {
        ULONG NextEntry = PVFS_PTR_DIFF(pFilterBuffer->pNotify, pNotifyInfo);

        pFilterBuffer->pNotify->NextEntryOffset = NextEntry;
    }

    pFilterBuffer->pNotify = pNotifyInfo;
    pFilterBuffer->Offset += BytesConsumed;

cleanup:

    return ntError;

error:
    pFilterBuffer->Status = ntError;
    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
NTSTATUS
PvfsNotifyFormatNotifyInfo(
    PPVFS_FCB pFcb,
    PCSTR pszPosixFilename,
    FILE_ACTION Action,
    PFILE_NOTIFY_INFORMATION pNotifyInfo,
    ULONG ulBytesAvailable,
    PULONG pulBytesConsumed
    )
{
    size_t PathPrefix = 0;
    size_t FilenameBytes = 0;
    size_t PadBytes = 0;
    size_t BytesNeeded = 0;
    PSTR pszFilename = NULL;
    PWSTR pwszFilename = NULL;
    PSTR pszCursor = NULL;
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;

    PathPrefix = strlen(pFcb->pszFilename);
    if ((strncmp(pFcb->pszFilename, pszPosixFilename, PathPrefix) == 0) &&
        (pszPosixFilename[PathPrefix] == '/'))
    {
        pszPosixFilename += PathPrefix + /*'/'*/1;
    }

    ntError = LwRtlCStringDuplicate(&pszFilename, pszPosixFilename);
    BAIL_ON_NT_STATUS(ntError);

    /* Convert to a Windows pathname equivalent */
    for (pszCursor=pszFilename; *pszCursor; pszCursor++)
    {
        if (*pszCursor == '/') {
            *pszCursor = '\\';
        }
    }

    ntError = LwRtlWC16StringAllocateFromCString(&pwszFilename, pszFilename);
    BAIL_ON_NT_STATUS(ntError);

    FilenameBytes = LwRtlWC16StringNumChars(pwszFilename) * sizeof(WCHAR);
    PadBytes = BytesNeeded =
        LW_FIELD_OFFSET(FILE_NOTIFY_INFORMATION, FileName) + FilenameBytes;
    PVFS_ALIGN_MEMORY(BytesNeeded, 4);
    PadBytes = BytesNeeded - PadBytes;

    if (ulBytesAvailable < BytesNeeded)
    {
        ntError = STATUS_NOTIFY_ENUM_DIR;
        BAIL_ON_NT_STATUS(ntError);
    }

    pNotifyInfo->NextEntryOffset = 0;
    pNotifyInfo->Action = Action;
    pNotifyInfo->FileNameLength = FilenameBytes;
    memcpy(&pNotifyInfo->FileName, pwszFilename, FilenameBytes);
    if (PadBytes)
    {
        memset((PBYTE)&pNotifyInfo->FileName + FilenameBytes, 0, PadBytes);
    }

    *pulBytesConsumed = BytesNeeded;

cleanup:
    LwRtlCStringFree(&pszFilename);
    LwRtlWC16StringFree(&pwszFilename);

    return ntError;

error:
    *pulBytesConsumed = 0;

    goto cleanup;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyFullReportCtxFree(
    PPVFS_NOTIFY_REPORT_RECORD *ppReport
    )
{
    PPVFS_NOTIFY_REPORT_RECORD pReport = NULL;

    if (ppReport && *ppReport)
    {
        pReport = (PPVFS_NOTIFY_REPORT_RECORD)*ppReport;

        if (pReport->pFcb)
        {
            PvfsReleaseFCB(&pReport->pFcb);
        }

        LwRtlCStringFree(&pReport->pszFilename);

        PVFS_FREE(ppReport);
    }

    return;
}


/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyCancelNotify(
    PVOID pContext
    );

NTSTATUS
PvfsScheduleCancelNotify(
    PPVFS_IRP_CONTEXT pIrpContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_IRP_CONTEXT pIrpCtx = NULL;

    BAIL_ON_INVALID_PTR(pIrpContext->pScb, ntError);

    pIrpCtx = PvfsReferenceIrpContext(pIrpContext);

    ntError = LwRtlQueueWorkItem(
                  gPvfsDriverState.ThreadPool,
                  PvfsNotifyCancelNotify,
                  pIrpCtx,
                  LW_SCHEDULE_HIGH_PRIORITY);
    BAIL_ON_NT_STATUS(ntError);

error:
    if (!NT_SUCCESS(ntError))
    {
        if (pIrpCtx)
        {
            PvfsReleaseIrpContext(&pIrpCtx);
        }
    }

    return ntError;
}


/*****************************************************************************
 ****************************************************************************/

static
PPVFS_IRP_CONTEXT_RECORD
PvfsNotifyFindIrpRecord(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpCtx
    );

static
VOID
PvfsNotifyRemoveIrpRecordFromFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord
    );

static
VOID
PvfsNotifyCancelNotify(
    PVOID pContext
    )
{
    NTSTATUS ntError = STATUS_UNSUCCESSFUL;
    PPVFS_IRP_CONTEXT pIrpCtx = (PPVFS_IRP_CONTEXT)pContext;
    PPVFS_FCB pFcb = NULL;
    PPVFS_CCB pCcb = NULL;
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = NULL;
    BOOLEAN bFcbLocked = FALSE;
    PLW_LIST_LINKS pFilterLink = NULL;
    PLW_LIST_LINKS pFilterNextLink = NULL;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;

    LWIO_ASSERT(pIrpCtx->pScb->pOwnerFcb);

    // We have the IrpCtx->Scb's reference so no need to take another
    pFcb = pIrpCtx->pScb->pOwnerFcb;

    ntError = PvfsAcquireCCB(pIrpCtx->pIrp->FileHandle, &pCcb);
    LWIO_ASSERT(ntError == STATUS_SUCCESS);

    LWIO_LOCK_MUTEX(bFcbLocked, &pFcb->NotifyMutex);

    pFilterLink = PvfsListTraverse(pFcb->pNotifyList, NULL);
    while (pFilterLink)
    {
        pFilter = LW_STRUCT_FROM_FIELD(
                      pFilterLink,
                      PVFS_NOTIFY_FILTER_RECORD,
                      NotifyList);

        pFilterNextLink = PvfsListTraverse(pFcb->pNotifyList, pFilterLink);

        if (pFilter->pCcb != pCcb)
        {
            pFilterLink = pFilterNextLink;
            pFilter = NULL;
            continue;
        }

        NOTIFY_LOG_DEBUG(
            "(%p:%p) Cleaning Filter %p via cancel IrpContext %p.",
            pFcb,
            pCcb,
            pFilter,
            pIrpCtx);

        pIrpRecord = PvfsNotifyFindIrpRecord(pFilter, pIrpCtx);
        if (pIrpRecord)
        {
            PvfsNotifyRemoveIrpRecordFromFilter(pFilter, pIrpRecord);
        }
        break;
    }

    LWIO_UNLOCK_MUTEX(bFcbLocked, &pFcb->NotifyMutex);

    if (pIrpRecord)
    {
        pIrpRecord->pIrpContext->pIrp->IoStatusBlock.Status = STATUS_CANCELLED;
        PvfsCompleteIrpContext(pIrpRecord->pIrpContext);
        PvfsFreeIrpRecord(&pIrpRecord);
    }
    else
    {
        pIrpCtx->pIrp->IoStatusBlock.Status = STATUS_CANCELLED;

        PvfsCompleteIrpContext(pIrpCtx);
    }

    if (pCcb)
    {
        PvfsReleaseCCB(pCcb);
    }

    if (pIrpCtx)
    {
        PvfsReleaseIrpContext(&pIrpCtx);
    }

    return;
}


/*****************************************************************************
 ****************************************************************************/

VOID
PvfsNotifyRemoveFilter(
    PPVFS_FCB pFcb,
    PPVFS_CCB pCcb
    )
{
    NTSTATUS ntError = STATUS_SUCCESS;
    PPVFS_NOTIFY_FILTER_RECORD pFilter = NULL;
    BOOLEAN bFcbLocked = FALSE;

    NOTIFY_LOG_TRACE("(%p:%p)", pFcb, pCcb);
    LWIO_ASSERT(pFcb);
    LWIO_ASSERT(pCcb);

    LWIO_LOCK_MUTEX(bFcbLocked, &pFcb->NotifyMutex);
    if (pCcb->pChangeFilter)
    {
        ntError = PvfsListRemoveItem(
                      pFcb->pNotifyList,
                      &pCcb->pChangeFilter->NotifyList);
        pFilter = pCcb->pChangeFilter;
        pCcb->pChangeFilter = NULL;
    }
    LWIO_UNLOCK_MUTEX(bFcbLocked, &pFcb->NotifyMutex);

    NOTIFY_LOG_TRACE("(%p:%p) status is %d", pFcb, pCcb, ntError);
    LWIO_ASSERT(ntError == STATUS_SUCCESS);

    PvfsFreeNotifyFilter(&pFilter);
}


/*****************************************************************************
 ****************************************************************************/

static
PPVFS_IRP_CONTEXT_RECORD
PvfsNotifyFindIrpRecord(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT pIrpCtx
    )
{
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord = NULL;
    PLW_LIST_LINKS pIrpRecordLink = NULL;

    pIrpRecordLink = LwListTraverse(&pFilter->pIrpRecordList, NULL);
    while (pIrpRecordLink)
    {
        pIrpRecord = LW_STRUCT_FROM_FIELD(
                         pIrpRecordLink,
                         PVFS_IRP_CONTEXT_RECORD,
                         RecordList);
        pIrpRecordLink = LwListTraverse(
                             &pFilter->pIrpRecordList,
                             pIrpRecordLink);

        if (pIrpRecord->pIrpContext == pIrpCtx)
        {
            NOTIFY_LOG_TRACE(
                "(%p:%p) Found IrpContext.",
                pFilter,
                pIrpCtx);
            break;
        }
        pIrpRecord = NULL;
    }
    return (pIrpRecord);
}

/*****************************************************************************
 ****************************************************************************/

static
VOID
PvfsNotifyRemoveIrpRecordFromFilter(
    PPVFS_NOTIFY_FILTER_RECORD pFilter,
    PPVFS_IRP_CONTEXT_RECORD pIrpRecord
    )
{
    PPVFS_IRP_CONTEXT_RECORD pIrpHeadRecord = NULL;
    PLW_LIST_LINKS pIrpRecordFirstLink = NULL;
    PLW_LIST_LINKS pIrpRecordNextLink = NULL;

    LWIO_ASSERT(pFilter);
    LWIO_ASSERT(pIrpRecord);

    NOTIFY_LOG_DEBUG("(%p:%p:%p) Removing IrpRecord.",
        pFilter,
        pIrpRecord,
        pIrpRecord->pIrpContext);

    pIrpRecordFirstLink = LwListTraverse(
                              &pFilter->pIrpRecordList,
                              NULL);
    pIrpRecordNextLink = LwListTraverse(
                             &pFilter->pIrpRecordList,
                             &pIrpRecord->RecordList);
   LwListRemove(&pIrpRecord->RecordList);

   if (pIrpRecordFirstLink &&
       (pIrpRecordFirstLink == pIrpRecordNextLink))
   {
        /* New IRP at the head.  Reconfigure the filter. */
       pIrpHeadRecord = LW_STRUCT_FROM_FIELD(
                            pIrpRecordNextLink,
                            PVFS_IRP_CONTEXT_RECORD,
                            RecordList);
       PvfsNotifyReconfigureFilter(pFilter, pIrpHeadRecord->pIrpContext);
    }
}
