/*
 * Copyright (c) 2008, 2009, 2010, 2011 Kernel Drivers, LLC.
 * Copyright (c) 2009, 2010, 2011 Your File System, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice,
 *   this list of conditions and the following disclaimer in the
 *   documentation
 *   and/or other materials provided with the distribution.
 * - Neither the names of Kernel Drivers, LLC and Your File System, Inc.
 *   nor the names of their contributors may be used to endorse or promote
 *   products derived from this software without specific prior written
 *   permission from Kernel Drivers, LLC and Your File System, Inc.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

//
// File: AFSVolumeInfo.cpp
//

#include "AFSCommon.h"

NTSTATUS
AFSQueryVolumeInfo( IN PDEVICE_OBJECT LibDeviceObject,
                    IN PIRP Irp)
{

    UNREFERENCED_PARAMETER(LibDeviceObject);
    NTSTATUS ntStatus = STATUS_SUCCESS;
    IO_STACK_LOCATION *pIrpSp;
    FS_INFORMATION_CLASS FsInformationClass = FileFsMaximumInformation;
    void *pBuffer = NULL;
    ULONG ulLength = 0;
    BOOLEAN bReleaseResource = FALSE;
    PFILE_OBJECT pFileObject = NULL;
    AFSFcb *pFcb = NULL;
    AFSObjectInfoCB *pObjectInfo = NULL;
    AFSVolumeCB *pVolumeCB = NULL;
    BOOLEAN bDosDevice = FALSE;

    pIrpSp = IoGetCurrentIrpStackLocation( Irp);

    __try
    {

        pFileObject = pIrpSp->FileObject;

        if( pFileObject == NULL)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request with NULL FileObject\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }

        pFcb = (AFSFcb *)pFileObject->FsContext;

        if( pFcb == NULL)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request with NULL Fcb\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }

        pObjectInfo = pFcb->ObjectInformation;

        if( pObjectInfo == NULL)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request with NULL ObjectInformation\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }

        bDosDevice = pFileObject->FileName.Length > 4 * sizeof( WCHAR) &&
            pFileObject->FileName.Buffer[0] == L'\\' &&
            pFileObject->FileName.Buffer[1] == L';' &&
            pFileObject->FileName.Buffer[3] == L':';

        pVolumeCB = pObjectInfo->VolumeCB;

        ulLength = pIrpSp->Parameters.QueryVolume.Length;
        FsInformationClass = pIrpSp->Parameters.QueryVolume.FsInformationClass;
        pBuffer = Irp->AssociatedIrp.SystemBuffer;

        AFSAcquireShared( pVolumeCB->VolumeLock,
                          TRUE);

        bReleaseResource = TRUE;

        //
        // Don't allow requests against IOCtl nodes
        //

        if( pFcb->Header.NodeTypeCode == AFS_IOCTL_FCB)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request against PIOCtl Fcb\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }
        else if( pFcb->Header.NodeTypeCode == AFS_SPECIAL_SHARE_FCB)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request against SpecialShare Fcb\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }
        else if( pFcb->Header.NodeTypeCode == AFS_INVALID_FCB)
        {

            AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                          AFS_TRACE_LEVEL_ERROR,
                          "AFSQueryVolumeInfo Failing request against SpecialShare Fcb\n"));

            try_return( ntStatus = STATUS_INVALID_DEVICE_REQUEST);
        }

        //
        // Process the request
        //

        switch( FsInformationClass)
        {

            case FileFsVolumeInformation:
            {

                ntStatus = AFSQueryFsVolumeInfo( &pVolumeCB->VolumeInformation,
                                                 (PFILE_FS_VOLUME_INFORMATION)pBuffer,
                                                 bDosDevice,
                                                 &ulLength);

                break;
            }

            case FileFsSizeInformation:
            {

                ntStatus = AFSQueryFsSizeInfo( &pVolumeCB->VolumeInformation,
                                               (PFILE_FS_SIZE_INFORMATION)pBuffer,
                                               &ulLength);

                break;
            }

            case FileFsDeviceInformation:
            {

                ntStatus = AFSQueryFsDeviceInfo( &pVolumeCB->VolumeInformation,
                                                 (PFILE_FS_DEVICE_INFORMATION)pBuffer,
                                                 &ulLength);

                break;
            }

            case FileFsAttributeInformation:
            {

                ntStatus = AFSQueryFsAttributeInfo( &pVolumeCB->VolumeInformation,
                                                    (PFILE_FS_ATTRIBUTE_INFORMATION)pBuffer,
                                                    &ulLength);

                break;
            }

            case FileFsFullSizeInformation:
            {

                ntStatus = AFSQueryFsFullSizeInfo( &pVolumeCB->VolumeInformation,
                                                   (PFILE_FS_FULL_SIZE_INFORMATION)pBuffer,
                                                   &ulLength);

                break;
            }

            default:

                AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                              AFS_TRACE_LEVEL_WARNING,
                              "AFSQueryVolumeInfo Invalid class %d\n",
                              FsInformationClass));

                ntStatus = STATUS_INVALID_PARAMETER;

                break;
        }

try_exit:

        //
        // Setup the Irp's information field to what we actually copied in.
        //

        Irp->IoStatus.Information = pIrpSp->Parameters.QueryVolume.Length - ulLength;

        if( bReleaseResource)
        {

            AFSReleaseResource( pVolumeCB->VolumeLock);
        }

        AFSCompleteRequest( Irp,
                            ntStatus);

    }
    __except( AFSExceptionFilter( __FUNCTION__, GetExceptionCode(), GetExceptionInformation()) )
    {

        AFSDbgTrace(( 0,
                      0,
                      "EXCEPTION - AFSQueryVolumeInfo FO %p InfoClass %d FCB %p ObjectInfo %p VolCB %p\n",
                      pFileObject,
                      FsInformationClass,
                      pFcb,
                      pObjectInfo,
                      pVolumeCB));

        AFSDumpTraceFilesFnc();
    }

    return ntStatus;
}

NTSTATUS
AFSSetVolumeInfo( IN PDEVICE_OBJECT DeviceObject,
                  IN PIRP Irp)
{

    UNREFERENCED_PARAMETER(DeviceObject);
    NTSTATUS ntStatus = STATUS_SUCCESS;
    IO_STACK_LOCATION *pIrpSp;

    pIrpSp = IoGetCurrentIrpStackLocation( Irp);

    __try
    {

        AFSDbgTrace(( AFS_SUBSYSTEM_FILE_PROCESSING,
                      AFS_TRACE_LEVEL_WARNING,
                      "AFSSetVolumeInfo Entry for FO %p\n",
                      pIrpSp->FileObject));

        AFSCompleteRequest( Irp,
                            ntStatus);

    }
    __except( AFSExceptionFilter( __FUNCTION__, GetExceptionCode(), GetExceptionInformation()) )
    {

        AFSDbgTrace(( 0,
                      0,
                      "EXCEPTION - AFSSetVolumeInfo\n"));

        AFSDumpTraceFilesFnc();
    }

    return ntStatus;
}

NTSTATUS
AFSQueryFsVolumeInfo( IN AFSVolumeInfoCB *VolumeInfo,
                      IN PFILE_FS_VOLUME_INFORMATION Buffer,
                      IN BOOLEAN bDosDevice,
                      IN OUT PULONG Length)

{

    NTSTATUS ntStatus = STATUS_BUFFER_TOO_SMALL;
    ULONG ulLabelLength;

    RtlZeroMemory( Buffer,
                   *Length);

    if( *Length >= (ULONG)sizeof( FILE_FS_VOLUME_INFORMATION))
    {

        if ( bDosDevice)
        {

            ulLabelLength = VolumeInfo->VolumeLabelLength;
        }
        else
        {

            ulLabelLength = VolumeInfo->VolumeLabelLength +
                VolumeInfo->CellLength + sizeof( WCHAR);
        }

        if( *Length >= (ULONG)(FIELD_OFFSET( FILE_FS_VOLUME_INFORMATION, VolumeLabel) + ulLabelLength))
        {

            ntStatus = STATUS_SUCCESS;
        }
        else
        {

            ntStatus = STATUS_BUFFER_OVERFLOW;
        }

        Buffer->VolumeCreationTime.QuadPart = VolumeInfo->VolumeCreationTime.QuadPart;

        Buffer->VolumeSerialNumber = VolumeInfo->VolumeID;

        Buffer->VolumeLabelLength = ulLabelLength;

        Buffer->SupportsObjects = FALSE;

        *Length -= FIELD_OFFSET( FILE_FS_VOLUME_INFORMATION, VolumeLabel);

        if( *Length > 0)
        {

            if ( bDosDevice)
            {

                RtlCopyMemory( Buffer->VolumeLabel,
                               VolumeInfo->VolumeLabel,
                               min( *Length, VolumeInfo->VolumeLabelLength));

                *Length -= min( *Length, VolumeInfo->VolumeLabelLength);
            }
            else
            {

                RtlCopyMemory( Buffer->VolumeLabel,
                               VolumeInfo->Cell,
                               min( *Length, VolumeInfo->CellLength));

                *Length -= min( *Length, VolumeInfo->CellLength);

                if ( *Length >= sizeof( WCHAR))
                {

                    Buffer->VolumeLabel[ VolumeInfo->CellLength / sizeof( WCHAR)] = L'#';

                    *Length -= sizeof( WCHAR);

                    if ( *Length > 0) {

                        RtlCopyMemory( &Buffer->VolumeLabel[ (VolumeInfo->CellLength + sizeof( WCHAR)) / sizeof( WCHAR)],
                                       VolumeInfo->VolumeLabel,
                                       min( *Length, VolumeInfo->VolumeLabelLength));

                        *Length -= min( *Length, VolumeInfo->VolumeLabelLength);
                    }
                }
            }
        }
    }

    return ntStatus;
}

NTSTATUS
AFSQueryFsSizeInfo( IN AFSVolumeInfoCB *VolumeInfo,
                    IN PFILE_FS_SIZE_INFORMATION Buffer,
                    IN OUT PULONG Length)
{

    NTSTATUS ntStatus = STATUS_SUCCESS;
    AFSFileID FileID;
    AFSVolumeSizeInfoCB VolumeSizeInfo;

    RtlZeroMemory( Buffer,
                   *Length);

    if( *Length >= sizeof( FILE_FS_SIZE_INFORMATION))
    {

        RtlZeroMemory( &FileID,
                       sizeof(AFSFileID));

        FileID.Cell = VolumeInfo->CellID;

        FileID.Volume = VolumeInfo->VolumeID;

        ntStatus = AFSRetrieveVolumeSizeInformation( NULL,
                                                     &FileID,
                                                     &VolumeSizeInfo);

        if ( NT_SUCCESS( ntStatus))
        {

            Buffer->TotalAllocationUnits.QuadPart = VolumeSizeInfo.TotalAllocationUnits.QuadPart;

            Buffer->AvailableAllocationUnits.QuadPart = VolumeSizeInfo.AvailableAllocationUnits.QuadPart;

            Buffer->SectorsPerAllocationUnit = VolumeSizeInfo.SectorsPerAllocationUnit;

            Buffer->BytesPerSector = VolumeSizeInfo.BytesPerSector;

            *Length -= sizeof( FILE_FS_SIZE_INFORMATION);
        }
    }
    else
    {

        ntStatus = STATUS_BUFFER_TOO_SMALL;
    }

    return ntStatus;
}

NTSTATUS
AFSQueryFsDeviceInfo( IN AFSVolumeInfoCB *VolumeInfo,
                      IN PFILE_FS_DEVICE_INFORMATION Buffer,
                      IN OUT PULONG Length)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    RtlZeroMemory( Buffer,
                   *Length);

    if( *Length >= (LONG)sizeof( FILE_FS_DEVICE_INFORMATION))
    {

        //
        // This value is used to determine the return type of
        // Win32 GetFileType().  Returning FILE_DEVICE_NETWORK_FILE_SYSTEM
        // results in GetFileType returning FILE_TYPE_UNKNOWN which breaks
        // msys-based applications.  They treat all files as character
        // special devices instead of files.
        //

        Buffer->DeviceType = FILE_DEVICE_DISK;

        Buffer->Characteristics = VolumeInfo->Characteristics;

        *Length -= sizeof( FILE_FS_DEVICE_INFORMATION);
    }
    else
    {

        ntStatus = STATUS_BUFFER_TOO_SMALL;
    }

    return ntStatus;
}

NTSTATUS
AFSQueryFsAttributeInfo( IN AFSVolumeInfoCB *VolumeInfo,
                         IN PFILE_FS_ATTRIBUTE_INFORMATION Buffer,
                         IN OUT PULONG Length)
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    RtlZeroMemory( Buffer,
                   *Length);

    if( *Length >= (LONG)(sizeof( FILE_FS_ATTRIBUTE_INFORMATION)))
    {

        Buffer->FileSystemAttributes = VolumeInfo->FileSystemAttributes;

        Buffer->MaximumComponentNameLength = 255;

        Buffer->FileSystemNameLength = 18;

        *Length -= FIELD_OFFSET( FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName);

        if( *Length >= 18)
        {

            RtlCopyMemory( Buffer->FileSystemName,
                           L"AFSRDRFsd",
                           18);

            *Length -= 18;
        }
        else
        {

            ntStatus = STATUS_BUFFER_OVERFLOW;
        }
    }
    else
    {

        ntStatus = STATUS_BUFFER_TOO_SMALL;
    }

    return ntStatus;
}

NTSTATUS
AFSQueryFsFullSizeInfo( IN AFSVolumeInfoCB *VolumeInfo,
                        IN PFILE_FS_FULL_SIZE_INFORMATION Buffer,
                        IN OUT PULONG Length)
{

    NTSTATUS ntStatus = STATUS_SUCCESS;
    AFSFileID FileID;
    AFSVolumeSizeInfoCB VolumeSizeInfo;

    RtlZeroMemory( Buffer,
                   *Length);

    if( *Length >= sizeof( FILE_FS_FULL_SIZE_INFORMATION))
    {

        RtlZeroMemory( &FileID,
                       sizeof(AFSFileID));

        FileID.Cell = VolumeInfo->CellID;

        FileID.Volume = VolumeInfo->VolumeID;

        ntStatus = AFSRetrieveVolumeSizeInformation( NULL,
                                                     &FileID,
                                                     &VolumeSizeInfo);

        if ( NT_SUCCESS( ntStatus))
        {

            Buffer->TotalAllocationUnits.QuadPart = VolumeSizeInfo.TotalAllocationUnits.QuadPart;

            Buffer->CallerAvailableAllocationUnits.QuadPart = VolumeSizeInfo.AvailableAllocationUnits.QuadPart;

            Buffer->ActualAvailableAllocationUnits.QuadPart = VolumeSizeInfo.AvailableAllocationUnits.QuadPart;

            Buffer->SectorsPerAllocationUnit = VolumeSizeInfo.SectorsPerAllocationUnit;

            Buffer->BytesPerSector = VolumeSizeInfo.BytesPerSector;

            *Length -= sizeof( FILE_FS_FULL_SIZE_INFORMATION);
        }
    }
    else
    {

        ntStatus = STATUS_BUFFER_TOO_SMALL;
    }

    return ntStatus;
}
