/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    filestm.c

Abstract:

    This modules implements IStream over a file.

Author:

    Jay Krell (a-JayK) June 2000

Revision History:

--*/

#define RTL_DECLARE_STREAMS 1
#define RTL_DECLARE_FILE_STREAM 1
#include "ntos.h"
#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "objidl.h"

#define RTLP_FILE_STREAM_NOT_IMPL(x) \
  KdPrintEx((DPFLTR_SXS_ID, DPFLTR_ERROR_LEVEL, "RTLSXS: %s() E_NOTIMPL", __FUNCTION__)); \
  return E_NOTIMPL;

#if !defined(RTLP_FILE_STREAM_HRESULT_FROM_STATUS)
  #if defined(RTLP_HRESULT_FROM_STATUS)
    #define RTLP_FILE_STREAM_HRESULT_FROM_STATUS(x) RTLP_HRESULT_FROM_STATUS(x)
  #else
    #define RTLP_FILE_STREAM_HRESULT_FROM_STATUS(x) HRESULT_FROM_WIN32(RtlNtStatusToDosErrorNoTeb(x))
    //#define RTLP_FILE_STREAM_HRESULT_FROM_STATUS(x) HRESULT_FROM_WIN32(RtlNtStatusToDosError(x))
    //#define RTLP_FILE_STREAM_HRESULT_FROM_STATUS(x)   HRESULT_FROM_NT(x)
  #endif
#endif

HRESULT
STDMETHODCALLTYPE
RtlInitFileStream(
    PRTL_FILE_STREAM FileStream
    )
{
    RtlZeroMemory(FileStream, sizeof(*FileStream));
    return NOERROR;
}

HRESULT
STDMETHODCALLTYPE
RtlCloseFileStream(
    PRTL_FILE_STREAM FileStream
    )
{
    const HANDLE FileHandle = FileStream->FileHandle;
    NTSTATUS Status = STATUS_SUCCESS;
    HRESULT Hr = NOERROR;

    if (FileHandle != NULL) {
        FileStream->FileHandle = NULL;
        Status = NtClose(FileHandle);
        if (!NT_SUCCESS(Status)) {
            Hr = RTLP_FILE_STREAM_HRESULT_FROM_STATUS(Status);
        }
    }
    return Hr;
}

ULONG
STDMETHODCALLTYPE
RtlAddRefFileStream(
    PRTL_FILE_STREAM FileStream
    )
{
    LONG ReferenceCount = InterlockedIncrement(&FileStream->ReferenceCount);
    return ReferenceCount;
}

ULONG
STDMETHODCALLTYPE
RtlReleaseFileStream(
    PRTL_FILE_STREAM FileStream
    )
{
    LONG ReferenceCount = InterlockedDecrement(&FileStream->ReferenceCount);
    if (ReferenceCount == 0 && FileStream->FinalRelease != NULL) {
        FileStream->FinalRelease(FileStream);
    }
    return ReferenceCount;
}

HRESULT
STDMETHODCALLTYPE
RtlQueryInterfaceFileStream(
    IStream*         Functions,
    PRTL_FILE_STREAM Data,
    const IID*       Interface,
    PVOID*           Object
    )
{
    if (IsEqualGUID(Interface, &IID_IUnknown)
        || IsEqualGUID(Interface, &IID_IStream)
        || IsEqualGUID(Interface, &IID_ISequentialStream)
        )
    {
        InterlockedIncrement(&Data->ReferenceCount);
        *Object = Functions;
        return NOERROR;
    }
    return E_NOINTERFACE;
}

