| /* |
| * 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; |
| } |