[RDBSS]
[reactos.git] / reactos / sdk / lib / drivers / rdbsslib / rdbss.c
index 348ae81..2672d5f 100644 (file)
@@ -491,6 +491,7 @@ NPAGED_LOOKASIDE_LIST RxContextLookasideList;
 FAST_MUTEX RxContextPerFileSerializationMutex;
 RDBSS_DATA RxData;
 FCB RxDeviceFCB;
+BOOLEAN RxLoudLowIoOpsEnabled = FALSE;
 RX_FSD_DISPATCH_VECTOR RxDeviceFCBVector[IRP_MJ_MAXIMUM_FUNCTION + 1] =
 {
     { RxCommonDispatchProblem },
@@ -593,11 +594,31 @@ DECLARE_CONST_UNICODE_STRING(unknownId, L"???");
 
 /* FUNCTIONS ****************************************************************/
 
+/*
+ * @implemented
+ */
 VOID
 CheckForLoudOperations(
     PRX_CONTEXT RxContext)
 {
-    UNIMPLEMENTED;
+    PAGED_CODE();
+
+#define ALLSCR_LENGTH (sizeof(L"all.scr") - sizeof(UNICODE_NULL))
+
+    /* Are loud operations enabled? */
+    if (RxLoudLowIoOpsEnabled)
+    {
+        PFCB Fcb;
+
+        /* If so, the operation will be loud only if filename ends with all.scr */
+        Fcb = (PFCB)RxContext->pFcb;
+        if (RtlCompareMemory(Add2Ptr(Fcb->PrivateAlreadyPrefixedName.Buffer, (Fcb->PrivateAlreadyPrefixedName.Length - ALLSCR_LENGTH)),
+                             L"all.scr", ALLSCR_LENGTH) == ALLSCR_LENGTH)
+        {
+            SetFlag(RxContext->LowIoContext.Flags, LOWIO_CONTEXT_FLAG_LOUDOPS);
+        }
+    }
+#undef ALLSCR_LENGTH
 }
 
 /*
@@ -746,6 +767,106 @@ RxAddToWorkque(
     ExQueueWorkItem((PWORK_QUEUE_ITEM)&RxContext->WorkQueueItem, Queue);
 }
 
+/*
+ * @implemented
+ */
+VOID
+RxAdjustFileTimesAndSize(
+    PRX_CONTEXT Context)
+{
+    PFCB Fcb;
+    PFOBX Fobx;
+    NTSTATUS Status;
+    PFILE_OBJECT FileObject;
+    LARGE_INTEGER CurrentTime;
+    FILE_BASIC_INFORMATION FileBasicInfo;
+    FILE_END_OF_FILE_INFORMATION FileEOFInfo;
+    BOOLEAN FileModified, SetLastChange, SetLastAccess, SetLastWrite, NeedUpdate;
+
+    PAGED_CODE();
+
+    FileObject = Context->CurrentIrpSp->FileObject;
+    /* If Cc isn't initialized, the file was not read nor written, nothing to do */
+    if (FileObject->PrivateCacheMap == NULL)
+    {
+        return;
+    }
+
+    /* Get now */
+    KeQuerySystemTime(&CurrentTime);
+
+    Fobx = (PFOBX)Context->pFobx;
+    /* Was the file modified? */
+    FileModified = BooleanFlagOn(FileObject->Flags, FO_FILE_MODIFIED);
+    /* We'll set last write if it was modified and user didn't update yet */
+    SetLastWrite = FileModified && !BooleanFlagOn(Fobx->Flags, FOBX_FLAG_USER_SET_LAST_WRITE);
+    /* File was accessed if: written or read (fastio), we'll update last access if user didn't */
+    SetLastAccess = SetLastWrite ||
+                    (BooleanFlagOn(FileObject->Flags, FO_FILE_FAST_IO_READ) &&
+                     !BooleanFlagOn(Fobx->Flags, FOBX_FLAG_USER_SET_LAST_ACCESS));
+    /* We'll set last change if it was modified and user didn't update yet */
+    SetLastChange = FileModified && !BooleanFlagOn(Fobx->Flags, FOBX_FLAG_USER_SET_LAST_CHANGE);
+
+    /* Nothing to update? Job done */
+    if (!FileModified && !SetLastWrite && !SetLastAccess && !SetLastChange)
+    {
+        return;
+    }
+
+    Fcb = (PFCB)Context->pFcb;
+    /* By default, we won't issue any MRxSetFileInfoAtCleanup call */
+    NeedUpdate = FALSE;
+    RtlZeroMemory(&FileBasicInfo, sizeof(FileBasicInfo));
+
+    /* Update lastwrite time if required */
+    if (SetLastWrite)
+    {
+        NeedUpdate = TRUE;
+        Fcb->LastWriteTime.QuadPart = CurrentTime.QuadPart;
+        FileBasicInfo.LastWriteTime.QuadPart = CurrentTime.QuadPart;
+    }
+
+    /* Update lastaccess time if required */
+    if (SetLastAccess)
+    {
+        NeedUpdate = TRUE;
+        Fcb->LastAccessTime.QuadPart = CurrentTime.QuadPart;
+        FileBasicInfo.LastAccessTime.QuadPart = CurrentTime.QuadPart;
+    }
+
+    /* Update lastchange time if required */
+    if (SetLastChange)
+    {
+        NeedUpdate = TRUE;
+        Fcb->LastChangeTime.QuadPart = CurrentTime.QuadPart;
+        FileBasicInfo.ChangeTime.QuadPart = CurrentTime.QuadPart;
+    }
+
+    /* If one of the date was modified, issue a call to mini-rdr */
+    if (NeedUpdate)
+    {
+        Context->Info.FileInformationClass = FileBasicInformation;
+        Context->Info.Buffer = &FileBasicInfo;
+        Context->Info.Length = sizeof(FileBasicInfo);
+
+        MINIRDR_CALL(Status, Context, Fcb->MRxDispatch, MRxSetFileInfoAtCleanup, (Context));
+        (void)Status;
+    }
+
+    /* If the file was modified, update its EOF */
+    if (FileModified)
+    {
+        FileEOFInfo.EndOfFile.QuadPart = Fcb->Header.FileSize.QuadPart;
+
+        Context->Info.FileInformationClass = FileEndOfFileInformation;
+        Context->Info.Buffer = &FileEOFInfo;
+        Context->Info.Length = sizeof(FileEOFInfo);
+
+        MINIRDR_CALL(Status, Context, Fcb->MRxDispatch, MRxSetFileInfoAtCleanup, (Context));
+        (void)Status;
+    }
+}
+
 /*
  * @implemented
  */
@@ -785,11 +906,177 @@ RxAllocateCanonicalNameBuffer(
     return STATUS_SUCCESS;
 }
 
+/*
+ * @implemented
+ */
 VOID
 RxCancelNotifyChangeDirectoryRequestsForFobx(
    PFOBX Fobx)
 {
-    UNIMPLEMENTED;
+    KIRQL OldIrql;
+    PLIST_ENTRY Entry;
+    PRX_CONTEXT Context;
+    LIST_ENTRY ContextsToCancel;
+
+    /* Init a list for the contexts to cancel */
+    InitializeListHead(&ContextsToCancel);
+
+    /* Lock our list lock */
+    KeAcquireSpinLock(&RxStrucSupSpinLock, &OldIrql);
+
+    /* Now, browse all the active contexts, to find the associated ones */
+    Entry = RxActiveContexts.Flink;
+    while (Entry != &RxActiveContexts)
+    {
+        Context = CONTAINING_RECORD(Entry, RX_CONTEXT, ContextListEntry);
+        Entry = Entry->Flink;
+
+        /* Not the IRP we're looking for, ignore */
+        if (Context->MajorFunction != IRP_MJ_DIRECTORY_CONTROL ||
+            Context->MinorFunction != IRP_MN_NOTIFY_CHANGE_DIRECTORY)
+        {
+            continue;
+        }
+
+        /* Not the FOBX we're looking for, ignore */
+        if ((PFOBX)Context->pFobx != Fobx)
+        {
+            continue;
+        }
+
+        /* No cancel routine (can't be cancel, then), ignore */
+        if (Context->MRxCancelRoutine == NULL)
+        {
+            continue;
+        }
+
+        /* Mark our context as cancelled */
+        SetFlag(Context->Flags, RX_CONTEXT_FLAG_CANCELLED);
+
+        /* Move it to our list */
+        RemoveEntryList(&Context->ContextListEntry);
+        InsertTailList(&ContextsToCancel, &Context->ContextListEntry);
+
+        InterlockedIncrement((volatile long *)&Context->ReferenceCount);
+    }
+
+    /* Done with the contexts */
+    KeReleaseSpinLock(&RxStrucSupSpinLock, OldIrql);
+
+    /* Now, handle all our "extracted" contexts */
+    while (!IsListEmpty(&ContextsToCancel))
+    {
+        Entry = RemoveHeadList(&ContextsToCancel);
+        Context = CONTAINING_RECORD(Entry, RX_CONTEXT, ContextListEntry);
+
+        /* If they had an associated IRP (should be always true) */
+        if (Context->CurrentIrp != NULL)
+        {
+            /* Then, call cancel routine */
+            ASSERT(Context->MRxCancelRoutine != NULL);
+            DPRINT1("Canceling %p with %p\n", Context, Context->MRxCancelRoutine);
+            Context->MRxCancelRoutine(Context);
+        }
+
+        /* And delete the context */
+        RxDereferenceAndDeleteRxContext(Context);
+    }
+}
+
+/*
+ * @implemented
+ */
+NTSTATUS
+RxCancelNotifyChangeDirectoryRequestsForVNetRoot(
+   PV_NET_ROOT VNetRoot,
+   BOOLEAN ForceFilesClosed)
+{
+    KIRQL OldIrql;
+    NTSTATUS Status;
+    PLIST_ENTRY Entry;
+    PRX_CONTEXT Context;
+    LIST_ENTRY ContextsToCancel;
+
+    /* Init a list for the contexts to cancel */
+    InitializeListHead(&ContextsToCancel);
+
+    /* Lock our list lock */
+    KeAcquireSpinLock(&RxStrucSupSpinLock, &OldIrql);
+
+    /* Now, browse all the active contexts, to find the associated ones */
+    Entry = RxActiveContexts.Flink;
+    while (Entry != &RxActiveContexts)
+    {
+        Context = CONTAINING_RECORD(Entry, RX_CONTEXT, ContextListEntry);
+        Entry = Entry->Flink;
+
+        /* Not the IRP we're looking for, ignore */
+        if (Context->MajorFunction != IRP_MJ_DIRECTORY_CONTROL ||
+            Context->MinorFunction != IRP_MN_NOTIFY_CHANGE_DIRECTORY)
+        {
+            continue;
+        }
+
+        /* Not the VNetRoot we're looking for, ignore */
+        if (Context->pFcb == NULL ||
+            (PV_NET_ROOT)Context->NotifyChangeDirectory.pVNetRoot != VNetRoot)
+        {
+            continue;
+        }
+
+        /* No cancel routine (can't be cancel, then), ignore */
+        if (Context->MRxCancelRoutine == NULL)
+        {
+            continue;
+        }
+
+        /* At that point, we found a matching context
+         * If we're not asked to force close, then fail - it's still open
+         */
+        if (!ForceFilesClosed)
+        {
+            Status = STATUS_FILES_OPEN;
+            break;
+        }
+
+        /* Mark our context as cancelled */
+        SetFlag(Context->Flags, RX_CONTEXT_FLAG_CANCELLED);
+
+        /* Move it to our list */
+        RemoveEntryList(&Context->ContextListEntry);
+        InsertTailList(&ContextsToCancel, &Context->ContextListEntry);
+
+        InterlockedIncrement((volatile long *)&Context->ReferenceCount);
+    }
+
+    /* Done with the contexts */
+    KeReleaseSpinLock(&RxStrucSupSpinLock, OldIrql);
+
+    if (Status != STATUS_SUCCESS)
+    {
+        return Status;
+    }
+
+    /* Now, handle all our "extracted" contexts */
+    while (!IsListEmpty(&ContextsToCancel))
+    {
+        Entry = RemoveHeadList(&ContextsToCancel);
+        Context = CONTAINING_RECORD(Entry, RX_CONTEXT, ContextListEntry);
+
+        /* If they had an associated IRP (should be always true) */
+        if (Context->CurrentIrp != NULL)
+        {
+            /* Then, call cancel routine */
+            ASSERT(Context->MRxCancelRoutine != NULL);
+            DPRINT1("Canceling %p with %p\n", Context, Context->MRxCancelRoutine);
+            Context->MRxCancelRoutine(Context);
+        }
+
+        /* And delete the context */
+        RxDereferenceAndDeleteRxContext(Context);
+    }
+
+    return Status;
 }
 
 VOID
@@ -1060,6 +1347,13 @@ RxCheckShareAccessPerSrvOpens(
     return STATUS_SUCCESS;
 }
 
+VOID
+RxCleanupPipeQueues(
+    PRX_CONTEXT Context)
+{
+    UNIMPLEMENTED;
+}
+
 /*
  * @implemented
  */
@@ -1123,7 +1417,7 @@ RxCloseAssociatedSrvOpen(
 
         /* Get the FCB from RX_CONTEXT */
         Fcb = (PFCB)RxContext->pFcb;
-        SrvOpen == NULL;
+        SrvOpen = NULL;
     }
 
     /* If we don't have RX_CONTEXT, allocte one, we'll need it */
@@ -1453,8 +1747,9 @@ RxCommonCleanup(
     NTSTATUS Status;
     PNET_ROOT NetRoot;
     PFILE_OBJECT FileObject;
-    PLARGE_INTEGER TruncateSize;
-    BOOLEAN NeedPurge, FcbTableAcquired, OneLeft, IsFile, FcbAcquired;
+    LARGE_INTEGER TruncateSize;
+    PLARGE_INTEGER TruncateSizePtr;
+    BOOLEAN NeedPurge, FcbTableAcquired, OneLeft, IsFile, FcbAcquired, LeftForDelete;
 
     PAGED_CODE();
 
@@ -1536,12 +1831,13 @@ RxCommonCleanup(
 
     NetRoot = (PNET_ROOT)Fcb->pNetRoot;
     FcbTableAcquired = FALSE;
-    OneLeft = FALSE;
+    LeftForDelete = FALSE;
+    OneLeft = (Fcb->UncleanCount == 1);
 
     _SEH2_TRY
     {
         /* Unclean count and delete on close? Verify whether we're the one */
-        if (Fcb->UncleanCount == 1 && BooleanFlagOn(Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE))
+        if (OneLeft && BooleanFlagOn(Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE))
         {
             if (RxAcquireFcbTableLockExclusive(&NetRoot->FcbTable, FALSE))
             {
@@ -1563,9 +1859,10 @@ RxCommonCleanup(
                 FcbTableAcquired = TRUE;
             }
 
+            /* That means we'll perform the delete on close! */
             if (Fcb->UncleanCount == 1)
             {
-                OneLeft = TRUE;
+                LeftForDelete = TRUE;
             }
             else
             {
@@ -1575,18 +1872,82 @@ RxCommonCleanup(
         }
 
         IsFile = FALSE;
-        TruncateSize = NULL;
+        TruncateSizePtr = NULL;
         /* Handle cleanup for pipes and printers */
         if (NetRoot->Type == NET_ROOT_PIPE || NetRoot->Type == NET_ROOT_PRINT)
         {
-            UNIMPLEMENTED;
+            RxCleanupPipeQueues(Context);
         }
         /* Handle cleanup for files */
         else if (NetRoot->Type == NET_ROOT_DISK || NetRoot->Type == NET_ROOT_WILD)
         {
+            Context->LowIoContext.Flags |= LOWIO_CONTEXT_FLAG_SAVEUNLOCKS;
             if (NodeType(Fcb) == RDBSS_NTC_STORAGE_TYPE_FILE)
             {
-                UNIMPLEMENTED;
+                /* First, unlock */
+                FsRtlFastUnlockAll(&Fcb->Specific.Fcb.FileLock, FileObject, RxGetRequestorProcess(Context), Context);
+
+                /* If there are still locks to release, proceed */
+                if (Context->LowIoContext.ParamsFor.Locks.LockList != NULL)
+                {
+                    RxInitializeLowIoContext(&Context->LowIoContext, LOWIO_OP_UNLOCK_MULTIPLE);
+                    Context->LowIoContext.ParamsFor.Locks.Flags = 0;
+                    Status = RxLowIoLockControlShell(Context);
+                }
+
+                /* Fix times and size */
+                RxAdjustFileTimesAndSize(Context);
+
+                /* If we're the only one left... */
+                if (OneLeft)
+                {
+                    /* And if we're supposed to delete on close */
+                    if (LeftForDelete)
+                    {
+                        /* Update the sizes */
+                        RxAcquirePagingIoResource(Context, Fcb);
+                        Fcb->Header.FileSize.QuadPart = 0;
+                        Fcb->Header.ValidDataLength.QuadPart = 0;
+                        RxReleasePagingIoResource(Context, Fcb);
+                    }
+                    /* Otherwise, call the mini-rdr to adjust sizes */
+                    else
+                    {
+                        /* File got grown up, fill with zeroes */
+                        if (!BooleanFlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) &&
+                            (Fcb->Header.ValidDataLength.QuadPart < Fcb->Header.FileSize.QuadPart))
+                        {
+                            MINIRDR_CALL(Status, Context, Fcb->MRxDispatch, MRxZeroExtend, (Context));
+                            Fcb->Header.ValidDataLength.QuadPart = Fcb->Header.FileSize.QuadPart;
+                        }
+
+                        /* File was truncated, let mini-rdr proceed */
+                        if (BooleanFlagOn(Fcb->FcbState, FCB_STATE_TRUNCATE_ON_CLOSE))
+                        {
+                            MINIRDR_CALL(Status, Context, Fcb->MRxDispatch, MRxTruncate, (Context));
+                            ClearFlag(Fcb->FcbState, FCB_STATE_TRUNCATE_ON_CLOSE);
+
+                            /* Keep track of file change for Cc uninit */
+                            TruncateSize.QuadPart = Fcb->Header.FileSize.QuadPart;
+                            TruncateSizePtr = &TruncateSize;
+                        }
+                    }
+                }
+
+                /* If RxMarkFobxOnCleanup() asked for purge, make sure we're the only one left first */
+                if (NeedPurge)
+                {
+                    if (!OneLeft)
+                    {
+                        NeedPurge = FALSE;
+                    }
+                }
+                /* Otherwise, try to see whether we can purge */
+                else
+                {
+                    NeedPurge = (OneLeft && (LeftForDelete || !BooleanFlagOn(Fcb->FcbState, FCB_STATE_COLLAPSING_ENABLED)));
+                }
+
                 IsFile = TRUE;
             }
         }
@@ -1613,7 +1974,7 @@ RxCommonCleanup(
             if (Fcb->NonPaged->SectionObjectPointers.DataSectionObject != NULL &&
                 Fcb->UncleanCount == Fcb->UncachedUncleanCount)
             {
-                DPRINT1("Flushing %p due to last cached handle cleanup\n", Context);
+                DPRINT("Flushing %p due to last cached handle cleanup\n", Context);
                 RxFlushFcbInSystemCache(Fcb, TRUE);
             }
         }
@@ -1622,7 +1983,7 @@ RxCommonCleanup(
             /* Always */
             if (Fcb->NonPaged->SectionObjectPointers.DataSectionObject != NULL)
             {
-                DPRINT1("Flushing %p on cleanup\n", Context);
+                DPRINT("Flushing %p on cleanup\n", Context);
                 RxFlushFcbInSystemCache(Fcb, TRUE);
             }
         }
@@ -1635,32 +1996,32 @@ RxCommonCleanup(
                 if (Fcb->UncachedUncleanCount == Fcb->UncleanCount &&
                     Fcb->NonPaged->SectionObjectPointers.DataSectionObject != NULL)
                 {
-                    DPRINT1("Flushing FCB in system cache for %p\n", Context);
+                    DPRINT("Flushing FCB in system cache for %p\n", Context);
                     RxPurgeFcbInSystemCache(Fcb, NULL, 0, FALSE, TRUE);
                 }
             }
         }
 
-        /* If purge required, flush */
-        if (!OneLeft && NeedPurge)
+        /* If purge required, and not about to delete, flush */
+        if (!LeftForDelete && NeedPurge)
         {
-            DPRINT1("Flushing FCB in system cache for %p\n", Context);
+            DPRINT("Flushing FCB in system cache for %p\n", Context);
             RxFlushFcbInSystemCache(Fcb, TRUE);
         }
 
         /* If it was a file, drop cache */
         if (IsFile)
         {
-            DPRINT1("Uninit cache map for file\n");
-            RxUninitializeCacheMap(Context, FileObject, TruncateSize);
+            DPRINT("Uninit cache map for file\n");
+            RxUninitializeCacheMap(Context, FileObject, TruncateSizePtr);
         }
 
-        /* If that's the one left, or if it needs purge, flush */
-        if (OneLeft || NeedPurge)
+        /* If that's the one left for deletion, or if it needs purge, flush */
+        if (LeftForDelete || NeedPurge)
         {
-            RxPurgeFcbInSystemCache(Fcb, NULL, 0, FALSE, !OneLeft);
-            /* Also remove from FCB table */
-            if (OneLeft)
+            RxPurgeFcbInSystemCache(Fcb, NULL, 0, FALSE, !LeftForDelete);
+            /* If that's for deletion, also remove from FCB table */
+            if (LeftForDelete)
             {
                 RxRemoveNameNetFcb(Fcb);
                 RxReleaseFcbTableLock(&NetRoot->FcbTable);
@@ -2066,7 +2427,7 @@ RxCommonDevFCBClose(
     /* Our FOBX if set, has to be a VNetRoot */
     if (NetRoot != NULL)
     {
-        RxAcquirePrefixTableLockShared(Context->RxDeviceObject->pRxNetNameTable, TRUE);
+        RxAcquirePrefixTableLockExclusive(Context->RxDeviceObject->pRxNetNameTable, TRUE);
         if (NetRoot->NodeTypeCode == RDBSS_NTC_V_NETROOT)
         {
             --NetRoot->NumberOfOpens;
@@ -3786,17 +4147,6 @@ RxFastIoWrite(
     return FALSE;
 }
 
-NTSTATUS
-NTAPI
-RxFinalizeConnection(
-    IN OUT PNET_ROOT NetRoot,
-    IN OUT PV_NET_ROOT VNetRoot OPTIONAL,
-    IN LOGICAL ForceFilesClosed)
-{
-    UNIMPLEMENTED;
-    return STATUS_NOT_IMPLEMENTED;
-}
-
 NTSTATUS
 RxFindOrCreateFcb(
     PRX_CONTEXT RxContext,
@@ -4945,12 +5295,43 @@ RxIsMemberOfTopLevelIrpAllocatedContextsList(
     return Found;
 }
 
+/*
+ * @implemented
+ */
 BOOLEAN
 RxIsOkToPurgeFcb(
     PFCB Fcb)
 {
-    UNIMPLEMENTED;
-    return FALSE;
+    PLIST_ENTRY Entry;
+
+    /* No associated SRV_OPEN, it's OK to purge */
+    if (IsListEmpty(&Fcb->SrvOpenList))
+    {
+        return TRUE;
+    }
+
+    /* Only allow to purge if all the associated SRV_OPEN
+     * - have no outstanding opens ongoing
+     * - have only read attribute set
+     */
+    for (Entry = Fcb->SrvOpenList.Flink;
+         Entry != &Fcb->SrvOpenList;
+         Entry = Entry->Flink)
+    {
+        PSRV_OPEN SrvOpen;
+
+        SrvOpen = CONTAINING_RECORD(Entry, SRV_OPEN, SrvOpenQLinks);
+
+        /* Failing previous needs, don't allow purge */
+        if (SrvOpen->UncleanFobxCount != 0 ||
+            (SrvOpen->DesiredAccess & 0xFFEFFFFF) != FILE_READ_ATTRIBUTES)
+        {
+            return FALSE;
+        }
+    }
+
+    /* All correct, allow purge */
+    return TRUE;
 }
 
 /*
@@ -5093,6 +5474,33 @@ RxLowIoIoCtlShellCompletion(
     return Status;
 }
 
+NTSTATUS
+RxLowIoLockControlShell(
+    IN PRX_CONTEXT RxContext)
+{
+    UNIMPLEMENTED;
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+/*
+ * @implemented
+ */
+NTSTATUS
+NTAPI
+RxLowIoNotifyChangeDirectoryCompletion(
+    PRX_CONTEXT RxContext)
+{
+    PAGED_CODE();
+
+    DPRINT("Completing NCD with: %lx, %lx\n", RxContext->IoStatusBlock.Status, RxContext->IoStatusBlock.Information);
+
+    /* Just copy back the IO_STATUS to the IRP */
+    RxSetIoStatusStatus(RxContext, RxContext->IoStatusBlock.Status);
+    RxSetIoStatusInfo(RxContext, RxContext->IoStatusBlock.Information);
+
+    return RxContext->IoStatusBlock.Status;
+}
+
 /*
  * @implemented
  */
@@ -5237,12 +5645,63 @@ RxLowIoReadShellCompletion(
     return Status;
 }
 
+/*
+ * @implemented
+ */
 NTSTATUS
 RxNotifyChangeDirectory(
     PRX_CONTEXT RxContext)
 {
-    UNIMPLEMENTED;
-    return STATUS_NOT_IMPLEMENTED;
+    PIRP Irp;
+    NTSTATUS Status;
+    PIO_STACK_LOCATION Stack;
+
+    PAGED_CODE();
+
+    /* The IRP can abviously wait */
+    SetFlag(RxContext->Flags, RX_CONTEXT_FLAG_WAIT);
+
+    /* Initialize its lowio */
+    RxInitializeLowIoContext(&RxContext->LowIoContext, LOWIO_OP_NOTIFY_CHANGE_DIRECTORY);
+
+    _SEH2_TRY
+    {
+        /* Lock user buffer */
+        Stack = RxContext->CurrentIrpSp;
+        RxLockUserBuffer(RxContext, IoWriteAccess, Stack->Parameters.NotifyDirectory.Length);
+
+        /* Copy parameters from IO_STACK */
+        RxContext->LowIoContext.ParamsFor.NotifyChangeDirectory.WatchTree = BooleanFlagOn(Stack->Flags, SL_WATCH_TREE);
+        RxContext->LowIoContext.ParamsFor.NotifyChangeDirectory.CompletionFilter = Stack->Parameters.NotifyDirectory.CompletionFilter;
+        RxContext->LowIoContext.ParamsFor.NotifyChangeDirectory.NotificationBufferLength = Stack->Parameters.NotifyDirectory.Length;
+
+        /* If we have an associated MDL */
+        Irp = RxContext->CurrentIrp;
+        if (Irp->MdlAddress != NULL)
+        {
+            /* Then, call mini-rdr */
+            RxContext->LowIoContext.ParamsFor.NotifyChangeDirectory.pNotificationBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
+            if (RxContext->LowIoContext.ParamsFor.NotifyChangeDirectory.pNotificationBuffer != NULL)
+            {
+                Status = RxLowIoSubmit(RxContext, RxLowIoNotifyChangeDirectoryCompletion);
+            }
+            else
+            {
+                Status = STATUS_INSUFFICIENT_RESOURCES;
+            }
+        }
+        else
+        {
+            Status = STATUS_INVALID_PARAMETER;
+        }
+    }
+    _SEH2_FINALLY
+    {
+        /* All correct */
+    }
+    _SEH2_END;
+
+    return Status;
 }
 
 NTSTATUS