/*++ (c) 1998 Seagate Software, Inc. All rights reserved. Module Name: fsaitemr.cpp Abstract: This class contains represents a scan item (i.e. file or directory) for NTFS 5.0. Author: Chuck Bardeen [cbardeen] 1-Dec-1996 Revision History: Michael Lotz [lotz ] 13-Jan-1997 --*/ #include "stdafx.h" #define WSB_TRACE_IS WSB_TRACE_BIT_FSA #include "wsb.h" #include "fsa.h" #include "fsaitem.h" #include "rpdata.h" #include "rpio.h" #include "rpguid.h" #include "fsaitemr.h" #define SHARE_FLAGS (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) #define EXCLUSIVE_FLAG ( 0 ) // exclusive open without sharing of the file // // Notice these two bits are NOT the same location???? // #define BIT_FOR_TRUNCATED FILE_ATTRIBUTE_OFFLINE #define BIT_FOR_RP FILE_ATTRIBUTE_REPARSE_POINT // // File extensions that are treated as special cases for truncate // #define EXT_FOR_EXE L".exe" #define EXT_FOR_DLL L".dll" // // Macros and defines for exe and dll headers // #define SIZE_OF_NT_SIGNATURE sizeof(DWORD) // // Macros // /* Offset to PE file signature */ #define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \ ((PIMAGE_DOS_HEADER)a)->e_lfanew)) /* MS-OS header identifies the NT PEFile signature dword; the PEFILE header exists just after that dword. */ #define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \ ((PIMAGE_DOS_HEADER)a)->e_lfanew + \ SIZE_OF_NT_SIGNATURE)) /* PE optional header is immediately after PEFile header. */ #define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \ ((PIMAGE_DOS_HEADER)a)->e_lfanew + \ SIZE_OF_NT_SIGNATURE + \ sizeof (IMAGE_FILE_HEADER))) /* Section headers are immediately after PE optional header. */ #define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + \ ((PIMAGE_DOS_HEADER)a)->e_lfanew + \ SIZE_OF_NT_SIGNATURE + \ sizeof (IMAGE_FILE_HEADER) + \ sizeof (IMAGE_OPTIONAL_HEADER))) HRESULT OpenObject ( IN WCHAR const *pwszFile, IN ULONG CreateOptions, IN ULONG DesiredAccess, IN ULONG ShareAccess, IN ULONG CreateDisposition, OUT IO_STATUS_BLOCK *IoStatusBlock, OUT HANDLE *ObjectHandle ) /*++ Implements: A wrapper function for NtCreateFile OpenObject --*/ // // Simple wrapper for NtCreateFile // { HRESULT hr = S_OK; NTSTATUS ntStatus; BOOL bStatus; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING str; RTL_RELATIVE_NAME_U RelativeName; PVOID StrBuffer = NULL; BOOL bReleaseRelativeName = FALSE; WsbTraceIn(OLESTR("OpenObject"), OLESTR("")); // // Null out the pointer so we know when it was allocated // str.Buffer = NULL; RelativeName.RelativeName.Buffer = NULL; try { // // Convert input name into special format with \??\ // //bStatus = RtlDosPathNameToNtPathName_U( pwszFile, // &str, // NULL, // NULL ); //WsbAffirm( bStatus, E_FAIL); bStatus = RtlDosPathNameToRelativeNtPathName_U( pwszFile, &str, NULL, &RelativeName ); WsbAffirm( bStatus, E_FAIL); bReleaseRelativeName = TRUE; StrBuffer = str.Buffer; if ( RelativeName.RelativeName.Length ) { str = RelativeName.RelativeName; } else { RelativeName.ContainingDirectory = NULL; } InitializeObjectAttributes( &ObjectAttributes, &str, 0, RelativeName.ContainingDirectory, NULL ); ntStatus = NtCreateFile( ObjectHandle, DesiredAccess | SYNCHRONIZE, &ObjectAttributes, IoStatusBlock, NULL, // pallocationsize (none!) FILE_ATTRIBUTE_NORMAL, ShareAccess, CreateDisposition, CreateOptions | FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, NULL, // EA buffer (none!) 0); // // Right now if the file is not a reparse point the above open // fails -- so now try it without the FILE_OPEN_REPARSE_POINT // if ( STATUS_NOT_A_REPARSE_POINT == ntStatus) { WsbAffirmNtStatus( NtCreateFile( ObjectHandle, DesiredAccess | SYNCHRONIZE, &ObjectAttributes, IoStatusBlock, NULL, // pallocationsize (none!) FILE_ATTRIBUTE_NORMAL, ShareAccess, CreateDisposition, CreateOptions | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_POSIX_SEMANTICS, NULL, // EA buffer (none!) 0 ) ); } else { WsbAffirmNtStatus( ntStatus ); } } WsbCatch( hr ); // // Clean up the memory if we allocated it // if (bReleaseRelativeName) { RtlReleaseRelativeName(&RelativeName); } if (NULL != StrBuffer) { RtlFreeHeap(RtlProcessHeap(), 0, StrBuffer); } //if( NULL == str.Buffer ) { // bStatus = RtlFreeHeap( RtlProcessHeap(), 0, str.Buffer ); //} WsbTraceOut(OLESTR("OpenObject"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return( hr ); } // OpenObject HRESULT CopyPlaceholderToRP ( IN CONST FSA_PLACEHOLDER *pPlaceholder, OUT PREPARSE_DATA_BUFFER pReparseBuffer, IN BOOL bTruncated ) /*++ Implements: A wrapper function for copying the placeholder data into the reparse point CopyPlaceholderToRP --*/ // // Simple wrapper moving the data from the scan item in-memory // placeholder information into a reparse point buffer // { HRESULT hr = S_OK; PRP_DATA pHsmData; WsbTraceIn(OLESTR("CopyPlaceholderToRP"), OLESTR("")); WsbTrace(OLESTR(" fileStart = %I64d, dataStart = %I64d, dataStreamStart = %I64d\n"), pPlaceholder->fileStart, pPlaceholder->dataStart, pPlaceholder->dataStreamStart); WsbTrace(OLESTR(" fileSize = %I64d, dataSize = %I64d, dataStreamSize = %I64d\n"), pPlaceholder->fileSize, pPlaceholder->dataSize, pPlaceholder->dataStreamSize); try { // // Validate the pointers passed in // WsbAssert( NULL != pPlaceholder, E_POINTER ); WsbAssert( NULL != pReparseBuffer, E_POINTER ); // // Setup the pointer to our hsm data // pHsmData = (PRP_DATA) &pReparseBuffer->GenericReparseBuffer.DataBuffer[0]; // // Set the generic reparse point header information for the tag and size // pReparseBuffer->ReparseTag = IO_REPARSE_TAG_HSM ; pReparseBuffer->ReparseDataLength = sizeof(RP_DATA); pReparseBuffer->Reserved = 0 ; // // Set the private data that is the vendor id and version number // pHsmData->vendorId = RP_MSFT_VENDOR_ID; pHsmData->version = RP_VERSION; // // Assume for now that there is only one placeholder // This needs to be updated // pHsmData->numPrivateData = 1; pHsmData->fileIdentifier = GUID_NULL; ZeroMemory(pHsmData->data.reserved, RP_RESV_SIZE); // // If the file is to indicate the file is truncated then set the bit // otherwise make sure it is off // RP_INIT_BITFLAG( pHsmData->data.bitFlags ); if( bTruncated ) { RP_SET_TRUNCATED_BIT( pHsmData->data.bitFlags ); } else { RP_CLEAR_TRUNCATED_BIT( pHsmData->data.bitFlags ); } // // Set the truncate on close bit as needed // if( pPlaceholder->truncateOnClose ) { RP_SET_TRUNCATE_ON_CLOSE_BIT( pHsmData->data.bitFlags ); } else { RP_CLEAR_TRUNCATE_ON_CLOSE_BIT( pHsmData->data.bitFlags ); } // // Set the Premigrate on close bit as needed // if( pPlaceholder->premigrateOnClose ) { RP_SET_PREMIGRATE_ON_CLOSE_BIT( pHsmData->data.bitFlags ); } else { RP_CLEAR_PREMIGRATE_ON_CLOSE_BIT( pHsmData->data.bitFlags ); } // // Set the global bit flags based on the placeholder data // For now since we are assuming one placeholder then set // them the same. pHsmData->globalBitFlags = pHsmData->data.bitFlags; // // Move over the data parts of the information // pHsmData->data.migrationTime.QuadPart = WsbFTtoLL( pPlaceholder->migrationTime ); pHsmData->data.hsmId = pPlaceholder->hsmId; pHsmData->data.bagId = pPlaceholder->bagId; pHsmData->data.fileStart.QuadPart = pPlaceholder->fileStart; pHsmData->data.fileSize.QuadPart = pPlaceholder->fileSize; pHsmData->data.dataStart.QuadPart = pPlaceholder->dataStart; pHsmData->data.dataSize.QuadPart = pPlaceholder->dataSize; pHsmData->data.fileVersionId.QuadPart = pPlaceholder->fileVersionId; pHsmData->data.verificationData.QuadPart = pPlaceholder->verificationData; pHsmData->data.verificationType = pPlaceholder->verificationType; pHsmData->data.recallCount = pPlaceholder->recallCount; pHsmData->data.recallTime.QuadPart = WsbFTtoLL( pPlaceholder->recallTime ); pHsmData->data.dataStreamStart.QuadPart = pPlaceholder->dataStreamStart; pHsmData->data.dataStreamSize.QuadPart = pPlaceholder->dataStreamSize; pHsmData->data.dataStream = pPlaceholder->dataStream; pHsmData->data.dataStreamCRCType = pPlaceholder->dataStreamCRCType; pHsmData->data.dataStreamCRC.QuadPart = pPlaceholder->dataStreamCRC; // // Lastly generate the check sum // RP_GEN_QUALIFIER(pHsmData, pHsmData->qualifier); // // Now set the bit that tells the filter that it is us setting the reparse point. // This is not included in the qualifier checksum generation. // RP_SET_ORIGINATOR_BIT( pHsmData->data.bitFlags ); } WsbCatch(hr); WsbTraceOut(OLESTR("CopyPlaceholderToRP"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return( hr ); } HRESULT CopyRPDataToPlaceholder ( IN CONST PRP_DATA pHsmData, OUT FSA_PLACEHOLDER *pPlaceholder ) /*++ Implements: A wrapper function for moving the Reparse Point into generic FSA_PLACEHOLDER format CopyRPDataToPlaceholder --*/ { HRESULT hr = S_OK; ULONG qualifier; // Used to checksum the data WsbTraceIn(OLESTR("CopyRPDataToPlaceholder"), OLESTR("")); WsbTrace(OLESTR(" fileStart = %I64d, dataStart = %I64d, dataStreamStart = %I64d\n"), pHsmData->data.fileStart.QuadPart, pHsmData->data.dataStart.QuadPart, pHsmData->data.dataStreamStart.QuadPart); WsbTrace(OLESTR(" fileSize = %I64d, dataSize = %I64d, dataStreamSize = %I64d\n"), pHsmData->data.fileSize.QuadPart, pHsmData->data.dataSize.QuadPart, pHsmData->data.dataStreamSize.QuadPart); // // Simple wrapper moving the data from the reparse point buffer into the // generic placeholder information // try { // // Validate the pointers passed in // WsbAssert( NULL != pHsmData, E_POINTER ); WsbAssert( NULL != pPlaceholder, E_POINTER ); // // Just in case, we clear out the originator bit. // RP_CLEAR_ORIGINATOR_BIT( pHsmData->data.bitFlags ); // // Verify the check sum and the key private fields // RP_GEN_QUALIFIER(pHsmData, qualifier); WsbAffirm( pHsmData->qualifier == qualifier, E_FAIL ); WsbAffirm( RP_MSFT_VENDOR_ID == pHsmData->vendorId, E_FAIL ); WsbAffirm( RP_VERSION == pHsmData->version, E_FAIL ); // // Now that everything worked, save the values in our private data // pPlaceholder->migrationTime = WsbLLtoFT( pHsmData->data.migrationTime.QuadPart ); pPlaceholder->hsmId = pHsmData->data.hsmId; pPlaceholder->bagId = pHsmData->data.bagId; pPlaceholder->fileStart = pHsmData->data.fileStart.QuadPart; pPlaceholder->fileSize = pHsmData->data.fileSize.QuadPart; pPlaceholder->dataStart = pHsmData->data.dataStart.QuadPart; pPlaceholder->dataSize = pHsmData->data.dataSize.QuadPart; pPlaceholder->fileVersionId = pHsmData->data.fileVersionId.QuadPart; pPlaceholder->verificationData = pHsmData->data.verificationData.QuadPart; pPlaceholder->verificationType = pHsmData->data.verificationType; pPlaceholder->recallCount = pHsmData->data.recallCount; pPlaceholder->recallTime = WsbLLtoFT( pHsmData->data.recallTime.QuadPart ); pPlaceholder->dataStreamStart = pHsmData->data.dataStreamStart.QuadPart; pPlaceholder->dataStreamSize = pHsmData->data.dataStreamSize.QuadPart; pPlaceholder->dataStream = pHsmData->data.dataStream; pPlaceholder->dataStreamCRCType = pHsmData->data.dataStreamCRCType; pPlaceholder->dataStreamCRC = pHsmData->data.dataStreamCRC.QuadPart; // // Set placeholder bits // if( RP_FILE_IS_TRUNCATED( pHsmData->data.bitFlags ) ) { pPlaceholder->isTruncated = TRUE; } else { pPlaceholder->isTruncated = FALSE; } if( RP_FILE_DO_TRUNCATE_ON_CLOSE( pHsmData->data.bitFlags ) ) { pPlaceholder->truncateOnClose = TRUE; } else { pPlaceholder->truncateOnClose = FALSE; } if( RP_FILE_DO_PREMIGRATE_ON_CLOSE( pHsmData->data.bitFlags ) ) { pPlaceholder->premigrateOnClose = TRUE; } else { pPlaceholder->premigrateOnClose = FALSE; } } WsbCatch(hr); WsbTraceOut(OLESTR("CopyRPDataToPlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return( hr ); } HRESULT CopyRPToPlaceholder ( IN CONST PREPARSE_DATA_BUFFER pReparseBuffer, OUT FSA_PLACEHOLDER *pPlaceholder ) /*++ Implements: A wrapper function for moving the Reparse Point into generic FSA_PLACEHOLDER format CopyRPToPlaceholder --*/ { HRESULT hr = S_OK; PRP_DATA pHsmData; WsbTraceIn(OLESTR("CopyRPToPlaceholder"), OLESTR("")); // // Simple wrapper moving the data from the reparse point buffer into the // generic placeholder information // try { // // Validate the pointers passed in // WsbAssert( NULL != pReparseBuffer, E_POINTER ); WsbAssert( NULL != pPlaceholder, E_POINTER ); // // Get the pointers setup correctly to this buffer because the // type REPARSE_DATA_BUFFER actually doesn't have any space // allocated for the data and that is our own type, so get pointers // pointing into the real allocated space so we can use them // pHsmData = (PRP_DATA) &pReparseBuffer->GenericReparseBuffer.DataBuffer[0]; // // Validate the key public fields to make sure it is data we // understand // WsbAffirm( IO_REPARSE_TAG_HSM == pReparseBuffer->ReparseTag , S_FALSE ); WsbAffirm( sizeof(RP_DATA) == pReparseBuffer->ReparseDataLength , S_FALSE ); // // Copy over the RP_DATA information // WsbAffirmHr(CopyRPDataToPlaceholder(pHsmData, pPlaceholder)); } WsbCatch(hr); WsbTraceOut(OLESTR("CopyRPToPlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return( hr ); } HRESULT CFsaScanItem::CalculateCurrentCRCAndUSN( IN LONGLONG offset, IN LONGLONG size, OUT ULONG *pCurrentCRC, OUT LONGLONG *pUsn ) { HRESULT hr = S_OK; HRESULT hrTest = S_OK; CWsbStringPtr path; IO_STATUS_BLOCK IoStatusBlock; HANDLE handle = INVALID_HANDLE_VALUE; FILE_BASIC_INFORMATION basicInformation; try { WsbTraceIn(OLESTR("CFsaScanItem::CalculateCurrentCRCAndUSN"), OLESTR("offset = <%I64d>, size = <%I64d>"), offset, size); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); WsbTrace(OLESTR("CFsaScanItem::CalculateCurrentCRCAndUSN for file <%ls>"), (OLECHAR *)path); // Open the file. WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE, FILE_READ_DATA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes // basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; WsbAssertNtStatus( NtSetInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Calculate the CRC // WsbAffirmHr(CalculateCurrentCRCInternal(handle, offset, size, pCurrentCRC)); // // Calculate the USN // *pUsn = 0; hr = WsbGetUsnFromFileHandle(handle, FALSE, pUsn); if (S_OK != hr) { // // If we can't get the USN set it to 0 which is an invalid // USN and keep going. *pUsn = 0; hr = S_OK; } // // Close the file // NtClose( handle ); handle = INVALID_HANDLE_VALUE; } WsbCatch( hr ); // // Close the file for sure // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CalculateCurrentCRCAndUSN"), OLESTR("hr = <%ls>, CRC is <%ls>, USN is <%ls>"), WsbHrAsString(hr), WsbPtrToUlongAsString(pCurrentCRC), WsbPtrToLonglongAsString(pUsn)); return(hr); } HRESULT CFsaScanItem::CalculateCurrentCRCInternal( IN HANDLE handle, IN LONGLONG offset, IN LONGLONG size, ULONG *pCurrentCRC ) { HRESULT hr = S_OK; HRESULT hrTest = S_OK; register ULONG crc32 = 0; LONGLONG bytesRemaining; LONGLONG bytesToRead; ULONG bufferSize; ULONG bytesRead; CHAR * pBuffer = 0; CHAR * pCurrent; IO_STATUS_BLOCK IoStatusBlock; try { WsbTraceIn(OLESTR("CFsaScanItem::CalculateCurrentCRCInternal"), OLESTR("offset = <%I64d>, size = <%I64d>"), offset, size); // set initial value of CRC to 'pre-conditioning value' INITIALIZE_CRC(crc32); // // Set up to read where we want to start // LARGE_INTEGER startPoint; startPoint.QuadPart = offset; // Get the size of the file. bytesToRead = size; // // Figure out the size of the buffer to create // if (bytesToRead < 1024*1024) { // // Allocate one buffer the size of the file // bufferSize = (ULONG)bytesToRead; } else { bufferSize = (1024 * 1024); } pBuffer = (CHAR *)malloc(bufferSize); if (0 == pBuffer) { // // Try again for half the space // bufferSize = bufferSize / 2; pBuffer = (CHAR *)malloc(bufferSize); if (0 == pBuffer) { WsbThrow( E_OUTOFMEMORY ); } } // Start calculating CRC by processing a 'chunk' of the file at a time. // While there are still chunks left, read that amount. Otherwise read the amount left. for (bytesRemaining = bytesToRead; bytesRemaining > 0; bytesRemaining -= bytesRead) { // Read data from the file. WsbAssertNtStatus(NtReadFile(handle, NULL, NULL, NULL, &IoStatusBlock, pBuffer, bufferSize, &startPoint, NULL)); bytesRead = (DWORD)IoStatusBlock.Information; startPoint.QuadPart += bytesRead; // Each byte needs to be added into the CRC. for (pCurrent = pBuffer; (pCurrent < (pBuffer + bytesRead)) && (S_OK == hr); pCurrent++) { hrTest = WsbCRCReadFile((UCHAR *)pCurrent, &crc32); if (S_OK != hrTest) { hr = S_FALSE; } } } // return ones-complement of the calc'd CRC value - this is the actual CRC FINIALIZE_CRC(crc32); *pCurrentCRC = crc32; } WsbCatch( hr ); // // Make sure allocated memory is freed // if (0 != pBuffer) { free(pBuffer); } WsbTraceOut(OLESTR("CalculateCurrentCRCInternal"), OLESTR("hr = <%ls>, CRC is <%ls>"), WsbHrAsString(hr), WsbPtrToUlongAsString(pCurrentCRC)); return(hr); } HRESULT CFsaScanItem::CreatePlaceholder( IN LONGLONG offset, IN LONGLONG size, IN FSA_PLACEHOLDER placeholder, IN BOOL checkUsn, IN LONGLONG usn, OUT LONGLONG *pUsn ) /*++ Implements: IFsaScanItem::CreatePlaceholder(). --*/ { HRESULT hr = S_OK; CWsbStringPtr path; HANDLE handle = INVALID_HANDLE_VALUE; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; PREPARSE_DATA_BUFFER pReparseBuffer; UCHAR ReparseBuffer[sizeof(REPARSE_DATA_BUFFER) + sizeof(RP_DATA) + 10]; NTSTATUS ntStatus; FILE_BASIC_INFORMATION basicInformation; LONGLONG lastWriteTime; LONGLONG nowUsn = 0; CWsbStringPtr volName; ULONG attributes; WsbTraceIn(OLESTR("CFsaScanItem::CreatePlaceholder"), OLESTR("offset = <%I64d>, size = <%I64d>, checkUsn = <%ls>, usn = <%I64d>"), offset, size, WsbBoolAsString(checkUsn), usn); try { BOOL wasReadOnly = FALSE; // // Set the offset and size information // placeholder.dataStreamStart = offset; placeholder.dataStreamSize = size; // // Get the pointers setup correctly to this buffer because the // type REPARSE_DATA_BUFFER actually doesn't have any space // allocated for the data and that is our own type, so get pointers // pointing into the real allocated space so we can use them // pReparseBuffer = (PREPARSE_DATA_BUFFER)ReparseBuffer; WsbAffirmHr( CopyPlaceholderToRP( &placeholder, pReparseBuffer, placeholder.isTruncated ) ); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); // Save whether this was readonly for later if (S_OK == IsReadOnly()) { wasReadOnly = TRUE; } // // Make sure the file is read/write WsbAffirmHr( MakeReadWrite() ); // // Open the file to put the placeholder information in the reparse point // DesiredAccess = FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // // Tell the USN journal that we are the source of the changes. // WsbAffirmHr(m_pResource->GetPath(&volName, 0)); WsbAffirmHr(WsbMarkUsnSource(handle, volName)); // // Get the USN from the file now before any writes occur. // Note: NtSetInformationFile will not change the USN if you set the FileAttributes to 0 // and the dates to -1. Setting the attributes to 0 leaves them unchanged. // // (For now we skip this check for read-only files because the call to MakeReadWrite // changes the USN. This needs to be fixed in the future.) // if (checkUsn && !wasReadOnly) { // // Get the current USN for this file // hr = WsbGetUsnFromFileHandle(handle, FALSE, &nowUsn); if (S_OK != hr) { nowUsn = 0; hr = S_OK; } } // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); lastWriteTime = basicInformation.LastWriteTime.QuadPart; // // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes // indicate the file is offline. You must do this AFTER you // get the USN because the NtSetInformationFile changes the USN // basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; // // Set the attributes to 0 to avoid the usn change (the file attributes will remain unchanged). // attributes = basicInformation.FileAttributes; basicInformation.FileAttributes = 0; // No change to attributes WsbAssertNtStatus( NtSetInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); basicInformation.FileAttributes = attributes; // // Make sure that the modify time of the file matches that // of the placeholder data. // if (lastWriteTime != placeholder.fileVersionId) { // // The file has changed - don't put the reparse point on the file. // hr = FSA_E_REPARSE_NOT_WRITTEN_FILE_CHANGED; WsbLogEvent(FSA_MESSAGE_REPARSE_NOT_WRITTEN_FILE_CHANGED, 0, NULL, WsbAbbreviatePath(path, 120), WsbHrAsString(hr), NULL); WsbThrow( hr ); } else if (checkUsn) { // // If we are to check the USN do it now // // // Rember if a USN is 0, it is not useful information so we can't // rely on it // WsbTrace(OLESTR("CFsaScanItem::CreatePlaceholder premig usn = <%I64d>, current usn <%I64d>\n"), usn, nowUsn); if ((0 != nowUsn) && (0 != usn) && (nowUsn != usn)) { // // The file has changed - don't put the reparse point on the file. // hr = FSA_E_REPARSE_NOT_WRITTEN_FILE_CHANGED; WsbLogEvent(FSA_MESSAGE_REPARSE_NOT_WRITTEN_FILE_CHANGED, 0, NULL, WsbAbbreviatePath(path, 120), WsbHrAsString(hr), NULL); WsbThrow( hr ); } } // // Make the file able to be a sparse file // Note we assert only if the error is not a disk full error because we can get STATUS_NO_DISK_SPACE from this call and we // do not want to see the log for that error. // This is because the file must be padded out to a 16 cluster boundry before being made sparse. // // Note that this call does not affect the files data. It just enables "sparseness" for the file. // ntStatus = NtFsControlFile( handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_SET_SPARSE, NULL, 0, NULL, 0 ); if (!NT_SUCCESS(ntStatus)) { if (STATUS_DISK_FULL == ntStatus) { // Check whether the disk is really full, otherwise, assume that user has passed the quota limit // Note: we ignore errors here and assume disk-full hr = CheckIfDiskFull(); if (S_OK == hr) { hr = FSA_E_REPARSE_NOT_CREATED_DISK_FULL; } else if (S_FALSE == hr) { hr = FSA_E_REPARSE_OWNER_PASS_QUOTA; } else { // error WsbTraceAlways(OLESTR("CFsaScanItem::CreatePlaceholder: failed to check disk space after DISK_FULL error. hr=<%ls>\n"), WsbHrAsString(hr)); hr = FSA_E_REPARSE_NOT_CREATED_DISK_FULL; } } else { hr = HRESULT_FROM_NT(ntStatus); } WsbLogEvent(FSA_MESSAGE_REPARSE_NOT_CREATED, 0, NULL, WsbAbbreviatePath(path, 120), WsbHrAsString(hr), NULL); WsbThrow(hr); } // // Do the work of setting the Reparse Point // ntStatus = NtFsControlFile( handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_SET_REPARSE_POINT, pReparseBuffer, FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + pReparseBuffer->ReparseDataLength, NULL, 0 ); // // Check the return code, if everything worked update the in memory flag // if (!NT_SUCCESS(ntStatus)) { if (STATUS_DISK_FULL == ntStatus) { // Check whether the disk is really full, otherwise, assume that user has passed the quota limit // Note: we ignore errors here and assume disk-full hr = CheckIfDiskFull(); if (S_OK == hr) { hr = FSA_E_REPARSE_NOT_CREATED_DISK_FULL; } else if (S_FALSE == hr) { hr = FSA_E_REPARSE_OWNER_PASS_QUOTA; } else { // error WsbTraceAlways(OLESTR("CFsaScanItem::CreatePlaceholder: failed to check disk space after DISK_FULL error. hr=<%ls>\n"), WsbHrAsString(hr)); hr = FSA_E_REPARSE_NOT_CREATED_DISK_FULL; } } else { hr = HRESULT_FROM_NT(ntStatus); } WsbLogEvent(FSA_MESSAGE_REPARSE_NOT_CREATED, 0, NULL, WsbAbbreviatePath(path, 120), WsbHrAsString(hr), NULL); WsbThrow(hr); } // // Now that we change the bit, change the in memory flags for // this scan item // m_findData.dwFileAttributes |= BIT_FOR_RP; // // Set the OFFLINE attribute to indicate the correct status of // the file // if( placeholder.isTruncated ) { basicInformation.FileAttributes |= BIT_FOR_TRUNCATED; } else { basicInformation.FileAttributes &= ~BIT_FOR_TRUNCATED; } basicInformation.FileAttributes |= FILE_ATTRIBUTE_NORMAL; // Just in case result was zero (then no attributes would be set) WsbAssertNtStatus( NtSetInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Set the in memory copy of the attributes to the right values // m_findData.dwFileAttributes = basicInformation.FileAttributes; // // Restore original attributes if required (must be done before retrieving the USN // since changing attributes changes the USN as well) // if (TRUE == m_changedAttributes) { RestoreAttributes(); } // // Before we close the file, get the USN to return to the caller // Writing the reparse information will change the USN. // hr = WsbGetUsnFromFileHandle(handle, TRUE, pUsn); if (S_OK != hr) { *pUsn = 0; hr = S_OK; } // // Close the file since we are done with it and set the handle to invalid // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; // // Now that everything worked change the in memory flags for // this scan item // m_placeholder = placeholder; m_gotPlaceholder = TRUE; WsbTrace( OLESTR("(CreatePlaceholder) Reparse CRC <%ls>\n"), WsbLonglongAsString( m_placeholder.dataStreamCRC ) ); } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CFsaScanItem::CreatePlaceholder"), OLESTR("hr = <%ls>, usn = <%ls>"), WsbHrAsString(hr), WsbPtrToLonglongAsString(pUsn)); return(hr); } HRESULT CFsaScanItem::DeletePlaceholder( IN LONGLONG /*offset*/, IN LONGLONG /*size*/ ) /*++ Implements: IFsaScanItem::DeletePlaceholder(). --*/ { HRESULT hr = S_OK; CWsbStringPtr path; HANDLE handle = INVALID_HANDLE_VALUE; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; PREPARSE_DATA_BUFFER pReparseBuffer; UCHAR ReparseBuffer[sizeof(REPARSE_DATA_BUFFER) + sizeof(RP_DATA) + 10]; NTSTATUS ntStatus; FILE_BASIC_INFORMATION basicInformation; WsbTraceIn(OLESTR("CFsaScanItem::DeletePlaceholder"), OLESTR("")); // // Remove the Reparse Point off the file // try { // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); // Make sure it is read/write WsbAffirmHr( MakeReadWrite() ); // // Open the file to remove the placeholder information in the reparse point // DesiredAccess = FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes // indicate the file is offline // basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes &= ~BIT_FOR_TRUNCATED; basicInformation.FileAttributes |= FILE_ATTRIBUTE_NORMAL; // Just in case result was zero (then no attributes would be set) WsbAssertNtStatus( NtSetInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); m_findData.dwFileAttributes &= ~BIT_FOR_TRUNCATED; m_originalAttributes &= ~BIT_FOR_TRUNCATED; // // Get the pointers setup correctly to this buffer because the // type REPARSE_DATA_BUFFER actually doesn't have any space // allocated for the data and that is our own type, so get pointers // pointing into the real allocated space so we can use them // pReparseBuffer = (PREPARSE_DATA_BUFFER)ReparseBuffer; pReparseBuffer->ReparseTag = IO_REPARSE_TAG_HSM ; pReparseBuffer->ReparseDataLength = 0 ; pReparseBuffer->Reserved = 0 ; // // Do the work of deleting the Reparse Point // ntStatus = NtFsControlFile( handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_DELETE_REPARSE_POINT, pReparseBuffer, FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer), NULL, 0 ); // // Check the return code - verify this is the correct way to check // WsbAssertNtStatus( ntStatus ); // // Close the file since we are done with it and set the handle to invalid // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; // // Now that everything worked change the in memory flags for // this scan item // m_findData.dwFileAttributes &= ~BIT_FOR_RP; m_gotPlaceholder = FALSE; } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CFsaScanItem::DeletePlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::GetFromRPIndex( BOOL first ) /* Get file information from the Reparse Point Index --*/ { HRESULT hr = S_OK; BOOLEAN bFirst; bFirst = (BOOLEAN)( first ? TRUE : FALSE ); WsbTraceIn(OLESTR("CFsaScanItem::GetFromRPIndex"), OLESTR("")); try { HRESULT hrFindFileId; IO_STATUS_BLOCK IoStatusBlock; IFsaScanItem * pScanItem; FILE_REPARSE_POINT_INFORMATION ReparsePointInfo; NTSTATUS Status; WsbAssert(0 != m_handleRPI, E_FAIL); try_again: Status = NtQueryDirectoryFile(m_handleRPI, NULL, // Event NULL, // ApcRoutine NULL, // ApcContext &IoStatusBlock, &ReparsePointInfo, sizeof(ReparsePointInfo), FileReparsePointInformation, TRUE, // ReturnSingleEntry NULL, // FileName bFirst ); // RestartScan if (Status != STATUS_SUCCESS) { WsbTrace(OLESTR("CFsaScanItem::GetFromRPIndex: CreateFileW failed, GetLastError = %ld\n"), GetLastError()); WsbThrow(WSB_E_NOTFOUND); } // Reset some items in case this isn't the first call to // FindFileId if (INVALID_HANDLE_VALUE != m_handle) { FindClose(m_handle); m_handle = INVALID_HANDLE_VALUE; } if (TRUE == m_changedAttributes) { RestoreAttributes(); } // Find the file from the ID (not efficient or elegant, perhaps, but // the code is already there). pScanItem = this; hrFindFileId = m_pResource->FindFileId(ReparsePointInfo.FileReference, m_pSession, &pScanItem); // If the FindFileId failed, we just skip that item and get the // next one. This is to keep the scan from just stopping on this // item. FindFileId could fail because the file has been deleted // already or the NT code could have a bug that prevents finding // the file name from the ID when the ID ends with 0x5C. if (!SUCCEEDED(hrFindFileId)) { bFirst = FALSE; goto try_again; } WsbAffirmHr(pScanItem->Release()); // Get rid of extra ref. count } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::GetFromRPIndex"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::CheckUsnJournalForChanges( LONGLONG StartUsn, LONGLONG StopUsn, BOOL* pChanged ) /* Check the USN Journal for changes to the unnamed data stream for this file between the given USNs. --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::CheckUsnJournalForChanges"), OLESTR("")); *pChanged = FALSE; try { LONGLONG fileId; CWsbStringPtr volName; ULONGLONG volUsnJournalId; WsbAffirm(StartUsn <= StopUsn, E_UNEXPECTED); WsbAffirmHr(m_pResource->GetPath(&volName, 0)); WsbAffirmHr(m_pResource->GetUsnId(&volUsnJournalId)); WsbAffirmHr(GetFileId(&fileId)); WsbAffirmHr(WsbCheckUsnJournalForChanges(volName, volUsnJournalId, fileId, StartUsn, StopUsn, pChanged)); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::CheckUsnJournalForChanges"), OLESTR("changed = %ls, hr = <%ls>"), WsbBoolAsString(*pChanged), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::FindFirstInRPIndex( IN IFsaResource* pResource, IN IHsmSession* pSession ) /*++ Implements: IFsaResource::FindFirstInRPIndex --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::FindFirstInRPIndex"), OLESTR("")); try { CWsbStringPtr path; WsbAssert(0 != pResource, E_POINTER); // Store off some of the scan information. m_pResource = pResource; m_pSession = pSession; // Generate the Reparse Point Index directory name for this volume WsbAffirmHr(pResource->GetPath(&path, 0)); WsbAffirmHr(path.Prepend("\\\\?\\")); WsbAffirmHr(path.Append("$Extend\\$Reparse:$R:$INDEX_ALLOCATION")); WsbTrace(OLESTR("CFsaScanItem::FindFirstInRPIndex: path = <%ls>\n"), static_cast(path)); // Open the Reparse Point Index m_handleRPI = CreateFileW(static_cast(path), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | SECURITY_IMPERSONATION, NULL ); if (INVALID_HANDLE_VALUE == m_handleRPI) { WsbTrace(OLESTR("CFsaScanItem::FindFirstInRPIndex: CreateFileW failed, GetLastError = %ld\n"), GetLastError()); WsbThrow(WSB_E_NOTFOUND); } // Get file information WsbAffirmHr(GetFromRPIndex(TRUE)); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::FindFirstInRPIndex"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::FindFirstPlaceholder( IN OUT LONGLONG* pOffset, IN OUT LONGLONG* pSize, IN OUT FSA_PLACEHOLDER* pPlaceholder ) /*++ Implements: IFsaScanItem::FindFirstPlaceholder(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::FindFirstPlaceholder"), OLESTR("")); try { WsbAssert(0 != pOffset, E_POINTER); WsbAssert(0 != pSize, E_POINTER); WsbAssert(0 != pPlaceholder, E_POINTER); // Until these routines get rewritten, assume that the first placeholder is the one for the // who file that is returned by GetPlaceholder(). *pOffset = 0; WsbAffirmHr(GetLogicalSize(pSize)); // The code above assumes that a WSB_E_NOTFOUND error will be returned if there is no // reparse point. try { WsbAffirmHr(GetPlaceholder(*pOffset, *pSize, pPlaceholder)); } WsbCatchAndDo(hr, if (E_UNEXPECTED == hr) {hr = WSB_E_NOTFOUND;}); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::FindFirstPlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::FindNextInRPIndex( void ) /*++ Implements: IFsaResource::FindNextInRPIndex --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::FindNextInRPIndex"), OLESTR("")); try { WsbAssert(0 != m_handleRPI, E_FAIL); // Get file information WsbAffirmHr(GetFromRPIndex(FALSE)); } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::FindNextInRPIndex"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::FindNextPlaceholder( IN OUT LONGLONG* pOffset, IN OUT LONGLONG* pSize, IN OUT FSA_PLACEHOLDER* pPlaceholder ) /*++ Implements: IFsaScanItem::FindNextPlaceholder(). --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::FindNext"), OLESTR("")); try { WsbAssert(0 != pOffset, E_POINTER); WsbAssert(0 != pSize, E_POINTER); WsbAssert(0 != pPlaceholder, E_POINTER); // Until these routines get rewritten, assume there is only one placeholder. hr = WSB_E_NOTFOUND; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::FindNextPlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::GetFileId( OUT LONGLONG* pFileId ) /*++ Implements: IFsaScanItem::GetFileId(). --*/ { HANDLE handle = INVALID_HANDLE_VALUE; HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::GetFileId"), OLESTR("")); try { ULONG DesiredAccess; FILE_INTERNAL_INFORMATION iInfo; IO_STATUS_BLOCK IoStatusBlock; CWsbStringPtr path; WsbAssert(0 != pFileId, E_POINTER); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); WsbTrace(OLESTR("CFsaScanItem::GetFileId, full Path = <%ls>\n"), static_cast(path)); // // Open the file // DesiredAccess = FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // Get the internal information WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, &iInfo, sizeof(FILE_INTERNAL_INFORMATION), FileInternalInformation )); // Get the file id *pFileId = iInfo.IndexNumber.QuadPart; // // Close the file since we are done with it and set the handle to invalid // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; } WsbCatchAndDo(hr, WsbTrace(OLESTR("CFsaScanItem::GetFileId, GetLastError = %lx\n"), GetLastError()); ); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CFsaScanItem::GetFileId"), OLESTR("Hr = <%ls>, FileId = %I64x"), WsbHrAsString(hr), *pFileId); return(hr); } HRESULT CFsaScanItem::GetFileUsn( OUT LONGLONG* pFileUsn ) /*++ Routine Description: Get the current USN Journal number for this file. Arguments: pFileUsn - Pointer to File USN to be returned. Return Value: S_OK - success --*/ { HANDLE handle = INVALID_HANDLE_VALUE; HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::GetFileUsn"), OLESTR("")); try { ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; CWsbStringPtr path; WsbAssert(pFileUsn, E_POINTER); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); WsbTrace(OLESTR("CFsaScanItem::GetFileUsn, full Path = <%ls>\n"), static_cast(path)); // // Open the file // DesiredAccess = FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // Get the internal information WsbAffirmHr(WsbGetUsnFromFileHandle(handle, FALSE, pFileUsn)); // // Close the file since we are done with it and set the handle to invalid // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; } WsbCatchAndDo(hr, WsbTrace(OLESTR("CFsaScanItem::GetFileUsn, GetLastError = %lx\n"), GetLastError()); ); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CFsaScanItem::GetFileUsn"), OLESTR("Hr = <%ls>, FileUsn = %I64d"), WsbHrAsString(hr), *pFileUsn); return(hr); } HRESULT CFsaScanItem::GetPlaceholder( IN LONGLONG offset, IN LONGLONG size, OUT FSA_PLACEHOLDER* pPlaceholder ) /*++ Implements: IFsaScanItem::GetPlaceholder(). --*/ { WsbTraceIn(OLESTR("CFsaScanItem::GetPlaceholder"), OLESTR("")); HRESULT hr = S_OK; // // If we already have the placeholder information just return it // try { // // Validate the file is managed. If it is the affirm will succeed. // If the file is not managed then we can only tell the caller the // problem. // WsbAffirmHr(hr = IsManaged(offset, size)); // // Make sure the file is managed - will return S_OK // WsbAffirm( S_OK == hr, FSA_E_NOTMANAGED ); // // Assert that the internal flag for the data is set, should // always be on if the hr was S_OK above // WsbAssert( m_gotPlaceholder, E_UNEXPECTED ); // // Copy the data to the callers structure // *pPlaceholder = m_placeholder; } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::GetPlaceholder"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::HasExtendedAttributes( void ) /*++ Implements: IFsaScanItem::HasExtendedAttributes(). --*/ { HRESULT hr = S_FALSE; HANDLE handle = INVALID_HANDLE_VALUE; CWsbStringPtr path; ULONG desiredAccess; IO_STATUS_BLOCK ioStatusBlock; FILE_EA_INFORMATION eaInformation; try { // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null WsbAffirmHr(GetFullPathAndName(OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr(GetFullPathAndName(NULL, NULL, &path, 0)); // Open the file to get the attributes desiredAccess = FILE_READ_ATTRIBUTES; WsbAffirmHr(OpenObject(path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, desiredAccess, SHARE_FLAGS, FILE_OPEN, &ioStatusBlock, &handle)); // The open worked, our handle should be valid but we check to be // safe and sure WsbAssertHandle(handle); // Get the current attributes of the file. WsbAssertNtStatus(NtQueryInformationFile(handle, &ioStatusBlock, (VOID*) &eaInformation, sizeof(eaInformation ), FileEaInformation)); // Close the file since we are done with it and set the handle to invalid WsbAssertNtStatus(NtClose(handle)); handle = INVALID_HANDLE_VALUE; // Are there any EAs present? if (eaInformation.EaSize != 0) { hr = S_OK; } } WsbCatch(hr); // if we opened the file we need to close it if (INVALID_HANDLE_VALUE != handle) { NtClose(handle); } return(hr); } HRESULT CFsaScanItem::IsALink( void ) /*++ Implements: IFsaScanItem::IsALink(). --*/ { HRESULT hr = S_FALSE; LONGLONG size; // // The file is a link if it is a reparse point and it is not our // type. // WsbAffirmHr(GetLogicalSize(&size)); if (((m_findData.dwFileAttributes & BIT_FOR_RP) != 0) && (!(IsManaged(0, size) == S_OK))) { hr = S_OK; } return(hr); } HRESULT CFsaScanItem::IsManaged( IN LONGLONG /*offset*/, IN LONGLONG /*size*/ ) /*++ Implements: IFsaScanItem::IsManaged(). --*/ { HRESULT hr = S_FALSE; CWsbStringPtr path; HANDLE handle = INVALID_HANDLE_VALUE; IO_STATUS_BLOCK IoStatusBlock; UCHAR ReparseBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; NTSTATUS ntStatus; ULONG DesiredAccess; BOOL actualOfflineStatus = FALSE; BOOL readReparseData = FALSE; // Used to know if we got an error reading the reparse data BOOL changeOfflineStatus = FALSE; FILE_BASIC_INFORMATION basicInformation; CWsbStringPtr volName; HRESULT saveHr; WsbTraceIn(OLESTR("CFsaScanItem::IsManaged"), OLESTR("")); // // If the file has a reparse point then we need to get the information // so we can tell if it is our type. Whether it is premigrated or // truncate is not for this function to care, if it is either then // the return is S_OK. // // // If we already know we manage this file and have the placeholder // information then tell caller // if ( m_gotPlaceholder) { hr = S_OK; actualOfflineStatus = m_placeholder.isTruncated; readReparseData = TRUE; // // We don't know the answer so lets first check the reparse point bit. // If it is not set then this is not managed by us // } else if ( (m_findData.dwFileAttributes & BIT_FOR_RP) == 0) { hr = S_FALSE; actualOfflineStatus = FALSE; readReparseData = TRUE; // // So we know it has a reparse point but do not know what kind so // lets get the data and fill in our global if we need // } else { try { // // If the reparse point is not our type we get out now. This avoids a problem with SIS keeping // the backing file open when one of their link files is open. Once we open the link file the backing file is // opened by their filter and held open. If we attempt to migrate it later we get an error because it is open exclusive. // This bit of code prevents us from being the one to trigger this condition - there is nothing we can do if some other // process caused it to happen. // if (m_findData.dwReserved0 != IO_REPARSE_TAG_HSM) { readReparseData = TRUE; WsbThrow(S_FALSE); } // // Create the real file name we need to open, under the // covers this allocates the buffer since the path pointer // is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); // // Open the file to read the placeholder information in the reparse point // //DesiredAccess = FILE_READ_DATA | FILE_READ_ATTRIBUTES ; DesiredAccess = FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // // Read the placeholder information // ntStatus = NtFsControlFile( handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_GET_REPARSE_POINT, NULL, 0, &ReparseBuffer, sizeof( ReparseBuffer ) ); // // Verify that the get really worked. NOTE: If the reparse // point is not there, it could be that it has been deleted since // we last got the bits. We should just indicate that the file // is not managed. // if (STATUS_NOT_A_REPARSE_POINT == ntStatus) { readReparseData = TRUE; WsbThrow(S_FALSE); } WsbAssertNtStatus( ntStatus ); // // Close the file since we are done with it // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; readReparseData = TRUE; // // Get the pointers setup correctly to this buffer because the // type REPARSE_DATA_BUFFER actually doesn't have any space // allocated for the data and that is our own type, so get pointers // pointing into the real allocated space so we can use them // WsbAffirmHrOk( CopyRPToPlaceholder( (PREPARSE_DATA_BUFFER)ReparseBuffer, &m_placeholder ) ); actualOfflineStatus = m_placeholder.isTruncated; // // Set flag indicating placeholder found and information in memory // m_gotPlaceholder = TRUE; hr = S_OK; } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } } saveHr = hr; // Check the actual offline status against the offline bit and fix it if necessary. if (readReparseData) { // If there was no error getting the reparse data WsbTrace(OLESTR("CFsaScanItem::IsManaged: Checking offline status %x - actual = %x\n"), m_findData.dwFileAttributes & BIT_FOR_TRUNCATED, actualOfflineStatus ); switch (actualOfflineStatus) { case TRUE: if (!(m_findData.dwFileAttributes & BIT_FOR_TRUNCATED)) { // Offline bit is not set and should be - set it. m_findData.dwFileAttributes |= BIT_FOR_TRUNCATED; m_originalAttributes |= BIT_FOR_TRUNCATED; // Just in case we have changed to read/write; changeOfflineStatus = TRUE; } break; case FALSE: if (m_findData.dwFileAttributes & BIT_FOR_TRUNCATED) { // Offline bit is set and should not be - clear it. m_findData.dwFileAttributes &= ~BIT_FOR_TRUNCATED; m_originalAttributes &= ~BIT_FOR_TRUNCATED; // Just in case we have changed to read/write; changeOfflineStatus = TRUE; } break; } if (changeOfflineStatus) { // Set the new attribute WsbTrace(OLESTR("CFsaScanItem::IsManaged: Changing offline status %x - actual = %x\n"), m_findData.dwFileAttributes & BIT_FOR_TRUNCATED, actualOfflineStatus ); try { // // Create the real file name we need to open, under the // covers this allocates the buffer since the path pointer // is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); // // Open the file to set attributes // DesiredAccess = FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); WsbAffirmHr(m_pResource->GetPath(&volName, 0)); WsbAffirmHr(WsbMarkUsnSource(handle, volName)); // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes = m_findData.dwFileAttributes; WsbAffirmNtStatus(NtSetInformationFile(handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof(basicInformation), FileBasicInformation)); // // Close the file since we are done with it // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } } } hr = saveHr; WsbTraceOut(OLESTR("CFsaScanItem::IsManaged"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::IsPremigrated( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::IsPremigrated(). --*/ { HRESULT hr = S_FALSE; HRESULT hrTest = S_FALSE; WsbTraceIn(OLESTR("CFsaScanItem::IsPremigrated"), OLESTR("")); // We really need to look at the placeholder information to figure // this out (is offline, and is out type of HSM. // // If the file is NOT truncated AND is a reparse point and is a // managed one then the file is a premigrated file // // if ( !(m_findData.dwFileAttributes & BIT_FOR_TRUNCATED) && // m_findData.dwFileAttributes & BIT_FOR_RP && // IsManaged() == S_OK) { try { if ( m_findData.dwFileAttributes & BIT_FOR_RP ) { WsbAffirmHr(hrTest = IsManaged(offset, size)); if ((S_OK == hrTest) && ( !m_placeholder.isTruncated )) { hr = S_OK; } } } WsbCatch (hr); WsbTraceOut(OLESTR("CFsaScanItem::IsPremigrated"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::IsTruncated( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::IsTruncated(). --*/ { HRESULT hr = S_FALSE; HRESULT hrTest = S_FALSE; WsbTraceIn(OLESTR("CFsaScanItem::IsTruncated"), OLESTR("")); // // If the bit is on that indicates we have truncated the file AND // the file is a reparse point AND the reparse point is one of // our types (i.e. it really is our information stuffed away in // there the it really is a truncated file // // if ( // ???? m_findData.dwFileAttributes & BIT_FOR_TRUNCATED && // m_findData.dwFileAttributes & BIT_FOR_RP && // IsManaged() == S_OK && RP_FILE_IS_TRUNCATED( m_placeholder.bitFlags ) ) { try { if ( m_findData.dwFileAttributes & BIT_FOR_RP ) { WsbAffirmHr(hrTest = IsManaged(offset, size)); if ((S_OK == hrTest) && ( m_placeholder.isTruncated )) { hr = S_OK; } } } WsbCatch (hr); WsbTraceOut(OLESTR("CFsaScanItem::IsTruncated"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::GetVersionId( LONGLONG *fileVersionId ) /*++ Implements: IFsaScanItem::GetVersionId(). --*/ { HRESULT hr = E_FAIL; HANDLE handle = INVALID_HANDLE_VALUE; CWsbStringPtr path; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; FILE_BASIC_INFORMATION basicInformation; try { // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); // // Open the file to get the attributes // DesiredAccess = FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Close the file since we are done with it and set the handle to invalid // WsbAssertNtStatus( NtClose( handle ) ); handle = INVALID_HANDLE_VALUE; *fileVersionId = basicInformation.LastWriteTime.QuadPart; hr = S_OK; } WsbCatch( hr ); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } return( hr ); } HRESULT CFsaScanItem::MakeReadWrite( ) /*++ Routine Description: Make the file attributes read/write if they aren't already. Arguments: pUsn - Pointer to File USN to check (if != 0) and to be returned after the change. Return Value: S_OK - success --*/ { HRESULT hr = S_OK; CWsbStringPtr path; IO_STATUS_BLOCK IoStatusBlock; HANDLE handle = INVALID_HANDLE_VALUE; FILE_BASIC_INFORMATION basicInformation; if (S_OK == IsReadOnly()) { try { // NOTE: MakeReadOnly(), IsReadOnly(), and RestoreAttributes() seem like dangerous implementations, since // the used cached information and reset all the attirbutes. It is also assuming that the // application wants the file reset to read only after FindNext() or the destructor. This // may not be true for a general purpose application. Unfortunately, it seems to risky to // try to change this implementation now. // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null WsbAffirmHr(GetFullPathAndName(OLESTR("\\\\?\\"), NULL, &path, 0)); // Open the file. WsbAffirmHr(OpenObject(path, FILE_NON_DIRECTORY_FILE, FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &handle)); // The open worked, our handle should be valid but we check to be // safe and sure WsbAffirmHandle(handle); // Get the current attributes of the file and the times WsbAffirmNtStatus(NtQueryInformationFile(handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof(basicInformation), FileBasicInformation)); // Make sure it is still read only. if ((basicInformation.FileAttributes & FILE_ATTRIBUTE_READONLY) != 0) { m_originalAttributes = basicInformation.FileAttributes; // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; basicInformation.FileAttributes |= FILE_ATTRIBUTE_NORMAL; // Just in case result was zero (then no attributes would be set) WsbAffirmNtStatus(NtSetInformationFile(handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof(basicInformation), FileBasicInformation)); m_changedAttributes = TRUE; } // Close the file NtClose(handle); handle = INVALID_HANDLE_VALUE; } WsbCatch(hr); // Close the file for sure if (INVALID_HANDLE_VALUE != handle) { NtClose(handle); } } return(hr); } HRESULT CFsaScanItem::PrepareForManage( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::PrepareForManage(). --*/ { UNREFERENCED_PARAMETER(offset); UNREFERENCED_PARAMETER(size); return S_OK; } HRESULT CFsaScanItem::RestoreAttributes( ) /*++ Implements: IFsaScanItem::RestoreAttributes --*/ { HRESULT hr = E_FAIL; CWsbStringPtr path; IO_STATUS_BLOCK IoStatusBlock; HANDLE handle = INVALID_HANDLE_VALUE; FILE_BASIC_INFORMATION basicInformation; try { // NOTE: MakeReadOnly(), IsReadOnly(), and RestoreAttributes() seem like dangerous implementations, since // the used cached information and reset all the attirbutes. It is also assuming that the // application wants the file reset to read only after FindNext() or the destructor. This // may not be true for a general purpose application. Unfortunately, it seems to risky to // try to change this implementation now. // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null WsbTrace(OLESTR("CFsaScanItem::RestoreAttributes - Restoring attributes to %x"), m_originalAttributes); WsbAffirmHr(GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); // Open the file. WsbAffirmHr(OpenObject(path, FILE_NON_DIRECTORY_FILE, FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &handle)); // The open worked, our handle should be valid but we check to be // safe and sure WsbAffirmHandle(handle); // Get the current attributes of the file and the times WsbAffirmNtStatus(NtQueryInformationFile(handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof(basicInformation), FileBasicInformation)); // Set the time flags so that when we close the handle the // time are not updated on the file and the FileAttributes basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes = m_originalAttributes; WsbAffirmNtStatus(NtSetInformationFile(handle, &IoStatusBlock, (PVOID)&basicInformation, sizeof(basicInformation), FileBasicInformation)); // Close the file NtClose(handle); handle = INVALID_HANDLE_VALUE; m_changedAttributes = FALSE; } WsbCatch(hr); // Close the file for sure if (INVALID_HANDLE_VALUE != handle) { NtClose(handle); } return(hr); } HRESULT CFsaScanItem::Truncate( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::Truncate(). --*/ { HRESULT hr = S_OK; BOOL fileIsTruncated = FALSE; LONGLONG usn = 0; WsbTraceIn(OLESTR("CFsaScanItem::Truncate"), OLESTR("")); try { // call the engine if (IsManaged(offset, size) == S_OK) { WsbAffirmHr(m_pResource->ValidateForTruncate((IFsaScanItem*) this, offset, size, usn)); } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::Truncate"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::TruncateValidated( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::TruncateValidated(). --*/ { HRESULT hr = S_OK; HRESULT truncateHr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::TruncateValidated"), OLESTR("")); try { IFsaScanItem* pMe = this; truncateHr = TruncateInternal(offset, size); // // Note: Must check for S_OK since TruncateInternal may return FSA_E_ITEMCHANGED or FSA_E_ITEMINUSE // Both are "Success hr", but imply no truncation was done // if (S_OK == truncateHr) { WsbAffirmHr(m_pResource->RemovePremigrated(pMe, offset, size)); WsbAffirmHr(m_pResource->AddTruncated(pMe, offset, size)); } } WsbCatch(hr); // The important hr to return to the caller is the actual result of the truncation hr = truncateHr; WsbTraceOut(OLESTR("CFsaScanItem::TruncateValidated"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::TruncateInternal( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::TruncateInternal(). --*/ { HRESULT hr = E_FAIL; CWsbStringPtr path; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS ntStatus; FILE_END_OF_FILE_INFORMATION sizeInformation; FILE_BASIC_INFORMATION basicInformation; PREPARSE_DATA_BUFFER pReparseBuffer; UCHAR ReparseBuffer[sizeof(REPARSE_DATA_BUFFER) + sizeof(RP_DATA) + 10]; CWsbStringPtr fileName; CWsbStringPtr jobName; LONGLONG fileVersionId; ULONG i = 0; CWsbStringPtr volName; WsbTraceIn(OLESTR("CFsaScanItem::TruncateInternal"), OLESTR("")); // Putting these statistics in the registry is probably not the best // place for them, but it's the easiest solution for now #define TEMPORARY_TRUNCATE_STATISTICS 1 #if defined(TEMPORARY_TRUNCATE_STATISTICS) // Try to increment the truncate-attempt count in the registry WsbIncRegistryValueDWORD(NULL, FSA_REGISTRY_PARMS, OLESTR("TruncateCalls")); #endif // Get strings for tracing and error logging (ignore errors?!) GetFullPathAndName( 0, 0, &fileName, 0); m_pSession->GetName(&jobName, 0); m_handleVerify = INVALID_HANDLE_VALUE; try { LONGLONG fileUsn1 = 0, fileUsn2 = 0; // If the file is not migrated, then we can't truncate it if (S_OK != IsPremigrated(offset, size)) { if (S_OK != IsManaged(offset, size)) { hr = FSA_E_NOTMANAGED; WsbLogEvent(FSA_MESSAGE_TRUNCSKIPPED_ISNOTMANAGED, 0, NULL, (OLECHAR*) jobName, WsbAbbreviatePath(fileName, 120), WsbHrAsString(hr), NULL); WsbThrow(hr); } else { // // Do not bother to log an event here as this should only // happen if someone uses rstest or some other program // to truncate a file that is already truncated. WsbThrow(FSA_E_FILE_ALREADY_MANAGED); } } WsbAssert( m_gotPlaceholder, E_UNEXPECTED ); // // Setup the reparse point data with that which was on the file // with the bit in the data indicating it is truncated // pReparseBuffer = (PREPARSE_DATA_BUFFER)ReparseBuffer; WsbAffirmHr( CopyPlaceholderToRP( &m_placeholder, pReparseBuffer, TRUE ) ); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); // // Open the file exclusively for read-only so we can get the usn before and after // making the file R/W, without letting anybody to make a "real" change in the middle // DesiredAccess = FILE_READ_DATA | FILE_READ_ATTRIBUTES; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE, DesiredAccess, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &m_handleVerify ) ); WsbAssertHandle( m_handleVerify ); // // Get usn before making R/W // This usn is used to compare with the usn which we kept in the premigrated list. // MakeReadWrite may chnage the usn so we need to get it before // if (S_OK != WsbGetUsnFromFileHandle(m_handleVerify, FALSE, &fileUsn1)) { fileUsn1 = 0; } // Make sure it is read/write WsbAffirmHr( MakeReadWrite() ); // // Get usn after making R/W // This usn will be use to compare with the usn of the file after we'll open it for R/W. We need // this comparison in order to ensure that nobody changed the file before we opened it again for R/W. // if (S_OK != WsbGetUsnFromFileHandle(m_handleVerify, TRUE, &fileUsn2)) { fileUsn2 = 0; } // Close the file NtClose( m_handleVerify ); m_handleVerify = INVALID_HANDLE_VALUE; // // Open the file (for R/W) // DesiredAccess = FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH, DesiredAccess, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &m_handleVerify ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( m_handleVerify ); // // Tell the USN journal that we are the source of the changes. // WsbAffirmHr(m_pResource->GetPath(&volName, 0)); WsbAffirmHr(WsbMarkUsnSource(m_handleVerify, volName)); // // Get the current attributes of the file and the times // WsbAffirmNtStatus( NtQueryInformationFile( m_handleVerify, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); fileVersionId = basicInformation.LastWriteTime.QuadPart; // // Set the time flags so that when we close the handle the // times are not updated on the file and the FileAttributes // indicate the file is offline // basicInformation.CreationTime.QuadPart = -1; basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes = 0; // Do not change attributes yet WsbAffirmNtStatus( NtSetInformationFile( m_handleVerify, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); // // Do the check to see if the file changed // hr = VerifyInternal(offset, size, fileUsn1, fileUsn2); // // Note: Must check for S_OK since VerifyInternal may return FSA_E_ITEMCHANGED or FSA_E_ITEMINUSE // Both are "Success hr", but should cause no truncation !! // if (S_OK != hr) { WsbThrow(hr); } // // Change the in memory flags for this scan item // m_findData.dwFileAttributes |= BIT_FOR_TRUNCATED; // // Rewrite the reparse point with the new flag // ntStatus = NtFsControlFile( m_handleVerify, NULL, NULL, NULL, &IoStatusBlock, FSCTL_SET_REPARSE_POINT, pReparseBuffer, FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) + pReparseBuffer->ReparseDataLength, NULL, 0 ); // // Check the return code // WsbAffirmNtStatus( ntStatus ); // // It really happened so we need to flip the in memory copy of the // isTruncated flag so it reflects reality // m_placeholder.isTruncated = TRUE; // // Set the file size to zero to truncate the file sizeInformation.EndOfFile.QuadPart = 0 ; WsbAffirmNtStatus( NtSetInformationFile( m_handleVerify, &IoStatusBlock, &sizeInformation, sizeof( sizeInformation ), FileEndOfFileInformation ) ); // // Set the logical file size to the original size sizeInformation.EndOfFile.QuadPart = m_placeholder.dataStreamSize; WsbAffirmNtStatus( NtSetInformationFile( m_handleVerify, &IoStatusBlock, &sizeInformation, sizeof( sizeInformation ), FileEndOfFileInformation ) ); // // Now that the truncation is complete we set the OFFLINE attribute. // basicInformation.CreationTime.QuadPart = -1; // Make sure we do nothing with dates basicInformation.LastAccessTime.QuadPart = -1; basicInformation.LastWriteTime.QuadPart = -1; basicInformation.ChangeTime.QuadPart = -1; basicInformation.FileAttributes = m_findData.dwFileAttributes; WsbAffirmNtStatus(NtSetInformationFile( m_handleVerify, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation )); // Since we have restored the original attributes we can reset the flag that was possibly set by MakeReadWrite m_changedAttributes = FALSE; hr = S_OK; } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != m_handleVerify) { NtClose( m_handleVerify ); m_handleVerify = INVALID_HANDLE_VALUE; } // If the file data had changed (so we didn't truncate it) log event and // remove placeholder info if (FSA_E_ITEMCHANGED == hr) { WsbLogEvent(FSA_MESSAGE_TRUNCSKIPPED_ISCHANGED, 0, NULL, (OLECHAR*) jobName, WsbAbbreviatePath(fileName, 80), WsbHrAsString(hr), NULL); DeletePlaceholder(offset, size); } WsbTraceOut(OLESTR("CFsaScanItem::TruncateInternal"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::Verify( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::Verify(). --*/ { HRESULT hr = E_FAIL; CWsbStringPtr path; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; WsbTraceIn(OLESTR("CFsaScanItem::Verify"), OLESTR("")); m_handleVerify = INVALID_HANDLE_VALUE; try { WsbAssert( m_gotPlaceholder, E_UNEXPECTED ); // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); // // Open the file // DesiredAccess = FILE_READ_DATA | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES ; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH, DesiredAccess, EXCLUSIVE_FLAG, FILE_OPEN, &IoStatusBlock, &m_handleVerify ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( m_handleVerify ); // // Do the check to see if the file changed // Note that it throws rather than affirms because FSA_E_ITEMCHANGED is a success WsbThrow(VerifyInternal(offset, size, 0, 0)); } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != m_handleVerify) { NtClose( m_handleVerify ); m_handleVerify = INVALID_HANDLE_VALUE; } WsbTraceOut(OLESTR("CFsaScanItem::Verify"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::VerifyInternal( IN LONGLONG offset, IN LONGLONG size, IN LONGLONG compareUsn1, IN LONGLONG compareUsn2 ) /*++ Implements: IFsaScanItem::VerifyInternal(). Note: This requires that m_handleVerify is set up with a handle to the file being verified. --*/ { HRESULT hr = E_FAIL; IO_STATUS_BLOCK IoStatusBlock; FILE_BASIC_INFORMATION basicInformation; CWsbStringPtr fileName; CWsbStringPtr jobName; LONGLONG fileVersionId; ULONG i = 0; CWsbStringPtr volName; LONGLONG realFileSize; RP_MSG in, out; DWORD outSize; WsbTraceIn(OLESTR("CFsaScanItem::VerifyInternal"), OLESTR("")); // Get strings for tracing and error logging (ignore errors?!) GetFullPathAndName( 0, 0, &fileName, 0); m_pSession->GetName(&jobName, 0); try { BOOL DoCRC = FALSE; BOOL DoUsnCheck = FALSE; LONGLONG premigListUsn; LONGLONG fileUsn; WsbAffirmHr(GetLogicalSize(&realFileSize)); // // Currently, avoid offset and size verification: // - Since we are not doing partial file migration, offset is always 0. // - Size in Validate would always be the same since it is taken from GetLogicalSize as well. // - Size in case of auto-truncation is not reliable since it is taken from the premigrated db, // where there could be bogus records from previous migrations of the file // /*** if ( (realFileSize != size) || (offset != 0) ) { WsbThrow(FSA_E_ITEMCHANGED); } ***/ UNREFERENCED_PARAMETER(size); // WsbAssertHandle( m_handleVerify ); // // Get the USN from the premigration list and the USN from the file. // We need to get the USN from the file now, before any NtSetInformationFile // is called because this changes the USN value. // If we have trouble getting the USN, just set them // to 0 and go on, we check for 0 as a special case. // if (S_OK != GetPremigratedUsn(&premigListUsn)) { premigListUsn = 0; } if (S_OK != WsbGetUsnFromFileHandle(m_handleVerify, FALSE, &fileUsn)) { fileUsn = 0; } WsbTrace(OLESTR("CFsaScanItem::VerifyInternal: premig USN <%I64d>, file USN <%I64d>\n"), premigListUsn, fileUsn ); WsbTrace(OLESTR("CFsaScanItem::VerifyInternal: Compare1 USN <%I64d>, Compare2 USN <%I64d>\n"), compareUsn1, compareUsn2 ); // // Get the current attributes of the file and the times // WsbAssertNtStatus( NtQueryInformationFile( m_handleVerify, &IoStatusBlock, (PVOID)&basicInformation, sizeof( basicInformation ), FileBasicInformation ) ); fileVersionId = basicInformation.LastWriteTime.QuadPart; // // Verify that the modify date & time has not changed since we // took the data // if( fileVersionId != m_placeholder.fileVersionId ) { WsbThrow(FSA_E_ITEMCHANGED); } // // If the file is memory mapped by another process and the original handle was closed we // are still able to open it for exclusive access here. We have to determine if the file // is mapped and if so we cannot truncate it. The only way to do this is from kernel // mode so we call our filter to do the check. // in.inout.command = RP_CHECK_HANDLE; WsbAssertStatus(DeviceIoControl(m_handleVerify, FSCTL_HSM_MSG, &in, sizeof(RP_MSG), &out, sizeof(RP_MSG), &outSize, NULL)); if (!out.msg.hRep.canTruncate) { hr = FSA_E_ITEMINUSE; WsbLogEvent(FSA_MESSAGE_TRUNCSKIPPED_ISMAPPED, 0, NULL, (OLECHAR*) jobName, WsbAbbreviatePath(fileName, 120), WsbHrAsString(hr), NULL); WsbThrow(hr); } // If the USN's don't match, then we need to check the // journal for changes // premigListUsn: The usn of the file immediately after it was migrated // compareUsn1: If not 0, the usn of the file before we (possibly) removed a read-only attribute // compareUsn2: If not 0, // if ((0 == fileUsn) || (0 == premigListUsn)) { // We don't have USN Journal info so force a CRC comparison DoCRC = TRUE; } else if ((compareUsn1 != 0) && (compareUsn2 != 0)) { // Need to compare with these input usn instead of a direct compare if ((premigListUsn != compareUsn1) || (fileUsn != compareUsn2)) { DoUsnCheck = TRUE; } } else if (fileUsn != premigListUsn) { DoUsnCheck = TRUE; } // Current usn indicates that file may have changed if (DoUsnCheck) { BOOL UsnChanged = FALSE; hr = CheckUsnJournalForChanges(premigListUsn, fileUsn, &UsnChanged); if (S_OK == hr) { if (UsnChanged) { // File changed, skip it WsbThrow(FSA_E_ITEMCHANGED); } } else { // Something failed, force a CRC comparison DoCRC = TRUE; WsbLogEvent(FSA_MESSAGE_USN_CHECK_FAILED, 0, NULL, WsbAbbreviatePath(fileName,120), WsbHrAsString(hr), NULL); hr = S_OK; } } // If the USNJ indicated a possible change, then we need to CRC // the data. if (DoCRC) { // // Check to be sure that the CRC in the placeholder matches // that of the file // ULONG currentCRC; #if defined(TEMPORARY_TRUNCATE_STATISTICS) // Try to increment the truncate-CRC count in the registry WsbIncRegistryValueDWORD(NULL, FSA_REGISTRY_PARMS, OLESTR("TruncateCRCs")); #endif WsbAffirmHr(CalculateCurrentCRCInternal(m_handleVerify, offset, realFileSize, ¤tCRC)); WsbTrace(OLESTR("CFsaScanItem::VerifyInternal: Current CRC <%ul>, Reparse CRC <%ls>\n"), currentCRC, WsbLonglongAsString( m_placeholder.dataStreamCRC ) ); if (currentCRC != m_placeholder.dataStreamCRC) { // // The file has changed since we migrated it so // don't truncate it. WsbThrow(FSA_E_ITEMCHANGED); } } hr = S_OK; } WsbCatch(hr); // If the file data had changed (so we didn't truncate it) log event // (cannot remove placeholder with DeletePlaceholder since the file is already opened exclusively if (FSA_E_ITEMCHANGED == hr) { WsbLogEvent(FSA_MESSAGE_TRUNCSKIPPED_ISCHANGED, 0, NULL, (OLECHAR*) jobName, WsbAbbreviatePath(fileName, 120), WsbHrAsString(hr), NULL); } WsbTraceOut(OLESTR("CFsaScanItem::VerifyInternal"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::CheckIfSparse( IN LONGLONG offset, IN LONGLONG size ) /*++ Implements: IFsaScanItem::CheckIfSparse() Determines if the specified section is on disk or not (is sparse) FSA_E_FILE_IS_TOTALLY_SPARSE - There are no resident portions of the section FSA_E_FILE_IS_PARTIALLY_SPARSE - The section of the file has some resident/some sparse sections FSA_E_FILE_IS_NOT_SPARSE - The section is totally resident any others - error and we don't know the state of the file --*/ { HRESULT hr = E_FAIL; HANDLE handle = INVALID_HANDLE_VALUE; CWsbStringPtr path; ULONG DesiredAccess; IO_STATUS_BLOCK IoStatusBlock; NTSTATUS ntStatus; FILE_ALLOCATED_RANGE_BUFFER inRange; #define NUM_RANGE 10 FILE_ALLOCATED_RANGE_BUFFER outRange[NUM_RANGE]; PFILE_ALLOCATED_RANGE_BUFFER cRange; int idx; WsbTraceIn(OLESTR("CFsaScanItem::CheckIfSparse"), OLESTR("offset = <%I64d>, size = <%I64d>"), offset, size); // // If the file is really managed then we can check the allocation map // Otherwise we indicate that the data is all resident // try { // // Create the real file name we need to open, under the covers this // allocates the buffer since the path pointer is null // WsbAffirmHr( GetFullPathAndName( OLESTR("\\\\?\\"), NULL, &path, 0)); //WsbAffirmHr( GetFullPathAndName( NULL, NULL, &path, 0)); // // Open the file to check the allocation // DesiredAccess = FILE_READ_ATTRIBUTES | FILE_READ_DATA; WsbAffirmHr( OpenObject( path, FILE_NON_DIRECTORY_FILE | FILE_NO_INTERMEDIATE_BUFFERING, DesiredAccess, SHARE_FLAGS, FILE_OPEN, &IoStatusBlock, &handle ) ); // // The open worked, our handle should be valid but we check to be // safe and sure // WsbAssertHandle( handle ); memset(&outRange, 0, sizeof(FILE_ALLOCATED_RANGE_BUFFER) * NUM_RANGE); // // Check the allocation of the specified range // inRange.FileOffset.QuadPart = offset; inRange.Length.QuadPart = size; ntStatus = NtFsControlFile( handle, NULL, NULL, NULL, &IoStatusBlock, FSCTL_QUERY_ALLOCATED_RANGES, &inRange, sizeof(FILE_ALLOCATED_RANGE_BUFFER), &outRange, sizeof(FILE_ALLOCATED_RANGE_BUFFER) * NUM_RANGE); // // Check the return code but STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW are valid // if ( STATUS_SUCCESS != ntStatus && STATUS_BUFFER_OVERFLOW != ntStatus ) { WsbAssertNtStatus( ntStatus ); } cRange = (PFILE_ALLOCATED_RANGE_BUFFER) &outRange; for (idx = 0; idx < NUM_RANGE; idx++) { if (cRange->Length.QuadPart != 0) { WsbTrace(OLESTR("CFsaScanItem::CheckIfSparse - Resident range %u Offset: %I64u, length: %I64u\n"), idx, cRange->FileOffset.QuadPart, cRange->Length.QuadPart); } cRange++; } // // Close the file since we are done with it and set the handle to invalid // NtClose(handle); handle = INVALID_HANDLE_VALUE; // // If the initial allocated range does begin where we said to start and the length of the // allocated area is equal to the length we asked about then none of the data is sparse // if ( (outRange[0].FileOffset.QuadPart == offset) && (outRange[0].Length.QuadPart == size) ) { hr = FSA_E_FILE_IS_NOT_SPARSE; } else if (outRange[0].Length.QuadPart == 0) { hr = FSA_E_FILE_IS_TOTALLY_SPARSE; } else { hr = FSA_E_FILE_IS_PARTIALLY_SPARSE; } } WsbCatch(hr); // // if we opened the file we need to close it // if( INVALID_HANDLE_VALUE != handle) { NtClose( handle ); } WsbTraceOut(OLESTR("CFsaScanItem::CheckIfSparse"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); } HRESULT CFsaScanItem::CheckIfDiskFull( void ) /*++ Implements: CFsaScanItem::CheckIfDiskFull() Determines whether a disk is full or not - will consider full if free-space is below X, where X default is 10 MB Returns: S_OK Disk is full S_FALSE Disk is not full other Unexpected error --*/ { HRESULT hr = S_OK; WsbTraceIn(OLESTR("CFsaScanItem::CheckIfDiskFull"), OLESTR("")); try { ULARGE_INTEGER FreeBytesAvailableToCaller; ULARGE_INTEGER TotalNumberOfBytes; ULARGE_INTEGER TotalNumberOfFreeBytes; // Get volume free space CWsbStringPtr volumePath; WsbAffirmHr(m_pResource->GetPath(&volumePath, 0)); WsbAffirmStatus(GetDiskFreeSpaceEx(volumePath, &FreeBytesAvailableToCaller, &TotalNumberOfBytes, &TotalNumberOfFreeBytes)); // Get default from the Registry and compare ULONG maxSizeDiskFullKB = FSA_MAX_SIZE_DISK_FULL_DEFAULT; WsbAffirmHr(WsbRegistryValueUlongAsString(NULL, FSA_REGISTRY_PARMS, FSA_MAX_SIZE_DISK_FULL, &maxSizeDiskFullKB)); if (maxSizeDiskFullKB == 0) { // This means turning off quota check, i.e. always condiser disk as full hr = S_OK; } else if (TotalNumberOfFreeBytes.QuadPart <= (ULONGLONG )(maxSizeDiskFullKB * 1024)) { // Disk is considered full hr = S_OK; } else { // Disk free space is above the threshold hr = S_FALSE; } } WsbCatch(hr); WsbTraceOut(OLESTR("CFsaScanItem::CheckIfDiskFull"), OLESTR("hr = <%ls>"), WsbHrAsString(hr)); return(hr); }