Patchwork [v5,08/11] qemu-ga: Add Windows VSS requester to quiesce applications and filesystems

login
register
mail settings
Submitter Tomoki Sekiyama
Date July 3, 2013, 3:49 p.m.
Message ID <20130703154944.20767.47052.stgit@hds.com>
Download mbox | patch
Permalink /patch/256693/
State New
Headers show

Comments

Tomoki Sekiyama - July 3, 2013, 3:49 p.m.
Add VSS requester functions to qemu-ga.
This provides facility to request VSS service in Windows guest to quiesce
applications and filesystems. These functions are only supported in Windows
2003 or later. In older guests, these functions do nothing.

In several versions of Windows which don't support attribute
VSS_VOLSNAP_ATTR_NO_AUTORECOVERY, DoSnapshotSet fails with error
VSS_E_OBJECT_NOT_FOUND. In this patch, we just ignore this error.
To solve this fundamentally, we need a framework to handle mount writable
snapshot on guests, which is required by VSS auto-recovery feature
(a cleanup phase after snapshot is taken).

Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
---
 qga/Makefile.objs           |    1 
 qga/vss-win32-requester.cpp |  572 +++++++++++++++++++++++++++++++++++++++++++
 qga/vss-win32-requester.h   |   52 ++++
 3 files changed, 625 insertions(+)
 create mode 100644 qga/vss-win32-requester.cpp
 create mode 100644 qga/vss-win32-requester.h

Patch

diff --git a/qga/Makefile.objs b/qga/Makefile.objs
index 245df22..e43ae0e 100644
--- a/qga/Makefile.objs
+++ b/qga/Makefile.objs
@@ -5,6 +5,7 @@  qga-obj-y += qapi-generated/qga-qapi-types.o qapi-generated/qga-qapi-visit.o
 qga-obj-y += qapi-generated/qga-qmp-marshal.o
 
 ifeq ($(CONFIG_QGA_VSS),y)
+qga-obj-y += vss-win32-requester.o
 qga-provider-obj-y += vss-win32-provider/
 $(obj)/vss-win32-requester.o: QEMU_CXXFLAGS += -Wno-unknown-pragmas
 endif