HRESULT
STDMETHODCALLTYPE
RtlReadFileStream(
    PRTL_FILE_STREAM FileStream,
    PVOID              Buffer,
    ULONG              BytesToRead,
    ULONG*             BytesRead
    )
{
    //
    // based on Win32 ReadFile
    // we should allow asynchronous i/o here.. put the IO_STATUS_BLOCK
    // in the RTL_FILE_STREAM..
    //
    IO_STATUS_BLOCK IoStatusBlock;
    const HANDLE FileHandle = FileStream->FileHandle;
    NTSTATUS Status = STATUS_SUCCESS;
    HRESULT Hr = NOERROR;

    Status = NtReadFile(
            FileHandle,
            NULL, // optional event
            NULL, // optional apc routine
            NULL, // optional apc context
            &IoStatusBlock,
            Buffer,
            BytesToRead,
            NULL, // optional offset
            NULL  // optional "key"
            );

    if ( Status == STATUS_PENDING) {
        // Operation must complete before return & IoStatusBlock destroyed
        Status = NtWaitForSingleObject(FileHandle, FALSE, NULL);
        if (NT_SUCCESS(Status)) {
            Status = IoStatusBlock.Status;
        }
    }

    if (NT_SUCCESS(Status)) {
        *BytesRead = (ULONG)IoStatusBlock.Information; // cast from ULONG_PTR
        Hr = NOERROR;
    } else if (Status == STATUS_END_OF_FILE) {
        *BytesRead = 0;
        Hr = NOERROR;
    } else {
        if (NT_WARNING(Status)) {
            *BytesRead = (ULONG)IoStatusBlock.Information; // cast from ULONG_PTR
        }
        Hr = RTLP_FILE_STREAM_HRESULT_FROM_STATUS(Status);
    }
    return Hr;
}

HRESULT
STDMETHODCALLTYPE
RtlWriteFileStream(
    PRTL_FILE_STREAM FileStream,
    const VOID*      Buffer,
    ULONG            BytesToWrite,
    ULONG*           BytesWritten
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(Write);
}

HRESULT
STDMETHODCALLTYPE
RtlSeekFileStream(
    PRTL_FILE_STREAM   FileStream,
    LARGE_INTEGER      Distance,
    DWORD              Origin,
    ULARGE_INTEGER*    NewPosition
    )
{
    //
    // based closely on Win32 SetFilePointer
    //
    HRESULT Hr = NOERROR;
    NTSTATUS Status = STATUS_SUCCESS;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_POSITION_INFORMATION CurrentPosition;
    const HANDLE FileHandle = FileStream->FileHandle;

    switch (Origin) {
        case STREAM_SEEK_SET:
            CurrentPosition.CurrentByteOffset = Distance;
            break;

        case STREAM_SEEK_CUR:
            Status = NtQueryInformationFile(
                FileHandle,
                &IoStatusBlock,
                &CurrentPosition,
                sizeof(CurrentPosition),
                FilePositionInformation
                );
            if (!NT_SUCCESS(Status)) {
                Hr = RTLP_FILE_STREAM_HRESULT_FROM_STATUS(Status);
                goto Exit;
            }
            CurrentPosition.CurrentByteOffset.QuadPart += Distance.QuadPart;
            break;

        case STREAM_SEEK_END: {
                FILE_STANDARD_INFORMATION StandardInfo;

                Status = NtQueryInformationFile(
                            FileHandle,
                            &IoStatusBlock,
                            &StandardInfo,
                            sizeof(StandardInfo),
                            FileStandardInformation
                            );
                if (!NT_SUCCESS(Status)) {
                    Hr = RTLP_FILE_STREAM_HRESULT_FROM_STATUS(Status);
                    goto Exit;
                }
                // SetFilePointer uses + here. Which is correct?
                // Descriptions of how Seek work are always unclear on this point..
                CurrentPosition.CurrentByteOffset.QuadPart =
                                    StandardInfo.EndOfFile.QuadPart - Distance.QuadPart;
            }
            break;

        default:
        // You would expect this to be E_INVALIDARG, but since
        // the IStream
        // but IStream docs weakly suggest STG_E_INVALIDPOINTER.
            Hr = STG_E_INVALIDFUNCTION; // E_INVALIDARG?
            goto Exit;
    }
    if (CurrentPosition.CurrentByteOffset.QuadPart < 0) {
        // You would expect this to be E_INVALIDARG,
        // but IStream docs say to return STG_E_INVALIDPOINTER.
        Hr = STG_E_INVALIDPOINTER;
        goto Exit;
    }

    //
    // Set the current file position
    //

    Status = NtSetInformationFile(
                FileHandle,
                &IoStatusBlock,
                &CurrentPosition,
                sizeof(CurrentPosition),
                FilePositionInformation
                );

    NewPosition->QuadPart = CurrentPosition.CurrentByteOffset.QuadPart;
    Hr = NOERROR;
Exit:
    return Hr;
}

