/*++ Copyright (c) 1991 Microsoft Corporation Module Name: attrrec.hxx Abstract: This module contains the member function definitions for NTFS_ATTRIBUTE_RECORD, which models NTFS attribute records. An Attribute Record may be a template laid over a chunk of memory; in that case, it does not own the memory. It may also be told, upon initialization, to allocate its own memory and copy the supplied data. In that case, it is also responsible for freeing that memory. Attribute Records are passed between Attributes and File Record Segments. A File Record Segment can initialize an Attribute with a list of Attribute Records; when an Attribute is Set into a File Record Segment, it packages itself up into Attribute Records and inserts them into the File Record Segment. File Record Segments also use Attribute Records to scan through their list of attribute records, and to shuffle them around. Author: Bill McJohn (billmc) 14-June-91 Environment: ULIB, User Mode --*/ #include #define _NTAPI_ULIB_ #define _UNTFS_MEMBER_ #include "ulib.hxx" #include "error.hxx" #include "untfs.hxx" #include "wstring.hxx" #include "extents.hxx" #include "attrrec.hxx" #include "attrcol.hxx" #include "ntfsbit.hxx" #include "extents.hxx" #include "upcase.hxx" DEFINE_EXPORTED_CONSTRUCTOR( NTFS_ATTRIBUTE_RECORD, OBJECT, UNTFS_EXPORT ); UNTFS_EXPORT NTFS_ATTRIBUTE_RECORD::~NTFS_ATTRIBUTE_RECORD( ) { Destroy(); } VOID NTFS_ATTRIBUTE_RECORD::Construct( ) /*++ Routine Description: This method is the private worker function for object construction. Arguments: None. Return Value: None. --*/ { _Data = NULL; _MaximumLength = 0; _IsOwnBuffer = FALSE; _DisableUnUse = FALSE; } VOID NTFS_ATTRIBUTE_RECORD::Destroy( ) /*++ Routine Description: This method is the private worker function for object destruction. Arguments: None. Return Value: None. --*/ { if( _IsOwnBuffer && _Data != NULL ) { FREE( _Data ); } _Data = NULL; _MaximumLength = 0; _IsOwnBuffer = FALSE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::Initialize( IN OUT PVOID Data, IN ULONG MaximumLength, IN BOOLEAN MakeCopy ) /*++ Routine Description: This method initializes an NTFS_ATTRIBUTE_RECORD object, handing it a buffer with attribute record data. The caller may also ask the object to make a private copy of the data. Arguments: Data -- supplies a buffer containing the attribute record data the object will own. MaximumLength -- supplies the size of the buffer. MakeCopy -- supplies a flag indicating whether the object should copy the data to a private buffer. Return Value: TRUE upon successful completion. Notes: If MakeCopy is TRUE, then the object must allocate its own buffer and copy the attribute record data to it, in which case the object is also responsible for freeing that private buffer. It that flag is FALSE, then the object will cache a pointer to the buffer supplied by the client; the client is responsible for making sure that buffer remains valid for the lifetime of the NTFS_ATTRIBUTE_RECORD object. This object is reinitializable. --*/ { Destroy(); if( !MakeCopy ) { _Data = (PATTRIBUTE_RECORD_HEADER) Data; _MaximumLength = MaximumLength; _IsOwnBuffer = FALSE; return TRUE; } else { if( (_Data = (PATTRIBUTE_RECORD_HEADER) MALLOC( (UINT) MaximumLength )) == NULL ) { Destroy(); return FALSE; } _MaximumLength = MaximumLength; _IsOwnBuffer = TRUE; memcpy(_Data, Data, (UINT) MaximumLength); return TRUE; } } UNTFS_EXPORT BOOLEAN NTFS_ATTRIBUTE_RECORD::Initialize( IN OUT PVOID Data ) /*++ Routine Description: This version of Initialize takes it's maximum size from the attribute record. Arguments: Data - supplies a buffer containing the attribute record data. Return Value: TRUE upon successful completion. --*/ { Destroy(); _Data = (PATTRIBUTE_RECORD_HEADER) Data; _MaximumLength = _Data->RecordLength; _IsOwnBuffer = FALSE; return TRUE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::CreateResidentRecord( IN PCVOID Value, IN ULONG ValueLength, IN ATTRIBUTE_TYPE_CODE TypeCode, IN PCWSTRING Name, IN USHORT Flags, IN UCHAR ResidentFlags ) /*++ Routine Description: This method formats the object's buffer with a resident attribute record. Arguments: Value -- supplies the attribute value ValueLength -- supplies the length of the value TypeCode -- supplies the attribute type code Name -- supplies the name of the attribute (may be NULL) Flags -- supplies the attribute's flags. ResidentFlags -- supplies the attribute's resident flags Return Value: TRUE upon successful completion. --*/ { // Clear the memory first. memset(_Data, 0, (UINT) _MaximumLength); // We will arrange the attribute in the following order: // Attribute Record Header // Name (if any) // Value if( _MaximumLength < SIZE_OF_RESIDENT_HEADER ) { DebugAbort( "Create: buffer is too small.\n" ); return FALSE; } _Data->TypeCode = TypeCode; _Data->FormCode = RESIDENT_FORM; _Data->Flags = Flags; if( Name != NULL ) { _Data->NameLength = (UCHAR) Name->QueryChCount(); _Data->NameOffset = DwordAlign(SIZE_OF_RESIDENT_HEADER); _Data->Form.Resident.ValueOffset = QuadAlign( _Data->NameOffset + _Data->NameLength * sizeof( WCHAR ) ); } else { _Data->NameLength = 0; _Data->NameOffset = 0; _Data->Form.Resident.ValueOffset = QuadAlign(SIZE_OF_RESIDENT_HEADER); } _Data->Form.Resident.ValueLength = ValueLength; _Data->Form.Resident.ResidentFlags = ResidentFlags; _Data->RecordLength = QuadAlign(_Data->Form.Resident.ValueOffset + ValueLength ); if( _Data->RecordLength > _MaximumLength ) { return FALSE; } // Now that we're sure there's room, copy the name (if any) // and the value into their respective places. if( Name != NULL ) { Name->QueryWSTR( 0, _Data->NameLength, (PWSTR)((PBYTE)_Data + _Data->NameOffset), _Data->NameLength, FALSE ); } memcpy( (PBYTE)_Data + _Data->Form.Resident.ValueOffset, Value, (UINT) ValueLength ); return TRUE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::CreateNonresidentRecord( IN PCNTFS_EXTENT_LIST Extents, IN BIG_INT AllocatedLength, IN BIG_INT ActualLength, IN BIG_INT ValidLength, IN ATTRIBUTE_TYPE_CODE TypeCode, IN PCWSTRING Name, IN USHORT Flags, IN USHORT CompressionUnit ) /*++ Routine Description: This method formats the attribute record to hold a nonresident attribute. Arguments: Extents -- supplies an extent list describing the attribute value's disk storage. AllocatedLength -- supplies the allocated length of the value ActualLength -- supplies the actual length of the value ValidLength -- supplies the valid length of the value TypeCode -- supplies the attribute type code Name -- supplies the name of the attribute (may be NULL) Flags -- supplies the attribute's flags. CompressionUnit -- supplies the log in base 2 of the number of clusters per compression unit. --*/ { ULONG MappingPairsLength; VCN NextVcn, HighestVcn; // Clear the memory first. memset(_Data, 0, (UINT) _MaximumLength); // We will arrange the attribute in the following order: // Attribute Record Header // Name (if any) // Compressed Mapping Pairs if( _MaximumLength < SIZE_OF_NONRESIDENT_HEADER ) { DebugAbort( "Create: buffer is too small.\n" ); return FALSE; } _Data->TypeCode = TypeCode; _Data->FormCode = NONRESIDENT_FORM; _Data->Flags = Flags; if( Name != NULL ) { _Data->NameLength = (UCHAR) Name->QueryChCount(); _Data->NameOffset = DwordAlign(SIZE_OF_NONRESIDENT_HEADER); _Data->Form.Nonresident.MappingPairsOffset = (USHORT)DwordAlign( _Data->NameOffset + _Data->NameLength * sizeof( WCHAR ) ); } else { _Data->NameLength = 0; _Data->NameOffset = 0; _Data->Form.Nonresident.MappingPairsOffset = (USHORT)DwordAlign(SIZE_OF_NONRESIDENT_HEADER); } _Data->Form.Nonresident.CompressionUnit = (UCHAR)CompressionUnit; _Data->Form.Nonresident.AllocatedLength = AllocatedLength.GetLargeInteger(); _Data->Form.Nonresident.FileSize = ActualLength.GetLargeInteger(); _Data->Form.Nonresident.ValidDataLength = ValidLength.GetLargeInteger(); // Copy the name if( Name != NULL ) { if( (ULONG)(_Data->NameOffset + _Data->NameLength) > _MaximumLength ) { // There isn't enough room for the name. return FALSE; } Name->QueryWSTR( 0, _Data->NameLength, (PWSTR)((PBYTE)_Data + _Data->NameOffset), _Data->NameLength, FALSE ); } if( !Extents->QueryCompressedMappingPairs( (PVCN)&(_Data->Form.Nonresident.LowestVcn), &NextVcn, &MappingPairsLength, _MaximumLength - _Data->Form.Nonresident.MappingPairsOffset, (PVOID)((PBYTE)_Data + _Data->Form.Nonresident.MappingPairsOffset) ) ) { // Unable to get the compressed mapping pairs. DebugPrint( "Could not get compressed mapping pairs.\n" ); return FALSE; } HighestVcn = NextVcn - 1; memcpy( &_Data->Form.Nonresident.HighestVcn, &HighestVcn, sizeof(VCN) ); _Data->RecordLength = QuadAlign(_Data->Form.Nonresident.MappingPairsOffset + MappingPairsLength ); return TRUE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::Verify( IN PCNTFS_ATTRIBUTE_COLUMNS AttributeDefTable, IN BOOLEAN BeLenient ) CONST /*++ Routine Description: This routine verifies an attribute record for consistency against itself and against the attribute definition table. This routine will return FALSE if the attribute record contains any inconsistencies. Arguments: AttributeDefTable - Supplies the attribute definition table. Return Value: FALSE - The attribute record is inconsistent. TRUE - The attribute record is ok. --*/ { NTFS_EXTENT_LIST extent_list; BOOLEAN bad_mapping_pairs; ULONG index; ULONG column_flags; BIG_INT length; PFILE_NAME file_name; ULONG value_length; UCHAR i; PWCHAR p; DebugAssert(_Data); // Make sure that we can access at least the form code. if (FIELD_OFFSET(ATTRIBUTE_RECORD_HEADER, Instance) > _Data->RecordLength) { DebugPrintf("Attribute form code out-of-bounds.\n"); return FALSE; } // Make sure that the form code is either resident or non-resident. if (_Data->FormCode != RESIDENT_FORM && _Data->FormCode != NONRESIDENT_FORM) { DebugPrintf("Attribute %d has non-existent form code.\n", _Data->TypeCode); return FALSE; } // Make sure that the record is at least as big as the header // for the record if (_Data->FormCode == RESIDENT_FORM && _Data->RecordLength < SIZE_OF_RESIDENT_HEADER) { DebugPrintf("Attribute record res header out-of-bounds.\n"); return FALSE; } if (_Data->FormCode == NONRESIDENT_FORM && _Data->RecordLength < SIZE_OF_NONRESIDENT_HEADER) { DebugPrintf("Attribute record nonres header out-of-bounds.\n"); return FALSE; } switch (_Data->TypeCode) { case $STANDARD_INFORMATION: if (!IsResident() || (_Data->Form.Resident.ValueLength != sizeof(STANDARD_INFORMATION) && _Data->Form.Resident.ValueLength != SIZEOF_NEW_STANDARD_INFORMATION) ) { // This attribute must be resident and at least // as big as the above structure. DebugPrintf("The standard information is too small\n"); return FALSE; } // Fall through for next check. case $ATTRIBUTE_LIST: case $VOLUME_VERSION: case $SECURITY_DESCRIPTOR: case $VOLUME_NAME: case $VOLUME_INFORMATION: case $SYMBOLIC_LINK: case $EA_INFORMATION: case $EA_DATA: if (_Data->NameLength) { // These attribute may not have names. DebugPrintf("Attribute %d should not have a name.\n", _Data->TypeCode); return FALSE; } break; case $INDEX_ALLOCATION: // $INDEX_ALLOCATION's can't be resident. if (IsResident()) { DebugPrintf("Attribute %d has resident index allocation\n", _Data->TypeCode); return FALSE; } break; default: break; } // Make sure the name offset if well-aligned and in bounds. // Also make sure that the name does not have any unicode NULLs // in them. if (_Data->NameLength) { if (_Data->NameOffset%sizeof(WCHAR) || ULONG(_Data->NameOffset + _Data->NameLength) > _Data->RecordLength) { DebugPrintf("Corrupt name for attribute %d.\n", _Data->TypeCode); return FALSE; } p = (PWCHAR) ((PCHAR) _Data + _Data->NameOffset); for (i = 0; i < _Data->NameLength; i++) { if (!p[i]) { DebugPrintf("Unicode NULL in attribute name for attribute %d.\n", _Data->TypeCode); return FALSE; } } } // Make sure that things mesh with the attribute definition table. if (AttributeDefTable) { if (!AttributeDefTable->QueryIndex(_Data->TypeCode, &index)) { // The attribute type code doesn't exist in the attribute // definition table. DebugPrintf("Attribute %d does not exist in the definition table.\n", _Data->TypeCode); return FALSE; } column_flags = AttributeDefTable->QueryFlags(index); if (IsResident() && (_Data->Form.Resident.ResidentFlags & RESIDENT_FORM_INDEXED) && !(column_flags & ATTRIBUTE_DEF_INDEXABLE)) { // Non-indexable indexed attribute. DebugPrintf("Attribute %d is NOT indexable.\n", _Data->TypeCode); return FALSE; } if ((column_flags & ATTRIBUTE_DEF_MUST_BE_INDEXED) && !(IsResident() && (_Data->Form.Resident.ResidentFlags & RESIDENT_FORM_INDEXED))) { // Attribute must be indexed but isn't. DebugPrintf("Attribute %d is MUST be indexed.\n", _Data->TypeCode); return FALSE; } if ((column_flags & ATTRIBUTE_DEF_INDEXABLE) && _Data->NameLength) { // Indexable attributes cannot have names. DebugPrintf("Attribute %d cannot have a name.\n", _Data->TypeCode); return FALSE; } if ((column_flags & ATTRIBUTE_DEF_MUST_BE_NAMED) && !_Data->NameLength) { // Attribute must be named but isn't. DebugPrintf("Attribute %d MUST have a name.\n", _Data->TypeCode); return FALSE; } if ((column_flags & ATTRIBUTE_DEF_MUST_BE_RESIDENT) && !IsResident()) { // Attribute must be resident but isn't. DebugPrintf("Attribute %d MUST be resident.\n", _Data->TypeCode); return FALSE; } if (IsResident()) { length = _Data->Form.Resident.ValueLength; } else if (_Data->Form.Nonresident.LowestVcn == 0) { length = _Data->Form.Nonresident.FileSize; } else { length = 0; } if (length != 0) { if (length < AttributeDefTable->QueryMinimumLength(index)) { // Length is less than the minimum. DebugPrintf("Attribute %d has length less than the minimum.\n", _Data->TypeCode); return FALSE; } // Note that a value of -1 in the Length field of the // Attribute Definition Table entry indicates that the // attribute can be as large as it pleases. // // Note the length of the $STANDARD_INFORMATION attribute // is checked above. // if (AttributeDefTable->QueryMaximumLength(index) != -1 && length > AttributeDefTable->QueryMaximumLength(index) && _Data->TypeCode != $VOLUME_VERSION && _Data->TypeCode != $STANDARD_INFORMATION ) { // Length is greater than the maximum. DebugPrintf("Attribute %d has length greater than the maximum.\n", _Data->TypeCode); return FALSE; } } } if (IsResident()) { // Make sure that the value is in bounds and // make sure that name comes before value. if (_Data->Form.Resident.ValueLength) { if (_Data->Form.Resident.ValueOffset + _Data->Form.Resident.ValueLength > _Data->RecordLength) { DebugPrintf("Attribute %d has corrupt resident value.\n", _Data->TypeCode); return FALSE; } if (_Data->NameLength && _Data->NameOffset + _Data->NameLength > _Data->Form.Resident.ValueOffset) { DebugPrintf("Attribute %d colliding name and resident value.\n", _Data->TypeCode); return FALSE; } } // Make sure that if the attribute is indexed then it // has no name. if ((_Data->Form.Resident.ResidentFlags & RESIDENT_FORM_INDEXED) && _Data->NameLength) { DebugPrintf("Attribute %d is indexed AND has a name.\n", _Data->TypeCode); return FALSE; } } else { // Make sure that the mapping pairs are in bounds. if (_Data->Form.Nonresident.MappingPairsOffset >= _Data->RecordLength) { DebugPrintf("Attribute %d has mapping pairs that are out of bounds.\n", _Data->TypeCode); return FALSE; } if ((QueryFlags() & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0) { if (_Data->Form.Nonresident.LowestVcn == 0 && _Data->Form.Nonresident.MappingPairsOffset < 4 * sizeof(BIG_INT)) { DebugPrintf("Attribute %d MappingPairsOffset too small (%d).\n", _Data->TypeCode, _Data->Form.Nonresident.MappingPairsOffset); return FALSE; } } else { if (_Data->Form.Nonresident.LowestVcn == 0 && _Data->Form.Nonresident.MappingPairsOffset < 3 * sizeof(BIG_INT)) { DebugPrintf("Attribute %d has MappingPairsOffset too small (%d).\n", _Data->TypeCode, _Data->Form.Nonresident.MappingPairsOffset); } } // Make sure that the name comes before the mapping pairs. if (_Data->NameLength && _Data->NameOffset + _Data->NameLength > _Data->Form.Nonresident.MappingPairsOffset) { DebugPrintf("Attribute %d has its name colliding with the mapping pairs.\n", _Data->TypeCode); return FALSE; } // Validate the mapping pairs. if (!extent_list.Initialize(_Data->Form.Nonresident.LowestVcn, (PCHAR) _Data + _Data->Form.Nonresident.MappingPairsOffset, _Data->RecordLength - _Data->Form.Nonresident.MappingPairsOffset, &bad_mapping_pairs)) { DebugPrintf("Attribute %d has bad mapping pairs.\n", _Data->TypeCode); return FALSE; } if (extent_list.QueryNextVcn() != _Data->Form.Nonresident.HighestVcn + 1 && !BeLenient) { DebugPrintf("Attribute %d has an invalid highest vcn.\n", _Data->TypeCode); return FALSE; } // If the lowest vcn is 0 then make sure that the three sizes // make sense. if (_Data->Form.Nonresident.LowestVcn == 0 && !BeLenient) { if (_Data->Form.Nonresident.ValidDataLength > _Data->Form.Nonresident.FileSize || _Data->Form.Nonresident.FileSize > _Data->Form.Nonresident.AllocatedLength) { DebugPrintf("Attribute %d has inconsistent sizes.\n", _Data->TypeCode); return FALSE; } if ((QueryFlags() & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0 && _Data->Form.Nonresident.TotalAllocated > _Data->Form.Nonresident.AllocatedLength) { DebugPrintf("Attribute %d has inconsistent TotalAllocated.\n", _Data->TypeCode); #if 0 // // This would cause the attribute record to be deleted, which is considered // to be too harsh a penalty for this minor error. // return FALSE; #endif } if ((QueryFlags() & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0 && (_Data->Form.Nonresident.AllocatedLength % (1 << QueryCompressionUnit())) != 0) { DebugPrintf("Attribute %d has TotalAllocated not multiple of " "compression unit\n", _Data->TypeCode); return FALSE; } } } // $FILE_NAME attribute must follow additional special structure. if (_Data->TypeCode == $FILE_NAME) { if (!IsIndexed()) { DebugPrintf("File name attribute is not indexed.\n"); return FALSE; } file_name = (PFILE_NAME) ((PCHAR) _Data + _Data->Form.Resident.ValueOffset); value_length = _Data->Form.Resident.ValueLength; if (value_length < sizeof(FILE_NAME)) { DebugPrintf("Corrupt file name attribute.\n"); return FALSE; } if (NtfsFileNameGetLength(file_name) > value_length) { DebugPrintf("Corrupt file name attribute.\n"); return FALSE; } // Make sure that the file name has no NULL // characters in it. If it does then the attribute // is "corrupt". for (i = 0; i < file_name->FileNameLength; i++) { if (!file_name->FileName[i]) { DebugPrintf("Attribute %d has filename w/ null characters\n", _Data->TypeCode); return FALSE; } } } return TRUE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::UseClusters( IN OUT PNTFS_BITMAP VolumeBitmap, OUT PBIG_INT ClusterCount ) CONST /*++ Routine Description: This routine allocates the disk space claimed by this attribute record in the bitmap provided. A check is made to verify that the requested disk space is free before the allocation takes place. If the requested space is not available in the bitmap then this routine will return FALSE. Arguments: VolumeBitmap - Supplies the bitmap. ClusterCount - Receives the number of clusters allocated to this record. Not set if method fails. Return Value: FALSE - The request bitmap space was not available. TRUE - Success. --*/ { NTFS_EXTENT_LIST extent_list; ULONG num_extents; ULONG i, j; VCN next_vcn; LCN current_lcn; BIG_INT run_length; DebugAssert(VolumeBitmap); if (IsResident()) { *ClusterCount = 0; return TRUE; } if (!QueryExtentList(&extent_list)) { return FALSE; } num_extents = extent_list.QueryNumberOfExtents(); for (i = 0; i < num_extents; i++) { if (!extent_list.QueryExtent(i, &next_vcn, ¤t_lcn, &run_length)) { DebugAbort("Could not query extent"); return FALSE; } if (current_lcn == LCN_NOT_PRESENT) { continue; } // Make sure that the run is free before allocating. // If it is not, this indicates a cross-link. if (!VolumeBitmap->IsFree(current_lcn, run_length)) { DebugPrintf("cross-linked run starts at 0x%X for 0x%X\n", current_lcn.GetLowPart(), run_length.GetLowPart()); // Free everything so far allocated by this routine. for (j = 0; j < i; j++) { if (!extent_list.QueryExtent(j, &next_vcn, ¤t_lcn, &run_length)) { DebugAbort("Could not query extent"); return FALSE; } if (current_lcn == LCN_NOT_PRESENT) { continue; } VolumeBitmap->SetFree(current_lcn, run_length); } return FALSE; } VolumeBitmap->SetAllocated(current_lcn, run_length); } *ClusterCount = extent_list.QueryClustersAllocated(); return TRUE; } BOOLEAN NTFS_ATTRIBUTE_RECORD::UseClusters( IN OUT PNTFS_BITMAP VolumeBitmap, OUT PBIG_INT ClusterCount, IN ULONG AllowCrossLinkStart, IN ULONG AllowCrossLinkLength, OUT PBOOLEAN DidCrossLinkOccur ) CONST /*++ Routine Description: This routine allocates the disk space claimed by this attribute record in the bitmap provided. A check is made to verify that the requested disk space is free before the allocation takes place. If the requested space is not available in the bitmap then this routine will return FALSE. This methode assumes that the range specified by the Allow parameters are marked as allocated in the given bitmap. Arguments: VolumeBitmap - Supplies the bitmap. ClusterCount - Receives the number of clusters allocated to this record. Not set if method fails. AllowCrossLinkStart - Supplies the start of a range where cross-links are allowed. AllowCrossLinkLength - Supplies the length of the range where cross-links are allowed. DidCrossLinkOccur - Returns whether or not an allowable cross-link occurred. Return Value: FALSE - The request bitmap space was not available. TRUE - Success. --*/ { BOOLEAN r; DebugAssert(DidCrossLinkOccur); *DidCrossLinkOccur = FALSE; if (UseClusters(VolumeBitmap,ClusterCount)) { return TRUE; } *DidCrossLinkOccur = TRUE; VolumeBitmap->SetFree(AllowCrossLinkStart, AllowCrossLinkLength); r = UseClusters(VolumeBitmap,ClusterCount); VolumeBitmap->SetAllocated(AllowCrossLinkStart, AllowCrossLinkLength); return r; } BOOLEAN NTFS_ATTRIBUTE_RECORD::UnUseClusters( IN OUT PNTFS_BITMAP VolumeBitmap, IN ULONG LeaveInUseStart, IN ULONG LeaveInUseLength ) CONST /*++ Routine Description: This operation reverses a successful 'UseClusters' operation. This method assumes that the LeaveInUse range is already in use by the bitmap. Arguments: VolumeBitmap - Supplies the bitmap. LeaveInUseStart - Supplies the start of the range that this routine should leave in use. LeaveInUseLength - Supplies the length of the range that this routine should leave in use. Return Value: FALSE - Failure. TRUE - Success. --*/ { NTFS_EXTENT_LIST extent_list; ULONG num_extents; ULONG i; VCN next_vcn; LCN current_lcn; BIG_INT run_length; DebugAssert(VolumeBitmap); if (IsResident() || _DisableUnUse) { return TRUE; } if (!QueryExtentList(&extent_list)) { return FALSE; } num_extents = extent_list.QueryNumberOfExtents(); for (i = 0; i < num_extents; i++) { if (!extent_list.QueryExtent(i, &next_vcn, ¤t_lcn, &run_length)) { DebugAbort("Could not query extent"); return FALSE; } if (LCN_NOT_PRESENT == current_lcn) { continue; } VolumeBitmap->SetFree(current_lcn, run_length); } VolumeBitmap->SetAllocated(LeaveInUseStart, LeaveInUseLength); return TRUE; } NONVIRTUAL UNTFS_EXPORT BOOLEAN NTFS_ATTRIBUTE_RECORD::QueryName( OUT PWSTRING Name ) CONST /*++ Routine Description: This method returns the name of the attribute. Arguments: Name - Returns the name of the attribute. Return Value: FALSE - Failure. TRUE - Success. --*/ { if (FIELD_OFFSET(ATTRIBUTE_RECORD_HEADER, Flags) >= _MaximumLength || ULONG(_Data->NameOffset + _Data->NameLength) > _MaximumLength || _Data->NameLength == 0) { return Name->Initialize( "" ); } else { return Name->Initialize((PWSTR)((PBYTE)_Data + _Data->NameOffset), _Data->NameLength); } } VOID NTFS_ATTRIBUTE_RECORD::QueryValueLength( OUT PBIG_INT ValueLength, OUT PBIG_INT AllocatedLength, OUT PBIG_INT ValidLength, OUT PBIG_INT TotalAllocated ) CONST /*++ Routine Description: This method returns the actual, allocated, valid, and total allocated lengths of the attribute value associated with this record. If the attribute is resident, these values are all the length of the resident value, except total allocated, which is meaningless. If the attribute is nonresident, these four values are only meaningful if the LowestVcn of this attribute record is 0. Additionally, TotalAllocated is only valid for compressed attributes. Arguments: ValueLength -- receives the actual length of the value. AllocatedLength -- receives the allocated size of the value. (may be NULL if the caller doesn't care) ValidLength -- receives the valid length of the value. (may be NULL if the caller doesn't care) TotalAllocated -- receives the total allocated length of the value (may be NULL). Return Value: None. --*/ { DebugPtrAssert( _Data ); if( _Data->FormCode == RESIDENT_FORM ) { *ValueLength = _Data->Form.Resident.ValueLength; if( AllocatedLength != NULL ) { *AllocatedLength = _Data->Form.Resident.ValueLength; } if( ValidLength != NULL ) { *ValidLength = _Data->Form.Resident.ValueLength; } if (TotalAllocated != NULL ) { // no such value for resident attributes *TotalAllocated = 0; } } else { DebugAssert( _Data->FormCode == NONRESIDENT_FORM ); *ValueLength = _Data->Form.Nonresident.FileSize; if( AllocatedLength != NULL ) { *AllocatedLength = _Data->Form.Nonresident.AllocatedLength; } if( ValidLength != NULL ) { *ValidLength = _Data->Form.Nonresident.ValidDataLength; } if (TotalAllocated != NULL) { if ((_Data->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0) { *TotalAllocated = _Data->Form.Nonresident.TotalAllocated; } else { *TotalAllocated = 0; } } } } VOID NTFS_ATTRIBUTE_RECORD::SetTotalAllocated( IN BIG_INT TotalAllocated ) /*++ Routine Description: Set the "TotalAllocated" field in the attribute record. If the attribute record doesn't have a total allocated field because the attribute isn't compressed or because it's resident, this method has no effect. Arguments: TotalAllocated - the new value. Return Value: None. --*/ { DebugPtrAssert( _Data ); if( _Data->FormCode == RESIDENT_FORM ) { // no such value for resident attributes; ignore return; } DebugAssert( _Data->FormCode == NONRESIDENT_FORM ); if ((_Data->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0) { _Data->Form.Nonresident.TotalAllocated = TotalAllocated.GetLargeInteger(); } } UNTFS_EXPORT BOOLEAN NTFS_ATTRIBUTE_RECORD::QueryExtentList( OUT PNTFS_EXTENT_LIST ExtentList ) CONST /*++ Routine Description: Arguments: None. Return Value: A pointer to the extent list. A return value of NULL indicates that the attribute is resident or that an error occurred processing the compressed mapping pairs. (Clients should use IsResident to determine whether the attribute value is resident.) --*/ { DebugPtrAssert( _Data ); if( _Data->FormCode == NONRESIDENT_FORM && ExtentList->Initialize( _Data->Form.Nonresident.LowestVcn, (PVOID)((PBYTE)_Data + _Data->Form.Nonresident.MappingPairsOffset), _MaximumLength - _Data->Form.Nonresident. MappingPairsOffset ) ) { return TRUE; } else { return FALSE; } } BOOLEAN NTFS_ATTRIBUTE_RECORD::IsMatch( IN ATTRIBUTE_TYPE_CODE Type, IN PCWSTRING Name, IN PCVOID Value, IN ULONG ValueLength ) CONST /*++ Routine Description: This method determines whether the attribute record matches the parameters given. Arguments: Type -- Supplies the type code of the attribute. This is the primary key, and must always be present. Name -- Supplies a name to match. A name of NULL is the same as specifying the null string. Value -- Supplies the value to match. If this argument is null, any value matches. Only resident attributes can be checked for value matches. ValueLength -- Supplies the length of the value (if any). Notes: Value matching is not supported for nonresident attribute values; if a Value parameter is supplied, then no non-resident attribute records will match. --*/ { DSTRING RecordName; DebugPtrAssert( _Data ); if( Type != _Data->TypeCode ) { return FALSE; } if( Name != NULL ) { if( !RecordName.Initialize((PWSTR)((PBYTE)_Data + _Data->NameOffset), _Data->NameLength ) ) { return FALSE; } if( Name->Strcmp( &RecordName ) != 0 ) { return FALSE; } } else if (_Data->NameLength) { return FALSE; } if( Value != NULL && ( _Data->FormCode != RESIDENT_FORM || ValueLength != _Data->Form.Resident.ValueLength || memcmp( Value, (PBYTE)_Data + _Data->Form.Resident.ValueOffset, (UINT) ValueLength ) ) ) { return FALSE; } return TRUE; } LONG CompareAttributeRecords( IN PCNTFS_ATTRIBUTE_RECORD Left, IN PCNTFS_ATTRIBUTE_RECORD Right, IN PCNTFS_UPCASE_TABLE UpcaseTable ) /*++ Routine Description: This method compares two attribute records to determine their correct ordering in the File Record Segment. Arguments: Left -- Supplies the left-hand operand of the comparison. Right -- Supplies the right-hand operand of the comparison. UpcaseTable -- Supplies the upcase table for the volume. If this parameter is NULL, name comparison cannot be performed. Return Value: <0 if Left is less than Right 0 if Left equals Right >0 if Left is greater than Right. Notes: Attribute records are ordered first by type code and then by name. An attribute record without a name is less than any attribute record of the same type with a name. Name comparision is first done case-insensitive; if the names are equal by that metric, a case-sensitive comparision is made. The UpcaseTable parameter may be omitted if either or both names are zero-length, or if they are identical (including case). Otherwise, it must be supplied. --*/ { ULONG Result; // First, compare the type codes: // Result = Left->QueryTypeCode() - Right->QueryTypeCode(); if( Result != 0 ) { return Result; } // They have the same type code, so we have to compare the names. // Pass in TRUE for the IsAttribute parameter, to indicate that // we are comparing attribute names. // return( NtfsUpcaseCompare( Left->GetName(), Left->QueryNameLength(), Right->GetName(), Right->QueryNameLength(), UpcaseTable, TRUE ) ); }