public override AttributeRecord Clone()
        {
            NonResidentAttributeRecord clone = (NonResidentAttributeRecord)this.MemberwiseClone();

            clone.m_dataRunSequence = m_dataRunSequence.Clone();
            return(clone);
        }
 public NonResidentAttributeData(NTFSVolume volume, FileRecord fileRecord, NonResidentAttributeRecord attributeRecord)
 {
     m_volume          = volume;
     m_fileRecord      = fileRecord;
     m_attributeRecord = attributeRecord;
     m_contentType     = GetContentType(fileRecord, attributeRecord.AttributeType);
 }
Esempio n. 3
0
        public void Extend(ulong additionalLengthInBytes)
        {
            ulong currentSize = this.Length;

            if (m_attributeRecord is NonResidentAttributeRecord)
            {
                NonResidentAttributeData attributeData = new NonResidentAttributeData(m_volume, m_fileRecord, (NonResidentAttributeRecord)m_attributeRecord);
                attributeData.Extend(additionalLengthInBytes);
            }
            else
            {
                byte[] data              = ((ResidentAttributeRecord)m_attributeRecord).Data;
                ulong  finalDataLength   = (uint)data.Length + additionalLengthInBytes;
                ulong  finalRecordLength = (uint)m_attributeRecord.RecordLength + additionalLengthInBytes;
                if (finalRecordLength >= (ulong)m_volume.AttributeRecordLengthToMakeNonResident &&
                    m_attributeRecord.AttributeType != AttributeType.AttributeList) // We will create an attribute list with the right attribute form in advance.
                {
                    // Convert the attribute to non-resident
                    long clustersToAllocate = (long)Math.Ceiling((double)finalDataLength / m_volume.BytesPerCluster);
                    if (clustersToAllocate > m_volume.NumberOfFreeClusters)
                    {
                        throw new DiskFullException();
                    }
                    NonResidentAttributeRecord attributeRecord = NonResidentAttributeRecord.Create(m_attributeRecord.AttributeType, m_attributeRecord.Name);
                    NonResidentAttributeData   attributeData   = new NonResidentAttributeData(m_volume, null, attributeRecord);
                    attributeData.Extend(finalDataLength);
                    if (data.Length % m_volume.BytesPerCluster > 0)
                    {
                        int fillDataLength = m_volume.BytesPerCluster - (data.Length % m_volume.BytesPerCluster);
                        if ((uint)data.Length + (uint)fillDataLength < finalDataLength)
                        {
                            // Only the last cluster can be partially used, if this is not the last cluster, zero-fill it
                            data = ByteUtils.Concatenate(data, new byte[fillDataLength]);
                        }
                    }
                    attributeData.WriteClusters(0, data);
                    // Note that we overwrite the old attribute only after writing the non-resident data
                    if (m_fileRecord != null)
                    {
                        m_fileRecord.RemoveAttributeRecord(m_attributeRecord.AttributeType, m_attributeRecord.Name);
                        FileRecordHelper.InsertSorted(m_fileRecord.Attributes, attributeRecord);
                    }
                    m_attributeRecord = attributeRecord;
                }
                else
                {
                    int    currentLength = data.Length;
                    byte[] temp          = new byte[currentLength + (int)additionalLengthInBytes];
                    Array.Copy(data, temp, data.Length);
                    ((ResidentAttributeRecord)m_attributeRecord).Data = temp;
                }

                if (m_fileRecord != null)
                {
                    m_volume.UpdateFileRecord(m_fileRecord);
                }
            }
        }
        private static int CompareNonResidentAttributes(NonResidentAttributeRecord attribute1, NonResidentAttributeRecord attribute2)
        {
            int result = CompareAttributeTypes(attribute1, attribute2);

            if (result == 0)
            {
                result = attribute1.LowestVCN.CompareTo(attribute2.LowestVCN);
            }
            return(result);
        }
Esempio n. 5
0
 public static AttributeRecord Create(AttributeType type, string name, bool isResident)
 {
     if (isResident)
     {
         return(ResidentAttributeRecord.Create(type, name));
     }
     else
     {
         return(NonResidentAttributeRecord.Create(type, name));
     }
 }
Esempio n. 6
0
        /// <remarks>
        /// Only non-resident attributes can be fragmented.
        /// References:
        /// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc976808(v=technet.10)
        /// https://blogs.technet.microsoft.com/askcore/2009/10/16/the-four-stages-of-ntfs-file-growth/
        /// </remarks>
        public static List <AttributeRecord> GetAssembledAttributes(List <FileRecordSegment> segments)
        {
            List <AttributeRecord> result = new List <AttributeRecord>();
            // If two non-resident attributes have the same AttributeType and Name, then we need to assemble them back together.
            // Additional fragments immediately follow after the initial fragment.
            AttributeType currentAttributeType          = AttributeType.None;
            string        currentAttributeName          = String.Empty;
            List <NonResidentAttributeRecord> fragments = new List <NonResidentAttributeRecord>();

            foreach (FileRecordSegment segment in segments)
            {
                foreach (AttributeRecord attribute in segment.ImmediateAttributes)
                {
                    if (attribute.AttributeType == AttributeType.AttributeList)
                    {
                        continue;
                    }

                    bool additionalFragment = (attribute is NonResidentAttributeRecord) && (fragments.Count > 0) &&
                                              (attribute.AttributeType == currentAttributeType) && (attribute.Name == currentAttributeName);

                    if (!additionalFragment && fragments.Count > 0)
                    {
                        NonResidentAttributeRecord assembledAttribute = AssembleFragments(fragments, segments[0].NextAttributeInstance);
                        segments[0].NextAttributeInstance++;
                        result.Add(assembledAttribute);
                        fragments.Clear();
                    }

                    if (attribute is ResidentAttributeRecord)
                    {
                        result.Add(attribute);
                    }
                    else
                    {
                        fragments.Add((NonResidentAttributeRecord)attribute);
                        if (!additionalFragment)
                        {
                            currentAttributeType = attribute.AttributeType;
                            currentAttributeName = attribute.Name;
                        }
                    }
                }
            }

            if (fragments.Count > 0)
            {
                NonResidentAttributeRecord assembledAttribute = AssembleFragments(fragments, segments[0].NextAttributeInstance);
                segments[0].NextAttributeInstance++;
                result.Add(assembledAttribute);
            }

            return(result);
        }
