/* * QEMU Guest Agent win32 VSS Requester implementations * * Copyright Hitachi Data Systems Corp. 2013 * * Authors: * Tomoki Sekiyama * * 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 #include /* 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 pMetadata; COMPointer 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; } static 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; } static bool is_valid_vss_backup_type(VSS_BACKUP_TYPE vssBT) { return (vssBT > VSS_BT_UNDEFINED && vssBT < VSS_BT_OTHER); } static 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( 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 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 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; }