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); }
public static void ResumeMoveExtent(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied) { if (resumeRecord.OldStartSector == resumeRecord.NewStartSector) { throw new InvalidDataException("Invalid move record"); } if (resumeRecord.RestoreFromBuffer) { // we need to use the backup buffer to restore the data that may have been overwritten int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, resumeRecord.ExtentID); DynamicDiskExtent sourceExtent = volume.DynamicExtents[extentIndex]; byte[] backupBuffer = sourceExtent.Disk.ReadSectors((long)resumeRecord.BackupBufferStartSector, BackupBufferSizeLBA); if (resumeRecord.OldStartSector < resumeRecord.NewStartSector) { // move right long readCount = (long)resumeRecord.NumberOfCommittedSectors; int sectorsToRead = BackupBufferSizeLBA; long sectorIndex = sourceExtent.TotalSectors - readCount - sectorsToRead; sourceExtent.WriteSectors(sectorIndex, backupBuffer); System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex); } else { // move left long sectorIndex = (long)resumeRecord.NumberOfCommittedSectors; sourceExtent.WriteSectors(sectorIndex, backupBuffer); System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex); } } if (resumeRecord.OldStartSector < resumeRecord.NewStartSector) { MoveExtentRight(disks, volume, resumeRecord, ref bytesCopied); } else { MoveExtentLeft(disks, volume, resumeRecord, ref bytesCopied); } }
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); }
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); }
/// <summary> /// Sorted by first sector /// </summary> /// <returns>null if there was a problem reading extent information from disk</returns> public static List <DynamicDiskExtent> GetDiskExtents(DynamicDisk disk) { List <DynamicDiskExtent> result = new List <DynamicDiskExtent>(); PrivateHeader privateHeader = disk.PrivateHeader; if (privateHeader != null) { VolumeManagerDatabase database = VolumeManagerDatabase.ReadFromDisk(disk); if (database != null) { DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid); List <ExtentRecord> extentRecords = database.FindExtentsByDiskID(diskRecord.DiskId); foreach (ExtentRecord extentRecord in extentRecords) { DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord); result.Add(extent); } DynamicDiskExtentsHelper.SortExtentsByFirstSector(result); return(result); } } return(null); }
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"); } }
/// <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); } }