/**
 * @file sys/write.c
 *
 * @copyright 2015-2026 Bill Zissimopoulos
 */
/*
 * This file is part of WinFsp.
 *
 * You can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 3 as published by the Free Software
 * Foundation.
 *
 * Licensees holding a valid commercial license may use this software
 * in accordance with the commercial license agreement provided in
 * conjunction with the software.  The terms and conditions of any such
 * commercial license agreement shall govern, supersede, and render
 * ineffective any application of the GPLv3 license to this software,
 * notwithstanding of any reference thereto in the software or
 * associated repository.
 */

#include <sys/driver.h>

FAST_IO_WRITE FspFastIoWrite;
static NTSTATUS FspFsvolWrite(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
static NTSTATUS FspFsvolWriteCached(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp,
    BOOLEAN CanWait);
static VOID FspFsvolWriteCachedDeferred(PVOID Context1, PVOID Context2);
static NTSTATUS FspFsvolWriteNonCached(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp,
    BOOLEAN CanWait);
FSP_IOPREP_DISPATCH FspFsvolWritePrepare;
FSP_IOCMPL_DISPATCH FspFsvolWriteComplete;
static FSP_IOP_REQUEST_FINI FspFsvolWriteNonCachedRequestFini;
FSP_DRIVER_DISPATCH FspWrite;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FspFastIoWrite)
#pragma alloc_text(PAGE, FspFsvolWrite)
#pragma alloc_text(PAGE, FspFsvolWriteCached)
#pragma alloc_text(PAGE, FspFsvolWriteNonCached)
#pragma alloc_text(PAGE, FspFsvolWritePrepare)
#pragma alloc_text(PAGE, FspFsvolWriteComplete)
#pragma alloc_text(PAGE, FspFsvolWriteNonCachedRequestFini)
#pragma alloc_text(PAGE, FspWrite)
#endif

enum
{
    /* WriteNonCached */
    RequestIrp                          = 0,
    RequestCookie                       = 1,
    RequestSafeMdl                      = 1,
    RequestAddress                      = 2,
    RequestProcess                      = 3,
};
FSP_FSCTL_STATIC_ASSERT(RequestCookie == RequestSafeMdl, "");

