/* $Id: fs-win.cpp $ */
/** @file
 * IPRT - File System, Win32.
 */

/*
 * Copyright (C) 2006-2007 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define LOG_GROUP RTLOGGROUP_FS
#include <windows.h>

#include <iprt/fs.h>
#include <iprt/path.h>
#include <iprt/string.h>
#include <iprt/param.h>
#include <iprt/err.h>
#include <iprt/log.h>
#include <iprt/assert.h>
#include "internal/fs.h"

/* from ntdef.h */
typedef LONG NTSTATUS;

/* from ntddk.h */
typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef enum _FSINFOCLASS {
    FileFsAttributeInformation = 5,
} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;

/* from ntifs.h */

typedef struct _FILE_FS_ATTRIBUTE_INFORMATION {
    ULONG FileSystemAttributes;
    LONG MaximumComponentNameLength;
    ULONG FIleSystemNameLength;
    WCHAR FileSystemName[1];
} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION;

extern "C" NTSTATUS NTAPI NtQueryVolumeInformationFile(HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS);

/**
 * Checks quickly if this is an correct root specification.
 * Root specs ends with a slash of some kind.
 *
 * @returns indicator.
 * @param   pszFsPath       Path to check.
 */
static bool rtFsIsRoot(const char *pszFsPath)
{
    /*
     * UNC has exactly two slashes..
     *
     * Anything else starting with slashe(s) requires
     * expansion and will have to take the long road.
     */
    if (RTPATH_IS_SLASH(pszFsPath[0]))
    {
        if (    !RTPATH_IS_SLASH(pszFsPath[1])
            ||  RTPATH_IS_SLASH(pszFsPath[2]))
            return false;

        /* end of machine name */
        const char *pszSlash = strpbrk(pszFsPath + 2, "\\/");
        if (!pszSlash)
            return false;

        /* end of service name. */
        pszSlash = strpbrk(pszSlash + 1, "\\/");
        if (!pszSlash)
            return false;

        return pszSlash[1] == '\0';
    }

    /*
     * Ok the other alternative is driver letter.
     */
    return  pszFsPath[0] >= 'A' && pszFsPath[0] <= 'Z'
        &&  pszFsPath[1] == ':'
        &&  RTPATH_IS_SLASH(pszFsPath[2])
        && !pszFsPath[3];
}



/**
 * Finds the root of the specified volume.
 *
 * @returns iprt status code.
 * @param   pszFsPath       Path within the filesystem. Verified as one byte or more.
 * @param   ppwszFsRoot     Where to store the returned string. Free with rtFsFreeRoot(),
 */
static int rtFsGetRoot(const char *pszFsPath, PRTUTF16 *ppwszFsRoot)
{
    /*
     * Do straight forward stuff first,
     */
    if (rtFsIsRoot(pszFsPath))
        return RTStrToUtf16(pszFsPath, ppwszFsRoot);

    /*
     * Expand and add slash (if required).
     */
    char szFullPath[RTPATH_MAX];
    int rc = RTPathAbs(pszFsPath, szFullPath, sizeof(szFullPath));
    if (RT_FAILURE(rc))
        return rc;
    size_t cb = strlen(szFullPath);
    if (!RTPATH_IS_SLASH(szFullPath[cb - 1]))
    {
        AssertReturn(cb + 1 < RTPATH_MAX, VERR_FILENAME_TOO_LONG);
        szFullPath[cb] = '\\';
        szFullPath[++cb] = '\0';
    }

    /*
     * Convert the path.
     */
    rc = RTStrToUtf16(szFullPath, ppwszFsRoot);
    if (RT_FAILURE(rc))
        return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;

    /*
     * Walk the path until our proper API is happy or there is no more path left.
     */
    PRTUTF16 pwszStart = *ppwszFsRoot;
    if (!GetVolumeInformationW(pwszStart, NULL, 0, NULL, NULL, 0, NULL, 0))
    {
        PRTUTF16 pwszEnd = pwszStart + RTUtf16Len(pwszStart);
        PRTUTF16 pwszMin = pwszStart + 2;
        do
        {
            /* Strip off the last path component. */
            while (pwszEnd-- > pwszMin)
                if (RTPATH_IS_SLASH(*pwszEnd))
                    break;
            AssertReturn(pwszEnd >= pwszMin, VERR_INTERNAL_ERROR); /* leaks, but that's irrelevant for an internal error. */
            pwszEnd[1] = '\0';
        } while (!GetVolumeInformationW(pwszStart, NULL, 0, NULL, NULL, 0, NULL, 0));
    }

    return VINF_SUCCESS;
}

