/*++ Copyright (c) 2001 Microsoft Corporation Abstract: module common.cxx | Implementation of SnapshotWriter common code Author: Michael C. Johnson [mikejohn] 03-Feb-2000 Stefan R. Steiner [SSteiner] 27-Jul-2001 Description: Contains general code and definitions used by the Shim writer and the various other writers. Revision History: reuvenl 05/02/2002 Incorporated code from common.h/cpp into this module --*/ #include "stdafx.h" #include "vssmsg.h" #include "wrtcommon.hxx" #include #include //////////////////////////////////////////////////////////////////////// // Standard foo for file name aliasing. This code block must be after // all includes of VSS header files. // #ifdef VSS_FILE_ALIAS #undef VSS_FILE_ALIAS #endif #define VSS_FILE_ALIAS "WSHCOMNC" // Definitions for internal static functions static HRESULT ConstructSecurityAttributes ( IN OUT PSECURITY_ATTRIBUTES psaSecurityAttributes, IN BOOL bIncludeBackupOperator ); static VOID CleanupSecurityAttributes( IN PSECURITY_ATTRIBUTES psaSecurityAttributes ); static BOOL FixupFileInfo( IN LPCWSTR pwszPathName, IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwExtraAttributes); static PWCHAR const GetStringFromStateCode (DWORD dwState); static DWORD const GetControlCodeFromTargetState (const DWORD dwTargetState); static DWORD const GetNormalisedState (DWORD dwCurrentState); static HRESULT WaitForServiceToEnterState (SC_HANDLE shService, DWORD dwMaxDelayInMilliSeconds, const DWORD dwDesiredState); /* ** The first group of (overloaded) routines manipulate UNICODE_STRING ** strings. The rules which apply here are: ** ** 1) The Buffer field points to an array of characters (WCHAR) with ** the length of the buffer being specified in the MaximumLength ** field. If the Buffer field is non-NULL it must point to a valid ** buffer capable of holding at least one character. If the Buffer ** field is NULL, the MaximumLength and Length fields must both be ** zero. ** ** 2) Any valid string in the buffer is always terminated with a ** UNICODE_NULL. ** ** 3) the MaximumLength describes the length of the buffer measured in ** bytes. This value must be even. ** ** 4) The Length field describes the number of valid characters in the ** buffer measured in BYTES, excluding the termination ** character. Since the string must always have a termination ** character ('\0'), the maximum value of Length is MaximumLength - 2. ** ** ** The routines available are:- ** ** StringInitialise () ** StringTruncate () ** StringSetLength () ** StringAllocate () ** StringFree () ** StringCreateFromString () ** StringAppendString () ** StringCreateFromExpandedString () ** */ /* **++ ** ** Routine Description: ** ** ** Arguments: ** ** ** Side Effects: ** ** ** Return Value: ** ** Any HRESULT ** **-- */ HRESULT StringInitialise (PUNICODE_STRING pucsString) { pucsString->Buffer = NULL; pucsString->Length = 0; pucsString->MaximumLength = 0; return (NOERROR); } /* StringInitialise () */ HRESULT StringInitialise (PUNICODE_STRING pucsString, LPCWSTR pwszString) { return (StringInitialise (pucsString, (PWCHAR) pwszString)); } HRESULT StringInitialise (PUNICODE_STRING pucsString, PWCHAR pwszString) { HRESULT hrStatus = NOERROR; ULONG ulStringLength = wcslen (pwszString) * sizeof (WCHAR); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } else { pucsString->Buffer = pwszString; pucsString->Length = (USHORT) ulStringLength; pucsString->MaximumLength = (USHORT) (ulStringLength + sizeof (UNICODE_NULL)); } return (hrStatus); } /* StringInitialise () */ HRESULT StringTruncate (PUNICODE_STRING pucsString, USHORT usSizeInChars) { HRESULT hrStatus = NOERROR; USHORT usNewLength = (USHORT)(usSizeInChars * sizeof (WCHAR)); if (usNewLength > pucsString->Length) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } else { pucsString->Buffer [usSizeInChars] = UNICODE_NULL; pucsString->Length = usNewLength; } return (hrStatus); } /* StringTruncate () */ HRESULT StringSetLength (PUNICODE_STRING pucsString) { HRESULT hrStatus = NOERROR; ULONG ulStringLength = wcslen (pucsString->Buffer) * sizeof (WCHAR); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } else { pucsString->Length = (USHORT) ulStringLength; pucsString->MaximumLength = (USHORT) UMAX (pucsString->MaximumLength, pucsString->Length + sizeof (UNICODE_NULL)); } return (hrStatus); } /* StringSetLength () */ HRESULT StringAllocate (PUNICODE_STRING pucsString, USHORT usMaximumStringLengthInBytes) { HRESULT hrStatus = NOERROR; LPVOID pvBuffer = NULL; SIZE_T cActualLength = 0; pvBuffer = HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, usMaximumStringLengthInBytes); hrStatus = GET_STATUS_FROM_POINTER (pvBuffer); if (SUCCEEDED (hrStatus)) { pucsString->Buffer = (PWCHAR)pvBuffer; pucsString->Length = 0; pucsString->MaximumLength = usMaximumStringLengthInBytes; cActualLength = HeapSize (GetProcessHeap (), 0, pvBuffer); if ((cActualLength <= MAXUSHORT) && (cActualLength > usMaximumStringLengthInBytes)) { pucsString->MaximumLength = (USHORT) cActualLength; } } return (hrStatus); } /* StringAllocate () */ HRESULT StringFree (PUNICODE_STRING pucsString) { HRESULT hrStatus = NOERROR; BOOL bSucceeded; if (NULL != pucsString->Buffer) { bSucceeded = HeapFree (GetProcessHeap (), 0, pucsString->Buffer); hrStatus = GET_STATUS_FROM_BOOL (bSucceeded); } if (SUCCEEDED (hrStatus)) { pucsString->Buffer = NULL; pucsString->Length = 0; pucsString->MaximumLength = 0; } return (hrStatus); } /* StringFree () */ HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString) { HRESULT hrStatus = NOERROR; hrStatus = StringAllocate (pucsNewString, pucsOriginalString->MaximumLength); if (SUCCEEDED (hrStatus)) { memcpy (pucsNewString->Buffer, pucsOriginalString->Buffer, pucsOriginalString->Length); pucsNewString->Length = pucsOriginalString->Length; pucsNewString->Buffer [pucsNewString->Length / sizeof (WCHAR)] = UNICODE_NULL; } return (hrStatus); } /* StringCreateFromString () */ HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString) { HRESULT hrStatus = NOERROR; ULONG ulStringLength = wcslen (pwszOriginalString) * sizeof (WCHAR); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } if (SUCCEEDED (hrStatus)) { hrStatus = StringAllocate (pucsNewString, (USHORT) (ulStringLength + sizeof (UNICODE_NULL))); } if (SUCCEEDED (hrStatus)) { memcpy (pucsNewString->Buffer, pwszOriginalString, ulStringLength); pucsNewString->Length = (USHORT) ulStringLength; pucsNewString->Buffer [pucsNewString->Length / sizeof (WCHAR)] = UNICODE_NULL; } return (hrStatus); } /* StringCreateFromString () */ HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString, DWORD dwExtraChars) { HRESULT hrStatus = NOERROR; ULONG ulStringLength = pucsOriginalString->MaximumLength + (dwExtraChars * sizeof (WCHAR)); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } if (SUCCEEDED (hrStatus)) { hrStatus = StringAllocate (pucsNewString, (USHORT) (ulStringLength + sizeof (UNICODE_NULL))); } if (SUCCEEDED (hrStatus)) { memcpy (pucsNewString->Buffer, pucsOriginalString->Buffer, pucsOriginalString->Length); pucsNewString->Length = pucsOriginalString->Length; pucsNewString->Buffer [pucsNewString->Length / sizeof (WCHAR)] = UNICODE_NULL; } return (hrStatus); } /* StringCreateFromString () */ HRESULT StringCreateFromString (PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString, DWORD dwExtraChars) { HRESULT hrStatus = NOERROR; ULONG ulStringLength = (wcslen (pwszOriginalString) + dwExtraChars) * sizeof (WCHAR); if (ulStringLength >= (MAXUSHORT - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } if (SUCCEEDED (hrStatus)) { hrStatus = StringAllocate (pucsNewString, (USHORT) (ulStringLength + sizeof (UNICODE_NULL))); } if (SUCCEEDED (hrStatus)) { memcpy (pucsNewString->Buffer, pwszOriginalString, wcslen(pwszOriginalString)*sizeof(WCHAR)); pucsNewString->Length = (USHORT) (wcslen(pwszOriginalString)*sizeof(WCHAR)); pucsNewString->Buffer [pucsNewString->Length / sizeof (WCHAR)] = UNICODE_NULL; } return (hrStatus); } /* StringCreateFromString () */ HRESULT StringAppendString (PUNICODE_STRING pucsTarget, PUNICODE_STRING pucsSource) { HRESULT hrStatus = NOERROR; if (pucsSource->Length > (pucsTarget->MaximumLength - pucsTarget->Length - sizeof (UNICODE_NULL))) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } else { memmove (&pucsTarget->Buffer [pucsTarget->Length / sizeof (WCHAR)], pucsSource->Buffer, pucsSource->Length + sizeof (UNICODE_NULL)); pucsTarget->Length += pucsSource->Length; } /* ** There should be no reason in this code using this routine to ** have to deal with a short buffer so trap potential problems. */ BS_ASSERT (SUCCEEDED (hrStatus)); return (hrStatus); } /* StringAppendString () */ HRESULT StringAppendString (PUNICODE_STRING pucsTarget, PWCHAR pwszSource) { HRESULT hrStatus = NOERROR; UNICODE_STRING ucsSource; StringInitialise (&ucsSource, pwszSource); hrStatus = StringAppendString (pucsTarget, &ucsSource); /* ** There should be no reason in this code using this routine to ** have to deal with a short buffer so trap potential problems. */ BS_ASSERT (SUCCEEDED (hrStatus)); return (hrStatus); } /* StringAppendString () */ HRESULT StringCreateFromExpandedString (PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString) { return (StringCreateFromExpandedString (pucsNewString, pwszOriginalString, 0)); } HRESULT StringCreateFromExpandedString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString) { return (StringCreateFromExpandedString (pucsNewString, pucsOriginalString->Buffer, 0)); } HRESULT StringCreateFromExpandedString (PUNICODE_STRING pucsNewString, PUNICODE_STRING pucsOriginalString, DWORD dwExtraChars) { return (StringCreateFromExpandedString (pucsNewString, pucsOriginalString->Buffer, dwExtraChars)); } HRESULT StringCreateFromExpandedString (PUNICODE_STRING pucsNewString, LPCWSTR pwszOriginalString, DWORD dwExtraChars) { HRESULT hrStatus = NOERROR; DWORD dwStringLength; /* ** Remember, ExpandEnvironmentStringsW () includes the terminating null in the response. */ dwStringLength = ExpandEnvironmentStringsW (pwszOriginalString, NULL, 0) + dwExtraChars; hrStatus = GET_STATUS_FROM_BOOL (0 != dwStringLength); if (SUCCEEDED (hrStatus) && ((dwStringLength * sizeof (WCHAR)) > MAXUSHORT)) { hrStatus = HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); } if (SUCCEEDED (hrStatus)) { hrStatus = StringAllocate (pucsNewString, (USHORT)(dwStringLength * sizeof (WCHAR))); } if (SUCCEEDED (hrStatus)) { /* ** 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)); hrStatus = GET_STATUS_FROM_BOOL (0 != dwStringLength); if (SUCCEEDED (hrStatus)) { pucsNewString->Length = (USHORT) ((dwStringLength - 1) * sizeof (WCHAR)); } } return (hrStatus); } /* StringCreateFromExpandedString () */ /* **++ ** ** Routine Description: ** ** Closes a standard Win32 handle and set it to INVALID_HANDLE_VALUE. ** Safe to be called multiple times on the same handle or on a handle ** initialised to INVALID_HANDLE_VALUE or NULL. ** ** ** Arguments: ** ** phHandle Address of the handle to be closed ** ** ** Side Effects: ** ** ** Return Value: ** ** Any HRESULT from CloseHandle() ** **-- */ HRESULT CommonCloseHandle (PHANDLE phHandle) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"CommonCloseHandle"); HRESULT hrStatus = NOERROR; BOOL bSucceeded; if ((INVALID_HANDLE_VALUE != *phHandle) && (NULL != *phHandle)) { bSucceeded = CloseHandle (*phHandle); hrStatus = GET_STATUS_FROM_BOOL (bSucceeded); if (SUCCEEDED (hrStatus)) { *phHandle = INVALID_HANDLE_VALUE; } } return (hrStatus); } /* CommonCloseHandle () */ #define VALID_PATH( path ) ( ( (wcslen(path) >= 2) && ( path[0] == DIR_SEP_CHAR ) && ( path[1] == DIR_SEP_CHAR ) ) || \ ( (wcslen(path) >= 3) && iswalpha( path[0] ) && ( path[1] == L':' ) && ( path[2] == DIR_SEP_CHAR ) ) ) /*++ ** ** Routine Description: ** ** Creates any number of directories along a path. Only works for ** full path names with no relative elements in it. Other than that ** it works identically as CreateDirectory() works and sets the same ** error codes except it doesn't return an error if the complete ** path already exists. ** ** Arguments: ** ** pwszPathName - The path with possible directory components to create. ** ** lpSecurityAttributes - ** ** Return Value: ** ** TRUE - Sucessful ** FALSE - GetLastError() can return one of these (and others): ** ERROR_ALREADY_EXISTS - when something other than a file exists somewhere in the path. ** ERROR_BAD_PATHNAME - when \\servername alone is specified in the pathname ** ERROR_ACCESS_DENIED - when x:\ alone is specified in the pathname and x: exists ** ERROR_PATH_NOT_FOUND - when x: alone is specified in the pathname and x: doesn't exist. ** Should not get this error code for any other reason. ** ERROR_INVALID_NAME - when pathname doesn't start with either x:\ or \\ ** **-- */ BOOL VsCreateDirectories ( IN LPCWSTR pwszPathName, IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwExtraAttributes) { DWORD dwObjAttribs, dwRetPreserve; BOOL bRet; CVssFunctionTracer ft(VSSDBG_WRTCMN, L"VsCreateDirectories"); /* ** Make sure the path starts with the valid path prefixes */ if (!VALID_PATH (pwszPathName)) { SetLastError (ERROR_INVALID_NAME); return FALSE; } /* ** Save away the current last error code. */ dwRetPreserve = GetLastError (); /* ** Now test for the most common case, the directory already exists. This ** is the performance path. */ dwObjAttribs = GetFileAttributesW (pwszPathName); if ((dwObjAttribs != 0xFFFFFFFF) && (dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY)) { /* ** Don't return an error if the directory already exists. ** This is the one case where this function differs from ** CreateDirectory(). Notice that even though another type of ** file may exist with this pathname, no error is returned yet ** since I want the error to come from CreateDirectory() to ** get CreateDirectory() error behavior. ** ** Since we're successful restore the last error code. */ if (FixupFileInfo(pwszPathName, lpSecurityAttributes, dwExtraAttributes) == TRUE) { SetLastError (dwRetPreserve); return TRUE; } return FALSE; } /* ** Now try to create the directory using the full path. Even ** though we might already know it exists as something other than ** a directory, get the error from CreateDirectory() instead of ** having to try to reverse engineer all possible errors that ** CreateDirectory() can return in the above code. ** ** It is probably the second most common case that when this ** function is called that only the last component in the ** directory doesn't exist. Let's try to make it. ** ** Note that it appears if a UNC path is given with a number of ** non-existing path components the remote server automatically ** creates all of those components when CreateDirectory is called. ** Therefore, the next call is almost always successful when the ** path is a UNC. */ bRet = CreateDirectoryW (pwszPathName, lpSecurityAttributes); if (bRet) { SetFileAttributesW (pwszPathName, dwExtraAttributes); /* ** Set it back to the last error code */ SetLastError (dwRetPreserve); return TRUE; } else if (GetLastError () == ERROR_ALREADY_EXISTS) { /* ** Looks like someone created the name while we weren't ** looking. Check to see if it's a directory and return ** success if so, otherwise return the error that ** CreateDirectoryW() set. */ dwObjAttribs = GetFileAttributesW (pwszPathName); if ((dwObjAttribs != 0xFFFFFFFF) && (dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY)) { /* ** It's a directory. Declare victory. ** ** Restore the last error code */ if (FixupFileInfo(pwszPathName, lpSecurityAttributes, dwExtraAttributes) == TRUE) { SetLastError (dwRetPreserve); return TRUE; } return FALSE; } else { SetLastError (ERROR_ALREADY_EXISTS); return FALSE; } } else if (GetLastError () != ERROR_PATH_NOT_FOUND ) { return FALSE; } /* ** Allocate memory to hold the string while processing the path. ** The passed in string is a const. */ PWCHAR pwszTempPath = (PWCHAR) malloc ((wcslen (pwszPathName) + 1) * sizeof (WCHAR)); BS_ASSERT (pwszTempPath != NULL); wcscpy (pwszTempPath, pwszPathName); /* ** Appears some components in the path don't exist. Now try to ** create the components. */ PWCHAR pwsz, pwszSlash; /* ** First skip the drive letter part or the \\server\sharename ** part and get to the first slash before the first level ** directory component. */ if (pwszTempPath [1] == L':') { /* ** Path in the form of x:\..., skip first 2 chars */ pwsz = pwszTempPath + 2; } else { /* ** Path should be in form of \\servername\sharename. Can be ** \\?\d: Search to first slash after sharename ** ** First search to first char of the share name */ pwsz = pwszTempPath + 2; while ((*pwsz != L'\0') && (*pwsz != DIR_SEP_CHAR)) { ++pwsz; } /* ** Eat up all continuous slashes and get to first char of the ** share name */ while (*pwsz == DIR_SEP_CHAR) { ++pwsz; } if (*pwsz == L'\0') { /* ** This shouldn't have happened since the CreateDirectory ** call should have caught it. Oh, well, deal with it. */ SetLastError (ERROR_BAD_PATHNAME); free (pwszTempPath); return FALSE; } /* ** Now at first char of share name, let's search for first ** slash after the share name to get to the (first) shash in ** front the first level directory. */ while ((*pwsz != L'\0') && (*pwsz != DIR_SEP_CHAR)) { ++pwsz; } } /* ** Eat up all continuous slashes before the first level directory */ while (*pwsz == DIR_SEP_CHAR) { ++pwsz; } /* ** Now at first char of the first level directory, let's search ** for first slash after the directory. */ while ((*pwsz != L'\0') && (*pwsz != DIR_SEP_CHAR)) { ++pwsz; } /* ** If pwsz is pointing to a null char, that means only the first ** level directory needs to be created. Fall through to the leaf ** node create directory. */ while (*pwsz != L'\0') { pwszSlash = pwsz; // Keep pointer to the separator /* ** Eat up all continuous slashes. */ while (*pwsz == DIR_SEP_CHAR) { ++pwsz; } if (*pwsz == L'\0') { /* ** There were just slashes at the end of the path. Break ** out of loop, let the leaf node CreateDirectory create ** the last directory. */ break; } /* ** Terminate the directory path at the current level. */ *pwszSlash = L'\0'; dwObjAttribs = GetFileAttributesW (pwszTempPath); if ((dwObjAttribs == 0XFFFFFFFF) || ((dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY) == 0)) { bRet = CreateDirectoryW (pwszTempPath, lpSecurityAttributes); if (bRet) { SetFileAttributesW (pwszTempPath, dwExtraAttributes); } else { if (ERROR_ALREADY_EXISTS != GetLastError ()) { /* ** Restore the slash. */ *pwszSlash = DIR_SEP_CHAR; free (pwszTempPath); return FALSE; } else { /* ** Looks like someone created the name whilst we ** weren't looking. Check to see if it's a ** directory and continue if so, otherwise return ** the error that CreateDirectoryW() set. */ dwObjAttribs = GetFileAttributesW (pwszTempPath); if ((dwObjAttribs == 0xFFFFFFFF) || ((dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY) == 0)) { /* ** It's not what we recognise as a ** directory. Declare failure. Set the error ** code to that which CreateDirectoryW() ** returned, restore the slash, free the ** buffer and get out of here. */ SetLastError (ERROR_ALREADY_EXISTS); *pwszSlash = DIR_SEP_CHAR; free (pwszTempPath); return FALSE; } /* ** If someone deletes the directory right before FixupFileInfo is called, the function will fail, and we'll return a ** partially-constructed directory tree. This is not expected to happen (and the way we use these functions, only an ** Administrator can do this), so it's not worth the effort to retry on failure */ if (FixupFileInfo(pwszTempPath, lpSecurityAttributes, dwExtraAttributes) == FALSE) { free(pwszTempPath); return FALSE; } } } } /* ** Restore the slash. */ *pwszSlash = DIR_SEP_CHAR; /* ** Now at first char of the next level directory, let's search ** for first slash after the directory. */ while ((*pwsz != L'\0') && (*pwsz != DIR_SEP_CHAR)) { ++pwsz; } } free (pwszTempPath); pwszTempPath = NULL; /* ** Now make the last directory. */ dwObjAttribs = GetFileAttributesW (pwszPathName); if ((dwObjAttribs == 0xFFFFffff) || ((dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY) == 0)) { bRet = CreateDirectoryW (pwszPathName, lpSecurityAttributes); if (bRet) { SetFileAttributesW (pwszPathName, dwExtraAttributes); } else { // someone sneakily created the directory here dwObjAttribs = GetFileAttributesW (pwszPathName); if ((dwObjAttribs != 0xFFFFFFFF) && ((dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0)) { if (FixupFileInfo(pwszPathName, lpSecurityAttributes, dwExtraAttributes) == FALSE) return FALSE; } else return FALSE; } } else if ( (dwObjAttribs != 0xFFFFFFFF) && ((dwObjAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0)) // or here { if (FixupFileInfo(pwszPathName, lpSecurityAttributes, dwExtraAttributes) == FALSE) return FALSE; } SetLastError (dwRetPreserve); // Set back old last error code return TRUE; } /* **++ ** ** Routine Description: ** ** Changes the attributes and security information ** for a file or a directory. ** ** Arguments: ** ** pwszPathName The directory path to change ** lpSecurityAttributes The new security information for the directory ** dwExtraAttributes The new attributes for the directory ** ** ** Side Effects: ** ** None ** ** ** Return Value: ** ** TRUE -- Success FALSE -- Failure. GetLastError will contain the appropriate error code except ** for the case where we tried this on something that isn't a directory ** **-- */ BOOL FixupFileInfo( IN LPCWSTR pwszPathName, IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, IN DWORD dwExtraAttributes) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"FixupFileInfo"); // try and push down the required attributes if (SetFileAttributes(pwszPathName, dwExtraAttributes) == FALSE) { ft.hr = HRESULT_FROM_WIN32(GetLastError()); return FALSE; } // gather security information from the security descriptor PSID pOwner = NULL; PSID pGroup = NULL; PACL pDacl = NULL; PACL pSacl = NULL; BOOL ownerDefaulted, groupDefaulted, DaclDefaulted, SaclDefaulted, DaclPresent, SaclPresent; SECURITY_DESCRIPTOR_CONTROL control; DWORD revision = 0; if ((GetSecurityDescriptorOwner(lpSecurityAttributes->lpSecurityDescriptor, &pOwner, &ownerDefaulted) == FALSE) || (GetSecurityDescriptorGroup(lpSecurityAttributes->lpSecurityDescriptor, &pGroup, &groupDefaulted) == FALSE) || (GetSecurityDescriptorDacl(lpSecurityAttributes->lpSecurityDescriptor, &DaclPresent, &pDacl, &DaclDefaulted) == FALSE) || (GetSecurityDescriptorSacl(lpSecurityAttributes->lpSecurityDescriptor, &SaclPresent, &pSacl, &SaclDefaulted) == FALSE) || (GetSecurityDescriptorControl(lpSecurityAttributes->lpSecurityDescriptor, &control, &revision) == FALSE)) { ft.hr = HRESULT_FROM_WIN32(GetLastError()); return FALSE; } SECURITY_INFORMATION securityInformation; securityInformation = ((pDacl != NULL) ? DACL_SECURITY_INFORMATION : 0) | ((pDacl != NULL && ((control & SE_DACL_PROTECTED) != 0)) ? PROTECTED_DACL_SECURITY_INFORMATION : 0) | ((pSacl != NULL) ? SACL_SECURITY_INFORMATION : 0) | ((pSacl != NULL && ((control & SE_SACL_PROTECTED) != 0)) ? PROTECTED_SACL_SECURITY_INFORMATION : 0) | ((pOwner != NULL) ? OWNER_SECURITY_INFORMATION : 0) | ((pGroup != NULL) ? GROUP_SECURITY_INFORMATION : 0); // push down the new security information to the file ft.hr = SetNamedSecurityInfo(const_cast(pwszPathName), SE_FILE_OBJECT, securityInformation, pOwner, pGroup, pDacl, pSacl); return ft.HrSucceeded(); } /* ** The next set of rountes are used to change the state of SCM ** controlled services, typically between RUNNING and either PAUSED or ** STOPPED. ** ** The initial collection are for manipulating the states, control ** codes and getting the string equivalents to be used for tracing ** purposes. ** ** The major routines is VsServiceChangeState(). This is called ** specifying the reuiqred state for the service and after some ** validation, it makes the appropriate request of the SCM and calls ** WaitForServiceToEnterState() to wait until the services reaches the ** desired state, or it times out. */ static PWCHAR const GetStringFromStateCode (DWORD dwState) { PWCHAR pwszReturnedString = NULL; switch (dwState) { case 0: pwszReturnedString = L"UnSpecified"; break; case SERVICE_STOPPED: pwszReturnedString = L"Stopped"; break; case SERVICE_START_PENDING: pwszReturnedString = L"StartPending"; break; case SERVICE_STOP_PENDING: pwszReturnedString = L"StopPending"; break; case SERVICE_RUNNING: pwszReturnedString = L"Running"; break; case SERVICE_CONTINUE_PENDING: pwszReturnedString = L"ContinuePending"; break; case SERVICE_PAUSE_PENDING: pwszReturnedString = L"PausePending"; break; case SERVICE_PAUSED: pwszReturnedString = L"Paused"; break; default: pwszReturnedString = L"UNKKNOWN STATE"; break; } return (pwszReturnedString); } /* GetStringFromStateCode () */ static DWORD const GetControlCodeFromTargetState (const DWORD dwTargetState) { DWORD dwServiceControlCode; switch (dwTargetState) { case SERVICE_STOPPED: dwServiceControlCode = SERVICE_CONTROL_STOP; break; case SERVICE_PAUSED: dwServiceControlCode = SERVICE_CONTROL_PAUSE; break; case SERVICE_RUNNING: dwServiceControlCode = SERVICE_CONTROL_CONTINUE; break; default: dwServiceControlCode = 0; break; } return (dwServiceControlCode); } /* GetControlCodeFromTargetState () */ static DWORD const GetNormalisedState (DWORD dwCurrentState) { DWORD dwNormalisedState; switch (dwCurrentState) { case SERVICE_STOPPED: case SERVICE_STOP_PENDING: dwNormalisedState = SERVICE_STOPPED; break; case SERVICE_START_PENDING: case SERVICE_CONTINUE_PENDING: case SERVICE_RUNNING: dwNormalisedState = SERVICE_RUNNING; break; case SERVICE_PAUSED: case SERVICE_PAUSE_PENDING: dwNormalisedState = SERVICE_PAUSED; break; default: dwNormalisedState = 0; break; } return (dwNormalisedState); } /* GetNormalisedState () */ /* **++ ** ** Routine Description: ** ** Wait for the specified service to enter the specified ** state. The routine polls the serivce for it's current state ** every dwServiceStatePollingIntervalInMilliSeconds milliseconds ** to see if the service has reached the desired state. If the ** repeated delay eventually reaches the timeout period the ** routine stops polling and returns a failure status. ** ** NOTE: since this routine just sleeps between service state ** interrogations, it effectively stalls from the point of view ** of the caller. ** ** ** Arguments: ** ** shService handle to the service being manipulated ** dwMaxDelayInMilliSeconds timeout period ** dwDesiredState state to move the service into ** ** ** Side Effects: ** ** ** Return Value: ** ** HRESULT for ERROR_TIMOUT if the service did not reach the required state in the required time ** **-- */ static HRESULT WaitForServiceToEnterState (SC_HANDLE shService, DWORD dwMaxDelayInMilliSeconds, const DWORD dwDesiredState) { CVssFunctionTracer ft (VSSDBG_WRTCMN, L"WaitForServiceToEnterState"); DWORD dwRemainingDelay = dwMaxDelayInMilliSeconds; DWORD dwInitialState; const DWORD dwServiceStatePollingIntervalInMilliSeconds = 100; BOOL bSucceeded; SERVICE_STATUS sSStat; try { bSucceeded = QueryServiceStatus (shService, &sSStat); ft.hr = GET_STATUS_FROM_BOOL (bSucceeded); dwInitialState = sSStat.dwCurrentState; ft.Trace (VSSDBG_WRTCMN, L"Initial QueryServiceStatus returned: 0x%08X with current state '%s' and desired state '%s'", ft.hr, GetStringFromStateCode (dwInitialState), GetStringFromStateCode (dwDesiredState)); while ((dwDesiredState != sSStat.dwCurrentState) && (dwRemainingDelay > 0)) { Sleep (UMIN (dwServiceStatePollingIntervalInMilliSeconds, dwRemainingDelay)); dwRemainingDelay -= (UMIN (dwServiceStatePollingIntervalInMilliSeconds, dwRemainingDelay)); if (0 == dwRemainingDelay) { ft.Throw (VSSDBG_WRTCMN, HRESULT_FROM_WIN32 (ERROR_TIMEOUT), L"Exceeded maximum delay (%dms)", dwMaxDelayInMilliSeconds); } bSucceeded = QueryServiceStatus (shService, &sSStat); ft.ThrowIf (!bSucceeded, VSSDBG_WRTCMN, GET_STATUS_FROM_BOOL (bSucceeded), L"QueryServiceStatus shows '%s' as current state", GetStringFromStateCode (sSStat.dwCurrentState)); } ft.Trace (VSSDBG_WRTCMN, L"Service state change from '%s' to '%s' took %u milliseconds", GetStringFromStateCode (dwInitialState), GetStringFromStateCode (sSStat.dwCurrentState), dwMaxDelayInMilliSeconds - dwRemainingDelay); } VSS_STANDARD_CATCH (ft); return (ft.hr); } /* WaitForServiceToEnterState () */ /* **++ ** ** Routine Description: ** ** Changes the state of a service if appropriate. ** ** ** Arguments: ** ** pwszServiceName The real service name, i.e. cisvc ** dwRequestedState the state code for the state we wish to enter ** pdwReturnedOldState pointer to location to receive current service state. ** Can be NULL of current state not required ** pbReturnedStateChanged pointer to location to receive flag indicating if ** service changed state. Pointer can be NULL if flag ** value not required. ** ** ** Return Value: ** ** Any HRESULT resulting from faiure communication with the ** SCM (Service Control Manager). ** **-- */ HRESULT VsServiceChangeState (LPCWSTR pwszServiceName, DWORD dwRequestedState, PDWORD pdwReturnedOldState, PBOOL pbReturnedStateChanged) { CVssFunctionTracer ft (VSSDBG_WRTCMN, L"VsServiceChangeState"); SC_HANDLE shSCManager = NULL; SC_HANDLE shSCService = NULL; DWORD dwOldState = 0; BOOL bSucceeded; SERVICE_STATUS sSStat; const DWORD dwNormalisedRequestedState = GetNormalisedState (dwRequestedState); ft.Trace (VSSDBG_WRTCMN, L"Service '%s' requested to change to state '%s' (normalised to '%s')", pwszServiceName, GetStringFromStateCode (dwRequestedState), GetStringFromStateCode (dwNormalisedRequestedState)); RETURN_VALUE_IF_REQUIRED (pbReturnedStateChanged, FALSE); try { /* ** Connect to the local service control manager */ shSCManager = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS); ft.hr = GET_STATUS_FROM_HANDLE (shSCManager); ft.ThrowIf (ft.HrFailed (), VSSDBG_WRTCMN, ft.hr, L"Called OpenSCManager()"); /* ** Get a handle to the service */ shSCService = OpenService (shSCManager, pwszServiceName, SERVICE_ALL_ACCESS); ft.hr = GET_STATUS_FROM_HANDLE (shSCService); /* ** If it's an invalid name or the service doesn't exist then ** fail gracefully. For all other failures do the normal ** thing. Oh yes, if on the off-chance we should happen to ** succeed, carry on. */ if ((HRESULT_FROM_WIN32 (ERROR_INVALID_NAME) == ft.hr) || (HRESULT_FROM_WIN32 (ERROR_SERVICE_DOES_NOT_EXIST) == ft.hr)) { ft.Trace (VSSDBG_WRTCMN, L"'%s' service not found", pwszServiceName); } else if (ft.HrFailed ()) { /* ** See if the service doesn't exist */ ft.Throw (VSSDBG_WRTCMN, E_FAIL, L"ERROR - OpenService() returned: %d", ft.hr); } else { /* ** Now query the service to see what state it is in at the moment. */ bSucceeded = QueryServiceStatus (shSCService, &sSStat); ft.ThrowIf (!bSucceeded, VSSDBG_WRTCMN, GET_STATUS_FROM_BOOL (bSucceeded), L"QueryServiceStatus shows '%s' as current state", GetStringFromStateCode (sSStat.dwCurrentState)); dwOldState = sSStat.dwCurrentState; /* ** Now we decide what to do. ** If we are already in the requested state, we do nothing. ** If we are stopped and are requested to pause, we do nothing ** otherwise we make the attempt to change state. */ if (dwNormalisedRequestedState == dwOldState) { /* ** We are already in the requested state, so do ** nothing. We should even tell folk of that. We're ** proud to be doing nothing. */ ft.Trace (VSSDBG_WRTCMN, L"'%s' service is already in requested state: doing nothing", pwszServiceName); RETURN_VALUE_IF_REQUIRED (pdwReturnedOldState, dwOldState); } else if ((SERVICE_STOPPED == sSStat.dwCurrentState) && (SERVICE_PAUSED == dwNormalisedRequestedState)) { /* ** Do nothing. Just log the fact and move on. */ ft.Trace (VSSDBG_WRTCMN, L"Asked to PAUSE the '%s' service which is already STOPPED", pwszServiceName); RETURN_VALUE_IF_REQUIRED (pdwReturnedOldState, dwOldState); } else { /* ** We want a state which is different from the one ** we're in at the moment. Generally this just means ** calling ControlService() asking for the new state ** except if the service is currently stopped. If ** that's so, then we call StartService() */ if (SERVICE_STOPPED == sSStat.dwCurrentState) { /* ** Call StartService to get the ball rolling */ bSucceeded = StartService (shSCService, 0, NULL); } else { bSucceeded = ControlService (shSCService, GetControlCodeFromTargetState (dwNormalisedRequestedState), &sSStat); } ft.ThrowIf (!bSucceeded, VSSDBG_WRTCMN, GET_STATUS_FROM_BOOL (bSucceeded), (SERVICE_STOPPED == sSStat.dwCurrentState) ? L"StartService attempting '%s' to '%s', now at '%s'" : L"ControlService attempting '%s' to '%s', now at '%s'", GetStringFromStateCode (dwOldState), GetStringFromStateCode (dwNormalisedRequestedState), GetStringFromStateCode (sSStat.dwCurrentState)); RETURN_VALUE_IF_REQUIRED (pdwReturnedOldState, dwOldState); RETURN_VALUE_IF_REQUIRED (pbReturnedStateChanged, TRUE); ft.hr = WaitForServiceToEnterState (shSCService, 15000, dwNormalisedRequestedState); if (ft.HrFailed ()) { ft.Throw (VSSDBG_WRTCMN, ft.hr, L"WaitForServiceToEnterState() failed with 0x%08X", ft.hr); } } } } VSS_STANDARD_CATCH (ft); /* ** Now close the service and service control manager handles */ if (NULL != shSCService) CloseServiceHandle (shSCService); if (NULL != shSCManager) CloseServiceHandle (shSCManager); return (ft.hr); } /* VsServiceChangeState () */ /* **++ ** ** Routine Description: ** ** Deletes all the sub-directories and files in the specified ** directory and then deletes the directory itself. ** ** If the directory does not exist then simply return S_OK. ** ** Arguments: ** ** pucsDirectoryPath The directory path to clear out ** ** ** Side Effects: ** ** None ** ** ** Return Value: ** ** Out of memory or any HRESULT from ** ** RemoveDirectory() ** DeleteFile() ** FindFirstFile() ** **-- */ HRESULT RemoveDirectoryTree ( IN LPCWSTR pwszDirectoryPath ) { HANDLE hFileScan = INVALID_HANDLE_VALUE; DWORD dwSubDirectoriesEntered = 0; INT iCurrentPathCursor = 0; BOOL bSucceeded; WIN32_FIND_DATAW FileFindData; CBsString cwsCurrentPath; CVssFunctionTracer ft(VSSDBG_WRTCMN, L"RemoveDirectoryTree"); try { ft.Trace( VSSDBG_WRTCMN, L"Recursive delete of the '%s' directory", pwszDirectoryPath ); cwsCurrentPath = pwszDirectoryPath; iCurrentPathCursor = cwsCurrentPath.ReverseFind( DIR_SEP_CHAR ) + 1; while ( 1 ) { if ( HandleInvalid ( hFileScan ) ) { /* ** No valid scan handle so start a new scan */ ft.Trace( VSSDBG_WRTCMN, L"FindFirstFileW( %s, ... )", cwsCurrentPath.c_str() ); hFileScan = FindFirstFileW( cwsCurrentPath, &FileFindData); if ( ( hFileScan == INVALID_HANDLE_VALUE ) && ( ( ::GetLastError() == ERROR_FILE_NOT_FOUND ) || ( ::GetLastError() == ERROR_PATH_NOT_FOUND ) ) ) { // Directory is empty or does not exists ft.hr = S_OK; break; } ft.hr = GET_STATUS_FROM_HANDLE( hFileScan ); ft.CheckForError(VSSDBG_WRTCMN, L"RemoveDirectoryTree - FindFirstFileW"); cwsCurrentPath.GetBufferSetLength( iCurrentPathCursor ); cwsCurrentPath += FileFindData.cFileName; } else { /* ** Continue with the existing scan */ bSucceeded = FindNextFileW( hFileScan, &FileFindData ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); if ( HRESULT_FROM_WIN32( ERROR_NO_MORE_FILES ) == ft.hr ) { 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. */ cwsCurrentPath.GetBufferSetLength( iCurrentPathCursor - 1 ); ft.Trace( VSSDBG_WRTCMN, L"RemoveDirectoryW( %s, ... )", cwsCurrentPath.c_str() ); bSucceeded = RemoveDirectoryW( cwsCurrentPath ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); ft.CheckForError(VSSDBG_WRTCMN, L"RemoveDirectoryTree - RemoveDirectoryW"); dwSubDirectoriesEntered--; } if ( 0 == dwSubDirectoriesEntered ) { /* ** We are back to where we started except that the ** requested directory is now gone. Time to leave. */ ft.hr = S_OK; break; } else { /* ** Move back up one directory level, reset the cursor ** and prepare the path buffer to begin a new scan. */ iCurrentPathCursor = cwsCurrentPath.ReverseFind( DIR_SEP_CHAR ) + 1; cwsCurrentPath.GetBufferSetLength( iCurrentPathCursor); cwsCurrentPath += "\\*"; } /* ** 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; } ft.CheckForError( VSSDBG_WRTCMN, L"RemoveDirectoryTree - FindNextFileW" ); cwsCurrentPath.GetBufferSetLength( iCurrentPathCursor ); cwsCurrentPath += FileFindData.cFileName; } if (FileFindData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { SetFileAttributesW (cwsCurrentPath, FileFindData.dwFileAttributes ^ (FILE_ATTRIBUTE_READONLY)); } if ( !NameIsDotOrDotDot( FileFindData.cFileName ) ) { if ( ( FileFindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) || !( FileFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) { ft.Trace( VSSDBG_WRTCMN, L"DeleteFileW( %s, ... )", cwsCurrentPath.c_str() ); bSucceeded = DeleteFileW( cwsCurrentPath ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); ft.CheckForError( VSSDBG_WRTCMN, L"RemoveDirectoryTree - DeleteFileW" ); } else { ft.Trace( VSSDBG_WRTCMN, L"RemoveDirectoryW( %s, ... )", cwsCurrentPath.c_str() ); bSucceeded = RemoveDirectoryW( cwsCurrentPath ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); if ( HRESULT_FROM_WIN32( ERROR_DIR_NOT_EMPTY ) == ft.hr ) { ft.Trace( VSSDBG_WRTCMN, L"Dir not empty after calling RemoveDirectoryW( %s, ... )", cwsCurrentPath.c_str() ); /* ** The directory wasn't empty so move down one level, ** close the old scan and start a new one. */ FindClose (hFileScan); hFileScan = INVALID_HANDLE_VALUE; cwsCurrentPath += DIR_SEP_STRING L"*"; iCurrentPathCursor = cwsCurrentPath.GetLength() - 1; dwSubDirectoriesEntered++; } else { ft.CheckForError( VSSDBG_WRTCMN, L"RemoveDirectoryTree - RemoveDirectoryW" ); } } } } } VSS_STANDARD_CATCH(ft) if (!HandleInvalid (hFileScan)) FindClose (hFileScan); return( ft.hr ); } /* RemoveDirectoryTree () */ /* **++ ** ** Routine Description: ** ** 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. ** ** ** Arguments: ** ** psaSecurityAttributes Pointer to a SecurityAttributes ** structure which has already been ** setup to point to a blank ** security descriptor ** ** eSaType What we are building the SA for ** ** bIncludeBackupOperator Whether or not to include an ACE to ** grant BackupOperator access ** ** ** Return Value: ** ** Any HRESULT from ** InitializeSecurityDescriptor() ** AllocateAndInitializeSid() ** SetEntriesInAcl() ** SetSecurityDescriptorDacl() ** **-- */ static HRESULT ConstructSecurityAttributes ( IN OUT PSECURITY_ATTRIBUTES psaSecurityAttributes, IN BOOL bIncludeBackupOperator ) { DWORD dwStatus; DWORD dwAccessMask = 0; BOOL bSucceeded; PSID psidBackupOperators = NULL; PSID psidAdministrators = NULL; PACL paclDiscretionaryAcl = NULL; SID_IDENTIFIER_AUTHORITY sidNtAuthority = SECURITY_NT_AUTHORITY; EXPLICIT_ACCESS eaExplicitAccess [2]; CVssFunctionTracer ft(VSSDBG_WRTCMN, L"ConstructSecurityAttributes"); try { dwAccessMask = FILE_ALL_ACCESS; /* ** Initialise the security descriptor. */ bSucceeded = ::InitializeSecurityDescriptor ( psaSecurityAttributes->lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); ft.CheckForError( VSSDBG_WRTCMN, L"InitializeSecurityDescriptor" ); if ( bIncludeBackupOperator ) { /* ** Create a SID for the Backup Operators group. */ bSucceeded = ::AllocateAndInitializeSid ( &sidNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_BACKUP_OPS, 0, 0, 0, 0, 0, 0, &psidBackupOperators ); ft.hr = GET_STATUS_FROM_BOOL ( bSucceeded ); ft.CheckForError( VSSDBG_WRTCMN, L"AllocateAndInitializeSid" ); } /* ** Create a SID for the Administrators group. */ bSucceeded = ::AllocateAndInitializeSid ( &sidNtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdministrators); ft.hr = GET_STATUS_FROM_BOOL (bSucceeded); ft.CheckForError( VSSDBG_WRTCMN, L"AllocateAndInitializeSid" ); /* ** 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 ); ft.hr = HRESULT_FROM_WIN32 (dwStatus); ft.CheckForError( VSSDBG_WRTCMN, L"SetEntriesInAcl" ); /* ** Add the ACL to the security descriptor. */ bSucceeded = ::SetSecurityDescriptorDacl ( psaSecurityAttributes->lpSecurityDescriptor, TRUE, paclDiscretionaryAcl, FALSE ); ft.hr = GET_STATUS_FROM_BOOL (bSucceeded); ft.CheckForError( VSSDBG_WRTCMN, L"SetSecurityDescriptorDacl" ); paclDiscretionaryAcl = NULL; bSucceeded = ::SetSecurityDescriptorControl ( psaSecurityAttributes->lpSecurityDescriptor, SE_DACL_PROTECTED, SE_DACL_PROTECTED); ft.hr = GET_STATUS_FROM_BOOL (bSucceeded); ft.CheckForError( VSSDBG_WRTCMN, L"SetSecurityDescriptorControl" ); } VSS_STANDARD_CATCH( ft ); /* ** Clean up any left over junk. */ if ( NULL != psidAdministrators ) FreeSid ( psidAdministrators ); if ( NULL != psidBackupOperators ) FreeSid ( psidBackupOperators ); if ( NULL != paclDiscretionaryAcl ) LocalFree (paclDiscretionaryAcl ); return ( ft.hr ); } /* ConstructSecurityAttributes () */ static VOID CleanupSecurityAttributes( IN PSECURITY_ATTRIBUTES psaSecurityAttributes ) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"CleanupSecurityAttributes"); BOOL bSucceeded; BOOL bDaclPresent = FALSE; BOOL bDaclDefaulted = TRUE; PACL paclDiscretionaryAcl = NULL; try { bSucceeded = ::GetSecurityDescriptorDacl( psaSecurityAttributes->lpSecurityDescriptor, &bDaclPresent, &paclDiscretionaryAcl, &bDaclDefaulted ); if ( bSucceeded && bDaclPresent && !bDaclDefaulted && ( NULL != paclDiscretionaryAcl ) ) { LocalFree( paclDiscretionaryAcl ); } } VSS_STANDARD_CATCH( ft ); } /* CleanupSecurityAttributes () */ /* **++ ** ** Routine Description: ** ** Creates a new target directory specified by the target path ** member variable if not NULL. It will create any necessary ** parent directories too. ** ** NOTE: already exists type errors are ignored. ** ** ** Arguments: ** ** pwszTargetPath directory to create and apply security attributes ** ** ** Return Value: ** ** Any HRESULT resulting from memory allocation or directory creation attempts. **-- */ HRESULT CreateTargetPath( IN LPCWSTR pwszTargetPath ) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"CreateTargetPath"); ACL DiscretionaryAcl; SECURITY_ATTRIBUTES saSecurityAttributes; SECURITY_DESCRIPTOR sdSecurityDescriptor; BOOL bSucceeded; DWORD dwFileAttributes = 0; const DWORD dwExtraAttributes = FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; try { if ( NULL != pwszTargetPath ) { /* ** 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; ft.hr = ::ConstructSecurityAttributes( &saSecurityAttributes, FALSE ); ft.CheckForError( VSSDBG_WRTCMN, L"ConstructSecurityAttributes" ); CBsString expandedTarget; ft.hr = GET_STATUS_FROM_BOOL(expandedTarget.ExpandEnvironmentStrings(pwszTargetPath)); ft.CheckForError(VSSDBG_WRTCMN, L"ExpandEnvironmentStrings"); bSucceeded = ::VsCreateDirectories ( expandedTarget, &saSecurityAttributes, dwExtraAttributes ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); if ( ft.hr == HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) ) { ft.hr = S_OK; } ::CleanupSecurityAttributes( &saSecurityAttributes ); ft.CheckForError( VSSDBG_WRTCMN, L"VsCreateDirectories" ); } } VSS_STANDARD_CATCH( ft ); return( ft.hr ); } /* CreateTargetPath () */ /* **++ ** ** Routine Description: ** ** 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. ** ** ** Arguments: ** ** pwszTargetPath ** ** ** Return Value: ** ** Any HRESULT resulting from memory allocation or file and ** directory deletion attempts. **-- */ HRESULT CleanupTargetPath (LPCWSTR pwszTargetPath) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"CleanupTargetPath"); HRESULT hrStatus = NOERROR; DWORD dwFileAttributes = 0; BOOL bSucceeded; WCHAR wszTempBuffer [50]; UNICODE_STRING ucsTargetPath; UNICODE_STRING ucsTargetPathAlternateName; StringInitialise (&ucsTargetPath); StringInitialise (&ucsTargetPathAlternateName); if (NULL != pwszTargetPath) { hrStatus = StringCreateFromExpandedString (&ucsTargetPath, pwszTargetPath, MAX_PATH); if (SUCCEEDED (hrStatus)) { hrStatus = StringCreateFromString (&ucsTargetPathAlternateName, &ucsTargetPath, MAX_PATH); } if (SUCCEEDED (hrStatus)) { dwFileAttributes = GetFileAttributesW (ucsTargetPath.Buffer); hrStatus = GET_STATUS_FROM_BOOL ( -1 != dwFileAttributes); if ((HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND) == hrStatus) || (HRESULT_FROM_WIN32 (ERROR_PATH_NOT_FOUND) == hrStatus)) { hrStatus = NOERROR; dwFileAttributes = 0; } else if (SUCCEEDED (hrStatus)) { /* ** 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. */ hrStatus = RemoveDirectoryTree (CBsString(&ucsTargetPath)); if (FAILED (hrStatus)) { srand ((unsigned) time (NULL)); _itow (rand (), wszTempBuffer, 16); StringAppendString (&ucsTargetPathAlternateName, wszTempBuffer); bSucceeded = MoveFileW (ucsTargetPath.Buffer, ucsTargetPathAlternateName.Buffer); if (bSucceeded) { BsDebugTraceAlways (0, DEBUG_TRACE_VSSAPI, (L"VSSAPI::CleanupTargetPath: " L"FAILED to delete %s with status 0x%08X so renamed to %s", ucsTargetPath.Buffer, hrStatus, ucsTargetPathAlternateName.Buffer)); } else { BsDebugTraceAlways (0, DEBUG_TRACE_VSSAPI, (L"VSSAPI::CleanupTargetPath: " L"FAILED to delete %s with status 0x%08X and " L"FAILED to rename to %s with status 0x%08X", ucsTargetPath.Buffer, hrStatus, ucsTargetPathAlternateName.Buffer, GET_STATUS_FROM_BOOL (bSucceeded))); } } } } } StringFree (&ucsTargetPathAlternateName); StringFree (&ucsTargetPath); return (hrStatus); } /* CleanupTargetPath () */ /* **++ ** ** Routine Description: ** ** Moves the contents of the source directory to the target directory. ** ** Arguments: ** ** pwszSourceDirectoryPath Source directory for the files to be moved ** pwszTargetDirectoryPath Target directory for the files to be moved ** ** ** Side Effects: ** ** An intermediate error can leave directory in a partial moved ** state where some of the files have been moved but not all. ** ** ** Return Value: ** ** Any HRESULT from FindFirstFile() etc or from MoveFileEx() ** ** Remarks - copied from the wrtrshim\src\common.cpp file and ** switched to using CBsStrings. **-- */ HRESULT MoveFilesInDirectory ( IN CBsString cwsSourceDirectoryPath, IN CBsString cwsTargetDirectoryPath ) { CVssFunctionTracer ft( VSSDBG_WRTCMN, L"MoveFilesInDirectory" ); HANDLE hFileScan = INVALID_HANDLE_VALUE; try { WIN32_FIND_DATA sFileInformation; if ( cwsSourceDirectoryPath.Tail() != DIR_SEP_CHAR ) cwsSourceDirectoryPath += DIR_SEP_CHAR; if ( cwsTargetDirectoryPath.Tail() != DIR_SEP_CHAR ) cwsTargetDirectoryPath += DIR_SEP_CHAR; hFileScan = ::FindFirstFileW ( cwsSourceDirectoryPath + L'*', &sFileInformation ); ft.hr = GET_STATUS_FROM_BOOL( INVALID_HANDLE_VALUE != hFileScan ); ft.CheckForError( VSSDBG_WRTCMN, L"FindFirstFileW" ); BOOL bMoreFiles; do { if ( !NameIsDotOrDotDot( sFileInformation.cFileName ) ) { BOOL bSucceeded; CBsString cwsSourceFile = cwsSourceDirectoryPath + sFileInformation.cFileName; CBsString cwsTargetFile = cwsTargetDirectoryPath + sFileInformation.cFileName; ft.Trace( VSSDBG_WRTCMN, L"Moving '%s' to '%s'", cwsSourceFile.c_str(), cwsTargetFile.c_str() ); bSucceeded = ::MoveFileExW( cwsSourceDirectoryPath + sFileInformation.cFileName, cwsTargetDirectoryPath + sFileInformation.cFileName, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); ft.hr = GET_STATUS_FROM_BOOL( bSucceeded ); ft.CheckForError( VSSDBG_WRTCMN, L"MoveFileExW" ); } bMoreFiles = ::FindNextFileW( hFileScan, &sFileInformation ); } while ( bMoreFiles ); /* ** If the last move operation was successful determine the ** reason for terminating the scan. No need to report an ** error if all that happened was that we have finished ** what we were asked to do. */ ft.hr = GET_STATUS_FROM_FILESCAN( bMoreFiles ); ft.CheckForError( VSSDBG_WRTCMN, L"FindNextFileW" ); } VSS_STANDARD_CATCH( ft ); if ( hFileScan != INVALID_HANDLE_VALUE ) ::FindClose( hFileScan ); return( ft.hr ); } /* **++ ** ** Routine Description: ** ** Checks a path against an array of pointers to volume names to ** see if path is affected by any of the volumes in the array ** ** ** Arguments: ** ** pwszPath Path to be checked ** ulVolumeCount Number of volumes in volume array ** ppwszVolumeNamesArray address of the array ** pbReturnedFoundInVolumeArray pointer to a location to store the ** result of the check ** ** ** Side Effects: ** ** None ** ** ** Return Value: ** ** Any HRESULT from:- ** GetVolumePathNameW() ** GetVolumeNameForVolumeMountPoint() ** **-- */ HRESULT IsPathInVolumeArray (IN LPCWSTR pwszPath, IN const ULONG ulVolumeCount, IN LPCWSTR *ppwszVolumeNamesArray, OUT PBOOL pbReturnedFoundInVolumeArray) { CVssFunctionTracer ft(VSSDBG_WRTCMN, L"IsPathInVolumeArray"); HRESULT hrStatus = NOERROR; BOOL bFound = FALSE; BOOL bContinue = TRUE; ULONG ulIndex; WCHAR wszVolumeName [MAX_VOLUMENAME_LENGTH]; UNICODE_STRING ucsVolumeMountPoint; StringInitialise (&ucsVolumeMountPoint); if ((0 == ulVolumeCount) || (NULL == pbReturnedFoundInVolumeArray)) { BS_ASSERT (false); bContinue = FALSE; } if (bContinue) { /* ** We need a string that is at least as big as the supplied ** path. */ hrStatus = StringAllocate (&ucsVolumeMountPoint, wcslen (pwszPath) * sizeof (WCHAR)); bContinue = SUCCEEDED (hrStatus); } if (bContinue) { /* ** Get the volume mount point */ bContinue = GetVolumePathNameW (pwszPath, ucsVolumeMountPoint.Buffer, ucsVolumeMountPoint.MaximumLength / sizeof (WCHAR)); hrStatus = GET_STATUS_FROM_BOOL (bContinue); } if (bContinue) { /* ** Get the volume name */ bContinue = GetVolumeNameForVolumeMountPointW (ucsVolumeMountPoint.Buffer, wszVolumeName, SIZEOF_ARRAY (wszVolumeName)); hrStatus = GET_STATUS_FROM_BOOL (bContinue); } if (bContinue) { /* ** Search to see if that volume is within snapshotted volumes */ for (ulIndex = 0; !bFound && (ulIndex < ulVolumeCount); ulIndex++) { BS_ASSERT (NULL != ppwszVolumeNamesArray [ulIndex]); if (0 == wcscmp (wszVolumeName, ppwszVolumeNamesArray [ulIndex])) { bFound = TRUE; } } } RETURN_VALUE_IF_REQUIRED (pbReturnedFoundInVolumeArray, bFound); StringFree (&ucsVolumeMountPoint); return (hrStatus); } /* IsPathInVolumeArray () */ /* **++ ** ** Routine Description: ** ** Routine to classify the many assorted internal writer errors ** into one of the narrow set of responses a writer is permitted ** to send back to the requestor. ** ** ** Arguments: ** ** hrStatus HRESULT to be classified ** ** ** Return Value: ** ** One of the following list depending upon the supplied status. ** ** VSS_E_WRITERERROR_OUTOFRESOURCES ** VSS_E_WRITERERROR_RETRYABLE ** VSS_E_WRITERERROR_NONRETRYABLE ** VSS_E_WRITERERROR_TIMEOUT ** VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT ** ** **-- */ const HRESULT ClassifyWriterFailure (HRESULT hrWriterFailure) { BOOL bStatusUpdated; return (ClassifyWriterFailure (hrWriterFailure, bStatusUpdated)); } /* ClassifyWriterFailure () */ /* **++ ** ** Routine Description: ** ** Routine to classify the many assorted internal writer errors ** into one of the narrow set of responses a writer is permitted ** to send back to the requestor. ** ** ** Arguments: ** ** hrStatus HRESULT to be classified ** bStatusUpdated TRUE if the status is re-mapped ** ** ** Return Value: ** ** One of the following list depending upon the supplied status. ** ** VSS_E_WRITERERROR_OUTOFRESOURCES ** VSS_E_WRITERERROR_RETRYABLE ** VSS_E_WRITERERROR_NONRETRYABLE ** VSS_E_WRITERERROR_TIMEOUT ** VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT ** ** **-- */ const HRESULT ClassifyWriterFailure (HRESULT hrWriterFailure, BOOL &bStatusUpdated) { HRESULT hrStatus; switch (hrWriterFailure) { case NOERROR: case VSS_E_WRITERERROR_OUTOFRESOURCES: case VSS_E_WRITERERROR_RETRYABLE: case VSS_E_WRITERERROR_NONRETRYABLE: case VSS_E_WRITERERROR_TIMEOUT: case VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT: /* ** These are ok as they are so no need to transmogrify them. */ hrStatus = hrWriterFailure; bStatusUpdated = FALSE; break; case E_OUTOFMEMORY: case HRESULT_FROM_WIN32 (ERROR_NOT_ENOUGH_MEMORY): case HRESULT_FROM_WIN32 (ERROR_NO_MORE_SEARCH_HANDLES): case HRESULT_FROM_WIN32 (ERROR_NO_MORE_USER_HANDLES): case HRESULT_FROM_WIN32 (ERROR_NO_LOG_SPACE): case HRESULT_FROM_WIN32 (ERROR_DISK_FULL): hrStatus = VSS_E_WRITERERROR_OUTOFRESOURCES; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_NOT_READY): hrStatus = VSS_E_WRITERERROR_RETRYABLE; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_TIMEOUT): hrStatus = VSS_E_WRITERERROR_TIMEOUT; bStatusUpdated = TRUE; break; case E_UNEXPECTED: case E_INVALIDARG: // equal to HRESULT_FROM_WIN32 (ERROR_INVALID_PARAMETER) case E_ACCESSDENIED: case HRESULT_FROM_WIN32 (ERROR_PATH_NOT_FOUND): case HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND): case HRESULT_FROM_WIN32 (ERROR_PRIVILEGE_NOT_HELD): case HRESULT_FROM_WIN32 (ERROR_NOT_LOCKED): case HRESULT_FROM_WIN32 (ERROR_LOCKED): default: hrStatus = VSS_E_WRITERERROR_NONRETRYABLE; bStatusUpdated = TRUE; break; } return (hrStatus); } /* ClassifyWriterFailure () */ /* **++ ** ** Routine Description: ** ** Routine to classify the many assorted internal shim errors ** into one of the narrow set of responses a writer is permitted ** to send back to the requestor. ** ** ** Arguments: ** ** hrStatus HRESULT to be classified ** ** ** Return Value: ** ** One of the following list depending upon the supplied status. ** ** E_OUTOFMEMORY ** E_ACCESSDENIED ** E_INVALIDARG ** E_UNEXPECTED ** VSS_E_WRITERERROR_OUTOFRESOURCES ** VSS_E_WRITERERROR_RETRYABLE ** VSS_E_WRITERERROR_NONRETRYABLE ** VSS_E_WRITERERROR_TIMEOUT ** VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT **-- */ const HRESULT ClassifyShimFailure (HRESULT hrWriterFailure) { BOOL bStatusUpdated; return (ClassifyShimFailure (hrWriterFailure, bStatusUpdated)); } /* ClassifyShimFailure () */ /* **++ ** ** Routine Description: ** ** Routine to classify the many assorted internal shim errors ** into one of the narrow set of responses a writer is permitted ** to send back to the requestor. ** ** ** Arguments: ** ** hrStatus HRESULT to be classified ** bStatusUpdated TRUE if the status is re-mapped ** ** ** Return Value: ** ** One of the following list depending upon the supplied status. ** ** E_OUTOFMEMORY ** E_ACCESSDENIED ** E_INVALIDARG ** E_UNEXPECTED ** VSS_E_BAD_STATE ** VSS_E_SNAPSHOT_SET_IN_PROGRESS ** VSS_E_WRITERERROR_OUTOFRESOURCES ** VSS_E_WRITERERROR_RETRYABLE ** VSS_E_WRITERERROR_NONRETRYABLE ** VSS_E_WRITERERROR_TIMEOUT ** VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT **-- */ const HRESULT ClassifyShimFailure (HRESULT hrWriterFailure, BOOL &bStatusUpdated) { HRESULT hrStatus; switch (hrWriterFailure) { case NOERROR: case E_OUTOFMEMORY: case E_ACCESSDENIED: case E_INVALIDARG: // equal to HRESULT_FROM_WIN32 (ERROR_INVALID_PARAMETER) case E_UNEXPECTED: case VSS_E_BAD_STATE: case VSS_E_SNAPSHOT_SET_IN_PROGRESS: case VSS_E_WRITERERROR_RETRYABLE: case VSS_E_WRITERERROR_NONRETRYABLE: case VSS_E_WRITERERROR_TIMEOUT: case VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT: case VSS_E_WRITERERROR_OUTOFRESOURCES: /* ** These are ok as they are so no need to transmogrify them. */ hrStatus = hrWriterFailure; bStatusUpdated = FALSE; break; case HRESULT_FROM_WIN32 (ERROR_NOT_LOCKED): hrStatus = VSS_E_BAD_STATE; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_LOCKED): hrStatus = VSS_E_SNAPSHOT_SET_IN_PROGRESS; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_NOT_ENOUGH_MEMORY): case HRESULT_FROM_WIN32 (ERROR_NO_MORE_SEARCH_HANDLES): case HRESULT_FROM_WIN32 (ERROR_NO_MORE_USER_HANDLES): case HRESULT_FROM_WIN32 (ERROR_NO_LOG_SPACE): case HRESULT_FROM_WIN32 (ERROR_DISK_FULL): hrStatus = E_OUTOFMEMORY; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_PRIVILEGE_NOT_HELD): hrStatus = E_ACCESSDENIED; bStatusUpdated = TRUE; break; case HRESULT_FROM_WIN32 (ERROR_TIMEOUT): case HRESULT_FROM_WIN32 (ERROR_PATH_NOT_FOUND): case HRESULT_FROM_WIN32 (ERROR_FILE_NOT_FOUND): case HRESULT_FROM_WIN32 (ERROR_NOT_READY): default: hrStatus = E_UNEXPECTED; bStatusUpdated = TRUE; break; } return (hrStatus); } /* ClassifyShimFailure () */ /* **++ ** ** Routine Description: ** ** Routine to classify the many assorted internal shim or shim ** writer errors into one of the narrow set of responses we are ** permitted to send back to the requestor. ** ** The determination is made to classify either as a shim error ** or as a writer error based upon whether or not a writer name ** is supplied. If it is supplied then the assumption is made ** that this is a writer failure and so the error is classified ** accordingly. ** ** Note that this is a worker routine for the LogFailure() macro ** and the two are intended to be used in concert. ** ** ** Arguments: ** ** pft Pointer to a Function trace class ** pwszNameWriter The name of the applicable writer or NULL or L"" ** pwszNameCalledRoutine The name of the routine that returned the failure status ** ** ** Side Effects: ** ** hr field of *pft updated ** ** ** Return Value: ** ** One of the following list depending upon the supplied status. ** ** E_OUTOFMEMORY ** E_ACCESSDENIED ** E_INVALIDARG ** E_UNEXPECTED ** VSS_E_BAD_STATE ** VSS_E_SNAPSHOT_SET_IN_PROGRESS ** VSS_E_WRITERERROR_OUTOFRESOURCES ** VSS_E_WRITERERROR_RETRYABLE ** VSS_E_WRITERERROR_NONRETRYABLE ** VSS_E_WRITERERROR_TIMEOUT ** VSS_E_WRITERERROR_INCONSISTENTSNAPSHOT ** **-- */ HRESULT LogFailureWorker (CVssFunctionTracer *pft, LPCWSTR pwszNameWriter, LPCWSTR pwszNameCalledRoutine) { if (pft->HrFailed ()) { BOOL bStatusRemapped; HRESULT hrStatusClassified = ((NULL == pwszNameWriter) || (L'\0' == pwszNameWriter [0])) ? ClassifyShimFailure (pft->hr, bStatusRemapped) : ClassifyWriterFailure (pft->hr, bStatusRemapped); if (bStatusRemapped) { if (((NULL == pwszNameCalledRoutine) || (L'\0' == pwszNameCalledRoutine [0])) && ((NULL == pwszNameWriter) || (L'\0' == pwszNameWriter [0]))) { pft->LogError (VSS_ERROR_SHIM_GENERAL_FAILURE, VSSDBG_WRTCMN << pft->hr << hrStatusClassified); pft->Trace (VSSDBG_WRTCMN, L"FAILED with status 0x%08lX (converted to 0x%08lX)", pft->hr, hrStatusClassified); } else if ((NULL == pwszNameCalledRoutine) || (L'\0' == pwszNameCalledRoutine [0])) { pft->LogError (VSS_ERROR_SHIM_WRITER_GENERAL_FAILURE, VSSDBG_WRTCMN << pft->hr << hrStatusClassified << pwszNameWriter); pft->Trace (VSSDBG_WRTCMN, L"FAILED in writer %s with status 0x%08lX (converted to 0x%08lX)", pwszNameWriter, pft->hr, hrStatusClassified); } else if ((NULL == pwszNameWriter) || (L'\0' == pwszNameWriter [0])) { pft->LogError (VSS_ERROR_SHIM_FAILED_SYSTEM_CALL, VSSDBG_WRTCMN << pft->hr << hrStatusClassified << pwszNameCalledRoutine); pft->Trace (VSSDBG_WRTCMN, L"FAILED calling routine %s with status 0x%08lX (converted to 0x%08lX)", pwszNameCalledRoutine, pft->hr, hrStatusClassified); } else { pft->LogError (VSS_ERROR_SHIM_WRITER_FAILED_SYSTEM_CALL, VSSDBG_WRTCMN << pft->hr << hrStatusClassified << pwszNameWriter << pwszNameCalledRoutine); pft->Trace (VSSDBG_WRTCMN, L"FAILED in writer %s calling routine %s with status 0x%08lX (converted to 0x%08lX)", pwszNameWriter, pwszNameCalledRoutine, pft->hr, hrStatusClassified); } pft->hr = hrStatusClassified; } } return (pft->hr); } /* LogFailureWorker () */