/*
 * QEMU Guest Agent win32 VSS Provider implementations
 *
 * Copyright Hitachi Data Systems Corp. 2013
 *
 * Authors:
 *  Tomoki Sekiyama   <tomoki.sekiyama@hds.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"
#include "vss-common.h"
#include "vss-debug.h"
#ifdef HAVE_VSS_SDK
#include <vscoordint.h>
#else
#include <vsadmin.h>
#endif
#include <vsprov.h>

#define VSS_TIMEOUT_MSEC (60*1000)

static long g_nComObjsInUse;
HINSTANCE g_hinstDll;

/* VSS common GUID's */

const CLSID CLSID_VSSCoordinator = { 0xE579AB5F, 0x1CC4, 0x44b4,
    {0xBE, 0xD9, 0xDE, 0x09, 0x91, 0xFF, 0x06, 0x23} };
const IID IID_IVssAdmin = { 0x77ED5996, 0x2F63, 0x11d3,
    {0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };

const IID IID_IVssHardwareSnapshotProvider = { 0x9593A157, 0x44E9, 0x4344,
    {0xBB, 0xEB, 0x44, 0xFB, 0xF9, 0xB0, 0x6B, 0x10} };
const IID IID_IVssSoftwareSnapshotProvider = { 0x609e123e, 0x2c5a, 0x44d3,
    {0x8f, 0x01, 0x0b, 0x1d, 0x9a, 0x47, 0xd1, 0xff} };
const IID IID_IVssProviderCreateSnapshotSet = { 0x5F894E5B, 0x1E39, 0x4778,
    {0x8E, 0x23, 0x9A, 0xBA, 0xD9, 0xF0, 0xE0, 0x8C} };
const IID IID_IVssProviderNotifications = { 0xE561901F, 0x03A5, 0x4afe,
    {0x86, 0xD0, 0x72, 0xBA, 0xEE, 0xCE, 0x70, 0x04} };

const IID IID_IVssEnumObject = { 0xAE1C7110, 0x2F60, 0x11d3,
    {0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };


void LockModule(BOOL lock)
{
    if (lock) {
        InterlockedIncrement(&g_nComObjsInUse);
    } else {
        InterlockedDecrement(&g_nComObjsInUse);
    }
}

/* Empty enumerator for VssObject */

class CQGAVSSEnumObject : public IVssEnumObject
{
public:
    STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    /* IVssEnumObject Methods */
    STDMETHODIMP Next(
        ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched);
    STDMETHODIMP Skip(ULONG celt);
    STDMETHODIMP Reset(void);
    STDMETHODIMP Clone(IVssEnumObject **ppenum);

    /* CQGAVSSEnumObject Methods */
    CQGAVSSEnumObject();
    ~CQGAVSSEnumObject();

private:
    long m_nRefCount;
};

CQGAVSSEnumObject::CQGAVSSEnumObject()
{
    m_nRefCount = 0;
    LockModule(TRUE);
}

CQGAVSSEnumObject::~CQGAVSSEnumObject()
{
    LockModule(FALSE);
}

STDMETHODIMP CQGAVSSEnumObject::QueryInterface(REFIID riid, void **ppObj)
{
    if (riid == IID_IUnknown || riid == IID_IVssEnumObject) {
        *ppObj = static_cast<void*>(static_cast<IVssEnumObject*>(this));
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CQGAVSSEnumObject::AddRef()
{
    return InterlockedIncrement(&m_nRefCount);
}

STDMETHODIMP_(ULONG) CQGAVSSEnumObject::Release()
{
    long nRefCount = InterlockedDecrement(&m_nRefCount);
    if (m_nRefCount == 0) {
        delete this;
    }
    return nRefCount;
}

STDMETHODIMP CQGAVSSEnumObject::Next(
    ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched)
{
    *pceltFetched = 0;
    return S_FALSE;
}

STDMETHODIMP CQGAVSSEnumObject::Skip(ULONG celt)
{
    return S_FALSE;
}

STDMETHODIMP CQGAVSSEnumObject::Reset(void)
{
    return S_OK;
}

STDMETHODIMP CQGAVSSEnumObject::Clone(IVssEnumObject **ppenum)
{
    return E_NOTIMPL;
}


/* QGAVssProvider */

class CQGAVssProvider :
    public IVssSoftwareSnapshotProvider,
    public IVssProviderCreateSnapshotSet,
    public IVssProviderNotifications
{
public:
    STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    /* IVssSoftwareSnapshotProvider Methods */
    STDMETHODIMP SetContext(LONG lContext);
    STDMETHODIMP GetSnapshotProperties(
        VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp);
    STDMETHODIMP Query(
        VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
        VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum);
    STDMETHODIMP DeleteSnapshots(
        VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
        BOOL bForceDelete, LONG *plDeletedSnapshots,
        VSS_ID *pNondeletedSnapshotID);
    STDMETHODIMP BeginPrepareSnapshot(
        VSS_ID SnapshotSetId, VSS_ID SnapshotId,
        VSS_PWSZ pwszVolumeName, LONG lNewContext);
    STDMETHODIMP IsVolumeSupported(
        VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider);
    STDMETHODIMP IsVolumeSnapshotted(
        VSS_PWSZ pwszVolumeName, BOOL *pbSnapshotsPresent,
        LONG *plSnapshotCompatibility);
    STDMETHODIMP SetSnapshotProperty(
        VSS_ID SnapshotId, VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId,
        VARIANT vProperty);
    STDMETHODIMP RevertToSnapshot(VSS_ID SnapshotId);
    STDMETHODIMP QueryRevertStatus(VSS_PWSZ pwszVolume, IVssAsync **ppAsync);

    /* IVssProviderCreateSnapshotSet Methods */
    STDMETHODIMP EndPrepareSnapshots(VSS_ID SnapshotSetId);
    STDMETHODIMP PreCommitSnapshots(VSS_ID SnapshotSetId);
    STDMETHODIMP CommitSnapshots(VSS_ID SnapshotSetId);
    STDMETHODIMP PostCommitSnapshots(
        VSS_ID SnapshotSetId, LONG lSnapshotsCount);
    STDMETHODIMP PreFinalCommitSnapshots(VSS_ID SnapshotSetId);
    STDMETHODIMP PostFinalCommitSnapshots(VSS_ID SnapshotSetId);
    STDMETHODIMP AbortSnapshots(VSS_ID SnapshotSetId);

    /* IVssProviderNotifications Methods */
    STDMETHODIMP OnLoad(IUnknown *pCallback);
    STDMETHODIMP OnUnload(BOOL bForceUnload);

    /* CQGAVssProvider Methods */
    CQGAVssProvider();
    ~CQGAVssProvider();

private:
    long m_nRefCount;
};

CQGAVssProvider::CQGAVssProvider()
{
    m_nRefCount = 0;
    LockModule(TRUE);
}

CQGAVssProvider::~CQGAVssProvider()
{
    LockModule(FALSE);
}

STDMETHODIMP CQGAVssProvider::QueryInterface(REFIID riid, void **ppObj)
{
    if (riid == IID_IUnknown) {
        *ppObj = static_cast<void*>(this);
        AddRef();
        return S_OK;
    }
    if (riid == IID_IVssSoftwareSnapshotProvider) {
        *ppObj = static_cast<void*>(
            static_cast<IVssSoftwareSnapshotProvider*>(this));
        AddRef();
        return S_OK;
    }
    if (riid == IID_IVssProviderCreateSnapshotSet) {
        *ppObj = static_cast<void*>(
            static_cast<IVssProviderCreateSnapshotSet*>(this));
        AddRef();
        return S_OK;
    }
    if (riid == IID_IVssProviderNotifications) {
        *ppObj = static_cast<void*>(
            static_cast<IVssProviderNotifications*>(this));
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CQGAVssProvider::AddRef()
{
    return InterlockedIncrement(&m_nRefCount);
}

STDMETHODIMP_(ULONG) CQGAVssProvider::Release()
{
    long nRefCount = InterlockedDecrement(&m_nRefCount);
    if (m_nRefCount == 0) {
        delete this;
    }
    return nRefCount;
}


/*
 * IVssSoftwareSnapshotProvider methods
 */

STDMETHODIMP CQGAVssProvider::SetContext(LONG lContext)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::GetSnapshotProperties(
    VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp)
{
    return VSS_E_OBJECT_NOT_FOUND;
}

STDMETHODIMP CQGAVssProvider::Query(
    VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
    VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum)
{
    try {
        *ppEnum = new CQGAVSSEnumObject;
    } catch (...) {
        return E_OUTOFMEMORY;
    }
    (*ppEnum)->AddRef();
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::DeleteSnapshots(
    VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
    BOOL bForceDelete, LONG *plDeletedSnapshots, VSS_ID *pNondeletedSnapshotID)
{
    *plDeletedSnapshots = 0;
    *pNondeletedSnapshotID = SourceObjectId;
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::BeginPrepareSnapshot(
    VSS_ID SnapshotSetId, VSS_ID SnapshotId,
    VSS_PWSZ pwszVolumeName, LONG lNewContext)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::IsVolumeSupported(
    VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider)
{
    HANDLE hEventFrozen;

    /* Check if a requester is qemu-ga by whether an event is created */
    hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
    if (!hEventFrozen) {
        *pbSupportedByThisProvider = FALSE;
        return S_OK;
    }
    CloseHandle(hEventFrozen);

    *pbSupportedByThisProvider = TRUE;
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::IsVolumeSnapshotted(VSS_PWSZ pwszVolumeName,
    BOOL *pbSnapshotsPresent, LONG *plSnapshotCompatibility)
{
    *pbSnapshotsPresent = FALSE;
    *plSnapshotCompatibility = 0;
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::SetSnapshotProperty(VSS_ID SnapshotId,
    VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId, VARIANT vProperty)
{
    return E_NOTIMPL;
}

STDMETHODIMP CQGAVssProvider::RevertToSnapshot(VSS_ID SnapshotId)
{
    return E_NOTIMPL;
}

STDMETHODIMP CQGAVssProvider::QueryRevertStatus(
    VSS_PWSZ pwszVolume, IVssAsync **ppAsync)
{
    return E_NOTIMPL;
}


/*
 * IVssProviderCreateSnapshotSet methods
 */

STDMETHODIMP CQGAVssProvider::EndPrepareSnapshots(VSS_ID SnapshotSetId)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::PreCommitSnapshots(VSS_ID SnapshotSetId)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::CommitSnapshots(VSS_ID SnapshotSetId)
{
    HRESULT hr = S_OK;
    HANDLE hEventFrozen, hEventThaw, hEventTimeout;

    hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
    if (!hEventFrozen) {
        return E_FAIL;
    }

    hEventThaw = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_THAW);
    if (!hEventThaw) {
        CloseHandle(hEventFrozen);
        return E_FAIL;
    }

    hEventTimeout = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_TIMEOUT);
    if (!hEventTimeout) {
        CloseHandle(hEventFrozen);
        CloseHandle(hEventThaw);
        return E_FAIL;
    }

    /* Send event to qemu-ga to notify filesystem is frozen */
    SetEvent(hEventFrozen);

    /* Wait until the snapshot is taken by the host. */
    if (WaitForSingleObject(hEventThaw, VSS_TIMEOUT_MSEC) != WAIT_OBJECT_0) {
        /* Send event to qemu-ga to notify the provider is timed out */
        SetEvent(hEventTimeout);
    }

    CloseHandle(hEventThaw);
    CloseHandle(hEventFrozen);
    CloseHandle(hEventTimeout);
    return hr;
}

STDMETHODIMP CQGAVssProvider::PostCommitSnapshots(
    VSS_ID SnapshotSetId, LONG lSnapshotsCount)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::PreFinalCommitSnapshots(VSS_ID SnapshotSetId)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::PostFinalCommitSnapshots(VSS_ID SnapshotSetId)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::AbortSnapshots(VSS_ID SnapshotSetId)
{
    return S_OK;
}

/*
 * IVssProviderNotifications methods
 */

STDMETHODIMP CQGAVssProvider::OnLoad(IUnknown *pCallback)
{
    return S_OK;
}

STDMETHODIMP CQGAVssProvider::OnUnload(BOOL bForceUnload)
{
    return S_OK;
}


/*
 * CQGAVssProviderFactory class
 */

class CQGAVssProviderFactory : public IClassFactory
{
public:
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP CreateInstance(
        IUnknown *pUnknownOuter, REFIID iid, void **ppv);
    STDMETHODIMP LockServer(BOOL lock) { return E_NOTIMPL; }

    CQGAVssProviderFactory();
    ~CQGAVssProviderFactory();

private:
    long m_nRefCount;
};

CQGAVssProviderFactory::CQGAVssProviderFactory()
{
    m_nRefCount = 0;
    LockModule(TRUE);
}

CQGAVssProviderFactory::~CQGAVssProviderFactory()
{
    LockModule(FALSE);
}

STDMETHODIMP CQGAVssProviderFactory::QueryInterface(REFIID riid, void **ppv)
{
    if (riid == IID_IUnknown || riid == IID_IClassFactory) {
        *ppv = static_cast<void*>(this);
        AddRef();
        return S_OK;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CQGAVssProviderFactory::AddRef()
{
    return InterlockedIncrement(&m_nRefCount);
}

STDMETHODIMP_(ULONG) CQGAVssProviderFactory::Release()
{
    long nRefCount = InterlockedDecrement(&m_nRefCount);
    if (m_nRefCount == 0) {
        delete this;
    }
    return nRefCount;
}

STDMETHODIMP CQGAVssProviderFactory::CreateInstance(
    IUnknown *pUnknownOuter, REFIID iid, void **ppv)
{
    CQGAVssProvider *pObj;

    if (pUnknownOuter) {
        return CLASS_E_NOAGGREGATION;
    }
    try {
        pObj = new CQGAVssProvider;
    } catch (...) {
        return E_OUTOFMEMORY;
    }
    HRESULT hr = pObj->QueryInterface(iid, ppv);
    if (FAILED(hr)) {
        delete pObj;
    }
    return hr;
}


/*
 * DLL functions
 */

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
{
    CQGAVssProviderFactory *factory;
    try {
        factory = new CQGAVssProviderFactory;
    } catch (...) {
        return E_OUTOFMEMORY;
    }
    factory->AddRef();
    HRESULT hr = factory->QueryInterface(riid, ppv);
    factory->Release();
    return hr;
}

STDAPI DllCanUnloadNow()
{
    return g_nComObjsInUse == 0 ? S_OK : S_FALSE;
}

EXTERN_C
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
{
    qga_debug("begin, reason = %lu", dwReason);
    if (dwReason == DLL_PROCESS_ATTACH) {
        g_hinstDll = hinstDll;
        DisableThreadLibraryCalls(hinstDll);
    }
    qga_debug_end;
    return TRUE;
}