Esempio n. 7
0
        private static NonResidentAttributeRecord FitMaxNumberOfRuns(NonResidentAttributeRecord record, int runIndex, int availableLength)
        {
            // Each attribute record is aligned to 8-byte boundary, we must have enough room for padding
            availableLength = (int)Math.Floor((double)availableLength / 8) * 8;
            // Note that we're using the original record Instance instead of using the FileRecordSegment.NextAttributeInstance
            NonResidentAttributeRecord slice    = new NonResidentAttributeRecord(record.AttributeType, record.Name, record.Instance);
            DataRunSequence            dataRuns = record.DataRunSequence;
            long clusterCount = 0;

            for (int index = 0; index < runIndex; index++)
            {
                clusterCount += dataRuns[index].RunLength;
            }
            slice.LowestVCN = clusterCount;
            slice.DataRunSequence.Add(dataRuns[runIndex]);

            if (runIndex == 0)
            {
                slice.CompressionUnit = record.CompressionUnit;
                slice.AllocatedLength = record.AllocatedLength;
                slice.FileSize        = record.FileSize;
                slice.ValidDataLength = record.ValidDataLength;
            }
            else
            {
                // The DataRunSequence of each NonResidentDataRecord fragment starts at absolute LCN
                long runLength   = dataRuns[runIndex].RunLength;
                long runStartLCN = dataRuns.GetDataClusterLCN(clusterCount);
                slice.DataRunSequence[0] = new DataRun(runLength, runStartLCN);
            }
            clusterCount += dataRuns[runIndex].RunLength;

            int sliceRecordLength = NonResidentAttributeRecord.HeaderLength + record.Name.Length * 2 + slice.DataRunSequence.RecordLength;

            if (sliceRecordLength > availableLength)
            {
                return(null);
            }

            runIndex++;
            while (runIndex < dataRuns.Count && sliceRecordLength + dataRuns[runIndex].RecordLength <= availableLength)
            {
                slice.DataRunSequence.Add(record.DataRunSequence[runIndex]);
                sliceRecordLength += dataRuns[runIndex].RecordLength;
                clusterCount      += dataRuns[runIndex].RunLength;
                runIndex++;
            }

            slice.HighestVCN = clusterCount - 1;
            return(slice);
        }
Esempio n. 8
0
        public AttributeRecord CreateAttributeListRecord(bool isResident)
        {
            AttributeRecord attribute;

            if (isResident)
            {
                attribute = AttributeRecord.Create(AttributeType.AttributeList, String.Empty, NextAttributeInstance);
            }
            else
            {
                attribute = NonResidentAttributeRecord.Create(AttributeType.AttributeList, String.Empty, NextAttributeInstance);
            }
            NextAttributeInstance++;
            FileRecordHelper.InsertSorted(m_immediateAttributes, attribute);
            return(attribute);
        }
Esempio n. 9
0
        private static NonResidentAttributeRecord AssembleFragments(List <NonResidentAttributeRecord> attributeFragments, ushort nextAttributeInstance)
        {
            // Attribute fragments are written to disk sorted by LowestVCN
            NonResidentAttributeRecord firstFragment = attributeFragments[0];

            if (firstFragment.LowestVCN != 0)
            {
                string message = String.Format("Attribute fragments must be sorted. Attribute type: {0}", firstFragment.AttributeType);
                throw new InvalidDataException(message);
            }

            NonResidentAttributeRecord attribute = NonResidentAttributeRecord.Create(firstFragment.AttributeType, firstFragment.Name, nextAttributeInstance);

            attribute.Flags           = firstFragment.Flags;
            attribute.LowestVCN       = 0;
            attribute.HighestVCN      = -1;
            attribute.CompressionUnit = firstFragment.CompressionUnit;
            attribute.AllocatedLength = firstFragment.AllocatedLength;
            attribute.FileSize        = firstFragment.FileSize;
            attribute.ValidDataLength = firstFragment.ValidDataLength;

            foreach (NonResidentAttributeRecord attributeFragment in attributeFragments)
            {
                if (attributeFragment.LowestVCN == attribute.HighestVCN + 1)
                {
                    // The DataRunSequence of each NonResidentDataRecord fragment starts at absolute LCN,
                    // We need to convert it to relative offset before adding it to the base DataRunSequence
                    long runLength      = attributeFragment.DataRunSequence[0].RunLength;
                    long absoluteOffset = attributeFragment.DataRunSequence[0].RunOffset;
                    long previousLCN    = attribute.DataRunSequence.LastDataRunStartLCN;
                    long relativeOffset = absoluteOffset - previousLCN;

                    int runIndex = attribute.DataRunSequence.Count;
                    attribute.DataRunSequence.AddRange(attributeFragment.DataRunSequence);
                    attribute.DataRunSequence[runIndex] = new DataRun(runLength, relativeOffset);
                    attribute.HighestVCN = attributeFragment.HighestVCN;
                }
                else
                {
                    throw new InvalidDataException("Invalid attribute fragments order");
                }
            }

            return(attribute);
        }
Esempio n. 10
0
        private static List <NonResidentAttributeRecord> SliceAttributeRecord(NonResidentAttributeRecord record, int remainingLengthInCurrentSegment, int bytesAvailableInSegment)
        {
            List <NonResidentAttributeRecord> result = new List <NonResidentAttributeRecord>();
            int numberOfRunsFitted = 0;
            int availableLength    = remainingLengthInCurrentSegment;

            while (numberOfRunsFitted < record.DataRunSequence.Count)
            {
                NonResidentAttributeRecord slice = FitMaxNumberOfRuns(record, numberOfRunsFitted, availableLength);
                if (slice != null)
                {
                    result.Add(slice);
                    numberOfRunsFitted += slice.DataRunSequence.Count;
                }
                availableLength = bytesAvailableInSegment;
            }

            return(result);
        }