diff --git a/qga/vss-win32-requester.cpp b/qga/vss-win32-requester.cpp
new file mode 100644
index 0000000..7f0ab26
--- /dev/null
+++ b/qga/vss-win32-requester.cpp
@@ -0,0 +1,572 @@ 
+/*
+ * QEMU Guest Agent win32 VSS Requester 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 <stdio.h>
+extern "C" {
+#include "guest-agent-core.h"
+}
+#include "vss-win32-requester.h"
+#include "vss-win32.h"
+#include "inc/win2003/vswriter.h"
+#include "inc/win2003/vsbackup.h"
+
+/* Max wait time for frozen event (VSS can only hold writes for 10 seconds) */
+#define VSS_TIMEOUT_FREEZE_MSEC 10000
+
+/* Call QueryStatus every 10 ms while waiting for frozen event */
+#define VSS_TIMEOUT_EVENT_MSEC 10
+
+
+/* Handle to VSSAPI.DLL */
+static HMODULE hLib;
+
+/* Functions in VSSAPI.DLL */
+typedef HRESULT(STDAPICALLTYPE * t_CreateVssBackupComponents)(
+    OUT IVssBackupComponents**);
+typedef void(APIENTRY * t_VssFreeSnapshotProperties)(IN VSS_SNAPSHOT_PROP*);
+static t_CreateVssBackupComponents pCreateVssBackupComponents;
+static t_VssFreeSnapshotProperties pVssFreeSnapshotProperties;
+
+/* Variables used while applications and filesystes are frozen by VSS */
+static struct QGAVSSContext {
+    IVssBackupComponents *pVssbc;  /* VSS requester interface */
+    IVssAsync *pAsyncSnapshot;     /* async info of VSS snapshot operation */
+    HANDLE hEventFrozen;           /* frozen event from qga-provider.dll */
+    HANDLE hEventThaw;             /* thaw event to qga-provider.dll */
+    int cFrozenVols;               /* number of frozen volumes */
+} vss_ctx;
+
+
+/* Check whether this OS version supports VSS providers */
+static bool vss_check_os_version(void)
+{
+    OSVERSIONINFO OSver;
+
+    OSver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+    GetVersionEx(&OSver);
+    if ((OSver.dwMajorVersion == 5 && OSver.dwMinorVersion >= 2) ||
+       OSver.dwMajorVersion > 5) {
+        return true;
+    }
+    return false;
+}
+
+static HRESULT call_vss_provider_func(const char *func_name)
+{
+    HMODULE provider_lib;
+    HRESULT hr;
+    FARPROC WINAPI func;
+
+    provider_lib = LoadLibraryA(QGA_PROVIDER_DLL);
+    if (!provider_lib) {
+        fprintf(stderr, "failed to load " QGA_PROVIDER_DLL "\n");
+        return E_FAIL;
+    }
+
+    func = GetProcAddress(provider_lib, func_name);
+    if (!func) {
+        fprintf(stderr, "failed to load %s from " QGA_PROVIDER_DLL "\n",
+                func_name);
+        FreeLibrary(provider_lib);
+        return E_FAIL;
+    }
+
+    hr = func();
+
+    FreeLibrary(provider_lib);
+    return hr;
+}
+
+int ga_install_vss_provider()
+{
+    if (!vss_check_os_version()) {
+        /* Do nothing if OS doesn't support providers. */
+        fprintf(stderr, "VSS provider is not supported in this OS version:"
+		"fsfreeze is disabled.\n");
+        return 0;
+    }
+
+    return SUCCEEDED(call_vss_provider_func("COMRegister")) ? 0 : EXIT_FAILURE;
+}
+
+void ga_uninstall_vss_provider()
+{
+    if (vss_check_os_version()) {
+        call_vss_provider_func("COMUnregister");
+    }
+}
+
+static HRESULT WaitForAsync(IVssAsync *pAsync)
+{
+    HRESULT ret, hr;
+
+    do {
+        hr = pAsync->Wait();
+        if (FAILED(hr)) {
+            ret = hr;
+            break;
+        }
+        hr = pAsync->QueryStatus(&ret, NULL);
+        if (FAILED(hr)) {
+            ret = hr;
+            break;
+        }
+    } while (ret == VSS_S_ASYNC_PENDING);
+
+    return ret;
+}
+
+gboolean vss_init(void)
+{
+    HRESULT hr;
+
+    vss_ctx.hEventFrozen = vss_ctx.hEventThaw = INVALID_HANDLE_VALUE;
+
+    if (!vss_check_os_version()) {
+        /* Do nothing if OS doesn't support providers. */
+        fprintf(stderr, "VSS provider is not supported in this OS version:"
+		"fsfreeze is disabled.\n");
+        return true;
+    }
+
+    CoInitialize(NULL);
+    hr = CoInitializeSecurity(
+        NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
+        RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL);
+    if (FAILED(hr)) {
+        fprintf(stderr, "failed to CoInitializeSecurity (error %lx)\n", hr);
+        return false;
+    }
+
+    hLib = LoadLibraryA("VSSAPI.DLL");
+    if (!hLib) {
+        fprintf(stderr, "failed to load VSSAPI.DLL\n");
+        return false;
+    }
+
+    pCreateVssBackupComponents = (t_CreateVssBackupComponents)
+        GetProcAddress(hLib,
+#ifdef _WIN64 /* 64bit environment */
+        "?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z"
+#else /* 32bit environment */
+        "?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z"
+#endif
+        );
+    pVssFreeSnapshotProperties = (t_VssFreeSnapshotProperties)
+        GetProcAddress(hLib, "VssFreeSnapshotProperties");
+    if (!pCreateVssBackupComponents || !pVssFreeSnapshotProperties) {
+        g_critical("failed to get proc address from VSSAPI.DLL");
+        return false;
+    }
+
+    return true;
+}
+
+static void vss_cleanup(void)
+{
+    if (vss_ctx.hEventFrozen != INVALID_HANDLE_VALUE) {
+        CloseHandle(vss_ctx.hEventFrozen);
+        vss_ctx.hEventFrozen = INVALID_HANDLE_VALUE;
+    }
+    if (vss_ctx.hEventThaw != INVALID_HANDLE_VALUE) {
+        CloseHandle(vss_ctx.hEventThaw);
+        vss_ctx.hEventThaw = INVALID_HANDLE_VALUE;
+    }
+    if (vss_ctx.pAsyncSnapshot) {
+        vss_ctx.pAsyncSnapshot->Release();
+        vss_ctx.pAsyncSnapshot = NULL;
+    }
+    if (vss_ctx.pVssbc) {
+        vss_ctx.pVssbc->Release();
+        vss_ctx.pVssbc = NULL;
+    }
+    vss_ctx.cFrozenVols = 0;
+}
+
+void vss_deinit(void)
+{
+    if (!vss_check_os_version()) {
+        return;
+    }
+
+    vss_cleanup();
+
+    CoUninitialize();
+
+    pCreateVssBackupComponents = NULL;
+    pVssFreeSnapshotProperties = NULL;
+    if (hLib) {
+        FreeLibrary(hLib);
+        hLib = NULL;
+    }
+}
+
+gboolean vss_initialized(void)
+{
+    return hLib != NULL;
+}
+
+static void vss_add_components(Error **err)
+{
+    unsigned int cWriters, i;
+    VSS_ID id, idInstance, idWriter;
+    BSTR bstrWriterName = NULL;
+    VSS_USAGE_TYPE usage;
+    VSS_SOURCE_TYPE source;
+    unsigned int cComponents, c1, c2, j;
+    IVssExamineWriterMetadata *pMetadata = NULL;
+    IVssWMComponent *pComponent = NULL;
+    PVSSCOMPONENTINFO pInfo = NULL;
+    HRESULT hr;
+
+    hr = vss_ctx.pVssbc->GetWriterMetadataCount(&cWriters);
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to get writer metadata count");
+        goto out;
+    }
+
+    for (i = 0; i < cWriters; i++) {
+        hr = vss_ctx.pVssbc->GetWriterMetadata(i, &id, &pMetadata);
+        if (FAILED(hr)) {
+            error_setg_win32(err, hr, "failed to get writer metadata of %d/%d",
+                             i, cWriters);
+            goto out;
+        }
+
+        hr = pMetadata->GetIdentity(&idInstance, &idWriter,
+                                    &bstrWriterName, &usage, &source);
+        if (FAILED(hr)) {
+            error_setg_win32(err, hr, "failed to get identity of writer %d/%d",
+                             i, cWriters);
+            goto out;
+        }
+
+        hr = pMetadata->GetFileCounts(&c1, &c2, &cComponents);
+        if (FAILED(hr)) {
+            error_setg_win32(err, hr, "failed to get file counts of %S",
+                             bstrWriterName);
+            goto out;
+        }
+
+        for (j = 0; j < cComponents; j++) {
+            hr = pMetadata->GetComponent(j, &pComponent);
+            if (FAILED(hr)) {
+                error_setg_win32(err, hr,
+                                 "failed to get component %d/%d of %S",
+                                 j, cComponents, bstrWriterName);
+                goto out;
+            }
+
+            hr = pComponent->GetComponentInfo(&pInfo);
+            if (FAILED(hr)) {
+                error_setg_win32(err, hr,
+                                 "failed to get component info %d/%d of %S",
+                                 j, cComponents, bstrWriterName);
+                goto out;
+            }
+
+            if (pInfo->bSelectable) {
+                hr = vss_ctx.pVssbc->AddComponent(idInstance, idWriter,
+                                                  pInfo->type,
+                                                  pInfo->bstrLogicalPath,
+                                                  pInfo->bstrComponentName);
+                if (FAILED(hr)) {
+                    error_setg_win32(err, hr, "failed to add component %S(%S)",
+                                     pInfo->bstrComponentName, bstrWriterName);
+                    goto out;
+                }
+            }
+            SysFreeString(bstrWriterName);
+            bstrWriterName = NULL;
+            pComponent->FreeComponentInfo(pInfo);
+            pInfo = NULL;
+            pComponent->Release();
+            pComponent = NULL;
+        }
+
+        pMetadata->Release();
+        pMetadata = NULL;
+    }
+out:
+    if (bstrWriterName) {
+        SysFreeString(bstrWriterName);
+    }
+    if (pComponent) {
+        if (pInfo) {
+            pComponent->FreeComponentInfo(pInfo);
+        }
+        pComponent->Release();
+    }
+    if (pMetadata) {
+        pMetadata->Release();
+    }
+}
+
+void qga_vss_fsfreeze_freeze(int *num_vols, Error **err)
+{
+    IVssAsync *pAsync = NULL;
+    HANDLE volume;
+    HRESULT hr;
+    LONG ctx;
+    GUID guidSnapshotSet = GUID_NULL;
+    SECURITY_DESCRIPTOR sd;
+    SECURITY_ATTRIBUTES sa;
+    WCHAR short_volume_name[64], *display_name = short_volume_name;
+    DWORD wait_status;
+    int num_fixed_drives = 0, i;
+
+    if (vss_ctx.pVssbc) { /* already frozen */
+        *num_vols = 0;
+        return;
+    }
+
+    g_assert(pCreateVssBackupComponents != NULL);
+    hr = pCreateVssBackupComponents(&vss_ctx.pVssbc);
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to create VSS backup components");
+        goto out;
+    }
+
+    hr = vss_ctx.pVssbc->InitializeForBackup();
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to initialize for backup");
+        goto out;
+    }
+
+    hr = vss_ctx.pVssbc->SetBackupState(true, true, VSS_BT_FULL, false);
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to set backup state");
+        goto out;
+    }
+
+    /*
+     * Currently writable snapshots are not supported.
+     * To prevent the final commit (which requires to write to snapshots),
+     * ATTR_NO_AUTORECOVERY and ATTR_TRANSPORTABLE are specified here.
+     */
+    ctx = VSS_CTX_APP_ROLLBACK | VSS_VOLSNAP_ATTR_TRANSPORTABLE |
+        VSS_VOLSNAP_ATTR_NO_AUTORECOVERY | VSS_VOLSNAP_ATTR_TXF_RECOVERY;
+    hr = vss_ctx.pVssbc->SetContext(ctx);
+    if (hr == (HRESULT)VSS_E_UNSUPPORTED_CONTEXT) {
+        /* Non-server version of Windows doesn't support ATTR_TRANSPORTABLE */
+        ctx &= ~VSS_VOLSNAP_ATTR_TRANSPORTABLE;
+        hr = vss_ctx.pVssbc->SetContext(ctx);
+    }
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to set backup context");
+        goto out;
+    }
+
+    hr = vss_ctx.pVssbc->GatherWriterMetadata(&pAsync);
+    if (SUCCEEDED(hr)) {
+        hr = WaitForAsync(pAsync);
+    }
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to gather writer metadata");
+        goto out;
+    }
+    pAsync->Release();
+    pAsync = NULL;
+
+    vss_add_components(err);
+    if (error_is_set(err)) {
+        goto out;
+    }
+
+    hr = vss_ctx.pVssbc->StartSnapshotSet(&guidSnapshotSet);
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to start snapshot set");
+        goto out;
+    }
+
+    volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name));
+    if (volume == INVALID_HANDLE_VALUE) {
+        error_setg_win32(err, hr, "failed to find first volume");
+        goto out;
+    }
+    for (;;) {
+        if (GetDriveTypeW(short_volume_name) == DRIVE_FIXED) {
+            VSS_ID pid;
+            hr = vss_ctx.pVssbc->AddToSnapshotSet(short_volume_name,
+                                                  g_gProviderId, &pid);
+            if (FAILED(hr)) {
+                WCHAR volume_path_name[PATH_MAX];
+                if (GetVolumePathNamesForVolumeNameW(
+                        short_volume_name, volume_path_name,
+                        sizeof(volume_path_name), NULL) && *volume_path_name) {
+                    display_name = volume_path_name;
+                }
+                error_setg_win32(err, hr, "failed to add %S to snapshot set",
+                                 display_name);
+                FindVolumeClose(volume);
+                goto out;
+            }
+            num_fixed_drives++;
+        }
+        if (!FindNextVolumeW(volume, short_volume_name,
+                             sizeof(short_volume_name))) {
+            FindVolumeClose(volume);
+            break;
+        }
+    }
+
+    if (num_fixed_drives == 0) {
+        goto out; /* If there is no fixed drive, just exit. */
+    }
+
+    hr = vss_ctx.pVssbc->PrepareForBackup(&pAsync);
+    if (SUCCEEDED(hr)) {
+        hr = WaitForAsync(pAsync);
+    }
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to prepare for backup");
+        goto out;
+    }
+    pAsync->Release();
+    pAsync = NULL;
+
+    hr = vss_ctx.pVssbc->GatherWriterStatus(&pAsync);
+    if (SUCCEEDED(hr)) {
+        hr = WaitForAsync(pAsync);
+    }
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to gather writer status");
+        goto out;
+    }
+    pAsync->Release();
+    pAsync = NULL;
+
+    /* Allow unrestricted access to events */
+    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
+    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
+    sa.nLength = sizeof(sa);
+    sa.lpSecurityDescriptor = &sd;
+    sa.bInheritHandle = FALSE;
+
+    vss_ctx.hEventFrozen = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_FROZEN);
+    if (vss_ctx.hEventFrozen == INVALID_HANDLE_VALUE) {
+        error_setg_win32(err, GetLastError(), "failed to create event %s",
+                         EVENT_NAME_FROZEN);
+        goto out;
+    }
+    vss_ctx.hEventThaw = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_THAW);
+    if (vss_ctx.hEventThaw == INVALID_HANDLE_VALUE) {
+        error_setg_win32(err, GetLastError(), "failed to create event %s",
+                         EVENT_NAME_THAW);
+        goto out;
+    }
+
+    /*
+     * Start VSS quiescing operations.
+     * CQGAVssProvider::CommitSnapshots will kick vss_ctx.hEventFrozen
+     * after the applications and filesystems are frozen.
+     */
+    hr = vss_ctx.pVssbc->DoSnapshotSet(&vss_ctx.pAsyncSnapshot);
+    if (FAILED(hr)) {
+        error_setg_win32(err, hr, "failed to do snapshot set");
+        goto out;
+    }
+
+    /* Need to call QueryStatus several times to make VSS provider progress */
+    for (i = 0; i < VSS_TIMEOUT_FREEZE_MSEC/VSS_TIMEOUT_EVENT_MSEC; i++) {
+        HRESULT hr2 = vss_ctx.pAsyncSnapshot->QueryStatus(&hr, NULL);
+        if (FAILED(hr2)) {
+            error_setg_win32(err, hr, "failed to do snapshot set");
+            goto out;
+        }
+        if (hr != VSS_S_ASYNC_PENDING) {
+            error_setg(err, "DoSnapshotSet exited without Frozen event");
+            goto out;
+        }
+        wait_status = WaitForSingleObject(vss_ctx.hEventFrozen,
+                                          VSS_TIMEOUT_EVENT_MSEC);
+        if (wait_status != WAIT_TIMEOUT) {
+            break;
+        }
+    }
+    if (wait_status != WAIT_OBJECT_0) {
+        error_setg(err, "Couldn't receive Frozen event from VSS provider");
+        goto out;
+    }
+
+    *num_vols = vss_ctx.cFrozenVols = num_fixed_drives;
+    return;
+
+out:
+    if (pAsync) {
+        pAsync->Release();
+    }
+    if (vss_ctx.pVssbc) {
+        vss_ctx.pVssbc->AbortBackup();
+    }
+    vss_cleanup();
+}
+
+
+void qga_vss_fsfreeze_thaw(int *num_vols, Error **err)
+{
+    IVssAsync *pAsync;
+
+    if (vss_ctx.hEventThaw == INVALID_HANDLE_VALUE) {
+        /*
+         * In this case, DoSnapshotSet is aborted or not started,
+         * and no volumes must be frozen. We return without an error.
+         */
+        *num_vols = 0;
+        return;
+    }
+
+    /* Tell the provider that the snapshot is finished. */
+    SetEvent(vss_ctx.hEventThaw);
+
+    g_assert(vss_ctx.pVssbc);
+    g_assert(vss_ctx.pAsyncSnapshot);
+
+    HRESULT hr = WaitForAsync(vss_ctx.pAsyncSnapshot);
+    switch (hr) {
+    case VSS_S_ASYNC_FINISHED:
+        hr = vss_ctx.pVssbc->BackupComplete(&pAsync);
+        if (SUCCEEDED(hr)) {
+            hr = WaitForAsync(pAsync);
+            pAsync->Release();
+        }
+        if (FAILED(hr)) {
+            error_setg_win32(err, hr, "failed to complete backup");
+        }
+        break;
+
+    case (HRESULT)VSS_E_OBJECT_NOT_FOUND:
+        /*
+         * On Windows earlier than 2008 SP2 which does not support
+         * VSS_VOLSNAP_ATTR_NO_AUTORECOVERY context, the final commit is not
+         * skipped and VSS is aborted by VSS_E_OBJECT_NOT_FOUND. However, as
+         * the system had been frozen until fsfreeze-thaw command was issued,
+         * we ignore this error.
+         */
+        vss_ctx.pVssbc->AbortBackup();
+        break;
+
+    case (HRESULT)VSS_E_HOLD_WRITES_TIMEOUT:
+        error_setg_win32(err, hr, "Couldn't hold writes. "
+                         "Fsfreeze is limited up to 10 seconds");
+        break;
+
+    default:
+        error_setg_win32(err, hr, "failed to do snapshot set");
+    }
+
+    if (error_is_set(err)) {
+        vss_ctx.pVssbc->AbortBackup();
+    }
+    *num_vols = vss_ctx.cFrozenVols;
+    vss_cleanup();
+}
diff --git a/qga/vss-win32-requester.h b/qga/vss-win32-requester.h
new file mode 100644
index 0000000..fe21446
--- /dev/null
+++ b/qga/vss-win32-requester.h
@@ -0,0 +1,52 @@ 
+/*
+ * QEMU Guest Agent VSS Requester declarations
+ *
+ * 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.
+ */
+
+#ifndef VSS_WIN32_REQUESTER_H
+#define VSS_WIN32_REQUESTER_H
+
+#include "qapi/error.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef CONFIG_QGA_VSS
+
+gboolean vss_init(void);
+void vss_deinit(void);
+gboolean vss_initialized(void);
+
+void qga_vss_fsfreeze_freeze(int *nr_volume, Error **err);
+void qga_vss_fsfreeze_thaw(int *nr_volume, Error **err);
+
+int ga_install_vss_provider(void);
+void ga_uninstall_vss_provider(void);
+
+#else
+
+static inline gboolean vss_init(void) { return true; }
+static inline void vss_deinit(void) { }
+static inline gboolean vss_initialized(void) { return false; }
+
+static inline void qga_vss_fsfreeze_freeze(int *nr_volume, Error **err) { }
+static inline void qga_vss_fsfreeze_thaw(int *nr_volume, Error **err) { }
+
+static inline int ga_install_vss_provider(void) { return 0; }
+static inline void ga_uninstall_vss_provider(void) { }
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif