public static void ExtendStripedVolume(DiskGroupDatabase database, StripedVolume volume, long additionalNumberOfExtentSectors) { if (additionalNumberOfExtentSectors % volume.SectorsPerStripe > 0) { throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector"); } List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.SizeLBA += (ulong)(additionalNumberOfExtentSectors * volume.NumberOfColumns); records.Add(volumeRecord); // we only want to extend the last extent in each column foreach (DynamicColumn column in volume.Columns) { DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1]; ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID); extentRecord = (ExtentRecord)extentRecord.Clone(); extentRecord.SizeLBA += (ulong)additionalNumberOfExtentSectors; records.Add(extentRecord); DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); } database.UpdateDatabase(records); }
public const int BackupBufferSizeLBA = 128; // there are about 180 contiguous free sectors in a private region /// <summary> /// Move extent to another disk /// </summary> public static void MoveExtentToAnotherDisk(List<DynamicDisk> disks, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } // copy the data long transferSizeLBA = Settings.MaximumTransferSizeLBA; for (long sectorIndex = 0; sectorIndex < relocatedExtent.TotalSectors; sectorIndex += transferSizeLBA) { long sectorsLeft = relocatedExtent.TotalSectors - sectorIndex; int sectorsToRead = (int)Math.Min(transferSizeLBA, sectorsLeft); byte[] data = sourceExtent.ReadSectors(sectorIndex, sectorsToRead); relocatedExtent.WriteSectors(sectorIndex, data); bytesCopied += sectorsToRead * sourceExtent.BytesPerSector; } // Update the database to point to the relocated extent DynamicDisk targetDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk); DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = targetDisk.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); }
public static void ConvertStripedVolumeToRaid(DiskGroupDatabase database, Guid volumeGuid) { List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volumeGuid); if (volumeRecord == null) { throw new MissingDatabaseRecordException("Volume record is missing"); } volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.VolumeTypeString = "raid5"; volumeRecord.ReadPolicy = ReadPolicyName.RAID; volumeRecord.VolumeFlags = VolumeFlags.DefaultUnknown | VolumeFlags.Writeback | VolumeFlags.Writecopy; records.Add(volumeRecord); ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0]; if (componentRecord == null) { throw new MissingDatabaseRecordException("Component record is missing"); } componentRecord = (ComponentRecord)componentRecord.Clone(); componentRecord.ExtentLayout = ExtentLayoutName.RAID5; records.Add(componentRecord); database.UpdateDatabase(records); }
public static DynamicVolume GetVolume(DiskGroupDatabase database, VolumeRecord volumeRecord) { List <ComponentRecord> componentRecords = database.FindComponentsByVolumeID(volumeRecord.VolumeId); if (volumeRecord.NumberOfComponents != (ulong)componentRecords.Count || componentRecords.Count == 0) { // database record is invalid throw new InvalidDataException("Number of components in volume record does not match actual number of component records"); } if (componentRecords.Count == 1) { ComponentRecord componentRecord = componentRecords[0]; return(GetVolume(database, volumeRecord, componentRecord)); } else // Mirrored volume { // Mirrored Simple Volume is the only kind of mirror suppored by Windows (only 2-way mirror is supported) // Veritas also supports Mirrored Stripe / Mirrored RAID-5 / Mirrored Spanned Volume (up to 32-way mirror is supported) List <DynamicVolume> volumes = new List <DynamicVolume>(); foreach (ComponentRecord componentRecord in componentRecords) { DynamicVolume volume = GetVolume(database, volumeRecord, componentRecord); volumes.Add(volume); } MirroredVolume mirroredVolume = new MirroredVolume(volumes, volumeRecord.VolumeGuid, database.DiskGroupGuid); mirroredVolume.VolumeID = volumeRecord.VolumeId; mirroredVolume.Name = volumeRecord.Name; return(mirroredVolume); } }
/// <summary> /// Update the database to point to the new extent location (same or different disk) /// </summary> public static void UpdateExtentLocation(DiskGroupDatabase database, DynamicVolume volume, DynamicDiskExtent relocatedExtent) { PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(relocatedExtent.Disk); DiskRecord targetDiskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid); VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); List <DatabaseRecord> records = new List <DatabaseRecord>(); ExtentRecord sourceExtentRecord = database.FindExtentByExtentID(relocatedExtent.ExtentID); ExtentRecord relocatedExtentRecord = (ExtentRecord)sourceExtentRecord.Clone(); relocatedExtentRecord.DiskId = targetDiskRecord.DiskId; relocatedExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(relocatedExtent.FirstSector, privateHeader); records.Add(relocatedExtentRecord); // we should update the disk records foreach (DynamicDiskExtent extent in volume.Extents) { DiskRecord diskRecord = database.FindDiskByDiskID(relocatedExtentRecord.DiskId); // there could be multiple extents on the same disk, make sure we only add each disk once if (!records.Contains(diskRecord)) { diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); } } // when moving to a new disk, we should update the new disk record as well if (!records.Contains(targetDiskRecord)) { records.Add(targetDiskRecord.Clone()); } database.UpdateDatabase(records); }
private static DynamicVolume GetVolume(DiskGroupDatabase database, VolumeRecord volumeRecord, ComponentRecord componentRecord) { if (componentRecord.ExtentLayout == ExtentLayoutName.Concatenated) { if (componentRecord.NumberOfExtents == 1) { // Simple volume return(GetSimpleVolume(database, componentRecord, volumeRecord));; } else { // spanned volume SpannedVolume volume = GetSpannedVolume(database, componentRecord, volumeRecord); return(volume); } } else if (componentRecord.ExtentLayout == ExtentLayoutName.Stripe) { // striped volume StripedVolume volume = GetStripedVolume(database, componentRecord, volumeRecord); return(volume); } else if (componentRecord.ExtentLayout == ExtentLayoutName.RAID5) { Raid5Volume volume = GetRAID5Volume(database, componentRecord, volumeRecord); return(volume); } else { return(null); } }
public static void ExtendRAID5Volume(DiskGroupDatabase database, Raid5Volume volume, long numberOfAdditionalExtentSectors) { if (numberOfAdditionalExtentSectors % volume.SectorsPerStripe > 0) { throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector"); } List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors * (volume.NumberOfColumns - 1), volume.BytesPerSector); records.Add(volumeRecord); foreach (DynamicColumn column in volume.Columns) { DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1]; ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID); extentRecord = (ExtentRecord)extentRecord.Clone(); extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors, volume.BytesPerSector); records.Add(extentRecord); DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); } database.UpdateDatabase(records); }
public static void AddDiskToRaid5Volume(List <DynamicDisk> disks, Raid5Volume volume, DiskExtent newExtent, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } // If there will be a power failure during the conversion, our RAID volume will resync during boot, // To prevent destruction of the data, we temporarily convert the array to striped volume VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid); ulong newExtentID = VolumeManagerDatabaseHelper.AddNewExtentToVolume(database, volume, newExtent); // Backup the first sector of the first extent to the last sector of the new extent // (We replace the filesystem boot record with our own sector for recovery purposes) byte[] filesystemBootRecord = volume.Extents[0].ReadSector(0); newExtent.WriteSectors(newExtent.TotalSectors - 1, filesystemBootRecord); AddDiskOperationBootRecord resumeRecord = new AddDiskOperationBootRecord(); resumeRecord.VolumeGuid = volume.VolumeGuid; PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(newExtent.Disk); // privateHeader cannot be null at this point resumeRecord.NumberOfCommittedSectors = 0; // we use volume.WriteSectors so that the parity information will be update // this way, we could recover the first sector of each extent if a disk will fail volume.WriteSectors(0, resumeRecord.GetBytes()); ResumeAddDiskToRaid5Volume(disks, volume, new DynamicDiskExtent(newExtent, newExtentID), resumeRecord, ref bytesCopied); }
public static List <DiskGroupDatabase> ReadFromDisks(List <DynamicDisk> disks) { Dictionary <Guid, List <DynamicDisk> > groups = new Dictionary <Guid, List <DynamicDisk> >(); foreach (DynamicDisk disk in disks) { Guid diskGroupGuid = disk.PrivateHeader.DiskGroupGuid; if (groups.ContainsKey(diskGroupGuid)) { groups[diskGroupGuid].Add(disk); } else { List <DynamicDisk> list = new List <DynamicDisk>(); list.Add(disk); groups.Add(diskGroupGuid, list); } } List <DiskGroupDatabase> result = new List <DiskGroupDatabase>(); foreach (List <DynamicDisk> groupDisks in groups.Values) { VolumeManagerDatabase database = VolumeManagerDatabase.ReadFromDisk(groupDisks[0]); if (database != null) // if there is issue with one disk (such as unsupported version) we skip the group entirely { DiskGroupDatabase groupDatabase = new DiskGroupDatabase(groupDisks, database.DatabaseHeader, database.DatabaseRecords, database.KernelUpdateLog); result.Add(groupDatabase); } } return(result); }
private static void MoveExtentLeft(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } DynamicDiskExtent relocatedExtent = DynamicDiskExtentHelper.GetByExtentID(volume.DynamicExtents, resumeRecord.ExtentID); if (resumeRecord.OldStartSector == (ulong)relocatedExtent.FirstSector) { // the database update was not completed (this must be a resume operation) relocatedExtent = new DynamicDiskExtent(relocatedExtent.Disk, (long)resumeRecord.NewStartSector, relocatedExtent.Size, resumeRecord.ExtentID); VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, relocatedExtent); } DiskExtent sourceExtent = new DiskExtent(relocatedExtent.Disk, (long)resumeRecord.OldStartSector, relocatedExtent.Size); MoveHelper.MoveExtentDataLeft(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); // get the updated volume (we just reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid); } // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
private static SpannedVolume GetSpannedVolume(DiskGroupDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { List <DynamicColumn> columns = GetDynamicVolumeColumns(database, componentRecord, volumeRecord); SpannedVolume volume = new SpannedVolume(columns[0], volumeRecord.VolumeGuid, database.DiskGroupGuid); volume.VolumeID = volumeRecord.VolumeId; volume.Name = volumeRecord.Name; volume.DiskGroupName = database.DiskGroupName; return(volume); }
public static ulong CreateSimpleVolume(DiskGroupDatabase database, DiskExtent extent) { List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = new VolumeRecord(); volumeRecord.Id = database.AllocateNewRecordID(); volumeRecord.Name = GetNextSimpleVolumeName(database.VolumeRecords); volumeRecord.VolumeTypeString = "gen"; volumeRecord.StateString = "ACTIVE"; volumeRecord.ReadPolicy = ReadPolicyName.Select; volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords); volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.DefaultUnknown; volumeRecord.NumberOfComponents = 1; volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector); volumeRecord.PartitionType = PartitionType.RAW; volumeRecord.VolumeGuid = Guid.NewGuid(); records.Add(volumeRecord); ComponentRecord componentRecord = new ComponentRecord(); componentRecord.Id = database.AllocateNewRecordID(); componentRecord.Name = volumeRecord.Name + "-01"; componentRecord.StateString = "ACTIVE"; componentRecord.ExtentLayout = ExtentLayoutName.Concatenated; componentRecord.NumberOfExtents = 1; componentRecord.VolumeId = volumeRecord.VolumeId; componentRecord.HasStripedExtentsFlag = false; componentRecord.NumberOfColumns = 0; records.Add(componentRecord); // we should update the disk record PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(extent.Disk); DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid); diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); ExtentRecord extentRecord = new ExtentRecord(); extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name); extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader); extentRecord.SizeLBA = volumeRecord.SizeLBA; extentRecord.ComponentId = componentRecord.ComponentId; extentRecord.DiskId = diskRecord.DiskId; extentRecord.HasColumnIndexFlag = false; records.Add(extentRecord); database.UpdateDatabase(records); return(volumeRecord.VolumeId); }
private static StripedVolume GetStripedVolume(DiskGroupDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { List <DynamicColumn> columns = GetDynamicVolumeColumns(database, componentRecord, volumeRecord); int bytesPerSector = DynamicVolume.GetBytesPerSector(columns, DynamicColumn.DefaultBytesPerSector); int sectorsPerStripe = (int)PublicRegionHelper.TranslateFromPublicRegionSizeLBA((int)componentRecord.StripeSizeLBA, bytesPerSector); StripedVolume volume = new StripedVolume(columns, sectorsPerStripe, volumeRecord.VolumeGuid, database.DiskGroupGuid); volume.VolumeID = volumeRecord.VolumeId; volume.Name = volumeRecord.Name; volume.DiskGroupName = database.DiskGroupName; return(volume); }
public static List <DynamicVolume> GetDynamicVolumes(List <DynamicDisk> disks) { List <DynamicVolume> result = new List <DynamicVolume>(); List <DiskGroupDatabase> diskGroupDatabases = DiskGroupDatabase.ReadFromDisks(disks); foreach (DiskGroupDatabase database in diskGroupDatabases) { foreach (VolumeRecord volumeRecord in database.VolumeRecords) { DynamicVolume volume = GetVolume(database, volumeRecord); result.Add(volume); } } return(result); }
private static List <DynamicColumn> GetDynamicVolumeColumns(DiskGroupDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { // extentRecords are sorted by offset in column List <ExtentRecord> extentRecords = database.FindExtentsByComponentID(componentRecord.ComponentId); if (componentRecord.NumberOfExtents != extentRecords.Count || extentRecords.Count == 0) { // database record is invalid throw new InvalidDataException("Number of extents in component record does not match actual number of extent records"); } SortedList <uint, List <DynamicDiskExtent> > columns = new SortedList <uint, List <DynamicDiskExtent> >(); foreach (ExtentRecord extentRecord in extentRecords) { DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); DynamicDisk disk = DynamicDiskHelper.FindDisk(database.Disks, diskRecord.DiskGuid); // we add nulls as well DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord); if (columns.ContainsKey(extentRecord.ColumnIndex)) { columns[extentRecord.ColumnIndex].Add(extent); } else { List <DynamicDiskExtent> list = new List <DynamicDiskExtent>(); list.Add(extent); columns.Add(extentRecord.ColumnIndex, list); } } List <DynamicColumn> result = new List <DynamicColumn>(); foreach (List <DynamicDiskExtent> extents in columns.Values) { result.Add(new DynamicColumn(extents)); } return(result); }
public static void ExtendSimpleVolume(DiskGroupDatabase database, SimpleVolume volume, long numberOfAdditionalSectors) { VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector); ExtentRecord extentRecord = database.FindExtentByExtentID(volume.DiskExtent.ExtentID); extentRecord = (ExtentRecord)extentRecord.Clone(); extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector); DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs diskRecord = (DiskRecord)diskRecord.Clone(); List <DatabaseRecord> records = new List <DatabaseRecord>(); records.Add(volumeRecord); records.Add(extentRecord); records.Add(diskRecord); database.UpdateDatabase(records); }
public static void ExtendSimpleVolume(DiskGroupDatabase database, SimpleVolume volume, long additionalNumberOfSectors) { VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.SizeLBA += (ulong)additionalNumberOfSectors; ExtentRecord extentRecord = database.FindExtentByExtentID(volume.DiskExtent.ExtentID); extentRecord = (ExtentRecord)extentRecord.Clone(); extentRecord.SizeLBA += (ulong)additionalNumberOfSectors; DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs diskRecord = (DiskRecord)diskRecord.Clone(); List <DatabaseRecord> records = new List <DatabaseRecord>(); records.Add(volumeRecord); records.Add(extentRecord); records.Add(diskRecord); database.UpdateDatabase(records); }
private static void MoveExtentRight(List <DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, resumeRecord.ExtentID); DynamicDiskExtent sourceExtent = volume.DynamicExtents[extentIndex]; DiskExtent relocatedExtent = new DiskExtent(sourceExtent.Disk, (long)resumeRecord.NewStartSector, sourceExtent.Size); MoveHelper.MoveExtentDataRight(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // even if the database update won't complete, the resume record was copied // update the database DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); } // get the updated volume (we moved an extent and possibly reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid); // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
private static SimpleVolume GetSimpleVolume(DiskGroupDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { List <ExtentRecord> extentRecords = database.FindExtentsByComponentID(componentRecord.ComponentId); if (extentRecords.Count == 1) { ExtentRecord extentRecord = extentRecords[0]; DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); DynamicDisk disk = DynamicDiskHelper.FindDisk(database.Disks, diskRecord.DiskGuid); // we add nulls as well DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord); SimpleVolume volume = new SimpleVolume(extent, volumeRecord.VolumeGuid, database.DiskGroupGuid); volume.VolumeID = volumeRecord.VolumeId; volume.Name = volumeRecord.Name; volume.DiskGroupName = database.DiskGroupName; return(volume); } else { // component / extent records are invalid throw new InvalidDataException("Number of extents in component record does not match actual number of extent records"); } }
private static void ResumeAddDiskToRaid5Volume(List <DynamicDisk> disks, Raid5Volume volume, DynamicDiskExtent newExtent, AddDiskOperationBootRecord resumeRecord, ref long bytesCopied) { // When reading from the volume, we must use the old volume (without the new disk) // However, when writing the boot sector to the volume, we must use the new volume or otherwise parity information will be invalid List <DynamicColumn> newVolumeColumns = new List <DynamicColumn>(); newVolumeColumns.AddRange(volume.Columns); newVolumeColumns.Add(new DynamicColumn(newExtent)); Raid5Volume newVolume = new Raid5Volume(newVolumeColumns, volume.SectorsPerStripe, volume.VolumeGuid, volume.DiskGroupGuid); int oldColumnCount = volume.Columns.Count; int newColumnCount = oldColumnCount + 1; long resumeFromStripe = (long)resumeRecord.NumberOfCommittedSectors / volume.SectorsPerStripe; // it would be prudent to write the new extent before committing to the operation, however, it would take much longer. // The number of sectors in extent / column is always a multiple of SectorsPerStripe. // We read enough stripes to write a vertical stripe segment in the new array, // We will read MaximumTransferSizeLBA and make sure maximumStripesToTransfer is multiple of (NumberOfColumns - 1). int maximumStripesToTransfer = (Settings.MaximumTransferSizeLBA / volume.SectorsPerStripe) / (newColumnCount - 1) * (newColumnCount - 1); long totalStripesInVolume = volume.TotalStripes; long stripeIndexInVolume = resumeFromStripe; while (stripeIndexInVolume < totalStripesInVolume) { // When we add a column, the distance between the stripes we read (later in the column) to thes one we write (earlier), // Is growing constantly (because we can stack more stripes in each vertical stripe), so we increment the number of stripes we // can safely transfer as we go. // (We assume that the segment we write will be corrupted if there will be a power failure) long stripeToReadIndexInColumn = stripeIndexInVolume / (oldColumnCount - 1); long stripeToWriteIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); long numberOfStripesSafeToTransfer = (stripeToReadIndexInColumn - stripeToWriteIndexInColumn) * (newColumnCount - 1); bool verticalStripeAtRisk = (numberOfStripesSafeToTransfer == 0); if (numberOfStripesSafeToTransfer == 0) { // The first few stripes in each column are 'at rist', meaning that we may overwrite crucial data (that is only stored in memory) // when writing the segment that will be lost forever if a power failure will occur during the write operation. // Note: The number of 'at risk' vertical stripes is equal to the number of columns in the old array - 1 numberOfStripesSafeToTransfer = (newColumnCount - 1); } int numberOfStripesToTransfer = (int)Math.Min(numberOfStripesSafeToTransfer, maximumStripesToTransfer); long stripesLeft = totalStripesInVolume - stripeIndexInVolume; numberOfStripesToTransfer = (int)Math.Min(numberOfStripesToTransfer, stripesLeft); byte[] segmentData = volume.ReadStripes(stripeIndexInVolume, numberOfStripesToTransfer); if (numberOfStripesToTransfer % (newColumnCount - 1) > 0) { // this is the last segment and we need to zero-fill it for the write: int numberOfStripesToWrite = (int)Math.Ceiling((decimal)numberOfStripesToTransfer / (newColumnCount - 1)) * (newColumnCount - 1); byte[] temp = new byte[numberOfStripesToWrite * volume.BytesPerStripe]; Array.Copy(segmentData, temp, segmentData.Length); segmentData = temp; } long firstStripeIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); if (verticalStripeAtRisk) { // we write 'at risk' stripes one at a time to the new volume, this will make sure they will not overwrite crucial data // (because they will be written in an orderly fashion, and not in bulk from the first column to the last) newVolume.WriteStripes(stripeIndexInVolume, segmentData); } else { WriteSegment(volume, newExtent, firstStripeIndexInColumn, segmentData); } // update resume record resumeRecord.NumberOfCommittedSectors += (ulong)(numberOfStripesToTransfer * volume.SectorsPerStripe); bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * volume.BytesPerSector; newVolume.WriteSectors(0, resumeRecord.GetBytes()); stripeIndexInVolume += numberOfStripesToTransfer; } // we're done, let's restore the filesystem boot record byte[] filesystemBootRecord = newExtent.ReadSector(newExtent.TotalSectors - 1); newVolume.WriteSectors(0, filesystemBootRecord); DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); }
public static ulong CreateRAID5Volume(DiskGroupDatabase database, List <DiskExtent> extents, bool isDegraded) { int numberOfColumns; if (isDegraded) { numberOfColumns = extents.Count + 1; } else { numberOfColumns = extents.Count; } List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = new VolumeRecord(); volumeRecord.Id = database.AllocateNewRecordID(); volumeRecord.Name = GetNextRAIDVolumeName(database.VolumeRecords); volumeRecord.VolumeTypeString = "raid5"; volumeRecord.StateString = "ACTIVE"; volumeRecord.ReadPolicy = ReadPolicyName.RAID; volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords); volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.Writecopy | VolumeFlags.DefaultUnknown; volumeRecord.NumberOfComponents = 1; volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors * (numberOfColumns - 1), extents[0].BytesPerSector); volumeRecord.PartitionType = PartitionType.RAW; volumeRecord.VolumeGuid = Guid.NewGuid(); records.Add(volumeRecord); ComponentRecord componentRecord = new ComponentRecord(); componentRecord.Id = database.AllocateNewRecordID(); componentRecord.Name = volumeRecord.Name + "-01"; componentRecord.StateString = "ACTIVE"; componentRecord.ExtentLayout = ExtentLayoutName.RAID5; componentRecord.NumberOfExtents = (uint)numberOfColumns; componentRecord.VolumeId = volumeRecord.VolumeId; componentRecord.HasStripedExtentsFlag = true; componentRecord.StripeSizeLBA = 128; // 64KB - the default componentRecord.NumberOfColumns = (uint)numberOfColumns; records.Add(componentRecord); for (int index = 0; index < extents.Count; index++) { DiskExtent extent = extents[index]; // we should update the disk records PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(extent.Disk); DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid); diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); ExtentRecord extentRecord = new ExtentRecord(); extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name); extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader); extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector); extentRecord.ComponentId = componentRecord.ComponentId; extentRecord.DiskId = diskRecord.DiskId; extentRecord.HasColumnIndexFlag = (index > 0); extentRecord.ColumnIndex = (uint)index; // zero based records.Add(extentRecord); } if (isDegraded) { // we have to make-up a disk // The DiskFlags and ExtentFlags are not necessary (they will be added later anyway when the disk group is reimported) DiskRecord diskRecord = new DiskRecord(); diskRecord.Id = database.AllocateNewRecordID(); diskRecord.Name = "Miss" + new Random().Next(100); diskRecord.DiskGuid = Guid.NewGuid(); diskRecord.DiskFlags = DiskFlags.Detached; records.Add(diskRecord); ExtentRecord extentRecord = new ExtentRecord(); extentRecord.Name = diskRecord.Name + "-01"; extentRecord.ExtentFlags = ExtentFlags.Recover; extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors, extents[0].BytesPerSector); extentRecord.ComponentId = componentRecord.ComponentId; extentRecord.DiskId = diskRecord.DiskId; extentRecord.HasColumnIndexFlag = true; extentRecord.ColumnIndex = (uint)extents.Count; // zero based records.Add(extentRecord); } database.UpdateDatabase(records); return(volumeRecord.VolumeId); }
/// <summary> /// Update the database (add the new extent) /// </summary> /// <param name="volume">RAID-5 or striped volume</param> /// <returns>new extent ID</returns> public static ulong AddNewExtentToVolume(DiskGroupDatabase database, DynamicVolume volume, DiskExtent newExtent) { PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(newExtent.Disk); List <DatabaseRecord> records = new List <DatabaseRecord>(); VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid); if (volumeRecord == null) { throw new MissingDatabaseRecordException("Volume record is missing"); } volumeRecord = (VolumeRecord)volumeRecord.Clone(); volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader); records.Add(volumeRecord); ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0]; if (componentRecord == null) { throw new MissingDatabaseRecordException("Component record is missing"); } componentRecord = (ComponentRecord)componentRecord.Clone(); componentRecord.NumberOfExtents++; componentRecord.NumberOfColumns++; records.Add(componentRecord); DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid); if (diskRecord == null) { throw new MissingDatabaseRecordException("Disk record is missing"); } diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); ExtentRecord newExtentRecord = new ExtentRecord(); newExtentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name); newExtentRecord.ComponentId = componentRecord.ComponentId; newExtentRecord.DiskId = diskRecord.DiskId; newExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(newExtent.FirstSector, privateHeader); newExtentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader); newExtentRecord.HasColumnIndexFlag = true; newExtentRecord.ColumnIndex = (uint)volume.Columns.Count; // zero based records.Add(newExtentRecord); // we should update the disk records and extent records foreach (DynamicDiskExtent extent in volume.Extents) { ExtentRecord extentRecord = database.FindExtentByExtentID(extent.ExtentID); if (extentRecord == null) { throw new MissingDatabaseRecordException("Extent record is missing"); } extentRecord = (ExtentRecord)extentRecord.Clone(); records.Add(extentRecord); diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); if (diskRecord == null) { throw new MissingDatabaseRecordException("Disk record is missing"); } // there could be multiple extents on the same disk, make sure we only add each disk once if (!records.Contains(diskRecord)) { diskRecord = (DiskRecord)diskRecord.Clone(); records.Add(diskRecord); } } database.UpdateDatabase(records); return(newExtentRecord.ExtentId); }
/// <summary> /// Move extent to a new location on the same disk /// </summary> public static void MoveExtentWithinSameDisk(List<DynamicDisk> disks, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied) { DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); if (database == null) { throw new DatabaseNotFoundException(); } MoveExtentOperationBootRecord resumeRecord = new MoveExtentOperationBootRecord(); // If there will be a power failure during the move, a RAID volume will resync during boot, // To prevent destruction of the data, we temporarily convert the array to striped volume if (volume is Raid5Volume) { VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid); resumeRecord.RestoreRAID5 = true; } // We want to write our own volume boot sector for recovery purposes, so we must find where to backup the old boot sector. // We don't want to store the backup in the range of the existing or relocated extent, because then we would have to move // the backup around during the move operation, other options include: // 1. Store it between sectors 1-62 (cons: Could be in use, Windows occasionally start a volume from sector 1) // 2. Find an easily compressible sector (e.g. zero-filled) within the existing extent, overwrite it with the backup, and restore it when the operation is done. // 3. use the LDM private region to store the sector. DynamicDisk dynamicDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk); // Note: backupSectorIndex will be from the beginning of the private region while backupBufferStartSector will be from the end // so there is no need to allocate them long backupSectorIndex = DynamicDiskHelper.FindUnusedSectorInPrivateRegion(dynamicDisk); resumeRecord.VolumeGuid = volume.VolumeGuid; resumeRecord.NumberOfCommittedSectors = 0; resumeRecord.ExtentID = sourceExtent.ExtentID; resumeRecord.OldStartSector = (ulong)sourceExtent.FirstSector; resumeRecord.NewStartSector = (ulong)relocatedExtent.FirstSector; resumeRecord.BootRecordBackupSector = (ulong)backupSectorIndex; long distanceLBA = Math.Abs((long)resumeRecord.NewStartSector - (long)resumeRecord.OldStartSector); if (distanceLBA < MoveHelper.BufferedModeThresholdLBA) { long backupBufferStartSector = DynamicDiskHelper.FindUnusedRegionInPrivateRegion(dynamicDisk, BackupBufferSizeLBA); if (backupBufferStartSector == -1) { throw new Exception("Private region is full"); } if (backupBufferStartSector <= backupSectorIndex) { throw new Exception("Private region structure is unknown"); } resumeRecord.BackupBufferStartSector = (ulong)backupBufferStartSector; resumeRecord.BackupBufferSizeLBA = BackupBufferSizeLBA; } // Backup the first sector of the first extent // (We replace the filesystem boot record with our own sector for recovery purposes) byte[] filesystemBootRecord = volume.ReadSector(0); relocatedExtent.Disk.WriteSectors(backupSectorIndex, filesystemBootRecord); // we write the resume record instead of the boot record volume.WriteSectors(0, resumeRecord.GetBytes()); if (sourceExtent.FirstSector < relocatedExtent.FirstSector) { // move right MoveExtentRight(disks, volume, resumeRecord, ref bytesCopied); } else { // move left // we write the resume record at the new location as well (to be able to resume if a power failure will occur immediately after updating the database) relocatedExtent.WriteSectors(0, resumeRecord.GetBytes()); DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, sourceExtent.ExtentID); // get the updated volume (we just moved an extent) volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid); MoveExtentLeft(disks, volume, resumeRecord, ref bytesCopied); } }