blob: 9884c65e707a30135fb8479afeaa2f177b99008f [file] [log] [blame]
/*
* 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 "qemu/osdep.h"
#include "vss-common.h"
#include "vss-debug.h"
#include "requester.h"
#include "install.h"
#include <vswriter.h>
#include <vsbackup.h>
/* Max wait time for frozen event (VSS can only hold writes for 10 seconds) */
#define VSS_TIMEOUT_FREEZE_MSEC 60000
/* Call QueryStatus every 10 ms while waiting for frozen event */
#define VSS_TIMEOUT_EVENT_MSEC 10
#define DEFAULT_VSS_BACKUP_TYPE VSS_BT_FULL
#define err_set(e, err, fmt, ...) { \
(e)->error_setg_win32_wrapper((e)->errp, __FILE__, __LINE__, __func__, \
err, fmt, ## __VA_ARGS__); \
qga_debug(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)
{
qga_debug_begin;
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)) {
qga_debug("failed to CoInitializeSecurity (error %lx)", hr);
return hr;
}
hLib = LoadLibraryA("VSSAPI.DLL");
if (!hLib) {
qga_debug("failed to load VSSAPI.DLL");
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) {
qga_debug("failed to get proc address from VSSAPI.DLL");
return HRESULT_FROM_WIN32(GetLastError());
}
pVssFreeSnapshotProperties = (t_VssFreeSnapshotProperties)
GetProcAddress(hLib, "VssFreeSnapshotProperties");
if (!pVssFreeSnapshotProperties) {
qga_debug("failed to get proc address from VSSAPI.DLL");
return HRESULT_FROM_WIN32(GetLastError());
}
qga_debug_end;
return S_OK;
}
static void requester_cleanup(void)
{
qga_debug_begin;
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;
qga_debug_end;
}
STDAPI requester_deinit(void)
{
qga_debug_begin;
requester_cleanup();
pCreateVssBackupComponents = NULL;
pVssFreeSnapshotProperties = NULL;
if (hLib) {
FreeLibrary(hLib);
hLib = NULL;
}
qga_debug_end;
return S_OK;
}
static HRESULT WaitForAsync(IVssAsync *pAsync)
{
qga_debug_begin;
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);
qga_debug_end;
return ret;
}
static void AddComponents(ErrorSet *errset)
{
qga_debug_begin;
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);
}
qga_debug_end;
}
DWORD get_reg_dword_value(HKEY baseKey, LPCSTR subKey, LPCSTR valueName,
DWORD defaultData)
{
qga_debug_begin;
DWORD regGetValueError;
DWORD dwordData;
DWORD dataSize = sizeof(DWORD);
regGetValueError = RegGetValue(baseKey, subKey, valueName, RRF_RT_DWORD,
NULL, &dwordData, &dataSize);
qga_debug_end;
if (regGetValueError != ERROR_SUCCESS) {
return defaultData;
}
return dwordData;
}
bool is_valid_vss_backup_type(VSS_BACKUP_TYPE vssBT)
{
return (vssBT > VSS_BT_UNDEFINED && vssBT < VSS_BT_OTHER);
}
VSS_BACKUP_TYPE get_vss_backup_type(
VSS_BACKUP_TYPE defaultVssBT = DEFAULT_VSS_BACKUP_TYPE)
{
qga_debug_begin;
VSS_BACKUP_TYPE vssBackupType;
vssBackupType = static_cast<VSS_BACKUP_TYPE>(
get_reg_dword_value(HKEY_LOCAL_MACHINE,
QGA_PROVIDER_REGISTRY_ADDRESS,
"VssOption",
defaultVssBT));
qga_debug_end;
if (!is_valid_vss_backup_type(vssBackupType)) {
return defaultVssBT;
}
return vssBackupType;
}
void requester_freeze(int *num_vols, void *mountpoints, ErrorSet *errset)
{
qga_debug_begin;
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;
int num_mount_points = 0;
VSS_BACKUP_TYPE vss_bt = get_vss_backup_type();
if (vss_ctx.pVssbc) { /* already frozen */
*num_vols = 0;
qga_debug("finished, already frozen");
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, 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;
}
if (mountpoints) {
PWCHAR volume_name_wchar;
for (volList *list = (volList *)mountpoints; list; list = list->next) {
size_t len = strlen(list->value) + 1;
size_t converted = 0;
VSS_ID pid;
volume_name_wchar = new wchar_t[len];
mbstowcs_s(&converted, volume_name_wchar, len,
list->value, _TRUNCATE);
hr = vss_ctx.pVssbc->AddToSnapshotSet(volume_name_wchar,
g_gProviderId, &pid);
if (FAILED(hr)) {
err_set(errset, hr, "failed to add %S to snapshot set",
volume_name_wchar);
delete[] volume_name_wchar;
goto out;
}
num_mount_points++;
delete[] volume_name_wchar;
}
if (num_mount_points == 0) {
/* If there is no valid mount points, just exit. */
goto out;
}
}
if (!mountpoints) {
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. */
}
}
qga_debug("preparing for backup");
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.
*/
qga_debug("do snapshot set");
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_TIMEOUT) {
err_set(errset, E_FAIL,
"timeout when try to receive Frozen event from VSS provider");
/* If we are here, VSS had timeout.
* Don't call AbortBackup, just return directly.
*/
goto out1;
}
if (wait_status != WAIT_OBJECT_0) {
err_set(errset, E_FAIL,
"couldn't receive Frozen event from VSS provider");
goto out;
}
if (mountpoints) {
*num_vols = vss_ctx.cFrozenVols = num_mount_points;
} else {
*num_vols = vss_ctx.cFrozenVols = num_fixed_drives;
}
qga_debug("end successful");
return;
out:
if (vss_ctx.pVssbc) {
vss_ctx.pVssbc->AbortBackup();
}
out1:
requester_cleanup();
CoUninitialize();
qga_debug_end;
}
void requester_thaw(int *num_vols, void *mountpints, ErrorSet *errset)
{
qga_debug_begin;
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;
qga_debug("finished, no volumes were frozen");
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();
StopService();
qga_debug_end;
}