1e9b65bb1b
This is particularly useful when we abort in error_propagate(), because there the stack backtrace doesn't lead to where the error was created. Looks like this: Unexpected error in parse_block_error_action() at .../qemu/blockdev.c:322: qemu-system-x86_64: -drive if=none,werror=foo: 'foo' invalid write error action Aborted (core dumped) Note: to get this example output, I monkey-patched drive_new() to pass &error_abort to blockdev_init(). To keep the error handling boiler plate from growing even more, all error_setFOO() become macros expanding into error_setFOO_internal() with additional __FILE__, __LINE__, __func__ arguments. Not exactly pretty, but it works. The macro trickery breaks down when you take the address of an error_setFOO(). Fortunately, we do that in just one place: qemu-ga's Windows VSS provider and requester DLL wants to call error_setg_win32() through a function pointer "to avoid linking glib to the DLL". Use error_setg_win32_internal() there. The use of the function pointer is already wrapped in a macro, so the churn isn't bad. Code size increases by some 35KiB for me (0.7%). Tolerable. Could be less if we passed relative rather than absolute source file names to the compiler, or forwent reporting __func__. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Acked-by: Laszlo Ersek <lersek@redhat.com>
506 lines
16 KiB
C++
506 lines
16 KiB
C++
/*
|
|
* 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_setg_win32((e)->errp, __FILE__, __LINE__, __func__, \
|
|
err, fmt, ## __VA_ARGS__))
|
|
/* Bad idea, works only when (e)->errp != NULL: */
|
|
#define err_is_set(e) ((e)->errp && *(e)->errp)
|
|
/* To lift this restriction, error_propagate(), like we do in QEMU code */
|
|
|
|
/* 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)
|
|
{
|
|
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) {
|
|
CloseHandle(vss_ctx.hEventFrozen);
|
|
vss_ctx.hEventFrozen = NULL;
|
|
}
|
|
if (vss_ctx.hEventThaw) {
|
|
CloseHandle(vss_ctx.hEventThaw);
|
|
vss_ctx.hEventThaw = NULL;
|
|
}
|
|
if (vss_ctx.hEventTimeout) {
|
|
CloseHandle(vss_ctx.hEventTimeout);
|
|
vss_ctx.hEventTimeout = NULL;
|
|
}
|
|
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);
|
|
|
|
/* 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) {
|
|
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) {
|
|
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) {
|
|
err_set(errset, GetLastError(), "failed to create event %s",
|
|
EVENT_NAME_TIMEOUT);
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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) {
|
|
/*
|
|
* 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();
|
|
}
|