HRESULT
STDMETHODCALLTYPE
RtlSetFileStreamSize(
    PRTL_FILE_STREAM  FileStream,
    ULARGE_INTEGER    NewSize
    )
{
    //
    // based on Win32 SetEndOfFile, but is independent of current seek pointer
    //
    NTSTATUS Status = STATUS_SUCCESS;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_END_OF_FILE_INFORMATION EndOfFile;
    FILE_ALLOCATION_INFORMATION Allocation;
    const HANDLE FileHandle = FileStream->FileHandle;

    EndOfFile.EndOfFile.QuadPart = NewSize.QuadPart;
    Allocation.AllocationSize.QuadPart = NewSize.QuadPart;

    Status = NtSetInformationFile(
                FileHandle,
                &IoStatusBlock,
                &EndOfFile,
                sizeof(EndOfFile),
                FileEndOfFileInformation
                );
    if (!NT_SUCCESS(Status)) {
        goto Exit;
    }

    Status = NtSetInformationFile(
                FileHandle,
                &IoStatusBlock,
                &Allocation,
                sizeof(Allocation),
                FileAllocationInformation
                );
    if (!NT_SUCCESS(Status)) {
        goto Exit;
    }
    Status = STATUS_SUCCESS;
Exit:
    if (NT_SUCCESS(Status)) {
        return NOERROR;
    } else {
        return RTLP_FILE_STREAM_HRESULT_FROM_STATUS(Status);
    }
}

HRESULT
STDMETHODCALLTYPE
RtlCopyFileStreamTo(
    PRTL_FILE_STREAM   FileStream,
    IStream*           AnotherStream,
    ULARGE_INTEGER     NumberOfBytesToCopyLargeInteger,
    ULARGE_INTEGER*    NumberOfBytesRead,
    ULARGE_INTEGER*    NumberOfBytesWrittenLargeInteger
    )
{
    //
    // Memory mapping where possible would be nice (but beware sockets and consoles).
    // see \vsee\lib\CWin32Stream.
    //
    RTLP_FILE_STREAM_NOT_IMPL(CopyTo);
}

HRESULT
STDMETHODCALLTYPE
RtlCommitFileStream(
    PRTL_FILE_STREAM FileStream,
    ULONG              Flags
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(Commit);
}

HRESULT
STDMETHODCALLTYPE
RtlRevertFileStream(
    PRTL_FILE_STREAM FileStream
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(Revert);
}

HRESULT
STDMETHODCALLTYPE
RtlLockFileStreamRegion(
    PRTL_FILE_STREAM FileStream,
    ULARGE_INTEGER     Offset,
    ULARGE_INTEGER     NumberOfBytes,
    ULONG              LockType
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(LockRegion);
}

HRESULT
STDMETHODCALLTYPE
RtlUnlockFileStreamRegion(
    PRTL_FILE_STREAM FileStream,
    ULARGE_INTEGER   Offset,
    ULARGE_INTEGER   NumberOfBytes,
    ULONG            LockType
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(UnlockRegion);
}

HRESULT
STDMETHODCALLTYPE
RtlStatFileStream(
    PRTL_FILE_STREAM FileStream,
    STATSTG*         StatusInformation,
    ULONG            Flags
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(Stat);
}

HRESULT
STDMETHODCALLTYPE
RtlCloneFileStream(
    PRTL_FILE_STREAM FileStream,
    IStream**          NewStream
    )
{
    RTLP_FILE_STREAM_NOT_IMPL(Clone);
}