diff options
Diffstat (limited to 'qga/vss-win32/requester.cpp')
-rw-r--r-- | qga/vss-win32/requester.cpp | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/qga/vss-win32/requester.cpp b/qga/vss-win32/requester.cpp new file mode 100644 index 0000000000..1e8dd3dfa8 --- /dev/null +++ b/qga/vss-win32/requester.cpp @@ -0,0 +1,507 @@ +/* + * 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> +#include "vss-common.h" +#include "requester.h" +#include "assert.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 + +#define err_set(e, err, fmt, ...) \ + ((e)->error_set((e)->errp, err, (e)->err_class, fmt, ## __VA_ARGS__)) +#define err_is_set(e) ((e)->errp && *(e)->errp) + + +/* 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; /* notify fs/writer freeze from provider */ + HANDLE hEventThaw; /* request provider to thaw */ + HANDLE hEventTimeout; /* notify timeout in provider */ + int cFrozenVols; /* number of frozen volumes */ +} vss_ctx; + +STDAPI requester_init(void) +{ + vss_ctx.hEventFrozen = INVALID_HANDLE_VALUE; + vss_ctx.hEventThaw = INVALID_HANDLE_VALUE; + vss_ctx.hEventTimeout = INVALID_HANDLE_VALUE; + + COMInitializer initializer; /* to call CoInitializeSecurity */ + HRESULT 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 hr; + } + + hLib = LoadLibraryA("VSSAPI.DLL"); + if (!hLib) { + fprintf(stderr, "failed to load VSSAPI.DLL\n"); + return HRESULT_FROM_WIN32(GetLastError()); + } + + pCreateVssBackupComponents = (t_CreateVssBackupComponents) + GetProcAddress(hLib, +#ifdef _WIN64 /* 64bit environment */ + "?CreateVssBackupComponents@@YAJPEAPEAVIVssBackupComponents@@@Z" +#else /* 32bit environment */ + "?CreateVssBackupComponents@@YGJPAPAVIVssBackupComponents@@@Z" +#endif + ); + if (!pCreateVssBackupComponents) { + fprintf(stderr, "failed to get proc address from VSSAPI.DLL\n"); + return HRESULT_FROM_WIN32(GetLastError()); + } + + pVssFreeSnapshotProperties = (t_VssFreeSnapshotProperties) + GetProcAddress(hLib, "VssFreeSnapshotProperties"); + if (!pVssFreeSnapshotProperties) { + fprintf(stderr, "failed to get proc address from VSSAPI.DLL\n"); + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +static void requester_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.hEventTimeout != INVALID_HANDLE_VALUE) { + CloseHandle(vss_ctx.hEventTimeout); + vss_ctx.hEventTimeout = 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; +} + +STDAPI requester_deinit(void) +{ + requester_cleanup(); + + pCreateVssBackupComponents = NULL; + pVssFreeSnapshotProperties = NULL; + if (hLib) { + FreeLibrary(hLib); + hLib = NULL; + } + + return S_OK; +} + +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; +} + +static void AddComponents(ErrorSet *errset) +{ + 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; + COMPointer<IVssExamineWriterMetadata> pMetadata; + COMPointer<IVssWMComponent> pComponent; + PVSSCOMPONENTINFO info; + HRESULT hr; + + hr = vss_ctx.pVssbc->GetWriterMetadataCount(&cWriters); + if (FAILED(hr)) { + err_set(errset, hr, "failed to get writer metadata count"); + goto out; + } + + for (i = 0; i < cWriters; i++) { + hr = vss_ctx.pVssbc->GetWriterMetadata(i, &id, pMetadata.replace()); + if (FAILED(hr)) { + err_set(errset, hr, "failed to get writer metadata of %d/%d", + i, cWriters); + goto out; + } + + hr = pMetadata->GetIdentity(&idInstance, &idWriter, + &bstrWriterName, &usage, &source); + if (FAILED(hr)) { + err_set(errset, hr, "failed to get identity of writer %d/%d", + i, cWriters); + goto out; + } + + hr = pMetadata->GetFileCounts(&c1, &c2, &cComponents); + if (FAILED(hr)) { + err_set(errset, hr, "failed to get file counts of %S", + bstrWriterName); + goto out; + } + + for (j = 0; j < cComponents; j++) { + hr = pMetadata->GetComponent(j, pComponent.replace()); + if (FAILED(hr)) { + err_set(errset, hr, + "failed to get component %d/%d of %S", + j, cComponents, bstrWriterName); + goto out; + } + + hr = pComponent->GetComponentInfo(&info); + if (FAILED(hr)) { + err_set(errset, hr, + "failed to get component info %d/%d of %S", + j, cComponents, bstrWriterName); + goto out; + } + + if (info->bSelectable) { + hr = vss_ctx.pVssbc->AddComponent(idInstance, idWriter, + info->type, + info->bstrLogicalPath, + info->bstrComponentName); + if (FAILED(hr)) { + err_set(errset, hr, "failed to add component %S(%S)", + info->bstrComponentName, bstrWriterName); + goto out; + } + } + SysFreeString(bstrWriterName); + bstrWriterName = NULL; + pComponent->FreeComponentInfo(info); + info = NULL; + } + } +out: + if (bstrWriterName) { + SysFreeString(bstrWriterName); + } + if (pComponent && info) { + pComponent->FreeComponentInfo(info); + } +} + +void requester_freeze(int *num_vols, ErrorSet *errset) +{ + COMPointer<IVssAsync> pAsync; + 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; + } + + CoInitialize(NULL); + + assert(pCreateVssBackupComponents != NULL); + hr = pCreateVssBackupComponents(&vss_ctx.pVssbc); + if (FAILED(hr)) { + err_set(errset, hr, "failed to create VSS backup components"); + goto out; + } + + hr = vss_ctx.pVssbc->InitializeForBackup(); + if (FAILED(hr)) { + err_set(errset, hr, "failed to initialize for backup"); + goto out; + } + + hr = vss_ctx.pVssbc->SetBackupState(true, true, VSS_BT_FULL, false); + if (FAILED(hr)) { + err_set(errset, 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)) { + err_set(errset, hr, "failed to set backup context"); + goto out; + } + + hr = vss_ctx.pVssbc->GatherWriterMetadata(pAsync.replace()); + if (SUCCEEDED(hr)) { + hr = WaitForAsync(pAsync); + } + if (FAILED(hr)) { + err_set(errset, hr, "failed to gather writer metadata"); + goto out; + } + + AddComponents(errset); + if (err_is_set(errset)) { + goto out; + } + + hr = vss_ctx.pVssbc->StartSnapshotSet(&guidSnapshotSet); + if (FAILED(hr)) { + err_set(errset, hr, "failed to start snapshot set"); + goto out; + } + + volume = FindFirstVolumeW(short_volume_name, sizeof(short_volume_name)); + if (volume == INVALID_HANDLE_VALUE) { + err_set(errset, 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; + } + err_set(errset, 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.replace()); + if (SUCCEEDED(hr)) { + hr = WaitForAsync(pAsync); + } + if (FAILED(hr)) { + err_set(errset, hr, "failed to prepare for backup"); + goto out; + } + + hr = vss_ctx.pVssbc->GatherWriterStatus(pAsync.replace()); + if (SUCCEEDED(hr)) { + hr = WaitForAsync(pAsync); + } + if (FAILED(hr)) { + err_set(errset, hr, "failed to gather writer status"); + goto out; + } + + /* 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) { + err_set(errset, 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) { + err_set(errset, GetLastError(), "failed to create event %s", + EVENT_NAME_THAW); + goto out; + } + vss_ctx.hEventTimeout = CreateEvent(&sa, TRUE, FALSE, EVENT_NAME_TIMEOUT); + if (vss_ctx.hEventTimeout == INVALID_HANDLE_VALUE) { + err_set(errset, GetLastError(), "failed to create event %s", + EVENT_NAME_TIMEOUT); + 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)) { + err_set(errset, 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)) { + err_set(errset, hr, "failed to do snapshot set"); + goto out; + } + if (hr != VSS_S_ASYNC_PENDING) { + err_set(errset, E_FAIL, + "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) { + err_set(errset, E_FAIL, + "couldn't receive Frozen event from VSS provider"); + goto out; + } + + *num_vols = vss_ctx.cFrozenVols = num_fixed_drives; + return; + +out: + if (vss_ctx.pVssbc) { + vss_ctx.pVssbc->AbortBackup(); + } + requester_cleanup(); + CoUninitialize(); +} + + +void requester_thaw(int *num_vols, ErrorSet *errset) +{ + COMPointer<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); + + assert(vss_ctx.pVssbc); + assert(vss_ctx.pAsyncSnapshot); + + HRESULT hr = WaitForAsync(vss_ctx.pAsyncSnapshot); + switch (hr) { + case VSS_S_ASYNC_FINISHED: + hr = vss_ctx.pVssbc->BackupComplete(pAsync.replace()); + if (SUCCEEDED(hr)) { + hr = WaitForAsync(pAsync); + } + if (FAILED(hr)) { + err_set(errset, 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 VSS_E_UNEXPECTED_PROVIDER_ERROR: + if (WaitForSingleObject(vss_ctx.hEventTimeout, 0) != WAIT_OBJECT_0) { + err_set(errset, hr, "unexpected error in VSS provider"); + break; + } + /* fall through if hEventTimeout is signaled */ + + case (HRESULT)VSS_E_HOLD_WRITES_TIMEOUT: + err_set(errset, hr, "couldn't hold writes: " + "fsfreeze is limited up to 10 seconds"); + break; + + default: + err_set(errset, hr, "failed to do snapshot set"); + } + + if (err_is_set(errset)) { + vss_ctx.pVssbc->AbortBackup(); + } + *num_vols = vss_ctx.cFrozenVols; + requester_cleanup(); + + CoUninitialize(); +} |