BOOLEAN FspFastIoWrite(
    PFILE_OBJECT FileObject,
    PLARGE_INTEGER ByteOffset,
    ULONG Length,
    BOOLEAN CanWait,
    ULONG Key,
    PVOID UserBuffer,
    PIO_STATUS_BLOCK IoStatus,
    PDEVICE_OBJECT DeviceObject)
{
    FSP_ENTER_FIO(PAGED_CODE());

    IoStatus->Status = STATUS_SUCCESS;
    IoStatus->Information = 0;

    if (!FspFileNodeIsValid(FileObject->FsContext))
    {
        IoStatus->Status = STATUS_INVALID_DEVICE_REQUEST;
        FSP_RETURN(Result = TRUE);
    }

    FSP_FILE_NODE* FileNode = FileObject->FsContext;
    LARGE_INTEGER WriteOffset = *ByteOffset;
    ULONG WriteLength = Length;
    BOOLEAN WriteToEndOfFile =
        FILE_WRITE_TO_END_OF_FILE == WriteOffset.LowPart && -1L == WriteOffset.HighPart;
    FSP_FSCTL_FILE_INFO FileInfo;
    UINT64 WriteEndOffset;
    BOOLEAN ExtendingFile;

    /* only regular files can be written */
    if (FileNode->IsDirectory)
    {
        IoStatus->Status = STATUS_INVALID_PARAMETER;
        FSP_RETURN(Result = TRUE);
    }

    /* do we have anything to write? */
    if (0 == WriteLength)
        FSP_RETURN(Result = TRUE);

    /* WinFsp cannot do Fast I/O when extending file */
    if (WriteToEndOfFile)
        FSP_RETURN(Result = FALSE);

    /* does the file support caching? is it write-through? */
    if (!FlagOn(FileObject->Flags, FO_CACHE_SUPPORTED) ||
        FlagOn(FileObject->Flags, FO_WRITE_THROUGH))
        FSP_RETURN(Result = FALSE);

    /* can we write without flushing? */
    Result = DEBUGTEST(90) &&
        CcCanIWrite(FileObject, WriteLength, CanWait, FALSE) &&
        CcCopyWriteWontFlush(FileObject, &WriteOffset, WriteLength);
    if (!Result)
        FSP_RETURN(Result = FALSE);

    /* try to acquire the FileNode Main exclusive */
    Result = DEBUGTEST(90) &&
        FspFileNodeTryAcquireExclusiveF(FileNode, FspFileNodeAcquireMain, CanWait);
    if (!Result)
        FSP_RETURN(Result = FALSE);

    /* is the file actually cached? */
    if (0 == FileObject->PrivateCacheMap)
    {
        FspFileNodeRelease(FileNode, Main);
        FSP_RETURN(Result = FALSE);
    }

    /* does the file have oplocks or file locks? */
    Result =
        FsRtlOplockIsFastIoPossible(FspFileNodeAddrOfOplock(FileNode)) &&
        !FsRtlAreThereCurrentFileLocks(&FileNode->FileLock);
    if (!Result)
    {
        FspFileNodeRelease(FileNode, Main);
        FSP_RETURN(Result = FALSE);
    }

    /* compute new file size */
    ASSERT(FspTimeoutInfinity32 ==
        FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.FileInfoTimeout);
    FspFileNodeGetFileInfo(FileNode, &FileInfo);
    if (WriteToEndOfFile)
        WriteOffset.QuadPart = FileInfo.FileSize;
    WriteEndOffset = WriteOffset.QuadPart + WriteLength;
    ExtendingFile = FileInfo.FileSize < WriteEndOffset;

    /* WinFsp cannot do Fast I/O when extending file */
    if (ExtendingFile)
    {
        FspFileNodeRelease(FileNode, Main);
        FSP_RETURN(Result = FALSE);
    }

    NTSTATUS Result0 =
        FspCcCopyWrite(FileObject, &WriteOffset, WriteLength, CanWait, UserBuffer);
    if (!NT_SUCCESS(Result0))
    {
        IoStatus->Status = Result0;
        IoStatus->Information = 0;
        FspFileNodeRelease(FileNode, Main);
        FSP_RETURN(Result = FALSE);
    }
    Result = STATUS_SUCCESS == Result0;

    if (Result)
    {
        SetFlag(FileObject->Flags, FO_FILE_MODIFIED);
        FileObject->CurrentByteOffset.QuadPart = WriteEndOffset;
        IoStatus->Information = WriteLength;
    }

    FspFileNodeRelease(FileNode, Main);

    FSP_LEAVE_FIO(
        "FileObject=%p, UserBuffer=%p, CanWait=%d, "
        "Key=%#lx, ByteOffset=%#lx:%#lx, Length=%ld",
        FileObject, UserBuffer, CanWait,
        Key, ByteOffset->HighPart, ByteOffset->LowPart, Length);
}

