windows-nt/Source/XPSP1/NT/base/cluster/service/vss/cvsscluster.cpp

1536 lines
57 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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 )