Esempio n. 11
0
        public static List <AttributeRecord> GetAssembledAttributes(List <FileRecordSegment> segments)
        {
            List <AttributeRecord> result = new List <AttributeRecord>();
            // we need to assemble fragmented attributes (if there are any)
            // if two attributes have the same AttributeType and Name, then we need to assemble them back together.
            // Note: only non-resident attributes can be fragmented
            // Reference: http://technet.microsoft.com/en-us/library/cc976808.aspx
            Dictionary <KeyValuePair <AttributeType, string>, List <NonResidentAttributeRecord> > fragments = new Dictionary <KeyValuePair <AttributeType, string>, List <NonResidentAttributeRecord> >();

            foreach (FileRecordSegment segment in segments)
            {
                foreach (AttributeRecord attribute in segment.ImmediateAttributes)
                {
                    if (attribute is ResidentAttributeRecord)
                    {
                        result.Add(attribute);
                    }
                    else
                    {
                        KeyValuePair <AttributeType, string> key = new KeyValuePair <AttributeType, string>(attribute.AttributeType, attribute.Name);
                        if (fragments.ContainsKey(key))
                        {
                            fragments[key].Add((NonResidentAttributeRecord)attribute);
                        }
                        else
                        {
                            List <NonResidentAttributeRecord> attributeFragments = new List <NonResidentAttributeRecord>();
                            attributeFragments.Add((NonResidentAttributeRecord)attribute);
                            fragments.Add(key, attributeFragments);
                        }
                    }
                }
            }

            // assemble all non-resident attributes
            foreach (List <NonResidentAttributeRecord> attributeFragments in fragments.Values)
            {
                // we assume attribute fragments are written to disk sorted by LowestVCN
                NonResidentAttributeRecord baseAttribute = attributeFragments[0];
                if (baseAttribute.LowestVCN != 0)
                {
                    string message = String.Format("Attribute fragments must be sorted, MftSegmentNumber: {0}, attribute type: {1}",
                                                   segments[0].MftSegmentNumber, baseAttribute.AttributeType);
                    throw new InvalidDataException(message);
                }

                if (baseAttribute.DataRunSequence.DataClusterCount != baseAttribute.HighestVCN + 1)
                {
                    string message = String.Format("Cannot properly assemble data run sequence 0, expected length: {0}, sequence length: {1}",
                                                   baseAttribute.HighestVCN + 1, baseAttribute.DataRunSequence.DataClusterCount);
                    throw new InvalidDataException(message);
                }

                for (int index = 1; index < attributeFragments.Count; index++)
                {
                    NonResidentAttributeRecord attributeFragment = attributeFragments[index];
                    if (attributeFragment.LowestVCN == baseAttribute.HighestVCN + 1)
                    {
                        // The DataRunSequence of each additional file record segment starts at absolute LCN,
                        // so we need to convert it to relative offset before adding it to the base DataRunSequence
                        long absoluteOffset = attributeFragment.DataRunSequence[0].RunOffset;
                        long previousLCN    = baseAttribute.DataRunSequence.LastDataRunStartLCN;
                        long relativeOffset = absoluteOffset - previousLCN;
                        attributeFragment.DataRunSequence[0].RunOffset = relativeOffset;

                        baseAttribute.DataRunSequence.AddRange(attributeFragment.DataRunSequence);
                        baseAttribute.HighestVCN = attributeFragment.HighestVCN;

                        if (baseAttribute.DataRunSequence.DataClusterCount != baseAttribute.HighestVCN + 1)
                        {
                            string message = String.Format("Cannot properly assemble data run sequence, expected length: {0}, sequence length: {1}",
                                                           baseAttribute.HighestVCN + 1, baseAttribute.DataRunSequence.DataClusterCount);
                            throw new InvalidDataException(message);
                        }
                    }
                    else
                    {
                        throw new InvalidDataException("Invalid attribute fragments order");
                    }
                }

                result.Add(baseAttribute);
            }

            return(result);
        }