static NTSTATUS FspFsvolWrite(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
{
    PAGED_CODE();

    /* is this a valid FileObject? */
    if (!FspFileNodeIsValid(IrpSp->FileObject->FsContext))
        return STATUS_INVALID_DEVICE_REQUEST;

    NTSTATUS Result;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    FSP_FILE_NODE *FileNode = FileObject->FsContext;

    /* is this an MDL complete request? */
    if (FlagOn(IrpSp->MinorFunction, IRP_MN_COMPLETE))
    {
        Result = FspCcMdlWriteComplete(FileObject,
            &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress);
        Irp->MdlAddress = 0;
        return Result;
    }

    /* only regular files can be written */
    if (FileNode->IsDirectory)
        return STATUS_INVALID_PARAMETER;

    /* do we have anything to write? */
    if (0 == IrpSp->Parameters.Write.Length)
    {
        Irp->IoStatus.Information = 0;
        return STATUS_SUCCESS;
    }

    /* are we doing cached or non-cached I/O? */
    if (FlagOn(FileObject->Flags, FO_CACHE_SUPPORTED) &&
        !FlagOn(Irp->Flags, IRP_PAGING_IO | IRP_NOCACHE))
        Result = FspFsvolWriteCached(FsvolDeviceObject, Irp, IrpSp, IoIsOperationSynchronous(Irp));
    else
        Result = FspFsvolWriteNonCached(FsvolDeviceObject, Irp, IrpSp, IoIsOperationSynchronous(Irp));

    return Result;
}

static NTSTATUS FspFsvolWriteCached(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp,
    BOOLEAN CanWait)
{
    PAGED_CODE();

    /* assert: must be top-level IRP */
    ASSERT(0 == FspIrpTopFlags(Irp));

    NTSTATUS Result;
    BOOLEAN Retrying = 0 != FspIrpRequest(Irp);
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    FSP_FILE_NODE *FileNode = FileObject->FsContext;
    LARGE_INTEGER WriteOffset = IrpSp->Parameters.Write.ByteOffset;
    ULONG WriteLength = IrpSp->Parameters.Write.Length;
    BOOLEAN WriteToEndOfFile =
        FILE_WRITE_TO_END_OF_FILE == WriteOffset.LowPart && -1L == WriteOffset.HighPart;
    BOOLEAN SynchronousIo = BooleanFlagOn(FileObject->Flags, FO_SYNCHRONOUS_IO);
    FSP_FSCTL_FILE_INFO FileInfo;
    CC_FILE_SIZES FileSizes;
    FILE_END_OF_FILE_INFORMATION EndOfFileInformation;
    UINT64 WriteEndOffset;
    BOOLEAN ExtendingFile;
    BOOLEAN DeferWrite;
    BOOLEAN Success;

    /* should we defer the write? */
    Success = DEBUGTEST(90) && CcCanIWrite(FileObject, WriteLength, CanWait, Retrying);
    DeferWrite = !Success;

    /* try to acquire the FileNode Main exclusive */
    Success = DEBUGTEST(90) &&
        FspFileNodeTryAcquireExclusiveF(FileNode, FspFileNodeAcquireMain, CanWait);
    if (!Success)
        return FspWqRepostIrpWorkItem(Irp, FspFsvolWriteCached, 0);

    /* perform oplock check */
    Result = FspFileNodeOplockCheckAsync(
        FileNode, FspFileNodeAcquireMain, FspFsvolWriteCached,
        Irp);
    if (STATUS_PENDING == Result)
        return Result;
    if (!NT_SUCCESS(Result))
    {
        FspFileNodeRelease(FileNode, Main);
        return Result;
    }

    /* check the file locks */
    if (!FsRtlCheckLockForWriteAccess(&FileNode->FileLock, Irp))
    {
        FspFileNodeRelease(FileNode, Main);
        return STATUS_FILE_LOCK_CONFLICT;
    }

    /* compute new file size */
    ASSERT(FspTimeoutInfinity32 ==
        FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.FileInfoTimeout);
    FspFileNodeGetFileInfo(FileNode, &FileInfo);
    if (WriteToEndOfFile)
        WriteOffset.QuadPart = FileInfo.FileSize;
    WriteEndOffset = WriteOffset.QuadPart + WriteLength;
    ExtendingFile = FileInfo.FileSize < WriteEndOffset;
    if (ExtendingFile && !CanWait)
    {
        /* need CanWait==TRUE for FspSendSetInformationIrp */
        FspFileNodeRelease(FileNode, Main);
        return FspWqRepostIrpWorkItem(Irp, FspFsvolWriteCached, 0);
    }

    /* initialize cache if not already initialized! */
    if (0 == FileObject->PrivateCacheMap)
    {
        FileSizes.AllocationSize.QuadPart = FileInfo.AllocationSize;
        FileSizes.FileSize.QuadPart = FileInfo.FileSize;
        FileSizes.ValidDataLength.QuadPart = MAXLONGLONG;

        Result = FspCcInitializeCacheMap(FileObject, &FileSizes, FALSE,
            &FspCacheManagerCallbacks, FileNode);
        if (!NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Main);
            return Result;
        }
    }

    /* should we defer the write? */
    if (DeferWrite)
    {
        Result = FspWqCreateIrpWorkItem(Irp, FspFsvolWriteCached, 0);
        if (NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Main);
            IoMarkIrpPending(Irp);
            CcDeferWrite(FileObject, FspFsvolWriteCachedDeferred, Irp, 0, WriteLength, Retrying);
            return STATUS_PENDING;
        }

        /* if we are unable to defer we will go ahead and (try to) service the IRP now! */
    }

    /* are we extending the file? */
    if (ExtendingFile)
    {
        ASSERT(CanWait);

        /* send EndOfFileInformation IRP; this will also set TruncateOnClose, etc. */
        EndOfFileInformation.EndOfFile.QuadPart = WriteEndOffset;
        Result = FspSendSetInformationIrp(FsvolDeviceObject/* bypass filters */, FileObject,
            FileEndOfFileInformation, &EndOfFileInformation, sizeof EndOfFileInformation);
        if (!NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Main);
            return Result;
        }

        /* double-check that the cache still exists in case CcSetFileSizes failed */
        if (0 == FileObject->SectionObjectPointer->SharedCacheMap)
        {
            FspFileNodeRelease(FileNode, Main);
            return STATUS_INSUFFICIENT_RESOURCES; // or STATUS_SECTION_TOO_BIG?
        }
    }

    /* double-check that the end offset is <= than the file size and fail if not */
    if (WriteEndOffset > (UINT64)CcGetFileSizePointer(FileObject)->QuadPart)
    {
        FspFileNodeRelease(FileNode, Main);
        return STATUS_INTERNAL_ERROR;
    }

    /*
     * From this point forward we must jump to the CLEANUP label on failure.
     */

    /* are we using the copy or MDL interface? */
    if (!FlagOn(IrpSp->MinorFunction, IRP_MN_MDL))
    {
        PVOID Buffer;

        Buffer = 0 == Irp->MdlAddress ?
            Irp->UserBuffer : MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
        if (0 == Buffer)
        {
            Result = STATUS_INSUFFICIENT_RESOURCES;
            goto cleanup;
        }

        Result = FspCcCopyWrite(FileObject, &WriteOffset, WriteLength, CanWait, Buffer);
        if (!NT_SUCCESS(Result) || STATUS_PENDING == Result)
            goto cleanup;

        Irp->IoStatus.Information = WriteLength;
    }
    else
    {
        Result = FspCcPrepareMdlWrite(FileObject, &WriteOffset, WriteLength, &Irp->MdlAddress,
            &Irp->IoStatus);
        if (!NT_SUCCESS(Result))
            goto cleanup;
        ASSERT(STATUS_PENDING != Result);
    }

    /* update the current file offset if synchronous I/O */
    if (SynchronousIo)
        FileObject->CurrentByteOffset.QuadPart = WriteEndOffset;

    /* mark the file object as modified (if not paging I/O) */
    SetFlag(FileObject->Flags, FO_FILE_MODIFIED);

    FspFileNodeRelease(FileNode, Main);

    return STATUS_SUCCESS;

cleanup:
    FspFileNodeRelease(FileNode, Main);

    if (STATUS_PENDING == Result)
        return FspWqRepostIrpWorkItem(Irp, FspFsvolWriteCached, 0);

    return Result;
}

static VOID FspFsvolWriteCachedDeferred(PVOID Context1, PVOID Context2)
{
    // !PAGED_CODE();

    FspWqPostIrpWorkItem(Context1);
}

static NTSTATUS FspFsvolWriteNonCached(
    PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp,
    BOOLEAN CanWait)
{
    PAGED_CODE();

    /* assert: either a top-level IRP or Paging I/O */
    ASSERT(0 == FspIrpTopFlags(Irp) || FlagOn(Irp->Flags, IRP_PAGING_IO));

    NTSTATUS Result;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    FSP_FILE_NODE *FileNode = FileObject->FsContext;
    FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
    LARGE_INTEGER WriteOffset = IrpSp->Parameters.Write.ByteOffset;
    ULONG WriteLength = IrpSp->Parameters.Write.Length;
    ULONG WriteKey = IrpSp->Parameters.Write.Key;
    BOOLEAN WriteToEndOfFile =
        FILE_WRITE_TO_END_OF_FILE == WriteOffset.LowPart && -1L == WriteOffset.HighPart;
    BOOLEAN PagingIo = BooleanFlagOn(Irp->Flags, IRP_PAGING_IO);
    FSP_FSCTL_TRANSACT_REQ *Request;
    BOOLEAN Success;

    ASSERT(FileNode == FileDesc->FileNode);

    /* no MDL requests on the non-cached path */
    if (FlagOn(IrpSp->MinorFunction, IRP_MN_MDL))
        return STATUS_INVALID_PARAMETER;

    /* paging I/O cannot change the file size */
    if (PagingIo && WriteToEndOfFile)
        return STATUS_INVALID_PARAMETER;

    /* stop CcWriteBehind from calling me! */
    if (FspIoqStopped(FspFsvolDeviceExtension(FsvolDeviceObject)->Ioq))
        return FspFsvolDeviceStoppedStatus(FsvolDeviceObject);

    /* probe and lock the user buffer */
    Result = FspLockUserBuffer(Irp, WriteLength, IoReadAccess);
    if (!NT_SUCCESS(Result))
        return Result;

    /* acquire FileNode exclusive Full */
    Success = DEBUGTEST(90) &&
        FspFileNodeTryAcquireExclusiveF(FileNode, FspFileNodeAcquireFull, CanWait);
    if (!Success)
        return FspWqRepostIrpWorkItem(Irp, FspFsvolWriteNonCached, 0);

    /* perform oplock check */
    if (!PagingIo)
    {
        Result = FspFileNodeOplockCheckAsync(
            FileNode, FspFileNodeAcquireFull, FspFsvolWriteNonCached,
            Irp);
        if (STATUS_PENDING == Result)
            return Result;
        if (!NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Full);
            return Result;
        }
    }

    /* check the file locks */
    if (!PagingIo && !FsRtlCheckLockForWriteAccess(&FileNode->FileLock, Irp))
    {
        FspFileNodeRelease(FileNode, Full);
        return STATUS_FILE_LOCK_CONFLICT;
    }

    /* if this is a non-cached transfer on a cached file then flush and purge the file */
    if (!PagingIo && 0 != FileObject->SectionObjectPointer->DataSectionObject)
    {
        if (!CanWait)
        {
            FspFileNodeRelease(FileNode, Full);
            return FspWqRepostIrpWorkItem(Irp, FspFsvolWriteNonCached, 0);
        }

        Result = FspFileNodeFlushAndPurgeCache(FileNode,
            IrpSp->Parameters.Write.ByteOffset.QuadPart,
            IrpSp->Parameters.Write.Length,
            TRUE);
        if (!NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Full);
            return Result;
        }
    }

    Request = FspIrpRequest(Irp);
    if (0 == Request)
    {
        /* create request */
        Result = FspIopCreateRequestEx(Irp, 0, 0, FspFsvolWriteNonCachedRequestFini, &Request);
        if (!NT_SUCCESS(Result))
        {
            FspFileNodeRelease(FileNode, Full);
            return Result;
        }
    }
    else
    {
        /* reuse existing request */
        ASSERT(Request->Size == sizeof *Request);
        ASSERT(Request->Hint == (UINT_PTR)Irp);
        FspIopResetRequest(Request, FspFsvolWriteNonCachedRequestFini);
        RtlZeroMemory(&Request->Req,
            sizeof *Request - FIELD_OFFSET(FSP_FSCTL_TRANSACT_REQ, Req));
    }


    Request->Kind = FspFsctlTransactWriteKind;
    Request->Req.Write.UserContext = FileNode->UserContext;
    Request->Req.Write.UserContext2 = FileDesc->UserContext2;
    Request->Req.Write.Offset = WriteOffset.QuadPart;
    Request->Req.Write.Length = WriteLength;
    Request->Req.Write.Key = WriteKey;
    Request->Req.Write.ConstrainedIo = !!PagingIo;

    FspFileNodeSetOwner(FileNode, Full, Request);
    FspIopRequestContext(Request, RequestIrp) = Irp;

    FSP_STATISTICS *Statistics = FspFsvolDeviceStatistics(FsvolDeviceObject);
    if (PagingIo)
    {
        FspStatisticsInc(Statistics, Base.UserFileWrites);
        FspStatisticsAdd(Statistics, Base.UserFileWriteBytes, WriteLength);
        FspStatisticsInc(Statistics, Base.UserDiskWrites);
    }
    else
    {
        FspStatisticsInc(Statistics, Specific.NonCachedWrites);
        FspStatisticsAdd(Statistics, Specific.NonCachedWriteBytes, WriteLength);
        FspStatisticsInc(Statistics, Specific.NonCachedDiskWrites);
    }

    return FSP_STATUS_IOQ_POST;
}