/**
 * Frees string returned by rtFsGetRoot().
 */
static void rtFsFreeRoot(PRTUTF16 pwszFsRoot)
{
    RTUtf16Free(pwszFsRoot);
}


RTR3DECL(int) RTFsQuerySizes(const char *pszFsPath, RTFOFF *pcbTotal, RTFOFF *pcbFree,
                             uint32_t *pcbBlock, uint32_t *pcbSector)
{
    /*
     * Validate & get valid root path.
     */
    AssertMsgReturn(VALID_PTR(pszFsPath) && *pszFsPath, ("%p", pszFsPath), VERR_INVALID_PARAMETER);
    PRTUTF16 pwszFsRoot;
    int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Free and total.
     */
    if (pcbTotal || pcbFree)
    {
        ULARGE_INTEGER cbTotal;
        ULARGE_INTEGER cbFree;
        if (GetDiskFreeSpaceExW(pwszFsRoot, &cbFree, &cbTotal, NULL))
        {
            if (pcbTotal)
                *pcbTotal = cbTotal.QuadPart;
            if (pcbFree)
                *pcbFree  = cbFree.QuadPart;
        }
        else
        {
            DWORD Err = GetLastError();
            rc = RTErrConvertFromWin32(Err);
            Log(("RTFsQuerySizes(%s,): GetDiskFreeSpaceEx failed with lasterr %d (%Rrc)\n",
                 pszFsPath, Err, rc));
        }
    }

    /*
     * Block and sector size.
     */
    if (    RT_SUCCESS(rc)
        &&  (pcbBlock || pcbSector))
    {
        DWORD dwDummy1, dwDummy2;
        DWORD cbSector;
        DWORD cSectorsPerCluster;
        if (GetDiskFreeSpaceW(pwszFsRoot, &cSectorsPerCluster, &cbSector, &dwDummy1, &dwDummy2))
        {
            if (pcbBlock)
                *pcbBlock = cbSector * cSectorsPerCluster;
            if (pcbSector)
                *pcbSector = cbSector;
        }
        else
        {
            DWORD Err = GetLastError();
            rc = RTErrConvertFromWin32(Err);
            Log(("RTFsQuerySizes(%s,): GetDiskFreeSpace failed with lasterr %d (%Rrc)\n",
                 pszFsPath, Err, rc));
        }
    }

    rtFsFreeRoot(pwszFsRoot);
    return rc;
}


/**
 * Query the serial number of a filesystem.
 *
 * @returns iprt status code.
 * @param   pszFsPath       Path within the mounted filesystem.
 * @param   pu32Serial      Where to store the serial number.
 */
RTR3DECL(int) RTFsQuerySerial(const char *pszFsPath, uint32_t *pu32Serial)
{
    /*
     * Validate & get valid root path.
     */
    AssertMsgReturn(VALID_PTR(pszFsPath) && *pszFsPath, ("%p", pszFsPath), VERR_INVALID_PARAMETER);
    AssertMsgReturn(VALID_PTR(pu32Serial), ("%p", pu32Serial), VERR_INVALID_PARAMETER);
    PRTUTF16 pwszFsRoot;
    int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Do work.
     */
    DWORD   dwMaxName;
    DWORD   dwFlags;
    DWORD   dwSerial;
    if (GetVolumeInformationW(pwszFsRoot, NULL, 0, &dwSerial, &dwMaxName, &dwFlags, NULL, 0))
        *pu32Serial = dwSerial;
    else
    {
        DWORD Err = GetLastError();
        rc = RTErrConvertFromWin32(Err);
        Log(("RTFsQuerySizes(%s,): GetDiskFreeSpaceEx failed with lasterr %d (%Rrc)\n",
             pszFsPath, Err, rc));
    }

    RTUtf16Free(pwszFsRoot);
    return rc;
}


/**
 * Query the properties of a mounted filesystem.
 *
 * @returns iprt status code.
 * @param   pszFsPath       Path within the mounted filesystem.
 * @param   pProperties     Where to store the properties.
 */
