#define _MODULE_VERSION_STR "X-1.3" #define _MODULE_STR "CVssCluster" #define _AUTHOR_STR "Conor Morrison" #pragma comment (compiler) #pragma comment (exestr, _MODULE_STR _MODULE_VERSION_STR) #pragma comment (user, _MODULE_STR _MODULE_VERSION_STR " Compiled on " __DATE__ " at " __TIME__ " by " _AUTHOR_STR) //++ // // Copyright (c) 2000 Microsoft Corporation // // FACILITY: // // CVssCluster // // MODULE DESCRIPTION: // // Implements cluster support for Vss (i.e. NT backup). // // ENVIRONMENT: // // User mode as part of an NT service. Adheres to the following state // transition diagram for CVssWriter: // // OnBackupComplete // Backup Complete <----------------IDLE -------------->Create Writer Metadata // | ^|^ | // +--------------------------+|+-------------------------+ // | // |OnBackupPrepare // | // | OnAbort // PREPARE BACKUP ---------> to IDLE // | // |OnPrepareSnapshot // | // | OnAbort // PREPARE SNAPSHOT ---------> to IDLE // | // |OnFreeze // | // | OnAbort // FREEZE ---------> to IDLE // | // |OnThaw // | // | OnAbort // THAW ---------> to IDLE // // // AUTHOR: // // Conor Morrison // // CREATION DATE: // // 18-Apr-2001 // // Revision History: // // X-1 CM Conor Morrison 18-Apr-2001 // Initial version to address bug #367566. // .1 Set restore method to custom and reboot required to false. // Check for component selected or bootable system state in // OnPrepareSnapshot and ignore if not. Add cleanup to Abort and // Thaw. Fix bug in RemoveDirectoryTree. // .2 Incorporate first review comments: change caption to be the component // name. Set bRestoreMetadata to false. Remove extraneous tracing. // Release the interface in the while loop. Cleanup after a non-cleanly // terminated backup. This is done in OnPrepareSnapshot. Tolerate // error_file_not_found at various places. // .3 More review comments. Reset g_bDoBackup in the prepare routine. // SetWriterFailure in more places - any time we veto we should set this. //-- extern "C" { #include "service.h" //CMCM! Mask build breaks. #define _LMERRLOG_ #define _LMHLOGDEFINED_ #define _LMAUDIT_ #include "lm.h" // for SHARE_INFO_502 } #include "CVssClusterp.h" // Up the warning level to 4 - we can survive... // #pragma warning( push, 4 ) // // Globals // UNICODE_STRING g_ucsBackupPathLocal; bool g_bDoBackup; // Assume we are not enabled until we find out otherwise. // // Forward declarations for static functions. // static HRESULT StringAllocate( PUNICODE_STRING pucsString, USHORT usMaximumStringLengthInBytes ); static void StringFree( PUNICODE_STRING pucsString ); static void StringAppendString( PUNICODE_STRING pucsTarget, PWCHAR pwszSource ); static void StringAppendString( PUNICODE_STRING pucsTarget, PUNICODE_STRING pucsSource ); static HRESULT StringTruncate (PUNICODE_STRING pucsString, USHORT usSizeInChars); static HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString, DWORD dwExtraChars); static HRESULT StringCreateFromExpandedString( PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString, DWORD dwExtraChars); static HRESULT DoClusterDatabaseBackup( void ); static HRESULT ConstructSecurityAttributes( PSECURITY_ATTRIBUTES psaSecurityAttributes, BOOL bIncludeBackupOperator ); static VOID CleanupSecurityAttributes( PSECURITY_ATTRIBUTES psaSecurityAttributes ); static HRESULT CreateTargetDirectory( OUT UNICODE_STRING* pucsTarget ); static HRESULT CleanupTargetDirectory( LPCWSTR pwszTargetPath ); static HRESULT RemoveDirectoryTree (PUNICODE_STRING pucsDirectoryPath); // // Some useful macros. // #define LOGERROR( _hr, _func ) ClRtlLogPrint( LOG_CRITICAL, "VSS: Error: 0x%1!08lx! from: %2\n", (_hr), L#_func ) #ifdef DBG #define LOGFUNCTIONENTRY( _name ) ClRtlLogPrint( LOG_NOISE, "VSS: Function: " #_name " Called.\n" ) #define LOGFUNCTIONEXIT( _name ) ClRtlLogPrint( LOG_NOISE, "VSS: Function: " #_name " Exiting.\n" ) #define LOGUNICODESTRING( _ustr ) ClRtlLogPrint( LOG_NOISE, "VSS: String %1 == %2\n", L#_ustr, (_ustr).Buffer ); #define LOGSTRING( _str ) ClRtlLogPrint( LOG_NOISE, "VSS: String %1 == %2\n", L#_str, _str ); #else #define LOGFUNCTIONENTRY( _name ) #define LOGFUNCTIONEXIT( _name ) #define LOGUNICODESTRING( _ustr ) #define LOGSTRING( _str ) #endif #define GET_HR_FROM_BOOL(_bSucceed) ((_bSucceed) ? NOERROR : HRESULT_FROM_WIN32 (GetLastError())) #define HandleInvalid(_Handle) ((NULL == (_Handle)) || (INVALID_HANDLE_VALUE == (_Handle))) #define GET_HR_FROM_HANDLE(_handle) ((!HandleInvalid(_handle)) ? NOERROR : HRESULT_FROM_WIN32 (GetLastError( ))) #define GET_HR_FROM_POINTER(_ptr) ((NULL != (_ptr)) ? NOERROR : E_OUTOFMEMORY) #define IS_VALID_PATH( _path ) ( ( ( pwszPathName[0] == DIR_SEP_CHAR ) && ( pwszPathName[1] == DIR_SEP_CHAR ) ) || \ ( isalpha( pwszPathName[0] ) && ( pwszPathName[1] == L':' ) && ( pwszPathName[2] == DIR_SEP_CHAR ) ) ) #define StringZero( _pucs ) ( (_pucs)->Buffer = NULL, (_pucs)->Length = 0, (_pucs)->MaximumLength = 0 ) // // Defines that identify us as a product to VSS - these are the same as in the old shim // #define COMPONENT_NAME L"Cluster Database" #define APPLICATION_STRING L"ClusterDatabase" #define SHARE_NAME L"__NtBackup_cluster" // Some borrowed defines from the shim stuff. // #ifndef DIR_SEP_STRING #define DIR_SEP_STRING L"\\" #endif #ifndef DIR_SEP_CHAR #define DIR_SEP_CHAR L'\\' #endif // // Define some constants that are borrowed from the original shim. These will // be used to build the path to the directory in which the cluster files will // be placed by the cluster backup. TARGET_PATH gives this full directory. In // Identify we tell the backup app which directory we are using so it knows // where to get the files from. // #define ROOT_REPAIR_DIR L"%SystemRoot%\\Repair" #define BACKUP_SUBDIR L"\\Backup" #define BOOTABLE_STATE_SUBDIR L"\\BootableSystemState" #define SERVICE_STATE_SUBDIR L"\\ServiceState" #define TARGET_PATH ROOT_REPAIR_DIR BACKUP_SUBDIR BOOTABLE_STATE_SUBDIR DIR_SEP_STRING APPLICATION_STRING //++ // DESCRIPTION: CreateIfNotExistAndSetAttributes // // Create the directory specified by pucsTarget if it does not // already exist and give it the security attributes supplied. // // PARAMETERS: // pucsTarget - string for the directory to create. Full path, possibly // with %var% // lpSecurityAttributes - Pointer to security attributes to apply to // directories created. // dwExtraAttributes - Additional attributes to apply to the directory. // // PRE-CONDITIONS: // None // // POST-CONDITIONS: // Directory created (or it already existed). // // RETURN VALUE: // S_OK - All went OK, directory created and set with attributes and // security supplied. // Error status from creating directory or setting attributes. Note that // ALREADY_EXISTS is not returned if the directory already exists. // However, if it a FILE of the same name as pucsTarget exists then this // error can be returned. //-- static HRESULT CreateIfNotExistAndSetAttributes( UNICODE_STRING* pucsTarget, IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwExtraAttributes) { LOGFUNCTIONENTRY( CreateIfNotExistAndSetAttributes ); HRESULT hr = S_OK; // Create the directory // LOGUNICODESTRING( *pucsTarget ); GET_HR_FROM_BOOL( CreateDirectoryW (pucsTarget->Buffer, lpSecurityAttributes ) ); ClRtlLogPrint( LOG_NOISE, "VSS: CreateIfNotExistAndSetAttributes: CreateDirectory returned: 0x%1!08lx!\n", hr ); if ( hr == HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) ) { DWORD dwObjAttribs = GetFileAttributesW( pucsTarget->Buffer ); if (( dwObjAttribs != 0xFFFFFFFF ) && ( dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY )) hr = S_OK; } // Note that we can fail with ALREADY_EXISTS if it is a file by the check above. // if ( FAILED ( hr )) { LOGERROR( hr, CreateDirectoryW ); goto ErrorExit; } // Set the extra attributes // if ( dwExtraAttributes != 0 ) { GET_HR_FROM_BOOL( SetFileAttributesW (pucsTarget->Buffer, dwExtraAttributes )); if ( FAILED ( hr )) { LOGERROR( hr, SetFileAttributesW ); goto ErrorExit; } } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: LOGFUNCTIONEXIT( CreateIfNotExistAndSetAttributes ); return hr; } //++ // DESCRIPTION: CreateTargetDirectory // // Create a new target directory (hardcoded) and return it in // pucsTarget member variable if not NULL. It will create any // necessary. Uses helper function that tolerates // ERROR_ALREADY_EXISTS. // // PARAMETERS: // pucsTarget - Address to receive unicode string giving path to // directory. // // PRE-CONDITIONS: // pucsTarget must be all zeros. // // POST-CONDITIONS: // pucsTarget points to buffer containing dir string. Memory was // allocated for this buffer. // // RETURN VALUE: // S_OK - all went well // Errors from creating directories or memory allocation failure. //-- static HRESULT CreateTargetDirectory( OUT UNICODE_STRING* pucsTarget ) { LOGFUNCTIONENTRY( CreateTargetDirectory ); HRESULT hr = NOERROR; SECURITY_ATTRIBUTES saSecurityAttributes, *psaSecurityAttributes=&saSecurityAttributes; SECURITY_DESCRIPTOR sdSecurityDescriptor; bool bSecurityAttributesConstructed = false; const DWORD dwExtraAttributes = FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; // // We really want a no access acl on this directory but because of various // problems with the EventLog and ConfigDir writers we will settle for // admin or backup operator access only. The only possible accessor is // Backup which is supposed to have the SE_BACKUP_NAME priv which will // effectively bypass the ACL. No one else needs to see this stuff. // saSecurityAttributes.nLength = sizeof( saSecurityAttributes ); saSecurityAttributes.lpSecurityDescriptor = &sdSecurityDescriptor; saSecurityAttributes.bInheritHandle = false; hr = ConstructSecurityAttributes( &saSecurityAttributes, false ); if ( FAILED( hr )) { LOGERROR( hr, ConstructSecurityAttributes ); goto ErrorExit; } bSecurityAttributesConstructed = true; // OK, now we have attributes we can do the directories. // // First expand the Root, checking that our input is NULL. // CL_ASSERT( pucsTarget->Buffer == NULL ); hr = StringCreateFromExpandedString( pucsTarget, ROOT_REPAIR_DIR, MAX_PATH ); if ( FAILED( hr )) { LOGERROR( hr, StringCreateFromExpandedString ); goto ErrorExit; } hr = CreateIfNotExistAndSetAttributes( pucsTarget, psaSecurityAttributes, dwExtraAttributes ); if ( FAILED ( hr )) { LOGERROR( hr, CreateIfNotExistAndSetAttributes ); goto ErrorExit; } StringAppendString( pucsTarget, BACKUP_SUBDIR ); hr = CreateIfNotExistAndSetAttributes( pucsTarget, psaSecurityAttributes, dwExtraAttributes ); if ( FAILED ( hr )) { LOGERROR( hr, CreateIfNotExistAndSetAttributes ); goto ErrorExit; } StringAppendString( pucsTarget, BOOTABLE_STATE_SUBDIR ); hr = CreateIfNotExistAndSetAttributes( pucsTarget, psaSecurityAttributes, dwExtraAttributes ); if ( FAILED ( hr )) { LOGERROR( hr, CreateIfNotExistAndSetAttributes ); goto ErrorExit; } StringAppendString( pucsTarget, DIR_SEP_STRING APPLICATION_STRING ); hr = CreateIfNotExistAndSetAttributes( pucsTarget, psaSecurityAttributes, dwExtraAttributes ); if ( FAILED ( hr )) { LOGERROR( hr, CreateIfNotExistAndSetAttributes ); goto ErrorExit; } // At this point we have TARGET_PATH created. // goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); (void) CleanupTargetDirectory( pucsTarget->Buffer ); ret: // In all cases we don't need the security attributes any more. // if ( bSecurityAttributesConstructed ) CleanupSecurityAttributes( &saSecurityAttributes ); return hr ; } // // There are only some valid statuses to pass to SetWriterFailure. These are // listed below. For now we just return VSS_E_WRITEERROR_NONRETRYABLE. We // could perhaps switch on the status and return something different depending // on hr. // VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT The snapshot contains only a // subset of the volumes needed to correctly back up an application // component. // VSS_E_WRITERERROR_NONRETRYABLE The writer failed due to an error that // would likely occur if another snapshot is created. // VSS_E_WRITERERROR_OUTRESOURCES The writer failed due to a resource // allocation error. // VSS_E_WRITERERROR_RETRYABLE The writer failed due to an error that would // likely not occur if another snapshot is created. // VSS_E_WRITERERROR_TIMEOUT The writer could not complete the snapshot // creation process due to a time-out between the freeze and thaw states. #if defined DBG #define SETWRITERFAILURE( ) { \ HRESULT __hrTmp = SetWriterFailure( VSS_E_WRITERERROR_NONRETRYABLE ); \ if ( FAILED( __hrTmp )) ClRtlLogPrint( LOG_CRITICAL, "VSS: Error from SetWriterFailure: %1!u!\n", (__hrTmp)); \ CL_ASSERT( !FAILED( __hrTmp )); \ } #else #define SETWRITERFAILURE( ) { \ (void) SetWriterFailure( VSS_E_WRITERERROR_NONRETRYABLE ); \ } #endif #define NameIsDotOrDotDot(_ptszName) \ (( L'.' == (_ptszName) [0]) \ && ((L'\0' == (_ptszName) [1]) \ || ((L'.' == (_ptszName) [1]) \ && (L'\0' == (_ptszName) [2])))) //++ // DESCRIPTION: CVssWriterCluster::OnIdentify // // Callback when a request for metadata comes in. This routine // identifies this applications special needs to the backup // utility. // // PARAMETERS: // IVssCreateWriterMetadata - Interface for some methods we can call. // // PRE-CONDITIONS: // Called from Idle state // // POST-CONDITIONS: // Backup returns to idle state. // // RETURN VALUE: // true - continue with snapshot operation. // false - Veto the snapshot creation. //-- bool STDMETHODCALLTYPE CVssWriterCluster::OnIdentify(IN IVssCreateWriterMetadata *pMetadata) { LOGFUNCTIONENTRY( OnIdentify ); HRESULT hr = S_OK; bool bRet = true; ClRtlLogPrint( LOG_NOISE, "VSS: OnIdentify. CVssCluster.cpp version %1 Add Component %2\n", _MODULE_VERSION_STR, COMPONENT_NAME ); // Add ourselves to the components. // hr = pMetadata->AddComponent (VSS_CT_FILEGROUP, // VSS_COMPONENT_TYPE enumeration value. NULL, // Pointer to a string containing the logical path of the DB or file group. COMPONENT_NAME, // Pointer to a string containing the name of the component. COMPONENT_NAME, // Pointer to a string containing the description of the component. NULL, // Pointer to a bitmap of the icon representing the database (for UI) 0, // Number of bytes in bitmap. false, // bRestoreMetadata - Boolean is true if there is writer metadata associated // with the backup of the component and false if not. false, // bNotifyOnBackupComplete false); // bSelectable - true if the component can be selectively backed up. if ( FAILED( hr )) { LOGERROR( hr, IVssCreateWriterMetadata::AddComponent ); bRet = false; // veto on failure goto ErrorExit; } ClRtlLogPrint( LOG_NOISE, "VSS: OnIdentify. Add Files To File Group target path: %1\n", TARGET_PATH ); hr= pMetadata->AddFilesToFileGroup (NULL, COMPONENT_NAME, TARGET_PATH, L"*", true, NULL); if ( FAILED ( hr )) { LOGERROR( hr, IVssCreateWriterMetadata::AddFilesToFileGroup ); bRet = false; // veto on failure goto ErrorExit; } // If we decide to go for copying the checkpoint file to the // CLUSDB for restore then we need to setup an alternate mapping. // // IVssCreateWriterMetadata::AddAlternateLocationMapping // [This is preliminary documentation and subject to change.] // // The AddAlternateLocationMapping method creates an alternate location mapping. // // HRESULT AddAlternateLocationMapping( // LPCWSTR wszPath, // LPCWSTR wszFilespec, // bool bRecursive, // LPCWSTR wszDestination // ); // Now, set the restore method to custom. This is because we need // special actions for restore. // hr = pMetadata->SetRestoreMethod( VSS_RME_CUSTOM, // VSS_RESTOREMETHOD_ENUM Method, L"", // LPCWSTR wszService, NULL, // LPCWSTR wszUserProcedure, VSS_WRE_NEVER, // VSS_WRITERRESTORE_ENUM wreWriterRestore, false // bool bRebootRequired ); // wszUserProcedure [out] String containing the URL of an HTML or // XML document describing to the user how the restore is to be // performed. if ( FAILED ( hr )) { LOGERROR( hr, IVssCreateWriterMetadata::SetRestoreMethod ); bRet = false; // veto on failure goto ErrorExit; } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); CL_ASSERT( bRet == false ); SETWRITERFAILURE( ); ret: return bRet; } // callback for prepare backup event // bool STDMETHODCALLTYPE CVssWriterCluster::OnPrepareBackup(IN IVssWriterComponents *pComponent) { LOGFUNCTIONENTRY( OnPrepareBackup ); bool bRet = true; UINT cComponents = 0; IVssComponent* pMyComponent = NULL; BSTR pwszName; g_bDoBackup = false; // Assume false until the loop below or IsBootableSystemStateBackedUp tells us otherwise. HRESULT hr = pComponent->GetComponentCount( &cComponents ); ClRtlLogPrint( LOG_NOISE, "VSS: GetComponentCount returned hr: 0x%1!08lx! cComponents: %2!u!\n", hr, cComponents ); if ( FAILED( hr )) { LOGERROR( hr, GetComponentCount ); bRet = false; goto ErrorExit; } // Now, loop over all the components and see if we are there. // for ( UINT iComponent = 0; !g_bDoBackup && iComponent < cComponents; ++iComponent ) { hr = pComponent->GetComponent( iComponent, &pMyComponent ); if ( FAILED( hr )) { ClRtlLogPrint( LOG_CRITICAL, "VSS: Failed to get Component: %1!u! hr: 0x%2!08lx!\n", iComponent, hr ); bRet = false; goto ErrorExit; } ClRtlLogPrint( LOG_CRITICAL, "VSS: Got Component: 0x%1!08lx!\n", pMyComponent ); // Now check the name. // hr = pMyComponent->GetComponentName( &pwszName ); if ( FAILED( hr )) { ClRtlLogPrint( LOG_CRITICAL, "VSS: Failed to get Component Name hr: 0x%1!08lx!\n", hr ); bRet = false; pMyComponent->Release( ); goto ErrorExit; } ClRtlLogPrint( LOG_CRITICAL, "VSS: Got component: %1\n", pwszName ); if ( wcscmp ( pwszName, COMPONENT_NAME ) == 0 ) g_bDoBackup = true; SysFreeString( pwszName ); pMyComponent->Release( ); } // OK, explicitly selected component count is 0 but we can be part // of a backup of bootable system state so check for that too. // if ( IsBootableSystemStateBackedUp( )) { ClRtlLogPrint( LOG_NOISE, "VSS: IsBootableSystemStateBackedUp returned true\n" ); g_bDoBackup = true; } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); CL_ASSERT( bRet == false ); SETWRITERFAILURE( ); ret: LOGFUNCTIONEXIT( OnPrepareBackup ); return bRet; } //++ // DESCRIPTION: CVssWriterCluster::OnPrepareSnapshot // // Callback for prepare snapshot event. Actually makes the call to backup // the cluster. Uses the target path declared in Identify so that the // ntbackup will pickup our files for us. Before doing anything the // directory is cleared out (if it exists) and the share deleted (if it // exists). // // PARAMETERS: // none // // PRE-CONDITIONS: // OnPrepareBackup was already called. // // POST-CONDITIONS: // The cluster database is backed up and the data copied to another // location for backup to save. // // RETURN VALUE: // true - continue with snapshot operation. // false - Veto the snapshot creation. //-- bool STDMETHODCALLTYPE CVssWriterCluster::OnPrepareSnapshot() { NET_API_STATUS NetStatus = NERR_Success; HRESULT hr = S_OK; bool bRet = true; UNICODE_STRING ucsBackupDir; LOGFUNCTIONENTRY( OnPrepareSnapshot ); if ( g_bDoBackup == false ) goto ret; // Delete the share if it exists. Tolerate errors but warns for anything // except NERR_NetNameNotFound. Break if debug. // NetStatus = NetShareDel( NULL, SHARE_NAME, 0 ); CL_ASSERT( NetStatus == NERR_Success || NetStatus == NERR_NetNameNotFound ); if ( NetStatus != NERR_Success && NetStatus != NERR_NetNameNotFound ) { ClRtlLogPrint( LOG_UNUSUAL, "VSS: OnPrepareSnapshot: Tolerating error: %1!u! from NetShareDel\n", NetStatus ); NetStatus = NERR_Success; } // Delete the directory if it exists. This can only be the case if we // prematurely exited a previous backup. // // First expand the Root, checking that our input is NULL. // StringZero( &ucsBackupDir ); hr = StringCreateFromExpandedString( &ucsBackupDir, ROOT_REPAIR_DIR, MAX_PATH ); if ( FAILED( hr )) { LOGERROR( hr, StringCreateFromExpandedString ); goto ErrorExit; } StringAppendString( &ucsBackupDir, BACKUP_SUBDIR ); StringAppendString( &ucsBackupDir, BOOTABLE_STATE_SUBDIR ); StringAppendString( &ucsBackupDir, DIR_SEP_STRING APPLICATION_STRING ); ClRtlLogPrint( LOG_NOISE, "VSS: OnPrepareSnapshot: Cleaning up target directory: %1\n", ucsBackupDir.Buffer ); hr = CleanupTargetDirectory( ucsBackupDir.Buffer ); if ( FAILED( hr ) ) { ClRtlLogPrint( LOG_UNUSUAL, "VSS: Tolerating error 0x%1!08lx! from CleanupTargetDirectory.\n", hr ); hr = S_OK; // tolerate this failure. } StringFree( &ucsBackupDir ); hr = DoClusterDatabaseBackup( ); if ( FAILED( hr ) ) { LOGERROR( hr, DoClusterDatabaseBackup ); SETWRITERFAILURE( ); bRet = false; // veto on failure goto ErrorExit; } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); CL_ASSERT( bRet == false ); SETWRITERFAILURE( ); ret: LOGFUNCTIONEXIT( OnPrepareSnapshot ); return bRet; } // callback for freeze event // bool STDMETHODCALLTYPE CVssWriterCluster::OnFreeze() { LOGFUNCTIONENTRY( OnFreeze ); LOGFUNCTIONEXIT( OnFreeze ); return true; } // callback for thaw event // bool STDMETHODCALLTYPE CVssWriterCluster::OnThaw() { LOGFUNCTIONENTRY( OnThaw ); if ( g_bDoBackup == false ) goto ret; if ( g_ucsBackupPathLocal.Buffer ) { ClRtlLogPrint( LOG_NOISE, "VSS: Cleaning up target directory: %1\n", g_ucsBackupPathLocal.Buffer ); HRESULT hr = CleanupTargetDirectory( g_ucsBackupPathLocal.Buffer ); if ( FAILED( hr ) ) { LOGERROR( hr, CVssWriterCluster::OnThaw ); ClRtlLogPrint( LOG_CRITICAL, "VSS: 0x%1!08lx! from CleanupTargetDirectory. Mapping to S_OK and continuing\n", hr ); hr = S_OK; // tolerate this failure. } } // Free the buffer if non-NULL. // StringFree ( &g_ucsBackupPathLocal ); LOGFUNCTIONEXIT( OnThaw ); ret: return true; } // callback if current sequence is aborted // bool STDMETHODCALLTYPE CVssWriterCluster::OnAbort() { LOGFUNCTIONENTRY( OnAbort ); bool bRet = OnThaw( ); LOGFUNCTIONEXIT( OnAbort ); return bRet; } //++ // DESCRIPTION: DoClusterDatabaseBackup // // Perform the backup of the cluster database. This function wraps // FmBackupClusterDatabase which does the right thing to do the cluster // backup. This function first creates a directory that will serve as the // destination for the backup. Next it creates a network share to point // to this directory and starts the cluster backup. After this is done it // cleans up. // // PARAMETERS: // none // // PRE-CONDITIONS: // . Called only from CVssWriterCluster::OnPrepareSnapshot. // . We must be the only call to backup in progress on this machine (the // share names will clash otherwise and clustering may not behave well // with multiple FmBackupClusterDatabase calls it the same time). // // POST-CONDITIONS: // Cluster database backed up to another location, ready for the backup tool to copy. // // RETURN VALUE: // S_OK - all went well. // Error status from creating directories or net shares or from cluster backup. //-- static HRESULT DoClusterDatabaseBackup( ) { LOGFUNCTIONENTRY( DoClusterDatabaseBackup ); HRESULT hr = S_OK; bool bNetShareAdded = false; SHARE_INFO_502 ShareInfo; UNICODE_STRING ucsComputerName; UNICODE_STRING ucsBackupPathNetwork; NET_API_STATUS NetStatus; StringZero( &ucsComputerName ); StringZero( &g_ucsBackupPathLocal ); StringZero( &ucsBackupPathNetwork ); // Create the directories and set the attributes and security and stuff. // Set g_ucsBackupPathLocal to the directory created. // hr = CreateTargetDirectory( &g_ucsBackupPathLocal ); if ( FAILED (hr )) { LOGERROR( hr, CreateTargetDirectory ); goto ErrorExit; } #ifdef DBG { // Check that the directory does exist. // DWORD dwFileAttributes = GetFileAttributesW( g_ucsBackupPathLocal.Buffer ); hr = GET_HR_FROM_BOOL( dwFileAttributes != -1 ); ClRtlLogPrint( LOG_NOISE, "VSS: GetFileAttributes(1) returned 0x%1!08lx! for path: %2\n", hr, g_ucsBackupPathLocal.Buffer ); } #endif hr = StringAllocate (&ucsComputerName, (MAX_COMPUTERNAME_LENGTH * sizeof (WCHAR)) + sizeof (UNICODE_NULL)); if ( FAILED( hr )) { LOGERROR( hr, StringAllocate ); goto ErrorExit; } DWORD dwNameLength = ucsComputerName.MaximumLength / sizeof (WCHAR); hr = GET_HR_FROM_BOOL( GetComputerNameW( ucsComputerName.Buffer, &dwNameLength )); if ( FAILED ( hr )) { LOGERROR( hr, GetComputerNameW ); goto ErrorExit; } ucsComputerName.Length = (USHORT) (dwNameLength * sizeof (WCHAR)); hr = StringAllocate (&ucsBackupPathNetwork, (USHORT) (sizeof (L'\\') + sizeof (L'\\') + ucsComputerName.Length + sizeof (L'\\') + ( wcslen( SHARE_NAME ) * sizeof( WCHAR ) ) + sizeof (UNICODE_NULL))); if ( FAILED ( hr )) { LOGERROR( hr, GetComputerNameW ); goto ErrorExit; } ClRtlLogPrint( LOG_NOISE, "VSS: backup path network size: %1!u!\n", ucsBackupPathNetwork.Length ); // // Should we uniquify the directory name at all here // to cater for the possiblity that we may be involved // in more than one snapshot at a time? // StringAppendString( &ucsBackupPathNetwork, L"\\\\" ); StringAppendString( &ucsBackupPathNetwork, &ucsComputerName ); StringAppendString( &ucsBackupPathNetwork, L"\\" ); StringAppendString( &ucsBackupPathNetwork, SHARE_NAME ); ClRtlLogPrint( LOG_NOISE, "VSS: backup path network: %1\n", ucsBackupPathNetwork.Buffer ); ZeroMemory( &ShareInfo, sizeof( ShareInfo )); ShareInfo.shi502_netname = SHARE_NAME; ShareInfo.shi502_type = STYPE_DISKTREE; ShareInfo.shi502_permissions = ACCESS_READ | ACCESS_WRITE | ACCESS_CREATE; ShareInfo.shi502_max_uses = 1; ShareInfo.shi502_path = g_ucsBackupPathLocal.Buffer; #ifdef DBG { // Check that the directory does exist. // DWORD dwFileAttributes = GetFileAttributesW( g_ucsBackupPathLocal.Buffer ); hr = GET_HR_FROM_BOOL( dwFileAttributes != -1 ); ClRtlLogPrint( LOG_NOISE, "VSS: GetFileAttributes(2) returned 0x%1!08lx! for path: %2\n", hr, g_ucsBackupPathLocal.Buffer ); } #endif // // Make sure to try to delete the share first in case for some reason it exists. // NetStatus = NetShareDel( NULL, SHARE_NAME, 0 ); ClRtlLogPrint( LOG_NOISE, "VSS: NetShareDel returned: %1!u!\n", NetStatus ); if ( NetStatus == NERR_NetNameNotFound ) NetStatus = NERR_Success; CL_ASSERT( NetStatus == NERR_Success ); #ifdef DBG { // Check that the directory does exist. // DWORD dwFileAttributes = GetFileAttributesW( g_ucsBackupPathLocal.Buffer ); hr = GET_HR_FROM_BOOL( dwFileAttributes != -1 ); ClRtlLogPrint( LOG_NOISE, "VSS: GetFileAttributes(3) returned 0x%1!08lx! for path: %2\n", hr, g_ucsBackupPathLocal.Buffer ); } #endif ClRtlLogPrint( LOG_NOISE, "VSS: NetShareAdd: Adding share: %1 with path: %2\n", SHARE_NAME, g_ucsBackupPathLocal.Buffer ); NetStatus = NetShareAdd( NULL, 502, (LPBYTE)(&ShareInfo), NULL ); ClRtlLogPrint( LOG_NOISE, "VSS: NetShareAdd completed: %1!u!\n", NetStatus ); if ( NetStatus != NERR_Success ) { LOGERROR( NetStatus, NetShareAdd ); if ( NetStatus == NERR_DuplicateShare ) { ClRtlLogPrint( LOG_NOISE, "VSS: Mapping NERR_DuplicateShare to success\n" ); NetStatus = NERR_Success; } else { hr = HRESULT_FROM_WIN32( NetStatus ); goto ErrorExit; } } bNetShareAdded = true; #ifdef DBG { // Check that the directory does exist. // DWORD dwFileAttributes = GetFileAttributesW( g_ucsBackupPathLocal.Buffer ); hr = GET_HR_FROM_BOOL( dwFileAttributes != -1 ); ClRtlLogPrint( LOG_NOISE, "VSS: GetFileAttributes returned 0x%1!08lx! for path: %2\n", hr, g_ucsBackupPathLocal.Buffer ); } #endif // If we are not logging to the quorum log then we don't do the backup. // if ( CsNoQuorumLogging || CsUserTurnedOffQuorumLogging ) { ClRtlLogPrint( LOG_NOISE, "VSS: Quorum logging is turned off. Not attempting backup.\n" ); // // CMCM! // We could opt to take a checkpoint and then setup alternate // path to ensure it is copied over CLUSDB on restore. // hr = S_OK; } else { ClRtlLogPrint( LOG_NOISE, "VSS: Calling FmBackupClusterDatabase with path: %1\n", ucsBackupPathNetwork.Buffer ); DWORD dwStatus = FmBackupClusterDatabase( ucsBackupPathNetwork.Buffer ); hr = HRESULT_FROM_WIN32( dwStatus ); ClRtlLogPrint( LOG_NOISE, "VSS: FmBackupClusterDatabase completed. hr: 0x%1!08lx! \n", hr ); if ( FAILED( hr ) ) { LOGERROR( hr, FmBackupClusterDatabase ); goto ErrorExit; } } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: #ifdef DBG ClRtlLogPrint( LOG_NOISE, "VSS:\n" ); ClRtlLogPrint( LOG_NOISE, "VSS: DEBUG - sleeping for 30s. This would be a good time to test killing backup in progress...\n" ); ClRtlLogPrint( LOG_NOISE, "VSS:\n" ); Sleep( 30*1000 ); #endif // Common cleanup for success or failure. // if ( bNetShareAdded ) { NetStatus = NetShareDel (NULL, SHARE_NAME, 0); ClRtlLogPrint( LOG_NOISE, "VSS: NetShareDel returned: %1!u!\n", NetStatus ); if ( NetStatus == NERR_NetNameNotFound ) NetStatus = NERR_Success; CL_ASSERT( NetStatus == NERR_Success ); } // Cleanup strings but leave the local path so we can cleanup the files later. // StringFree( &ucsComputerName ); StringFree( &ucsBackupPathNetwork ); LOGFUNCTIONEXIT( DoClusterDatabaseBackup ); return hr; } //++ // DESCRIPTION: ConstructSecurityAttributes // // Routines to construct and cleanup a security descriptor which can be // applied to limit access to an object to member of either the // Administrators or Backup Operators group. // // PARAMETERS: // psaSecurityAttributes - Pointer to a SecurityAttributes structure which // has already been setup to point to a blank // security descriptor. // bIncludeBackupOperator - Whether or not to include an ACE to grant // BackupOperator access // // PRE-CONDITIONS: // None. // // POST-CONDITIONS: // Security attributes created that are suitable for backup directory // // RETURN VALUE: // S_OK - Attributes created OK. // Error from setting up attributes or SID or ACL. //-- static HRESULT ConstructSecurityAttributes( PSECURITY_ATTRIBUTES psaSecurityAttributes, BOOL bIncludeBackupOperator ) { HRESULT hr = NOERROR; DWORD dwStatus; DWORD dwAccessMask = FILE_ALL_ACCESS; PSID psidBackupOperators = NULL; PSID psidAdministrators = NULL; PACL paclDiscretionaryAcl = NULL; SID_IDENTIFIER_AUTHORITY sidNtAuthority = SECURITY_NT_AUTHORITY; EXPLICIT_ACCESS eaExplicitAccess [2]; // // Initialise the security descriptor. // hr = GET_HR_FROM_BOOL( InitializeSecurityDescriptor( psaSecurityAttributes->lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION )); if ( FAILED( hr )) { LOGERROR( hr, InitializeSecurityDescriptor ); goto ErrorExit; } if ( bIncludeBackupOperator ) { // // Create a SID for the Backup Operators group. // hr = GET_HR_FROM_BOOL( AllocateAndInitializeSid( &sidNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_BACKUP_OPS, 0, 0, 0, 0, 0, 0, &psidBackupOperators )); if ( FAILED( hr )) { LOGERROR( hr, AllocateAndInitializeSid ); goto ErrorExit; } } // // Create a SID for the Administrators group. // hr = GET_HR_FROM_BOOL( AllocateAndInitializeSid( &sidNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators )); if ( FAILED( hr )) { LOGERROR( hr, InitializeSecurityDescriptor ); goto ErrorExit; } // // Initialize the array of EXPLICIT_ACCESS structures for an // ACEs we are setting. // // The first ACE allows the Backup Operators group full access // and the second, allowa the Administrators group full // access. // eaExplicitAccess[0].grfAccessPermissions = dwAccessMask; eaExplicitAccess[0].grfAccessMode = SET_ACCESS; eaExplicitAccess[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; eaExplicitAccess[0].Trustee.pMultipleTrustee = NULL; eaExplicitAccess[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; eaExplicitAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; eaExplicitAccess[0].Trustee.TrusteeType = TRUSTEE_IS_ALIAS; eaExplicitAccess[0].Trustee.ptstrName =( LPTSTR ) psidAdministrators; if ( bIncludeBackupOperator ) { eaExplicitAccess[1].grfAccessPermissions = dwAccessMask; eaExplicitAccess[1].grfAccessMode = SET_ACCESS; eaExplicitAccess[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; eaExplicitAccess[1].Trustee.pMultipleTrustee = NULL; eaExplicitAccess[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; eaExplicitAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; eaExplicitAccess[1].Trustee.TrusteeType = TRUSTEE_IS_ALIAS; eaExplicitAccess[1].Trustee.ptstrName =( LPTSTR ) psidBackupOperators; } // // Create a new ACL that contains the new ACEs. // dwStatus = SetEntriesInAcl( bIncludeBackupOperator ? 2 : 1, eaExplicitAccess, NULL, &paclDiscretionaryAcl ); hr = HRESULT_FROM_WIN32( dwStatus ); if ( FAILED( hr )) { LOGERROR( hr, SetEntriesInAcl ); goto ErrorExit; } // // Add the ACL to the security descriptor. // hr = GET_HR_FROM_BOOL( SetSecurityDescriptorDacl( psaSecurityAttributes->lpSecurityDescriptor, true, paclDiscretionaryAcl, false )); if ( FAILED( hr )) { LOGERROR( hr, SetSecurityDescriptorDacl ); goto ErrorExit; } paclDiscretionaryAcl = NULL; goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: // Cleanup (some may be NULL) FreeSid( psidAdministrators ); FreeSid( psidBackupOperators ); LocalFree( paclDiscretionaryAcl ); return hr; } //++ // DESCRIPTION: CleanupSecurityAttributes // // Deallocate the ACL if present with the security attributes. // // PARAMETERS: // psaSecurityAttributes - Pointer to a SecurityAttributes structure which // has already been setup to point to a blank // security descriptor. // // PRE-CONDITIONS: // psaSecurityAttributes points to security attributes as allocated by // ConstructSecurityAttributes. // // POST-CONDITIONS: // Memory freed if it was in use. // // RETURN VALUE: // None. //-- static VOID CleanupSecurityAttributes( PSECURITY_ATTRIBUTES psaSecurityAttributes ) { BOOL bDaclPresent = false; BOOL bDaclDefaulted = true; PACL paclDiscretionaryAcl = NULL; BOOL bSucceeded = GetSecurityDescriptorDacl( psaSecurityAttributes->lpSecurityDescriptor, &bDaclPresent, &paclDiscretionaryAcl, &bDaclDefaulted ); if ( bSucceeded && bDaclPresent && !bDaclDefaulted && ( paclDiscretionaryAcl != NULL )) { LocalFree( paclDiscretionaryAcl ); } } //++ // DESCRIPTION: CleanupTargetDirectory // // Deletes all the files present in the directory pointed at by the target // path member variable if not NULL. It will also remove the target // directory itself, eg for a target path of c:\dir1\dir2 all files under // dir2 will be removed and then dir2 itself will be deleted. // // PARAMETERS: // pwszTargetPath - full path to the directory to cleanup. // // PRE-CONDITIONS: // pwszTargetPath non NULL. // // POST-CONDITIONS: // Directory and contained files deleted. // // RETURN VALUE: // S_OK - Directory and contained files all cleaned up OK. // Error status from RemoveDirectoryTree or from GetFileAttributesW //-- static HRESULT CleanupTargetDirectory( LPCWSTR pwszTargetPath ) { LOGFUNCTIONENTRY( CleanupTargetDirectory ); HRESULT hr = NOERROR; DWORD dwFileAttributes = 0; BOOL bSucceeded; WCHAR wszTempBuffer [50]; UNICODE_STRING ucsTargetPath; UNICODE_STRING ucsTargetPathAlternateName; CL_ASSERT( pwszTargetPath != NULL ); StringZero( &ucsTargetPath ); StringZero( &ucsTargetPathAlternateName ); // // Create strings with extra space for appending onto later. // hr = StringCreateFromExpandedString( &ucsTargetPath, pwszTargetPath, MAX_PATH ); if ( FAILED( hr )) { LOGERROR( hr, StringCreateFromExpandedString ); goto ErrorExit; } hr = StringCreateFromString( &ucsTargetPathAlternateName, &ucsTargetPath, MAX_PATH ); if ( FAILED( hr )) { LOGERROR( hr, StringCreateFromString ); goto ErrorExit; } dwFileAttributes = GetFileAttributesW( ucsTargetPath.Buffer ); hr = GET_HR_FROM_BOOL( dwFileAttributes != -1 ); if (( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND )) || ( hr == HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ))) { hr = NOERROR; dwFileAttributes = 0; } if ( FAILED( hr )) { LOGERROR( hr, GetFileAttributesW ); goto ErrorExit; } // // If there is a file there then blow it away, or if it's // a directory, blow it and all it's contents away. This // is our directory and no one but us gets to play there. // The random rename directory could exist but it's only for cleanup anyway... // hr = RemoveDirectoryTree( &ucsTargetPath ); if ( hr == HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND )) hr = S_OK; if ( FAILED( hr )) { srand( (unsigned) GetTickCount( )); _itow( rand (), wszTempBuffer, 16 ); StringAppendString( &ucsTargetPathAlternateName, wszTempBuffer ); bSucceeded = MoveFileW( ucsTargetPath.Buffer, ucsTargetPathAlternateName.Buffer ); if (bSucceeded) { ClRtlLogPrint( LOG_UNUSUAL, "VSS: CleanupTargetDirectory failed to delete %1 with hr: 0x%2!08lx! so renamed to %3\n", ucsTargetPath.Buffer, hr, ucsTargetPathAlternateName.Buffer ); } else { ClRtlLogPrint( LOG_UNUSUAL, "VSS: CleanupTargetDirectory failed to delete %1 with hr: 0x%2!08lx!" " failed to rename to %3 with status 0x%4!08lx!\n", ucsTargetPath.Buffer, hr, ucsTargetPathAlternateName.Buffer, GET_HR_FROM_BOOL (bSucceeded) ); } } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: StringFree( &ucsTargetPathAlternateName ); StringFree( &ucsTargetPath ); LOGFUNCTIONEXIT( CleanupTargetDirectory ); return hr; } //++ // DESCRIPTION: RemoveDirectoryTree // // Deletes all the sub-directories and files in the specified directory // and then deletes the directory itself. // // PARAMETERS: // pucsDirectoryPath - pointer to the directory // // PRE-CONDITIONS: // Called only from CleanupTargetDirectory // // POST-CONDITIONS: // Directory tree deleted. // // RETURN VALUE: // S_OK - Directory tree deleted. // Error status from deleting directory or from allocating strings. //-- static HRESULT RemoveDirectoryTree( PUNICODE_STRING pucsDirectoryPath ) { LOGFUNCTIONENTRY( RemoveDirectoryTree ); HRESULT hr = NOERROR; HANDLE hFileScan = INVALID_HANDLE_VALUE; DWORD dwSubDirectoriesEntered = 0; USHORT usCurrentPathCursor = 0; PWCHAR pwchLastSlash = NULL; bool bContinue = true; UNICODE_STRING ucsCurrentPath; WIN32_FIND_DATAW FileFindData; StringZero (&ucsCurrentPath); LOGUNICODESTRING( *pucsDirectoryPath ); // Create the string with enough extra characters to allow all the // appending later on! // hr = StringCreateFromString (&ucsCurrentPath, pucsDirectoryPath, MAX_PATH); if ( FAILED ( hr )) { LOGERROR( hr, StringCreateFromString ); goto ErrorExit; } pwchLastSlash = wcsrchr (ucsCurrentPath.Buffer, DIR_SEP_CHAR); usCurrentPathCursor = (USHORT)(pwchLastSlash - ucsCurrentPath.Buffer) + 1; while ( SUCCEEDED( hr ) && bContinue ) { if ( HandleInvalid( hFileScan )) { // // No valid scan handle so start a new scan // ClRtlLogPrint( LOG_NOISE, "VSS: Starting scan: %1\n", ucsCurrentPath.Buffer ); hFileScan = FindFirstFileW( ucsCurrentPath.Buffer, &FileFindData ); hr = GET_HR_FROM_HANDLE( hFileScan ); if ( SUCCEEDED( hr )) { StringTruncate( &ucsCurrentPath, usCurrentPathCursor ); StringAppendString( &ucsCurrentPath, FileFindData.cFileName ); } } else { // // Continue with the existing scan // hr = GET_HR_FROM_BOOL( FindNextFileW( hFileScan, &FileFindData )); if (SUCCEEDED( hr )) { StringTruncate( &ucsCurrentPath, usCurrentPathCursor ); StringAppendString( &ucsCurrentPath, FileFindData.cFileName ); } else if ( hr == HRESULT_FROM_WIN32( ERROR_NO_MORE_FILES )) { FindClose( hFileScan ); hFileScan = INVALID_HANDLE_VALUE; if (dwSubDirectoriesEntered > 0) { // // This is a scan of a sub-directory that is now // complete so delete the sub-directory itself. // StringTruncate( &ucsCurrentPath, usCurrentPathCursor - 1 ); hr = GET_HR_FROM_BOOL( RemoveDirectory( ucsCurrentPath.Buffer )); dwSubDirectoriesEntered--; } if ( dwSubDirectoriesEntered == 0) { // // We are back to where we started except that the // requested directory is now gone. Time to leave. // bContinue = false; hr = NOERROR; } else { // // Move back up one directory level, reset the cursor // and prepare the path buffer to begin a new scan. // pwchLastSlash = wcsrchr( ucsCurrentPath.Buffer, DIR_SEP_CHAR ); usCurrentPathCursor =( USHORT )( pwchLastSlash - ucsCurrentPath.Buffer ) + 1; StringTruncate( &ucsCurrentPath, usCurrentPathCursor ); StringAppendString( &ucsCurrentPath, L"*" ); } // // No files to be processed on this pass so go back and try to // find another or leave the loop as we've finished the task. // continue; } } if (SUCCEEDED( hr )) { if ( FileFindData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { SetFileAttributesW( ucsCurrentPath.Buffer, FileFindData.dwFileAttributes ^ (FILE_ATTRIBUTE_READONLY) ); } if ( !NameIsDotOrDotDot( FileFindData.cFileName )) { if (( FileFindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) || !( FileFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )) { ClRtlLogPrint( LOG_NOISE, "VSS: RemoveDirectoryTree: Deleting file: %1\n", ucsCurrentPath.Buffer ); hr = GET_HR_FROM_BOOL( DeleteFileW( ucsCurrentPath.Buffer ) ); } else { ClRtlLogPrint( LOG_NOISE, "VSS: RemoveDirectoryTree: RemoveDirectory: %1\n", ucsCurrentPath.Buffer ); hr = GET_HR_FROM_BOOL( RemoveDirectory( ucsCurrentPath.Buffer )); if (hr == HRESULT_FROM_WIN32( ERROR_DIR_NOT_EMPTY )) { ClRtlLogPrint( LOG_NOISE, "VSS: RemoveDirectoryTree: dir not empty. Restarting scan.\n" ); // // The directory wasn't empty so move down one level, // close the old scan and start a new one. // hr = S_OK; FindClose( hFileScan ); hFileScan = INVALID_HANDLE_VALUE; StringAppendString( &ucsCurrentPath, DIR_SEP_STRING L"*" ); usCurrentPathCursor =( ucsCurrentPath.Length / sizeof (WCHAR) ) - 1; dwSubDirectoriesEntered++; } } } } LOGUNICODESTRING( ucsCurrentPath ); } // while if ( FAILED( hr )) { ClRtlLogPrint( LOG_NOISE, "VSS: RemoveDirectoryTree: exited while loop due to failed hr: 0x%1!08lx!\n", hr ); goto ErrorExit; } goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: if ( !HandleInvalid( hFileScan )) FindClose( hFileScan ); StringFree( &ucsCurrentPath ); return hr; } ////////////////////////////////////////////////////////////////////////// // Some useful UNICODE string stuff. ////////////////////////////////////////////////////////////////////////// // static HRESULT StringAllocate( PUNICODE_STRING pucsString, USHORT usMaximumStringLengthInBytes ) { HRESULT hr = NOERROR; LPVOID pvBuffer = NULL; SIZE_T cActualLength = 0; pvBuffer = HeapAlloc( GetProcessHeap( ), HEAP_ZERO_MEMORY, usMaximumStringLengthInBytes ); hr = GET_HR_FROM_POINTER( pvBuffer ); if ( FAILED (hr )) { LOGERROR( hr, StringAllocate ); goto ErrorExit; } pucsString->Buffer = (PWCHAR)pvBuffer; pucsString->Length = 0; pucsString->MaximumLength = usMaximumStringLengthInBytes; cActualLength = HeapSize ( GetProcessHeap( ), 0, pvBuffer ); if ( ( cActualLength <= MAXUSHORT ) && ( cActualLength > usMaximumStringLengthInBytes )) pucsString->MaximumLength = (USHORT) cActualLength; goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: ClRtlLogPrint( LOG_NOISE, "VSS: Allocated string at: 0x%1!08lx! Length: %2!u! MaxLength: %3!u!\n", pucsString->Buffer, pucsString->Length, pucsString->MaximumLength ); return hr; } static void StringFree( PUNICODE_STRING pucsString ) { HRESULT hr = NOERROR; CL_ASSERT( pucsString->Length <= pucsString->MaximumLength ); CL_ASSERT( ( pucsString->Buffer == NULL) ? pucsString->Length == 0 : pucsString->MaximumLength > 0 ); if ( pucsString->Buffer == NULL ) { ClRtlLogPrint( LOG_UNUSUAL, "VSS: StringFree. Attempt to free NULL buffer.\n" ); return; } ClRtlLogPrint( LOG_NOISE, "VSS: Freeing string at: %1\n", pucsString->Buffer ); ClRtlLogPrint( LOG_NOISE, "VSS: Freeing string at: 0x%1!08lx! Length: %2!u! MaxLength: %3!u!\n", pucsString->Buffer, pucsString->Length, pucsString->MaximumLength ); hr = GET_HR_FROM_BOOL( HeapFree( GetProcessHeap( ), 0, pucsString->Buffer )); CL_ASSERT ( SUCCEEDED( hr )); pucsString->Buffer = NULL; pucsString->Length = 0; pucsString->MaximumLength = 0; } static HRESULT StringCreateFromExpandedString( PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString, DWORD dwExtraChars) { HRESULT hr = NOERROR; DWORD dwStringLength; // // Remember, ExpandEnvironmentStringsW () includes the terminating null in the response. // dwStringLength = ExpandEnvironmentStringsW (pwszOriginalString, NULL, 0) + dwExtraChars; hr = GET_HR_FROM_BOOL( dwStringLength != 0 ); if ( FAILED ( hr )) { LOGERROR( hr, ExpandEnvironmentStringsW ); goto ErrorExit; } if ( (dwStringLength * sizeof (WCHAR)) > MAXUSHORT ) { hr = HRESULT_FROM_WIN32( ERROR_BAD_LENGTH ); LOGERROR( hr, ExpandEnvironmentStringsW ); goto ErrorExit; } hr = StringAllocate( pucsNewString, (USHORT)( dwStringLength * sizeof (WCHAR) )); if ( FAILED( hr )) { LOGERROR( hr, StringAllocate ); goto ErrorExit; } // // Note that if the expanded string has gotten bigger since we // allocated the buffer then too bad, we may not get all the // new translation. Not that we really expect these expanded // strings to have changed any time recently. // dwStringLength = ExpandEnvironmentStringsW (pwszOriginalString, pucsNewString->Buffer, pucsNewString->MaximumLength / sizeof (WCHAR)); hr = GET_HR_FROM_BOOL( dwStringLength != 0 ); if ( FAILED ( hr )) { LOGERROR( hr, ExpandEnvironmentStringsW ); goto ErrorExit; } pucsNewString->Length = (USHORT) ((dwStringLength - 1) * sizeof (WCHAR)); goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: CL_ASSERT( pucsNewString->Length <= pucsNewString->MaximumLength ); return hr; } static HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString, DWORD dwExtraChars) { HRESULT hr = NOERROR; ULONG ulStringLength = pucsOriginalString->MaximumLength + (dwExtraChars * sizeof (WCHAR)); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hr = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); goto ErrorExit; } hr = StringAllocate (pucsNewString, (USHORT) (ulStringLength + sizeof (UNICODE_NULL))); if ( FAILED( hr )) goto ErrorExit; memcpy (pucsNewString->Buffer, pucsOriginalString->Buffer, pucsOriginalString->Length); pucsNewString->Length = pucsOriginalString->Length; pucsNewString->Buffer [pucsNewString->Length / sizeof (WCHAR)] = UNICODE_NULL; goto ret; ErrorExit: CL_ASSERT( FAILED( hr )); ret: return hr; } static void StringAppendString( PUNICODE_STRING pucsTarget, PUNICODE_STRING pucsSource ) { CL_ASSERT( pucsTarget->Length <= pucsTarget->MaximumLength ); CL_ASSERT( pucsSource->Length <= pucsSource->MaximumLength ); CL_ASSERT( pucsTarget->Length + pucsSource->Length < pucsTarget->MaximumLength ); memmove( &pucsTarget->Buffer [pucsTarget->Length / sizeof (WCHAR)], pucsSource->Buffer, pucsSource->Length + sizeof( UNICODE_NULL )); pucsTarget->Length = pucsTarget->Length + pucsSource->Length; CL_ASSERT( pucsTarget->Length <= pucsTarget->MaximumLength ); CL_ASSERT( pucsSource->Length <= pucsSource->MaximumLength ); } static void StringAppendString( PUNICODE_STRING pucsTarget, PWCHAR pwszSource ) { CL_ASSERT( pucsTarget->Length <= pucsTarget->MaximumLength ); CL_ASSERT( pucsTarget->Length + ( wcslen( pwszSource ) * sizeof( WCHAR )) < pucsTarget->MaximumLength ); USHORT Length = (USHORT) wcslen( pwszSource ) * sizeof ( WCHAR ); memmove( &pucsTarget->Buffer [pucsTarget->Length / sizeof (WCHAR)], pwszSource, Length + sizeof( UNICODE_NULL )); pucsTarget->Length = pucsTarget->Length + Length; CL_ASSERT( pucsTarget->Length <= pucsTarget->MaximumLength ); } static HRESULT StringTruncate (PUNICODE_STRING pucsString, USHORT usSizeInChars) { HRESULT hr = NOERROR; USHORT usNewLength = (USHORT)(usSizeInChars * sizeof (WCHAR)); if (usNewLength > pucsString->Length) { hr = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } else { pucsString->Buffer [usSizeInChars] = UNICODE_NULL; pucsString->Length = usNewLength; } return hr; } #pragma warning( pop )