NTSTATUS FspFsvolWritePrepare(
    PIRP Irp, FSP_FSCTL_TRANSACT_REQ *Request)
{
    PAGED_CODE();

    if (FspWriteIrpShouldUseProcessBuffer(Irp, Request->Req.Write.Length))
    {
        NTSTATUS Result;
        PVOID Cookie;
        PVOID Address;
        PEPROCESS Process;
        PVOID SystemAddress = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

        if (0 == SystemAddress)
            return STATUS_INSUFFICIENT_RESOURCES; /* something is seriously screwy! */

        Result = FspProcessBufferAcquire(Request->Req.Write.Length, &Cookie, &Address);
        if (!NT_SUCCESS(Result))
            return Result;

        ASSERT(0 != Address);
        try
        {
            RtlCopyMemory(Address, SystemAddress, Request->Req.Write.Length);
        }
        except (EXCEPTION_EXECUTE_HANDLER)
        {
            Result = GetExceptionCode();
            Result = FsRtlIsNtstatusExpected(Result) ? STATUS_INVALID_USER_BUFFER : Result;

            FspProcessBufferRelease(Cookie, Address);

            return Result;
        }

        /* get a pointer to the current process so that we can release the buffer later */
        Process = PsGetCurrentProcess();
        ObReferenceObject(Process);

        Request->Req.Write.Address = (UINT64)(UINT_PTR)Address;

        FspIopRequestContext(Request, RequestCookie) = (PVOID)((UINT_PTR)Cookie | 1);
        FspIopRequestContext(Request, RequestAddress) = Address;
        FspIopRequestContext(Request, RequestProcess) = Process;

        return STATUS_SUCCESS;
    }
    else
    {
        NTSTATUS Result;
        FSP_SAFE_MDL *SafeMdl = 0;
        PVOID Address;
        PEPROCESS Process;

        /* create a "safe" MDL if necessary */
        if (!FspSafeMdlCheck(Irp->MdlAddress))
        {
            Result = FspSafeMdlCreate(Irp->MdlAddress, IoReadAccess, &SafeMdl);
            if (!NT_SUCCESS(Result))
                return Result;
        }

        /* map the MDL into user-mode */
        Result = FspMapLockedPagesInUserMode(
            0 != SafeMdl ? SafeMdl->Mdl : Irp->MdlAddress, &Address, FspMvMdlMappingNoWrite);
        if (!NT_SUCCESS(Result))
        {
            if (0 != SafeMdl)
                FspSafeMdlDelete(SafeMdl);

            return Result;
        }

        /* get a pointer to the current process so that we can unmap the address later */
        Process = PsGetCurrentProcess();
        ObReferenceObject(Process);

        Request->Req.Write.Address = (UINT64)(UINT_PTR)Address;

        FspIopRequestContext(Request, RequestSafeMdl) = SafeMdl;
        FspIopRequestContext(Request, RequestAddress) = Address;
        FspIopRequestContext(Request, RequestProcess) = Process;

        return STATUS_SUCCESS;
    }
}

NTSTATUS FspFsvolWriteComplete(
    PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response)
{
    FSP_ENTER_IOC(PAGED_CODE());

    if (!NT_SUCCESS(Response->IoStatus.Status))
    {
        Irp->IoStatus.Information = 0;
        Result = Response->IoStatus.Status;
        FSP_RETURN();
    }

    FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);

    if (Response->IoStatus.Information > Request->Req.Write.Length)
        FSP_RETURN(Result = STATUS_INTERNAL_ERROR);

    PFILE_OBJECT FileObject = IrpSp->FileObject;
    FSP_FILE_NODE *FileNode = FileObject->FsContext;
    LARGE_INTEGER WriteOffset = IrpSp->Parameters.Write.ByteOffset;
    BOOLEAN WriteToEndOfFile =
        FILE_WRITE_TO_END_OF_FILE == WriteOffset.LowPart && -1L == WriteOffset.HighPart;
    BOOLEAN PagingIo = BooleanFlagOn(Irp->Flags, IRP_PAGING_IO);
    BOOLEAN SynchronousIo = BooleanFlagOn(FileObject->Flags, FO_SYNCHRONOUS_IO);

    /* if we are top-level */
    if (0 == FspIrpTopFlags(Irp))
    {
        UINT64 OriginalFileSize = FileNode->Header.FileSize.QuadPart;

        /* update file info */
        FspFileNodeSetFileInfo(FileNode, FileObject, &Response->Rsp.Write.FileInfo, TRUE);

        if (OriginalFileSize != Response->Rsp.Write.FileInfo.FileSize)
            FspFileNodeNotifyChange(FileNode, FILE_NOTIFY_CHANGE_SIZE, FILE_ACTION_MODIFIED, FALSE);

        /* update the current file offset if synchronous I/O (and not paging I/O) */
        if (SynchronousIo && !PagingIo)
            FileObject->CurrentByteOffset.QuadPart = WriteToEndOfFile ?
                Response->Rsp.Write.FileInfo.FileSize :
                WriteOffset.QuadPart + Response->IoStatus.Information;

        /* mark the file object as modified (if not paging I/O) */
        if (!PagingIo)
            SetFlag(FileObject->Flags, FO_FILE_MODIFIED);

        FspIopResetRequest(Request, 0);
    }
    else
    {
        ASSERT(PagingIo);
        FspIopResetRequest(Request, 0);
    }

    Irp->IoStatus.Information = Response->IoStatus.Information;
    Result = STATUS_SUCCESS;

    FSP_LEAVE_IOC(
        "FileObject=%p, UserBuffer=%p, MdlAddress=%p, "
        "Key=%#lx, ByteOffset=%#lx:%#lx, Length=%ld",
        IrpSp->FileObject, Irp->UserBuffer, Irp->MdlAddress,
        IrpSp->Parameters.Write.Key,
        IrpSp->Parameters.Write.ByteOffset.HighPart, IrpSp->Parameters.Write.ByteOffset.LowPart,
        IrpSp->Parameters.Write.Length);
}

static VOID FspFsvolWriteNonCachedRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4])
{
    PAGED_CODE();

    PIRP Irp = Context[RequestIrp];

    if ((UINT_PTR)Context[RequestCookie] & 1)
    {
        PVOID Cookie = (PVOID)((UINT_PTR)Context[RequestCookie] & ~1);
        PVOID Address = Context[RequestAddress];
        PEPROCESS Process = Context[RequestProcess];

        if (0 != Address)
        {
            KAPC_STATE ApcState;
            BOOLEAN Attach;

            ASSERT(0 != Process);
            Attach = Process != PsGetCurrentProcess();

            if (Attach)
                KeStackAttachProcess(Process, &ApcState);
            FspProcessBufferRelease(Cookie, Address);
            if (Attach)
                KeUnstackDetachProcess(&ApcState);

            ObDereferenceObject(Process);
        }
    }
    else
    {
        FSP_SAFE_MDL *SafeMdl = Context[RequestSafeMdl];
        PVOID Address = Context[RequestAddress];
        PEPROCESS Process = Context[RequestProcess];

        if (0 != Address)
        {
            KAPC_STATE ApcState;
            BOOLEAN Attach;

            ASSERT(0 != Process);
            Attach = Process != PsGetCurrentProcess();

            if (Attach)
                KeStackAttachProcess(Process, &ApcState);
            MmUnmapLockedPages(Address, 0 != SafeMdl ? SafeMdl->Mdl : Irp->MdlAddress);
            if (Attach)
                KeUnstackDetachProcess(&ApcState);

            ObDereferenceObject(Process);
        }

        if (0 != SafeMdl)
            FspSafeMdlDelete(SafeMdl);
    }

    if (0 != Irp)
    {
        PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
        FSP_FILE_NODE *FileNode = IrpSp->FileObject->FsContext;

        FspFileNodeReleaseOwner(FileNode, Full, Request);
    }
}

NTSTATUS FspWrite(
    PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    FSP_ENTER_MJ(PAGED_CODE());

    switch (FspDeviceExtension(DeviceObject)->Kind)
    {
    case FspFsvolDeviceExtensionKind:
        FSP_RETURN(Result = FspFsvolWrite(DeviceObject, Irp, IrpSp));
    default:
        FSP_RETURN(Result = STATUS_INVALID_DEVICE_REQUEST);
    }

    FSP_LEAVE_MJ(
        "FileObject=%p, UserBuffer=%p, MdlAddress=%p, "
        "Key=%#lx, ByteOffset=%#lx:%#lx, Length=%ld",
        IrpSp->FileObject, Irp->UserBuffer, Irp->MdlAddress,
        IrpSp->Parameters.Write.Key,
        IrpSp->Parameters.Write.ByteOffset.HighPart, IrpSp->Parameters.Write.ByteOffset.LowPart,
        IrpSp->Parameters.Write.Length);
}