RTR3DECL(int) RTFsQueryProperties(const char *pszFsPath, PRTFSPROPERTIES pProperties)
{
    /*
     * Validate & get valid root path.
     */
    AssertMsgReturn(VALID_PTR(pszFsPath) && *pszFsPath, ("%p", pszFsPath), VERR_INVALID_PARAMETER);
    AssertMsgReturn(VALID_PTR(pProperties), ("%p", pProperties), VERR_INVALID_PARAMETER);
    PRTUTF16 pwszFsRoot;
    int rc = rtFsGetRoot(pszFsPath, &pwszFsRoot);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Do work.
     */
    DWORD   dwMaxName;
    DWORD   dwFlags;
    DWORD   dwSerial;
    if (GetVolumeInformationW(pwszFsRoot, NULL, 0, &dwSerial, &dwMaxName, &dwFlags, NULL, 0))
    {
        memset(pProperties, 0, sizeof(*pProperties));
        pProperties->cbMaxComponent   = dwMaxName;
        pProperties->fFileCompression = !!(dwFlags & FILE_FILE_COMPRESSION);
        pProperties->fCompressed      = !!(dwFlags & FILE_VOLUME_IS_COMPRESSED);
        pProperties->fReadOnly        = !!(dwFlags & FILE_READ_ONLY_VOLUME);
        pProperties->fSupportsUnicode = !!(dwFlags & FILE_UNICODE_ON_DISK);
        pProperties->fCaseSensitive   = false;    /* win32 is case preserving only */
        pProperties->fRemote          = false;    /* no idea yet */
    }
    else
    {
        DWORD Err = GetLastError();
        rc = RTErrConvertFromWin32(Err);
        Log(("RTFsQuerySizes(%s,): GetVolumeInformation failed with lasterr %d (%Rrc)\n",
             pszFsPath, Err, rc));
    }

    RTUtf16Free(pwszFsRoot);
    return rc;
}


/**
 * Internal helper for comparing a WCHAR string with a char string.
 *
 * @returns @c true if equal, @c false if not.
 * @param   pwsz1               The first string.
 * @param   cb1                 The length of the first string, in bytes.
 * @param   psz2                The second string.
 * @param   cch2                The length of the second string.
 */
static bool rtFsWinAreEqual(WCHAR const *pwsz1, size_t cch1, const char *psz2, size_t cch2)
{
    if (cch1 != cch2 * 2)
        return false;
    while (cch2-- > 0)
    {
        unsigned ch1 = *pwsz1++;
        unsigned ch2 = (unsigned char)*psz2++;
        if (ch1 != ch2)
            return false;
    }
    return true;
}


RTR3DECL(int) RTFsQueryType(const char *pszFsPath, PRTFSTYPE penmType)
{
    *penmType = RTFSTYPE_UNKNOWN;

    AssertPtrReturn(pszFsPath, VERR_INVALID_POINTER);
    AssertReturn(*pszFsPath, VERR_INVALID_PARAMETER);

    /*
     * Convert the path and try open it.
     */
    PRTUTF16 pwszFsPath;
    int rc = RTStrToUtf16(pszFsPath, &pwszFsPath);
    if (RT_SUCCESS(rc))
    {
        HANDLE hFile = CreateFileW(pwszFsPath,
                                   GENERIC_READ,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_FLAG_BACKUP_SEMANTICS,
                                   NULL);
        if (hFile != INVALID_HANDLE_VALUE)
        {
            /*
             * Use the NT api directly to get the file system name.
             */
            char            abBuf[8192];
            IO_STATUS_BLOCK Ios;
            NTSTATUS        rcNt = NtQueryVolumeInformationFile(hFile, &Ios,
                                                                abBuf, sizeof(abBuf),
                                                                FileFsAttributeInformation);
            if (rcNt >= 0)
            {
                PFILE_FS_ATTRIBUTE_INFORMATION pFsAttrInfo = (PFILE_FS_ATTRIBUTE_INFORMATION)abBuf;
                if (pFsAttrInfo->FIleSystemNameLength)
                {
                }
#define IS_FS(szName) \
    rtFsWinAreEqual(pFsAttrInfo->FileSystemName, pFsAttrInfo->FIleSystemNameLength, szName, sizeof(szName) - 1)
                if (IS_FS("NTFS"))
                    *penmType = RTFSTYPE_NTFS;
                else if (IS_FS("FAT"))
                    *penmType = RTFSTYPE_FAT;
                else if (IS_FS("FAT32"))
                    *penmType = RTFSTYPE_FAT;
                else if (IS_FS("VBoxSharedFolderFS"))
                    *penmType = RTFSTYPE_VBOXSHF;
#undef IS_FS
            }
            else
                rc = RTErrConvertFromNtStatus(rcNt);
            CloseHandle(hFile);
        }
        else
            rc = RTErrConvertFromWin32(GetLastError());
        RTUtf16Free(pwszFsPath);
    }
    return rc;
}