Esempio n. 12
0
        public static NTFSVolume Format(Volume volume, byte majorNTFSVersion, byte minorNTFSVersion, int bytesPerCluster, string volumeLabel)
        {
            if (volumeLabel.Length > VolumeNameRecord.MaxVolumeNameLength)
            {
                throw new InvalidNameException();
            }

            if (bytesPerCluster % volume.BytesPerSector > 0)
            {
                throw new ArgumentException("bytesPerCluster must be a multiple of volume.BytesPerSector");
            }

            if (majorNTFSVersion != 3 || (minorNTFSVersion != 0 && minorNTFSVersion != 1))
            {
                throw new NotSupportedException();
            }

            long volumeClusterCount = (volume.Size - NTFSBootRecord.Length) / bytesPerCluster;

            // We wish to make WriteVolumeBitmap() as simple as possible so we use a multiple of ExtendGranularity to avoid having to set bits at the end of the bitmap
            volumeClusterCount = (long)Math.Floor((double)volumeClusterCount / (VolumeBitmap.ExtendGranularity * 8)) * (VolumeBitmap.ExtendGranularity * 8);
            int            sectorsPerCluster          = bytesPerCluster / volume.BytesPerSector;
            int            bytesPerFileRecordSegment  = 1024; // Supported values are 1024 or 4096 (when formatted with /L)
            int            bytesPerIndexRecord        = 4096; // Legal values are 1024, 2048 or 4096. NTFS v5.1 driver will always use 4096.
            int            bootSegmentFileSize        = 8192;
            int            bootSegmentAllocatedLength = (int)Math.Ceiling((double)bootSegmentFileSize / bytesPerCluster) * bytesPerCluster;
            FileNameRecord bootFileNameRecord         = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$Boot", false, DateTime.Now);

            bootFileNameRecord.AllocatedLength = (ulong)bootSegmentAllocatedLength;
            bootFileNameRecord.FileSize        = (ulong)bootSegmentFileSize;
            bootFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment bootSegment = CreateBaseRecordSegment(MasterFileTable.BootSegmentNumber, (ushort)MasterFileTable.BootSegmentNumber, bootFileNameRecord);

            bootSegment.ReferenceCount = 1;
            NonResidentAttributeRecord bootDataRecord = (NonResidentAttributeRecord)bootSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            bootDataRecord.AllocatedLength = (ulong)bootSegmentAllocatedLength;
            bootDataRecord.FileSize        = (ulong)bootSegmentFileSize;
            bootDataRecord.ValidDataLength = (ulong)bootSegmentFileSize;
            int  bootDataClusterCount = (int)Math.Ceiling((double)8192 / bytesPerCluster);
            long bootDataStartLCN     = 0;

            bootDataRecord.DataRunSequence.Add(new DataRun(bootDataClusterCount, bootDataStartLCN));
            bootDataRecord.HighestVCN = bootDataClusterCount - 1;

            long           volumeBitmapFileSize         = (long)Math.Ceiling((double)volumeClusterCount / (VolumeBitmap.ExtendGranularity * 8)) * VolumeBitmap.ExtendGranularity;
            long           numberOfVolumeBitmapClusters = (long)Math.Ceiling((double)volumeBitmapFileSize / bytesPerCluster);
            long           volumeBitmapAllocatedLength  = numberOfVolumeBitmapClusters * bytesPerCluster;
            FileNameRecord volumeBitmapFileNameRecord   = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$Bitmap", false, DateTime.Now);

            volumeBitmapFileNameRecord.AllocatedLength = (ulong)volumeBitmapAllocatedLength;
            volumeBitmapFileNameRecord.FileSize        = (ulong)volumeBitmapFileSize;
            volumeBitmapFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment volumeBitmapSegment = CreateBaseRecordSegment(MasterFileTable.BitmapSegmentNumber, (ushort)MasterFileTable.BitmapSegmentNumber, volumeBitmapFileNameRecord);

            volumeBitmapSegment.ReferenceCount = 1;
            NonResidentAttributeRecord volumeBitmapDataRecord = (NonResidentAttributeRecord)volumeBitmapSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);
            long volumeBitmapStartLCN = bootDataClusterCount;

            volumeBitmapDataRecord.AllocatedLength = (ulong)volumeBitmapAllocatedLength;
            volumeBitmapDataRecord.FileSize        = (ulong)volumeBitmapFileSize;
            volumeBitmapDataRecord.ValidDataLength = (ulong)volumeBitmapFileSize;
            volumeBitmapDataRecord.DataRunSequence.Add(new DataRun(numberOfVolumeBitmapClusters, volumeBitmapStartLCN));
            volumeBitmapDataRecord.HighestVCN = numberOfVolumeBitmapClusters - 1;

            int            numberOfMftRecords = 64;
            int            mftDataLength      = numberOfMftRecords * bytesPerFileRecordSegment;
            FileNameRecord mftFileNameRecord  = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$MFT", false, DateTime.Now);

            mftFileNameRecord.AllocatedLength = (ulong)mftDataLength;
            mftFileNameRecord.FileSize        = (ulong)mftDataLength;
            mftFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment mftSegment = CreateBaseRecordSegment(MasterFileTable.MasterFileTableSegmentNumber, 1, mftFileNameRecord);

            mftSegment.ReferenceCount = 1;
            NonResidentAttributeRecord mftDataRecord = (NonResidentAttributeRecord)mftSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            mftDataRecord.AllocatedLength = (ulong)mftDataLength;
            mftDataRecord.FileSize        = (ulong)mftDataLength;
            mftDataRecord.ValidDataLength = (ulong)mftDataLength;
            int  mftDataClusterCount = (int)Math.Ceiling((double)mftDataLength / bytesPerCluster);
            long mftDataStartLCN     = volumeBitmapStartLCN + numberOfVolumeBitmapClusters;

            mftDataRecord.DataRunSequence.Add(new DataRun(mftDataClusterCount, mftDataStartLCN));
            mftDataRecord.HighestVCN = mftDataClusterCount - 1;
            NonResidentAttributeRecord mftBitmapRecord = (NonResidentAttributeRecord)mftSegment.CreateAttributeRecord(AttributeType.Bitmap, String.Empty, false);
            int mftBitmapLength       = (int)Math.Ceiling((double)numberOfMftRecords / (BitmapData.ExtendGranularity * 8)) * BitmapData.ExtendGranularity;
            int mftBitmapClusterCount = (int)Math.Ceiling((double)mftBitmapLength / bytesPerCluster);

            mftBitmapRecord.AllocatedLength = (ulong)(mftBitmapClusterCount * bytesPerCluster);
            mftBitmapRecord.FileSize        = (ulong)mftBitmapLength;
            mftBitmapRecord.ValidDataLength = (ulong)mftBitmapLength;
            long mftBitmapStartLCN = mftDataStartLCN + mftDataClusterCount;

            mftBitmapRecord.DataRunSequence.Add(new DataRun(mftBitmapClusterCount, mftBitmapStartLCN));
            mftBitmapRecord.HighestVCN = 0;

            int            bytesPerLogPage   = 4096;
            int            logFileDataLength = 512 * bytesPerLogPage;
            FileNameRecord logFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$LogFile", false, DateTime.Now);

            logFileNameRecord.AllocatedLength = (ulong)logFileDataLength;
            logFileNameRecord.FileSize        = (ulong)logFileDataLength;
            logFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment logFileSegment = CreateBaseRecordSegment(MasterFileTable.LogFileSegmentNumber, (ushort)MasterFileTable.LogFileSegmentNumber, logFileNameRecord);

            logFileSegment.ReferenceCount = 1;
            NonResidentAttributeRecord logFileDataRecord = (NonResidentAttributeRecord)logFileSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            logFileDataRecord.AllocatedLength = (ulong)logFileDataLength;
            logFileDataRecord.FileSize        = (ulong)logFileDataLength;
            logFileDataRecord.ValidDataLength = (ulong)logFileDataLength;
            int  logFileClusterCount = (int)Math.Ceiling((double)logFileDataLength / bytesPerCluster);
            long logFileStartLCN     = mftBitmapStartLCN + mftBitmapClusterCount;

            logFileDataRecord.DataRunSequence.Add(new DataRun(logFileClusterCount, logFileStartLCN));
            logFileDataRecord.HighestVCN = logFileClusterCount - 1;

            FileNameRecord volumeFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$Volume", false, DateTime.Now);

            volumeFileNameRecord.FileAttributes = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment volumeSegment = CreateBaseRecordSegment(MasterFileTable.VolumeSegmentNumber, (ushort)MasterFileTable.VolumeSegmentNumber, volumeFileNameRecord);

            volumeSegment.ReferenceCount = 1;
            VolumeNameRecord volumeName = (VolumeNameRecord)volumeSegment.CreateAttributeRecord(AttributeType.VolumeName, String.Empty);

            volumeName.VolumeName = volumeLabel;
            VolumeInformationRecord volumeInformation = (VolumeInformationRecord)volumeSegment.CreateAttributeRecord(AttributeType.VolumeInformation, String.Empty);

            volumeInformation.MajorVersion = majorNTFSVersion;
            volumeInformation.MinorVersion = minorNTFSVersion;
            volumeSegment.CreateAttributeRecord(AttributeType.Data, String.Empty);

            long logFileDataStartSector = logFileStartLCN * sectorsPerCluster;

            WriteLogFile(volume, logFileDataStartSector, logFileDataLength, bytesPerLogPage);

            long           attributeDefinitionStartLCN        = logFileStartLCN + logFileClusterCount;
            long           attributeDefinitionStartSector     = attributeDefinitionStartLCN * sectorsPerCluster;
            int            attributeDefinitionLength          = WriteAttributeDefinition(volume, attributeDefinitionStartSector, bytesPerCluster);
            int            attributeDefinitionClusterCount    = (int)Math.Ceiling((double)attributeDefinitionLength / bytesPerCluster);
            int            attributeDefinitionAllocatedLength = attributeDefinitionClusterCount * bytesPerCluster;
            FileNameRecord attributeDefinitionFileNameRecord  = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$AttrDef", false, DateTime.Now);

            attributeDefinitionFileNameRecord.AllocatedLength = (ulong)attributeDefinitionAllocatedLength;
            attributeDefinitionFileNameRecord.FileSize        = (ulong)attributeDefinitionLength;
            attributeDefinitionFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment attributeDefinitionSegment = CreateBaseRecordSegment(MasterFileTable.AttrDefSegmentNumber, (ushort)MasterFileTable.AttrDefSegmentNumber, attributeDefinitionFileNameRecord);

            attributeDefinitionSegment.ReferenceCount = 1;
            NonResidentAttributeRecord attributeDefinitionDataRecord = (NonResidentAttributeRecord)attributeDefinitionSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            attributeDefinitionDataRecord.AllocatedLength = (ulong)attributeDefinitionAllocatedLength;
            attributeDefinitionDataRecord.FileSize        = (ulong)attributeDefinitionLength;
            attributeDefinitionDataRecord.ValidDataLength = (ulong)attributeDefinitionLength;
            attributeDefinitionDataRecord.DataRunSequence.Add(new DataRun(attributeDefinitionClusterCount, attributeDefinitionStartLCN));
            attributeDefinitionDataRecord.HighestVCN = attributeDefinitionClusterCount - 1;

            FileNameRecord badClustersFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$BadClus", false, DateTime.Now);

            badClustersFileNameRecord.FileAttributes = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment badClustersSegment = CreateBaseRecordSegment(MasterFileTable.BadClusSegmentNumber, (ushort)MasterFileTable.BadClusSegmentNumber, badClustersFileNameRecord);

            badClustersSegment.ReferenceCount = 1;
            badClustersSegment.CreateAttributeRecord(AttributeType.Data, String.Empty);
            NonResidentAttributeRecord badClustersData = (NonResidentAttributeRecord)badClustersSegment.CreateAttributeRecord(AttributeType.Data, "$Bad", false);
            DataRun volumeDataRun = new DataRun();

            volumeDataRun.RunLength = volumeClusterCount;
            volumeDataRun.IsSparse  = true;
            badClustersData.DataRunSequence.Add(volumeDataRun);
            badClustersData.HighestVCN      = volumeClusterCount - 1;
            badClustersData.AllocatedLength = (ulong)(volumeClusterCount * bytesPerCluster);
            badClustersData.FileSize        = 0;
            badClustersData.ValidDataLength = 0;

            FileNameRecord secureFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$Secure", false, DateTime.Now);

            secureFileNameRecord.FileAttributes = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment secureSegment = CreateBaseRecordSegment(MasterFileTable.SecureSegmentNumber, (ushort)MasterFileTable.SecureSegmentNumber, secureFileNameRecord);

            secureSegment.IsSpecialIndex = true;
            secureSegment.ReferenceCount = 1;
            secureSegment.CreateAttributeRecord(AttributeType.Data, "$SDS");
            IndexRootRecord sdh = (IndexRootRecord)secureSegment.CreateAttributeRecord(AttributeType.IndexRoot, "$SDH");

            IndexHelper.InitializeIndexRoot(sdh, AttributeType.None, CollationRule.SecurityHash, bytesPerIndexRecord, bytesPerCluster);
            IndexRootRecord sii = (IndexRootRecord)secureSegment.CreateAttributeRecord(AttributeType.IndexRoot, "$SII");

            IndexHelper.InitializeIndexRoot(sii, AttributeType.None, CollationRule.UnsignedLong, bytesPerIndexRecord, bytesPerCluster);

            int            upcaseDataClusterCount = (int)Math.Ceiling((double)UpcaseFileLength / bytesPerCluster);
            FileNameRecord upcaseFileNameRecord   = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$UpCase", false, DateTime.Now);

            upcaseFileNameRecord.AllocatedLength = UpcaseFileLength;
            upcaseFileNameRecord.FileSize        = UpcaseFileLength;
            upcaseFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment upcaseSegment = CreateBaseRecordSegment(MasterFileTable.UpCaseSegmentNumber, (ushort)MasterFileTable.UpCaseSegmentNumber, upcaseFileNameRecord);

            upcaseSegment.ReferenceCount = 1;
            NonResidentAttributeRecord upcaseFileDataRecord = (NonResidentAttributeRecord)upcaseSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            upcaseFileDataRecord.AllocatedLength = UpcaseFileLength;
            upcaseFileDataRecord.FileSize        = UpcaseFileLength;
            upcaseFileDataRecord.ValidDataLength = UpcaseFileLength;
            long upcaseDataStartLCN = attributeDefinitionStartLCN + attributeDefinitionClusterCount;

            upcaseFileDataRecord.DataRunSequence.Add(new DataRun(upcaseDataClusterCount, upcaseDataStartLCN));
            upcaseFileDataRecord.HighestVCN = upcaseDataClusterCount - 1;

            FileNameRecord extendFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$Extend", true, DateTime.Now);

            extendFileNameRecord.FileAttributes = FileAttributes.System | FileAttributes.Hidden | FileAttributes.FileNameIndexPresent;
            FileRecordSegment extendSegment = CreateBaseRecordSegment(MasterFileTable.ExtendSegmentNumber, (ushort)MasterFileTable.ExtendSegmentNumber, extendFileNameRecord);

            extendSegment.IsDirectory    = true;
            extendSegment.ReferenceCount = 1;
            IndexRootRecord extendIndexRoot = (IndexRootRecord)extendSegment.CreateAttributeRecord(AttributeType.IndexRoot, IndexHelper.GetIndexName(AttributeType.FileName));

            IndexHelper.InitializeIndexRoot(extendIndexRoot, AttributeType.FileName, CollationRule.Filename, bytesPerIndexRecord, bytesPerCluster);

            FileNameRecord mftMirrorFileNameRecord   = new FileNameRecord(MasterFileTable.RootDirSegmentReference, "$MFTMirr", false, DateTime.Now);
            int            mftMirrorDataLength       = 4 * bytesPerFileRecordSegment;
            int            mftMirrorDataClusterCount = (int)Math.Ceiling((double)mftMirrorDataLength / bytesPerCluster);
            int            mftMirrorAllocatedLength  = mftMirrorDataClusterCount * bytesPerCluster;

            mftMirrorFileNameRecord.AllocatedLength = (ulong)mftMirrorAllocatedLength;
            mftMirrorFileNameRecord.FileSize        = (ulong)mftMirrorDataLength;
            mftMirrorFileNameRecord.FileAttributes  = FileAttributes.Hidden | FileAttributes.System;
            FileRecordSegment mftMirrorSegment = CreateBaseRecordSegment(MasterFileTable.MftMirrorSegmentNumber, (ushort)MasterFileTable.MftMirrorSegmentNumber, mftMirrorFileNameRecord);

            mftMirrorSegment.ReferenceCount = 1;
            NonResidentAttributeRecord mftMirrorDataRecord = (NonResidentAttributeRecord)mftMirrorSegment.CreateAttributeRecord(AttributeType.Data, String.Empty, false);

            mftMirrorDataRecord.AllocatedLength = (ulong)mftMirrorAllocatedLength;
            mftMirrorDataRecord.FileSize        = (ulong)mftMirrorDataLength;
            mftMirrorDataRecord.ValidDataLength = (ulong)mftMirrorDataLength;
            long mftMirrorDataStartLCN = upcaseDataStartLCN + upcaseDataClusterCount;

            mftMirrorDataRecord.DataRunSequence.Add(new DataRun(mftMirrorDataClusterCount, mftMirrorDataStartLCN));
            mftMirrorDataRecord.HighestVCN = mftMirrorDataClusterCount - 1;

            FileNameRecord rootDirFileNameRecord = new FileNameRecord(MasterFileTable.RootDirSegmentReference, ".", true, DateTime.Now);

            rootDirFileNameRecord.FileAttributes = FileAttributes.System | FileAttributes.Hidden | FileAttributes.FileNameIndexPresent;
            FileRecordSegment rootDirSegment = CreateBaseRecordSegment(MasterFileTable.RootDirSegmentNumber, (ushort)MasterFileTable.RootDirSegmentNumber, rootDirFileNameRecord);

            rootDirSegment.IsDirectory    = true;
            rootDirSegment.ReferenceCount = 1;

            IndexRecord rootDirIndexRecord = new IndexRecord();

            rootDirIndexRecord.RecordVBN = 0;
            // Note that we add the index entries according to collation rules
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(attributeDefinitionSegment.SegmentReference, attributeDefinitionFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(badClustersSegment.SegmentReference, badClustersFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(volumeBitmapSegment.SegmentReference, volumeBitmapFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(bootSegment.SegmentReference, bootFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(extendSegment.SegmentReference, extendFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(logFileSegment.SegmentReference, logFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(mftSegment.SegmentReference, mftFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(mftMirrorSegment.SegmentReference, mftMirrorFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(secureSegment.SegmentReference, secureFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(upcaseSegment.SegmentReference, upcaseFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(volumeSegment.SegmentReference, volumeFileNameRecord.GetBytes()));
            rootDirIndexRecord.IndexEntries.Add(new IndexEntry(rootDirSegment.SegmentReference, rootDirFileNameRecord.GetBytes()));

            long            rootDirIndexRecordStartLCN        = mftMirrorDataStartLCN + mftMirrorDataClusterCount;
            int             rootDirIndexRecordClusterCount    = (int)Math.Ceiling((double)bytesPerIndexRecord / bytesPerCluster);
            int             rootDirIndexRecordAllocatedLength = rootDirIndexRecordClusterCount * bytesPerCluster;
            string          rootDirIndexName = IndexHelper.GetIndexName(AttributeType.FileName);
            IndexRootRecord rootDirIndexRoot = (IndexRootRecord)rootDirSegment.CreateAttributeRecord(AttributeType.IndexRoot, rootDirIndexName);

            IndexHelper.InitializeIndexRoot(rootDirIndexRoot, AttributeType.FileName, CollationRule.Filename, bytesPerIndexRecord, bytesPerCluster);
            rootDirIndexRoot.IsParentNode = true;
            IndexEntry rootEntry = new IndexEntry();

            rootEntry.ParentNodeForm = true;
            rootEntry.SubnodeVBN     = 0;
            rootDirIndexRoot.IndexEntries.Add(rootEntry);
            IndexAllocationRecord rootDirIndexAllocation = (IndexAllocationRecord)rootDirSegment.CreateAttributeRecord(AttributeType.IndexAllocation, rootDirIndexName, false);

            rootDirIndexAllocation.AllocatedLength = (uint)rootDirIndexRecordAllocatedLength;
            rootDirIndexAllocation.FileSize        = (uint)bytesPerIndexRecord;
            rootDirIndexAllocation.ValidDataLength = (uint)bytesPerIndexRecord;
            rootDirIndexAllocation.DataRunSequence.Add(new DataRun(rootDirIndexRecordClusterCount, rootDirIndexRecordStartLCN));
            rootDirIndexAllocation.HighestVCN = rootDirIndexRecordClusterCount - 1;
            ResidentAttributeRecord rootDirBitmap = (ResidentAttributeRecord)rootDirSegment.CreateAttributeRecord(AttributeType.Bitmap, rootDirIndexName);

            rootDirBitmap.Data = new byte[BitmapData.ExtendGranularity];
            BitmapData.SetBit(rootDirBitmap.Data, 0);

            long numberOfClustersInUse   = rootDirIndexRecordStartLCN + rootDirIndexRecordClusterCount;
            long volumeBitmapStartSector = volumeBitmapStartLCN * sectorsPerCluster;

            WriteVolumeBitmap(volume, volumeBitmapStartSector, volumeClusterCount, numberOfClustersInUse);

            long mftBitmapStartSector = mftBitmapStartLCN * sectorsPerCluster;

            WriteMftBitmap(volume, mftBitmapStartSector, numberOfMftRecords, mftBitmapLength);

            // Write MFT data
            byte[] mftData            = new byte[mftDataLength];
            long   mftDataStartSector = mftDataStartLCN * sectorsPerCluster;

            WriteFileRecordSegment(mftData, mftSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, mftMirrorSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, logFileSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, volumeSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, attributeDefinitionSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, rootDirSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, volumeBitmapSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, bootSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, badClustersSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, secureSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, upcaseSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftData, extendSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            for (long segmentNumber = MasterFileTable.ExtendSegmentNumber + 1; segmentNumber < MasterFileTable.FirstReservedSegmentNumber; segmentNumber++)
            {
                FileRecordSegment systemSegment = CreateSystemReservedSegment(segmentNumber);
                WriteFileRecordSegment(mftData, systemSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            }

            volume.WriteSectors(mftDataStartSector, mftData);

            long upcaseDataStartSector = upcaseDataStartLCN * sectorsPerCluster;

            WriteUpCaseFile(volume, upcaseDataStartSector);

            long rootDirIndexRecordStartSector = rootDirIndexRecordStartLCN * sectorsPerCluster;

            WriteIndexRecord(volume, rootDirIndexRecordStartSector, rootDirIndexRecord, bytesPerIndexRecord);

            // Write MFT mirror data
            byte[] mftMirrorData            = new byte[mftMirrorDataLength];
            long   mftMirrorDataStartSector = mftMirrorDataStartLCN * sectorsPerCluster;

            WriteFileRecordSegment(mftMirrorData, mftSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftMirrorData, mftMirrorSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftMirrorData, logFileSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            WriteFileRecordSegment(mftMirrorData, volumeSegment, bytesPerFileRecordSegment, minorNTFSVersion);
            volume.WriteSectors(mftMirrorDataStartSector, mftMirrorData);

            NTFSBootRecord bootRecord = CreateNTFSBootRecord(volumeClusterCount, sectorsPerCluster, volume.BytesPerSector, bytesPerFileRecordSegment, bytesPerIndexRecord, mftDataStartLCN, mftMirrorDataStartLCN);

            volume.WriteSectors(0, bootRecord.GetBytes());
            volume.WriteSectors(volume.TotalSectors - 1, bootRecord.GetBytes());

            return(new NTFSVolume(volume));
        }
Esempio n. 13
0
 public NonResidentAttributeData(NTFSVolume volume, FileRecord fileRecord, NonResidentAttributeRecord attributeRecord)
 {
     m_volume          = volume;
     m_fileRecord      = fileRecord;
     m_attributeRecord = attributeRecord;
 }
Esempio n. 14
0
        public static void SliceAttributes(List <FileRecordSegment> segments, List <AttributeRecord> attributes, int bytesPerFileRecordSegment, ushort minorNTFSVersion)
        {
            int bytesAvailableInSegment = FileRecordSegment.GetNumberOfBytesAvailable(bytesPerFileRecordSegment, minorNTFSVersion);
            LinkedList <KeyValuePair <AttributeRecord, bool> > remainingAttributes = new LinkedList <KeyValuePair <AttributeRecord, bool> >();
            FileRecordSegment baseFileRecordSegment = segments[0];
            long segmentNumber   = baseFileRecordSegment.SegmentNumber;
            bool isMftFileRecord = (segmentNumber == MasterFileTable.MasterFileTableSegmentNumber || segmentNumber == MasterFileTable.MftMirrorSegmentNumber);

            foreach (AttributeRecord attribute in attributes)
            {
                if (attribute.AttributeType == AttributeType.StandardInformation ||
                    attribute.AttributeType == AttributeType.FileName)
                {
                    baseFileRecordSegment.ImmediateAttributes.Add(attribute);
                }
                else if (isMftFileRecord && attribute.AttributeType == AttributeType.Data)
                {
                    List <NonResidentAttributeRecord> slices = SliceAttributeRecord((NonResidentAttributeRecord)attribute, bytesPerFileRecordSegment / 2, bytesAvailableInSegment);
                    baseFileRecordSegment.ImmediateAttributes.Add(slices[0]);
                    slices.RemoveAt(0);
                    foreach (NonResidentAttributeRecord slice in slices)
                    {
                        remainingAttributes.AddLast(new KeyValuePair <AttributeRecord, bool>(slice, true));
                    }
                }
                else
                {
                    remainingAttributes.AddLast(new KeyValuePair <AttributeRecord, bool>(attribute, false));
                }
            }

            int segmentIndex = 1;
            int remainingLengthInCurrentSegment = bytesAvailableInSegment;

            while (remainingAttributes.Count > 0)
            {
                AttributeRecord attribute = remainingAttributes.First.Value.Key;
                bool            isSlice   = remainingAttributes.First.Value.Value;

                if (segmentIndex == segments.Count)
                {
                    MftSegmentReference newSegmentReference  = MftSegmentReference.NullReference;
                    FileRecordSegment   newFileRecordSegment = new FileRecordSegment(newSegmentReference.SegmentNumber, newSegmentReference.SequenceNumber, baseFileRecordSegment.SegmentReference);
                    newFileRecordSegment.IsInUse = true;
                    segments.Add(newFileRecordSegment);
                }

                if (attribute.RecordLength <= remainingLengthInCurrentSegment)
                {
                    remainingLengthInCurrentSegment -= (int)attribute.RecordLength;
                    segments[segmentIndex].ImmediateAttributes.Add(attribute);
                    remainingAttributes.RemoveFirst();
                    // Instead of renumbering each attribute slice in the new FileRecordSegment, we use the original Instance number.
                    if (segments[segmentIndex].NextAttributeInstance <= attribute.Instance)
                    {
                        segments[segmentIndex].NextAttributeInstance = (ushort)(attribute.Instance + 1);
                    }
                }
                else
                {
                    if (attribute is ResidentAttributeRecord || isSlice)
                    {
                        segmentIndex++;
                        remainingLengthInCurrentSegment = bytesAvailableInSegment;
                    }
                    else
                    {
                        NonResidentAttributeRecord        nonResidentAttribute = ((NonResidentAttributeRecord)attribute);
                        List <NonResidentAttributeRecord> slices = SliceAttributeRecord((NonResidentAttributeRecord)attribute, remainingLengthInCurrentSegment, bytesAvailableInSegment);
                        remainingAttributes.RemoveFirst();
                        slices.Reverse();
                        foreach (NonResidentAttributeRecord slice in slices)
                        {
                            remainingAttributes.AddFirst(new KeyValuePair <AttributeRecord, bool>(slice, true));
                        }
                    }
                }
            }
        }
        /// <remarks>
        /// Only non-resident attributes can be fragmented.
        /// References:
        /// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc976808(v=technet.10)
        /// https://blogs.technet.microsoft.com/askcore/2009/10/16/the-four-stages-of-ntfs-file-growth/
        /// </remarks>
        public static List <AttributeRecord> GetAssembledAttributes(List <FileRecordSegment> segments)
        {
            List <AttributeRecord> result = new List <AttributeRecord>();
            // If two non-resident attributes have the same AttributeType and Name, then we need to assemble them back together.
            List <NonResidentAttributeRecord> fragments = new List <NonResidentAttributeRecord>();

            foreach (FileRecordSegment segment in segments)
            {
                foreach (AttributeRecord attribute in segment.ImmediateAttributes)
                {
                    if (attribute.AttributeType == AttributeType.AttributeList)
                    {
                        continue;
                    }

                    if (attribute is ResidentAttributeRecord)
                    {
                        result.Add(attribute.Clone());
                    }
                    else
                    {
                        fragments.Add((NonResidentAttributeRecord)attribute);
                    }
                }
            }

            // Windows NTFS v5.1 driver will sometimes put in the base record segment resident attributes that sort after an attribute from the second segment,
            // and will sometimes put non-resident fragment with a non-zero LowestVCN in a segment with a lower segment number than the segment containing the first fragment (LowestVCN == 0),
            // (while keeping the attribute sorting rules within each segment).
            fragments.Sort(CompareNonResidentAttributes);

            AttributeType currentAttributeType = AttributeType.None;
            string        currentAttributeName = String.Empty;
            List <NonResidentAttributeRecord> currentAttributeFragments = new List <NonResidentAttributeRecord>();

            foreach (NonResidentAttributeRecord fragment in fragments)
            {
                bool additionalFragment = (currentAttributeFragments.Count > 0) &&
                                          (fragment.AttributeType == currentAttributeType) &&
                                          (fragment.Name == currentAttributeName);

                if (!additionalFragment && currentAttributeFragments.Count > 0)
                {
                    NonResidentAttributeRecord assembledAttribute = AssembleFragments(currentAttributeFragments);
                    result.Add(assembledAttribute);
                    currentAttributeFragments.Clear();
                }

                currentAttributeFragments.Add(fragment);
                if (!additionalFragment)
                {
                    currentAttributeType = fragment.AttributeType;
                    currentAttributeName = fragment.Name;
                }
            }

            if (currentAttributeFragments.Count > 0)
            {
                NonResidentAttributeRecord assembledAttribute = AssembleFragments(currentAttributeFragments);
                result.Add(assembledAttribute);
            }

            result.Sort(CompareAttributeTypes);
            return(result);
        }